Transactions trong Redis: MULTI/EXEC và WATCH

Photo of author

Văn Ngọc Tân

Transactions trong Redis cho phép nhóm nhiều lệnh thành một atomic unit — tất cả cùng thực thi hoặc không lệnh nào thực thi. Đây là chìa khóa cho các ứng dụng cần tính nhất quán dữ liệu.

Transactions là gì?

Redis transaction đảm bảo:

  • Atomic — Tất cả lệnh thực thi như một đơn vị
  • Sequential — Lệnh thực thi theo đúng thứ tự
  • Không rollback — Nếu lệnh giữa chừng lỗi, các lệnh trước vẫn đã thực thi
  • Không isolation — Client khác vẫn có thể đọc dữ liệu giữa transaction

MULTI / EXEC

Cú pháp cơ bản

# Bắt đầu transaction
MULTI
# OK

# Thêm lệnh vào queue
SET account:a 1000
# QUEUED

SET account:b 500
# QUEUED

# Thực thi tất cả
EXEC
# 1) OK
# 2) OK

Hủy transaction

MULTI
SET key "value"
# Thay đổi ý, hủy transaction
DISCARD
# OK

GET key
# (nil)  → lệnh SET không được thực thi

Lưu ý quan trọng

Đây là điểm mà nhiều Developer mới dễ hiểu nhầm nhất về Redis Transactions:

Redis Transaction KHÔNG có Rollback như SQL

Trong SQL (MySQL, PostgreSQL), nếu một lệnh trong transaction bị lỗi, bạn có thể ROLLBACK để trả dữ liệu về trạng thái cũ. Redis KHÔNG làm được điều này.

# SQL: Có ROLLBACK
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Nếu lỗi ở đây...
ROLLBACK;  -- Trả về trạng thái cũ, dữ liệu an toàn
COMMIT;

# Redis: KHÔNG có ROLLBACK
MULTI
DECRBY account:a 100
INCRBY account:b 100
EXEC
-- Nếu lệnh INCRBY chạy xong mà logic sai...
-- KHÔNG có cách nào undo, dữ liệu đã thay đổi rồi!

Hai trường hợp lỗi khác nhau

Trường hợp Hành vi Kết quả
Lỗi syntax (ví dụ: SET key không đủ tham số) Redis phát hiện ngay khi QUEUE → hủy cả transaction ✅ Không lệnh nào được thực thi
Lỗi logic (ví dụ: trừ tiền khi số dư = 0) Lệnh vẫn chạy thành công → KHÔNG rollback ❌ Dữ liệu đã bị thay đổi sai
# Ví dụ lỗi syntax → cả transaction bị hủy
MULTI
SET key1 "value1"
SET key2          # Sai syntax, thiếu value
EXEC
# (error) ERR wrong number of arguments for 'set' command
# → Cả 2 lệnh đều KHÔNG được thực thi

# Ví dụ lỗi logic → KHÔNG rollback
MULTI
DECRBY account:a 100   # account:a = 0, chạy xong → -100
INCRBY account:b 100   # Chạy xong → +100
EXEC
# Cả 2 lệnh đều thành công!
# account:a = -100 (sai logic, nhưng KHÔNG rollback)

WATCH giúp phát hiện conflict nhưng không tự rollback

WATCH chỉ reject transaction nếu dữ liệu bị thay đổi bởi client khác trước khi EXEC. Nó KHÔNG rollback — đơn giản là không thực thi transaction đó.

# WATCH: Phát hiện conflict, hủy transaction (chưa chạy)
WATCH account:a
# Client khác thay đổi account:a...
MULTI
DECRBY account:a 100
EXEC
# (nil) → Transaction bị hủy, KHÔNG có lệnh nào chạy
# Nhưng nếu EXEC đã chạy xong rồi → KHÔNG rollback được

WATCH: Optimistic Locking

WATCH giám sát keys — nếu có client khác thay đổi watched keys trước khi EXEC, transaction sẽ bị hủy.

Cách hoạt động

# Client A
WATCH account:a
GET account:a          # 1000
MULTI
DECRBY account:a 100
EXEC                 # Thành công nếu account:a chưa bị thay đổi
# OK

# Nếu Client B thay đổi account:a giữa WATCH và EXEC:
# Client B: SET account:a 500
# Client A: EXEC → (nil)  → Transaction bị hủy!

Ví dụ: Bank Transfer an toàn

