# Data Race vs. Race Condition

Vì thường sử dụng MySQL nhiều trong công việc nên phạm vi bài này sử dụng chủ yếu ví dụ MySQL, tuy nhiên từ những khái niệm và ví dụ này có thể mở rộng sang các loại CSDL khác.

## **Data Race trong MySQL**

Data Race xảy ra khi nhiều transaction (giao dịch) đồng thời đọc và ghi vào cùng một dữ liệu mà không có cơ chế đồng bộ thích hợp, dẫn đến mất dữ liệu hoặc dữ liệu không chính xác.

## **Ví dụ Data Race trong MySQL**

Giả sử có bảng `bank_accounts` chứa số dư tài khoản ngân hàng:

```sql
CREATE TABLE bank_accounts (
    account_id INT PRIMARY KEY,
    balance DECIMAL(10,2)
);
INSERT INTO bank_accounts (account_id, balance) VALUES (1001, 5000);
```

Hai giao dịch `T1` và `T2` cùng thực hiện rút tiền từ tài khoản `1001`.

##### **Transaction 1 (T1) - Rút 1000 VND**

```sql
START TRANSACTION;
SELECT balance FROM bank_accounts WHERE account_id = 1001;  -- balance = 5000
UPDATE bank_accounts SET balance = 5000 - 1000 WHERE account_id = 1001; -- balance = 4000
COMMIT;
```

##### **Transaction 2 (T2) - Rút 500 VND**

```sql
START TRANSACTION;
SELECT balance FROM bank_accounts WHERE account_id = 1001;  -- balance = 5000
UPDATE bank_accounts SET balance = 5000 - 500 WHERE account_id = 1001; -- balance = 4500
COMMIT;
```

#### **Kết quả sai do Data Race**

* Cả `T1` và `T2` đều đọc `balance = 5000`.
    
* `T1` ghi `balance = 4000`.
    
* `T2` ghi `balance = 4500`.
    
* **Giá trị đúng phải là** `3500`, nhưng cuối cùng lại là `4500` do mất bản cập nhật của T1.
    

## **Cách khắc phục Data Race trong MySQL**

### **1\. Dùng khóa hàng (Row Locking) với** `SELECT … FOR UPDATE`

```sql
START TRANSACTION;
SELECT balance FROM bank_accounts WHERE account_id = 1001 FOR UPDATE;
UPDATE bank_accounts SET balance = balance - 1000 WHERE account_id = 1001;
COMMIT;
```

`FOR UPDATE` khóa hàng, đảm bảo chỉ một transaction cập nhật tại một thời điểm.

### **2\. Dùng Isolation Level cao hơn (**`REPEATABLE READ` hoặc `SERIALIZABLE`)

```sql
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
```

### **3\. Optimistic Locking (sử dụng cột** `version`)

```sql
UPDATE bank_accounts 
SET balance = balance - 1000, version = version + 1 
WHERE account_id = 1001 AND version = 1;
```

## **Race Condition trong MySQL**

Race Condition xảy ra khi <mark>thứ tự thực thi của các transaction ảnh hưởng đến kết quả cuối cùng</mark>.

### **Ví dụ Race Condition trong MySQL**

Giả sử có hệ thống đặt vé máy bay với bảng `tickets`:

```sql
CREATE TABLE tickets (
    ticket_id INT PRIMARY KEY,
    available INT
);
INSERT INTO tickets (ticket_id, available) VALUES (123, 1);
```

Hai người dùng **A** và **B** cùng đặt vé:

##### **Transaction của A**

```sql
START TRANSACTION;
SELECT available FROM tickets WHERE ticket_id = 123;  -- available = 1
UPDATE tickets SET available = available - 1 WHERE ticket_id = 123;
COMMIT;
```

##### **Transaction của B (chạy gần như cùng lúc)**

```sql
START TRANSACTION;
SELECT available FROM tickets WHERE ticket_id = 123;  -- available = 1
UPDATE tickets SET available = available - 1 WHERE ticket_id = 123;
COMMIT;
```

**<mark>Lỗi xảy ra:</mark>** **<mark>Cả A và B đều mua được vé dù chỉ có 1 vé</mark>**

## **Cách khắc phục Race Condition trong MySQL**

### **1\. Sử dụng Locking (**`SELECT FOR UPDATE`)

```sql
START TRANSACTION;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
IF stock > 0 THEN
  UPDATE products SET stock = stock - 1 WHERE id = 1;
  INSERT INTO orders (product_id) VALUES (1);
END IF;
COMMIT;
```

**Giữ khóa trên hàng dữ liệu**, đảm bảo chỉ một giao dịch cập nhật vé tại một thời điểm.

### **2\. Dùng Atomic Operation với** `CAS - Compare And Swap`

```sql
UPDATE tickets 
SET available = available - 1 
WHERE ticket_id = 123 AND available > 0;
```

**Chỉ cập nhật nếu vé vẫn còn (**`available > 0`), tránh tình trạng overbooking.

### **3\. Sử dụng Queue (Message Queue như Kafka, RabbitMQ)**

* Gửi yêu cầu đặt vé vào một hàng đợi.
    
* Xử lý từng yêu cầu một cách tuần tự để đảm bảo không có Race Condition.
    

## **So sánh Data Race và Race Condition trong MySQL**

| **Tiêu chí** | **Data Race** | **Race Condition** |
| --- | --- | --- |
| **Nguyên nhân** | Nhiều transaction ghi vào cùng dữ liệu mà không đồng bộ | Thứ tự thực thi ảnh hưởng đến kết quả, lỗi logic nghiệp vụ |
| **Hậu quả** | Dữ liệu bị ghi sai hoặc mất dữ liệu | Hành vi không mong muốn, có thể không bị lỗi ngay lập tức |
| **Gây ra do** | Transaction không khóa dữ liệu | Transaction xử lý không đúng thứ tự |
| **Ví dụ** | Cập nhật số dư tài khoản sai | Đặt vé máy bay quá số lượng thực tế. Bán 1 món hàng cho 2 người |
| **Cách khắc phục** | Locking, Isolation Level, Optimistic Locking | Locking, Atomic Operation, Queue |
