再現シナリオ:
CREATE TABLE test (
ID int IDENTITY(1,1),
mykey nvarchar(255) NOT NULL,
exp_date datetime,
PRIMARY KEY (ID));
GO
CREATE INDEX not_expired_keys ON test (exp_date, mykey);
INSERT INTO test (mykey, exp_date) VALUES ('A', NULL);
トランザクション1を開始します。
-- add key B
BEGIN TRANSACTION;
INSERT INTO test (mykey, exp_date) VALUES ('B', NULL);
...
次にトランザクション2を並行して実行します。
-- expire key A
BEGIN TRANSACTION;
UPDATE test SET exp_date = GETDATE() WHERE exp_date IS NULL AND mykey = 'A'; -- <-- Blocking
ROLLBACK;
結局のところ、トランザクション1のコミットされていないINSERTは、トランザクション2のUPDATEをブロックしますが、それらは行のばらばらのセットに影響します(mykey = 'B'
とmykey = 'A'
)。
観察:
READ UNCOMMITTED
でも発生します。mykey
に一意のインデックスを配置すると、ブロッキングがなくなります。残念ながら、キーの有効期限が切れるとキー名を再利用できるので、それはできません。私の質問:
(好奇心から:) READ UNCOMMITTED
レベルでも、これらのステートメントが互いにブロックしているのはなぜですか?
それらを互いにブロックしないようにする簡単で信頼できる方法はありますか?
実行計画を見てみましょう。
最初のクエリ-挿入
BEGIN TRANSACTION;
INSERT INTO test (mykey, exp_date) VALUES ('B', NULL);
SQLサーバーがクラスター化インデックスの挿入操作を実行していることがわかります。
更新を見てみましょう
BEGIN TRANSACTION;
UPDATE test
SET exp_date = GETDATE()
WHERE exp_date IS NULL AND mykey = 'A' -- <-- Blocking
SQL Serverは、テーブルのクラスター化インデックスをスキャンし、一致する行を見つけるために別のインデックスを選択できる場合でも、テーブルにUロックをかけます。その理由は、テーブルに1行しかないためです。SQLServer Optimizerは、非クラスター化インデックスのデータを検索するよりも、クラスター化インデックスをスキャンする方が簡単です。
しかし、SQLサーバーで非クラスター化インデックスを使用するように強制するとどうなるでしょうか。
BEGIN TRANSACTION;
UPDATE test
SET exp_date = GETDATE()
FROM test WITH(INDEX = not_expired_keys)
WHERE exp_date IS NULL AND mykey = 'A' -- <-- No Blocking!!!
テーブルに行を追加すると、SQL Serverは更新する必要のある行を見つけるために非クラスター化インデックスを選択し、ブロッキングは発生しないと思います。