ข้ามไปยังเนื้อหา

Transactions

Transaction ใน Redis คือลำดับคำสั่งที่ถูก จัดคิว แล้วรันทั้งหมดเป็นบล็อกเดียวโดยไม่ถูกขัดจังหวะ ไม่มีคำสั่งของ client อื่นสามารถแทรกเข้าระหว่างคำสั่งที่จัดคิวไว้เมื่อ EXEC ถูกเรียก

คุณเปิด transaction ด้วย MULTI จากนั้นออกคำสั่ง (คำสั่งถูกจัดคิว ยังไม่รันทันที) แล้วยิงทั้งหมดด้วย EXEC หากต้องการยกเลิก queue ให้ใช้ DISCARD

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET balance 100
QUEUED
127.0.0.1:6379> DECRBY balance 30
QUEUED
127.0.0.1:6379> GET balance
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (integer) 70
3) "70"

สังเกต: ขณะอยู่ใน MULTI ทุกคำสั่งจะตอบกลับว่า QUEUED แทนผลลัพธ์จริง ผลลัพธ์จริงจะมาเป็นรายการเมื่อ EXEC ทำงาน

MULTI
SET balance 100
DECRBY balance 30
GET balance
EXEC

หากเปลี่ยนใจก่อนเรียก EXEC ให้ใช้ DISCARD เพื่อล้าง queue และออกจากบล็อก transaction

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET temp "draft"
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> EXISTS temp
(integer) 0
MULTI
SET temp "draft"
DISCARD
EXISTS temp

Redis transaction ไม่ rollback เมื่อเกิด command error มีสองประเภทของ error:

  • Syntax/type error ตอน queue (เช่น จำนวน argument ผิด) — จะยกเลิก transaction ทั้งหมด
  • Runtime error ระหว่าง EXEC (เช่น เรียก INCR กับค่าที่เป็น string) — Redis รันคำสั่งที่เหลือต่อไป เฉพาะคำสั่งนั้นที่ผิดพลาด
127.0.0.1:6379> SET mystr "hello"
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET k1 "a"
QUEUED
127.0.0.1:6379> INCR mystr
QUEUED
127.0.0.1:6379> SET k2 "b"
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) OK

k1 และ k2 ถูก set สำเร็จแม้ว่า INCR mystr จะผิดพลาด Redis ไม่ rollback คำสั่งที่สำเร็จแล้ว

WATCH ให้คุณทำ check-and-set (CAS): ดู key หนึ่งหรือหลาย key ก่อนเริ่ม transaction หาก key ที่ดูอยู่ถูก client อื่นแก้ไขก่อนที่ EXEC จะถูกเรียก transaction ทั้งหมดจะถูกยกเลิก และ EXEC จะคืน (nil) แทนรายการผลลัพธ์ โค้ดของคุณสามารถลองใหม่ได้

127.0.0.1:6379> SET stock 5
OK
127.0.0.1:6379> WATCH stock
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY stock 1
QUEUED
127.0.0.1:6379> EXEC
1) (integer) 4

หาก client อื่นเปลี่ยน stock ระหว่าง WATCH กับ EXEC การเรียก EXEC จะคืน (nil) และคุณต้องลองทั้งลำดับจาก WATCH ใหม่

UNWATCH ยกเลิก watch ทั้งหมดโดยไม่ยกเลิก connection

SET stock 5
WATCH stock
MULTI
DECRBY stock 1
EXEC
GET stock
ตัวเลือกBenefitCost
MULTI/EXEC transactionรับประกันว่าคำสั่งที่ queue ไว้จะรันต่อเนื่องโดยไม่ถูกขัดจังหวะไม่มี rollback — runtime error ในคำสั่งหนึ่งไม่ยกเลิกคำสั่งอื่นที่สำเร็จไปแล้ว
WATCH สำหรับ optimistic lockingตรวจจับ race condition ได้โดยไม่ต้อง lock resource จริง เหมาะกับ contention ต่ำต้องเขียน retry loop เอง และภายใต้ contention สูงอาจ retry ซ้ำหลายรอบ
Lua script แทน transaction เมื่อต้อง branchingได้ conditional logic แบบ atomic ที่ transaction ทำไม่ได้ซับซ้อนกว่า MULTI/EXEC และต้องเขียน Lua แยกต่างหาก
  • คาดหวังว่า MULTI/EXEC จะ rollback เหมือน SQL transaction — Redis transaction ไม่มี rollback runtime error ในคำสั่งหนึ่งจะไม่ยกเลิกคำสั่งอื่นที่สำเร็จไปแล้วใน EXEC เดียวกัน ต้องตรวจสอบผลลัพธ์แต่ละคำสั่งเอง
  • ลืมใช้ WATCH เมื่อทำ check-then-set ที่มี race condition — ถ้าอ่านค่าก่อนแล้ว MULTI/EXEC เขียนทีหลังโดยไม่ WATCH key ที่อ่าน client อื่นอาจแก้ไขค่าระหว่างนั้นได้โดยไม่มีการตรวจจับ
  • ไม่มี retry logic เมื่อ EXEC คืน (nil) — เมื่อ key ที่ WATCH ไว้ถูกแก้ไขก่อน EXEC transaction จะถูกยกเลิกและคืน (nil) โค้ดฝั่ง client ต้อง retry ทั้งลำดับตั้งแต่ WATCH ใหม่ ไม่ใช่ปล่อยผ่านเงียบ ๆ

💡 ตัวอย่างจากของจริง

ระบบ inventory/stock แบบง่าย — ใช้ WATCH บน key จำนวนสินค้าคงเหลือ ก่อนลดจำนวนด้วย MULTI/EXEC เพื่อป้องกันการขายเกินสต็อกเมื่อมีคำสั่งซื้อพร้อมกันหลายคำสั่ง

Sidekiq และ background job framework อื่น ๆ — ใช้ pattern คล้าย WATCH + MULTI/EXEC เพื่อทำ optimistic concurrency control เมื่อ worker หลายตัวแย่งกันหยิบงานจาก queue เดียวกัน

คำสั่ง Redis คืนค่าอะไรขณะอยู่ใน MULTI block?
เกิดอะไรขึ้นเมื่อเรียก EXEC และ client อื่นเปลี่ยน key ที่ดูอยู่?
Runtime error ในคำสั่งหนึ่งใน EXEC (เช่น INCR กับ string) ทำให้เกิดอะไร?
คำสั่งใดยกเลิก transaction ที่จัดคิวไว้โดยไม่รัน?