通用技術面試:Serializable 隔離保證什麼,為什麼交易仍會重試?
題幹與適用場景
面試官給你兩個並發交易:它們讀取相同業務規則,卻更新不同記錄。兩邊都看到「名額足夠」,最後整體狀態違反約束。請解釋較弱隔離級別會發生什麼、Serializable 如何阻止結果,以及應用程式如何處理失敗。
這道題考察資料庫並發控制的概念邊界。回答應區分快照可見性、鎖等待、衝突偵測和業務重試,不能只背四個隔離級別名稱。
面試官考察什麼
面試官關注你能否用一個具體交錯順序解釋異常,能否區分「結果等價於某個序列執行」和「所有交易真的排隊執行」,以及能否說明失敗是正確保護的一部分。Amazon 的產品面試指引要求候選人深入指標與決策依據;技術回答同樣需要把保證、代價和應用動作說清楚。
回答前要釐清的問題
先問資料庫實作、預設隔離級別、讀寫模式、約束是否能由資料庫表達,以及交易失敗後呼叫端能否安全重做。PostgreSQL 的 Repeatable Read 使用交易快照;Serializable 在此基礎上偵測可能的序列化異常並終止其中一個交易。不同資料庫的實作和預設行為不能直接互換。
30 秒回答框架
先給結論:Serializable 要求已提交結果等價於某個序列執行,但不代表交易全部串行排隊。再用兩個交易的寫偏斜交錯說明 Repeatable Read 的邊界,接著說明 Serializable 如何偵測危險衝突並回傳序列化失敗。最後給出重試整個交易、限制次數、保持冪等和監控衝突率的做法。
分步驟深入分析
第一步:用交錯順序描述異常
假設規則是「至少一名值班醫生保持在崗」。交易 A 讀取醫生 B 在崗,交易 B 讀取醫生 A 在崗;A 將自己設為休假,B 也將自己設為休假。每次交易都只更新自己的資料列,因此沒有直接寫寫衝突,卻讓最終狀態變成無人值班。這是寫偏斜,說明每個交易看到的快照正確,不代表組合後的業務不變量被保留。
第二步:區分快照穩定性與序列等價
Repeatable Read 讓同一交易的查詢看到穩定快照,不讀取後來提交的並發變更;它不保證所有讀寫集合都能排列成一個滿足業務規則的序列順序。快照隔離通常提高並發,但應用程式必須知道它保護了哪些異常、沒有保護哪些異常。
第三步:說明 Serializable 的保證
Serializable 的目標是讓已提交結果等價於某個一次只執行一個交易的順序。實作可以透過鎖、衝突偵測或 Serializable Snapshot Isolation 完成,不等於每次讀取都阻塞其他交易。PostgreSQL 會監控讀寫相依,發現可能形成序列化異常時讓一個交易失敗。
第四步:解釋為什麼要重試整個交易
序列化失敗發生在提交或接近提交時,資料庫不能替你猜測業務程式碼是否可重做。應用程式應回滾目前交易,從讀取第一筆資料開始完整重跑,而不是只重送最後一個 UPDATE。重試應有上限和退避;外部副作用要放在提交後,或用冪等鍵和可靠事件記錄隔離。
第五步:比較代價與選擇
Serializable 可能增加衝突、重試、記憶體或鎖管理開銷,吞吐量也會受存取模式影響。對餘額、庫存、配額等強不變量場景,可接受更高隔離和重試成本;對唯讀報表或允許短暫不一致的查詢,較弱級別可能更合適。選擇要由不變量、並發度、延遲目標和失敗處理能力共同決定。
高品質示範回答
Serializable 的保證是:所有已提交交易的結果,都等價於某個序列執行順序;它不要求資料庫把交易全部排隊。以「至少一名醫生在崗」為例,兩個交易在 Repeatable Read 下都讀到另一名醫生在崗,然後分別把自己設為休假。它們沒有更新同一資料列,卻共同破壞不變量,這就是寫偏斜。
Serializable 會偵測這種讀寫相依可能形成的序列化異常,讓其中一個交易以 serialization failure 結束。應用程式必須回滾並從交易開始處重試,設定有限次數和退避;寄信、扣款等外部副作用應在提交後處理,或使用冪等鍵。對庫存、餘額和配額我會優先選擇能保護業務不變量的隔離級別;對允許近似的報表則評估較低級別的吞吐收益,並以監控驗證異常率。
常見錯誤與改進
- 說 Serializable 等於全域排隊:改為「結果等價於某個序列執行順序」。
- 只說加鎖:補充衝突偵測和資料庫實作差異。
- 只重試最後一條 SQL:必須從交易開始完整重跑。
- 忽略副作用:說明提交後事件、冪等鍵或去重表。
- 把 Repeatable Read 說成永遠沒有異常:明確寫偏斜或序列化異常仍可能存在。
追問及應對
Why can a read-only transaction still fail at Serializable?
Its reads can participate in dependencies that make another transaction’s committed result non-serializable. The database may abort a transaction to preserve the global guarantee; the application should treat the error as retryable when the operation is safe to repeat.
Should every request use Serializable?
No. Start with the business invariant and workload. Use the stronger level where an anomaly is unacceptable, and choose a weaker level where approximate reads are acceptable and the performance trade-off is measured.
Why must the retry include all reads?
The conflict decision depends on the complete read and write set. Replaying only the final write can use stale assumptions and recreate the same anomaly.
How do you observe whether retries are healthy?
Track serialization failures, retry success rate, retry latency, exhausted attempts, and user-visible errors by transaction type. A high retry rate can indicate a hot invariant or an access pattern that needs redesign.