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
- Giữ transaction ngắn — Tránh block các client khác quá lâu
- Luôn dùng WATCH cho operations phụ thuộc vào giá trị hiện tại
- Implement retry logic — Khi WATCH detect conflict
- Dùng Lua scripts — Cho complex atomic operations
- 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
