web-dev-qa-db-ja.com

「無関係」のINSERTとUPDATEが互いにブロックしている

再現シナリオ:

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レベルでも、これらのステートメントが互いにブロックしているのはなぜですか?

  • それらを互いにブロックしないようにする簡単で信頼できる方法はありますか?

4
Heinzi

実行計画を見てみましょう。

最初のクエリ-挿入

BEGIN TRANSACTION;
INSERT INTO test (mykey, exp_date) VALUES ('B', NULL);

そしてその実行計画 enter image description here

SQLサーバーがクラスター化インデックスの挿入操作を実行していることがわかります。

更新を見てみましょう

BEGIN TRANSACTION;
UPDATE test 
SET exp_date = GETDATE() 
WHERE exp_date IS NULL AND mykey = 'A' -- <-- Blocking

そしてその実行計画 enter image description here

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!!!

そしてその実行計画 enter image description here

テーブルに行を追加すると、SQL Serverは更新する必要のある行を見つけるために非クラスター化インデックスを選択し、ブロッキングは発生しないと思います。