🎯 什麼情境該想到我
當你的流程是「寫 DB + 發一則 MQ / event」,而兩者可能不同步時(訊息比 commit 早到、或 commit 成功但發送失敗)。
症狀:worker 收到 message,去 DB 卻查不到對應資料。
⚙️ 怎麼用(三個層次,由淺到深)
- 最小修法:把發送移出 transaction,commit 成功後才發。
→ 仍留邊界:commit 成功但 send 失敗(crash/網路),訊息遺失。const order = await prisma.$transaction(tx => tx.order.create({ data })) await sendMessageToMq(order.id) // commit 之後才發送 - Outbox Pattern(嚴謹解):把「業務寫入」與「事件記錄」放同一個 transaction:
→ DB commit 與 event 產生變成原子操作,要嘛都有要嘛都沒有。await prisma.$transaction(async tx => { const order = await tx.order.create({ data }) await tx.outbox.create({ data: { topic: "order.created", payload: { orderId: order.id } } }) }) // 另有獨立 relay(polling 或 CDC)讀 outbox 表再發到 MQ
🧪 我實際套用的紀錄
- 2026-07-13:(待填)
⚠️ 注意 / 什麼時候不適用
- 反面教材:不要在 DB transaction 內直接發 MQ(commit 前發訊息 = 時序災難)。
- 上游修好後,下游仍要防禦,見 工具-MQ消費端防禦三原則。
🔗 相關工具
- 工具-MQ消費端防禦三原則 —— 消費端的配套:Outbox 從源頭保證一致,但 consumer 仍要防重複投遞與 replica 延遲
- 工具-區分修復與掩蓋症狀 —— 為什麼要用 Outbox 而不是在 consumer 加 retry:這張給你判斷「治本 vs 治標」的標準