次のようなジョブテーブルがあります。
CREATE TABLE [dbo].[Clearing](
[Skey] [decimal](19, 0) IDENTITY(1,1) NOT NULL,
[BsAcctId] [int] NULL,
[Status] [varchar](20) NULL,
CONSTRAINT [csPk_Clearing] PRIMARY KEY CLUSTERED ( [Skey] ASC )
)
このようなカバリングインデックス
CREATE NONCLUSTERED INDEX [IX_Status] ON [dbo].[Clearing]
(
[Status] ASC
)
INCLUDE ( [Skey], [BsAcctId])
このクエリを使用して次のジョブを選択します
select top (1) Skey, BsAcctId, Status from Clearing with ( readpast, updlock )
where (Clearing.Status = 'NEW')
order by Clearing.Skey
(実際のテーブルには約10の列があります。これらはすべて、インデックスのinclude()句と選択列リストに含まれています。)
実行計画は非常に簡単です。 IX_Statusを使用してインデックスシークを行い、次にトップオペレーターを使用します。インデックスは(status、skey)で並べ替えられているため、プランでは並べ替えは必要ありません。
テーブルは、AlwaysOn可用性グループのデータベースにあります。グループには2つのDBサーバーがあります。 (テストシステムです。)
通常、このテーブルとクエリは適切に機能します。そこで、Windowsの更新プログラムを適用し、通常どおり実行します。
2番目のフェイルオーバーが発生し、すべてのワーカープロセスが新しいプライマリへの新しい接続を取得すると、複数のプロセスが同じジョブを取得し始めるという意味で、クエリが失敗し始めます。
問題は負荷に関連しています。 4つのワーカープロセスが実行されていると、それは起こりませんでした。しかし、10人の労働者の場合、それは一貫して起こります。
これはSQL Server 2016 Enterpriseを使用しています。ある時点で実行プランがおかしいかどうかを確認するクエリストアを有効にしていません。
2回のフェイルオーバー後にクエリが失敗し始める理由について何か提案はありますか?
クエリはインデックスのみを使用しており、テーブルに触れていないため、UPDLOCKは信頼できますか?
更新1-選択を行った直後に(sp_lock @@ spidを使用して)spidが保持しているロックを一覧表示するようにプロセスを変更しました。同じskeyの場合、IX_Statusインデックス(indid = 9)で異なるKEYロックが保持されています。
KEY (aad9d6e672f9) U
KEY (154698b9131c) U
Update 2-インデックスヒントを使用しても効果がありませんでした。
更新3-クエリのorder by句を削除すると問題が回避されました。しかし、注文が必要な同じ問題を持つ2番目のテーブルがあります。
更新4-ワーカープロセスはdb接続プールを維持します。 ODBCはフェイルオーバーが発生したことを通知しないため、古いプライマリへの接続は、それらを使用しようとして失敗するまでプールに留まります。DB1をフェイルオーバーした後、DB2-> DB2- > DB1の場合、DB1への古い接続は通常どおり失敗しない場合があります。1つの接続が失われた後、プールされたすべての接続を閉じるように変更しました。これにより問題が回避されたようです(SQL Server ODBCこの疑惑を煽る「接続復元力」機能が追加されました。)
Davidが 彼の回答 で言及しているように、異なるインデックスを介してその行にアクセスした場合、複数のセッションから同じ行を取得できます。
UPDLOCK
ヒントは、特定のアクセス方法にのみ適用されます。非クラスター化インデックス行U
をロックしても、別のクエリがdifferentインデックス(を含む)でU
ロックを取得するのを防ぐことはできませんクラスタ化インデックス(ある場合)。
異なるセッションからこれら2つのクエリ(インデックスヒントを使用)を実行すると、同じ行が返されます。
-- Session 1
BEGIN TRANSACTION;
SELECT TOP (1) Skey, BsAcctId, Status
FROM dbo.Clearing WITH(READPAST, UPDLOCK, INDEX(2))
WHERE ([Status] = 'NEW')
ORDER BY Skey;
-- Session 2
BEGIN TRANSACTION;
SELECT TOP (1) Skey, BsAcctId, Status
FROM dbo.Clearing WITH(READPAST, UPDLOCK, INDEX(1))
WHERE ([Status] = 'NEW')
ORDER BY Skey;
フェイルオーバー後、受信クエリに対して新しい実行プランがコンパイルされます。これにより、フェイルオーバー後に新しい動作が発生した理由を説明できます。デビッドも言ったように、この問題を回避するためにインデックスを強制することができます。
補足として、ROWLOCK
は行の粒度で取得されたロックのみをスキップできるため、READPAST
ヒントも使用する必要があります。
あなたもこれを述べました:
問題は負荷に関連しています。 4つのワーカープロセスが実行されていると、それは起こりませんでした。しかし、10人の労働者の場合、それは一貫して起こります。
したがって、変更されたのはフェイルオーバーだけではなかったようです。アプリケーション側の並行性も向上しました。
私はあなたのテーブル/インデックスをいくつかのデータでロードしようとしました:
INSERT INTO dbo.Clearing
([Status])
SELECT TOP 100
'NEW'
FROM master.dbo.spt_values;
INSERT INTO dbo.Clearing
([Status])
SELECT TOP 10000
'COMPLETE'
FROM master.dbo.spt_values v1
CROSS JOIN master.dbo.spt_values v2;
次に、クエリと共にSQLクエリストレスをロードし、100ミリ秒ごとに一度に10スレッドで実行するように設定します。
それが実行されている間、SSMSの最後にEXEC sp_lock @spid1 = @my_spid;
を追加して、同じクエリを定期的に実行しました。同じセッションでSELECT
クエリを(ロールバックせずに)複数回実行すると、そのセッションが保持する複数のロックを取得できます。
%%lockres%%
述語を使用して確認できるもの:
SELECT * FROM dbo.Clearing WITH (NOLOCK, INDEX(2)) WHERE %%lockres%% = '(36aaaeef6267)';
SELECT * FROM dbo.Clearing WITH (NOLOCK, INDEX(2)) WHERE %%lockres%% = '(84d6be32a10d)';
同時実行性がなければ、セッションでSELECT
を複数回実行すると、通常は同じ行が取得されます。ただし、他のクエリは常にロックを取得および解放するため、さまざまな行を簡単に取得できます。したがって、同じIDを2回返すSELECT
に依存していないことを確認してください(ワークロードのコンテキスト全体を持っているわけではないので、これは単なる推測/ FYIです)。
取得する特定のロックのみに依存するのは危険な場合があります。 Paul White(またはそのような別のブログ)からのこのブログ投稿に記載されている最適化を検討してください。 共有ロックが見つからない場合
この投稿では、Xロックによって保護されている行がSELECT
クエリで引き続き読み取れる状況について概説しています。
SQL Serverには、適切な状況で行レベルの共有(S)ロックを取得しないようにする最適化が含まれています。特に、コミットされていないデータがなくてもコミットされたデータを読み取るリスクがない場合は、共有ロックをスキップできます。
そのクエリを満たすことができる2つの異なるインデックスがあります。したがって、2つの異なるプランを実行する2つのクエリは、それぞれ異なるインデックスのキーをロックする可能性があります。
クエリでインデックスを強制してみてください。