技術筆記
Laravel SQL Server Queue

Laravel Queue 在 SQL Server 上遇到的 deadlock,怎麼排查

問題

專案的佇列表(jobs table)建在 SQL Server 上,某天開始陸續收到

SQLSTATE[40001]: Serialization failure: 1205 Transaction (Process ID xx) was deadlocked on lock resources with another process and has been chosen as the deadlock victim.

worker 數量調到 4 個之後才開始出現,單一 worker 完全沒事。

排查

先用 SQL Server 內建的 deadlock graph 確認卡住的兩個查詢,發現都是 Laravel Queue 的 reserveJobsByPollTime 在搶同一批 jobs 資料列。SQL Server 預設的 READ COMMITTED 搭配 UPDLOCK 在高併發搶鎖時,很容易兩個 transaction 互相等對方釋放鎖。

MySQL 上用 SELECT ... FOR UPDATE SKIP LOCKED 完全沒問題,但 SQL Server 的 Laravel 佇列 driver 對 SKIP LOCKED 語法支援不完整,這是這次問題的根本原因。

解法

兩個方向都用上了:

  1. queue.php 裡對應連線的 retry_after 拉高,減少同一個 Job 被重複搶的機率。
  2. 資料庫連線層加上 READPAST hint,讓 worker 搶鎖時直接跳過已被鎖住的資料列, 而不是等待,行為上更接近 MySQL 的 SKIP LOCKED

改完之後同樣的併發量下,deadlock 訊息就沒再出現過。如果專案一開始就能選 MySQL 或 Redis 當佇列後端,其實可以少踩這個雷,但既有系統要接公部門的 SQL Server,這條路還是得走一次。