def transfer(from_acc, to_acc, amount):
    retries = 3
    while retries > 0:
        try:
            # Watch account nguồn
            r.watch(from_acc)
            
            # Kiểm tra số dư
            balance = int(r.get(from_acc) or 0)
            if balance < amount:
                r.unwatch()
                return False, "Insufficient funds"
            
            # Thực hiện transfer trong transaction
            pipe = r.pipeline()
            pipe.multi()
            pipe.decrby(from_acc, amount)
            pipe.incrby(to_acc, amount)
            result = pipe.execute()
            
            if result:
                return True, "Transfer successful"
            
        except redis.WatchError:
            # Account bị thay đổi, retry
            retries -= 1
            continue
    
    return False, "Transfer failed after retries"

# Sử dụng
success, msg = transfer("account:a", "account:b", 100)

Pipeline vs Transaction

Tiêu chí Pipeline Transaction (MULTI/EXEC)
Mục đích Tối ưu network round-trips Đảm bảo atomic execution
Atomic ❌ Không ✅ Có
Thứ tự Không đảm bảo Đảm bảo
Can thiệp giữa chừng ✅ Có thể ❌ Không (với WATCH)
Use case Batch operations Critical operations
# Pipeline: Gửi nhiều lệnh, không atomic
pipe = r.pipeline()
pipe.set("key1", "val1")
pipe.set("key2", "val2")
pipe.get("key1")
results = pipe.execute()  # [True, True, b"val1"]

# Transaction: Gửi nhiều lệnh, atomic
pipe = r.pipeline()
pipe.multi()
pipe.set("key1", "val1")
pipe.set("key2", "val2")
pipe.get("key1")
results = pipe.execute()  # [True, True, b"val1"]

Lua Scripts: Atomic tốt hơn

Lua scripts cung cấp atomicity tốt hơn transactions vì chạy như một lệnh duy nhất:

-- Lua script: Atomic transfer
local from_balance = tonumber(redis.call("GET", KEYS[1]) or 0)
local amount = tonumber(ARGV[1])

if from_balance >= amount then
    redis.call("DECRBY", KEYS[1], amount)
    redis.call("INCRBY", KEYS[2], amount)
    return 1  -- Thành công
else
    return 0  -- Không đủ tiền
end
# Chạy Lua script
script = """
local from_balance = tonumber(redis.call("GET", KEYS[1]) or 0)
local amount = tonumber(ARGV[1])
if from_balance >= amount then
    redis.call("DECRBY", KEYS[1], amount)
    redis.call("INCRBY", KEYS[2], amount)
    return 1
else
    return 0
end
"""

result = r.eval(script, 2, "account:a", "account:b", 100)
# 1 = thành công, 0 = thất bại

Use Cases thực tế

1. Inventory Management

# Giảm stock và tăng sold atomically
MULTI
DECRBY product:555:stock 1
INCRBY product:555:sold 1
EXEC

2. Atomic Counters

# Cập nhật nhiều counters cùng lúc
MULTI
INCR stats:page:views
INCR stats:api:calls
INCRBY stats:bandwidth 1024
EXEC

3. Distributed Lock

# Acquire lock
WATCH lock:resource
if GET lock:resource is None:
    MULTI
    SET lock:resource "process-1" EX 30
    EXEC
    # Nếu OK → lock acquired
    # Nếu (nil) → lock bị chiếm bởi process khác

Best Practices

  1. Giữ transaction ngắn — Tránh block các client khác quá lâu
  2. Luôn dùng WATCH cho operations phụ thuộc vào giá trị hiện tại
  3. Implement retry logic — Khi WATCH detect conflict
  4. Dùng Lua scripts — Cho complex atomic operations
  5. Không rely vào rollback — Redis không có rollback như SQL

Kết thúc Series Redis Cơ bản

Chúc mừng! Bạn đã hoàn thành series Redis cơ bản. Bạn đã học được:

  • ✅ Redis là gì và tại sao nên dùng
  • ✅ Cài đặt Redis trên mọi nền tảng
  • ✅ Strings, Lists, Hashes — các cấu trúc dữ liệu chính
  • ✅ Keys & Expiration — quản lý vòng đời dữ liệu
  • ✅ Pub/Sub — giao tiếp real-time
  • ✅ Transactions — atomic operations

👉 Xem tất cả bài viết về Redis

Redis transactions MULTI EXEC atomic operations
Transactions đảm bảo các lệnh được thực thi nguyên tử
0 0 đánh giá
Đánh giá bài viết
Theo dõi
Thông báo của
guest
0 Góp ý
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất
Phản hồi nội tuyến
Xem tất cả bình luận