SQL: 設計そのものが競合バグを招く
SQL: Incorrect by Construction
SQLの設計は、一見合理的なコードでも深刻な並行処理バグを招きやすい。トランザクションの原子性不足、TOCTOU問題、デッドロックなど、単純な送金処理ですら修正に多大な手間がかかる。私はRustのような「安全な並行処理」をデフォルトにする新しいデータベースツールが必要だと主張する。
SQLプログラムは完全に合理的に見えるのに、深刻なバグで溢れている。
HNでの議論
40- tcp_handshaker
投稿は、SQL Server のロックベースの READ COMMITTED デフォルト設定の下での初心者的な T-SQL を示し、それを「SQL は設計上誤っている」と一般化しています。
結論はずっと狭い範囲であるはずです。同じ行への二重引き落としについては、RCSI だけでなく、実際に有効化され使用されている真の SNAPSHOT 隔離レベルを使用すれば、敗者は更新コンフリクト/リトライになります。SQL の慣習的な修正方法は、条件付きの引き落とし UPDATE、行数チェック、制約、そして転送のための 1 つのトランザクションです。
デッドロックの例も誇張されています。SQL Server はサイクルを検出し、犠牲者をロールバックしてエラー 1205 [1] を返し、アプリケーションはそれをリトライすることが期待されます。
Microsoft のデフォルト設定は最悪で、著者はそれをずっと広い主張に変えてしまいました..
[1] - "Your transaction (process ID #...) was deadlocked on {lock | communication buffer | thread} resources with another process and has been chosen as the deadlock victim. Rerun your transaction"
"Deadlocks guide" - https://learn.microsoft.com/en-us/sql/relational-databases/s...
- hilariously
一般的には、ここで一連の更新ではなく、追記専用データ構造を推奨します。
BEGIN TRANSACTION;
IF EXISTS (
SELECT 1
FROM account_balances WITH (UPDLOCK, HOLDLOCK)
WHERE owner = 'alice'
AND balance >= 10
)
BEGIN
INSERT INTO account_ledger (owner, amount, memo)
VALUES
('alice', -10, 'transfer to bob'),
('bob', 10, 'transfer from alice');
END
COMMIT TRANSACTION;
あるいは、ロックを既に取得しているため更新パターンを使用したい場合は、
BEGIN TRANSACTION;
UPDATE accounts WITH (UPDLOCK)
SET balance = balance - 10
WHERE owner = 'alice'
AND balance >= 10;
IF @@ROWCOUNT = 1
BEGIN
UPDATE accounts
SET balance = balance + 10
WHERE owner = 'bob';
END
COMMIT TRANSACTION;
- crazygringo
これはまさに、トランザクションとロックに関する SQL 101 です。これらはデータベースにおける基本的な初歩的な概念です。
「設計上誤っている」ということはありません。
著者は元のスニペットが「完全に妥当に見える」と主張していますが、クライアント - サーバーデータベースについて少しでも知っていれば、それは絶対にそうではありません。