よくあるやつですが、諸々の事情で取り合えず書いときます。
めっちゃ簡略化したシナリオ
分離レベルはデフォルトの read committed
入庫と出庫 + 在庫を更新するケースで、
しかも update で現在値を計算するのではなく直接設定するケース。
※update 在庫 set 在庫数 = @計算後の値 where 商品Id = @商品Id みたいな update*1
1.お互いが関係無い商品なら
| 時系列 | 入庫 | 出庫 |
|---|---|---|
| 1 | begin tran | begin tran |
| 2 | 在庫取得 | 在庫取得 |
| 3 | 入庫データ追加 | 出庫データ追加 |
| 4 | 在庫更新 | 在庫更新 |
| 5 | commit | commit |
ちがう商品なのでロックの競合もなく、普通に正しく更新できる。
2.同じ商品なら
| 時系列 | 入庫 | 出庫 |
|---|---|---|
| 1 | begin tran | begin tran |
| 2 | 在庫取得 (Sロック※すぐ外れる) | 在庫取得 (Sロック同時OK + ※すぐ外れる) |
| 3 | 入庫データ追加 | 出庫データ追加 |
| 4 | 在庫更新 (Xロック) | 在庫更新 (Xロックは同時無理でロック待ち) |
| 5 | commit | - |
| 6 | - | 在庫更新 (入庫が終わったから処理出来る) |
| 7 | - | commit |
という流れになって、出庫での在庫更新が入庫の内容を反映せずに更新してしまう。。
※しつこく書くけど、update が 在庫数 = 在庫数 - @出庫数 なら問題ない!!
こういうのを防ぐには、検索時に更新ロック取ろうねって話しです。
3.同じ商品でも検索時に更新ロックを取ると…
| 時系列 | 入庫 | 出庫 |
|---|---|---|
| 1 | begin tran | begin tran |
| 2 | 在庫取得 (Uロック) | 在庫取得 (Uロックは同時無理でロック待ち) |
| 3 | 入庫データ追加 | - |
| 4 | 在庫更新 (Xロック) | - |
| 5 | commit | - |
| 6 | - | 在庫取得 (入庫が終わったからUロック取れる) |
| 7 | - | 出庫データ追加 |
| 8 | - | 在庫更新 (Xロック) |
| 9 | - | commit |
これで競合せずに出来るよねー
ドキュメントはここらへんで
SQL Server トランザクションのロックおよび行のバージョン管理ガイド - SQL Server | Microsoft Docs
*1:在庫数 = 在庫数 - @出庫数 なら問題でない