SQL Server 2005 でテーブルをロックするにはどうすればよいですか? また、ロックする必要がありますか?
-
09-06-2019 - |
質問
これについては少し説明が必要です。私が行ったのは、SQL Server 2005 に特定のカスタム メッセージ キューを作成することです。確認応答と完了の両方のタイムスタンプを含むメッセージを含むテーブルがあります。呼び出し元がキュー内の次のメッセージを取得するために実行するストアド プロシージャも、メッセージを確認応答します。ここまでは順調ですね。さて、システムで大量のトランザクション (1 分あたり数千件) が発生している場合、ストアド プロシージャの別の実行によってメッセージが確認応答される準備ができている間に、メッセージが確認応答される可能性はありませんか?ストアド プロシージャ内の SQL コードを示してみましょう。
--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId
--Select the entire message
...
...
上記のコードでは、同時に実行されている別のストアド プロシージャが同じ ID を取得し、同時にそれを確認しようとすることはできないでしょうか?別のストアド プロシージャがクエリしているメッセージを別のストアド プロシージャが認識できないようにするために、ある種のロックを実装できますか (実装する必要がありますか)。
うわー、これには意味があったでしょうか?言葉で言うのはちょっと難しいんですが…
解決
このようなもの
--Grab the next message id
begin tran
declare @MessageId uniqueidentifier
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands with(holdlock, updlock);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId
-- some error checking
commit tran
--Select the entire message
...
...
他のヒント
このような状況のようです OUTPUT
役に立つかもしれません:
-- Acknowledge and grab the next message
declare @message table (
-- ...your `ActionMessages` columns here...
)
update ActionMessages
set AcknowledgedTime = getdate()
output INSERTED.* into @message
where ActionMessageId in (select top(1) ActionMessageId from UnacknowledgedDemands)
and AcknowledgedTime is null
-- Use the data in @message, which will have zero or one rows assuming
-- `ActionMessageId` uniquely identifies a row (strongly implied in your question)
...
...
そこで、同じ操作で行を更新して取得し、クエリ オプティマイザーに伝えます。 その通り 私たちが行っていることは、できる限り粒度の高いロックを選択し、可能な限り短時間でそれを維持できるようにすることです。(ただし、列の接頭辞は INSERTED
, OUTPUT
トリガーのようなもので、 UPDATE
行を削除して新しい行を挿入するようなものです。)
あなたについてもっと詳しい情報が必要です ActionMessages
そして UnacknowledgedDemands
テーブル (ビュー/TVF/その他)、SQL Server の自動ロックに関するより深い知識は言うまでもなく、 and AcknowledgedTime is null
という条項が必要です。これは、サブ選択と更新の間の競合状態を防ぐためにあります。から選ぶなら確かに必要ないのですが ActionMessages
それ自体 (例: where AcknowledgedTime is null
とともに top
で update
, のサブ選択の代わりに、 UnacknowledgedDemands
)。たとえ不必要であっても無害であることを期待します。
ご了承ください OUTPUT
SQL Server 2005 以降に存在します。それが使用しているとのことですが、高齢者の SQL Server 2000 インストールとの互換性が必要な場合は、別の方法を選択する必要があります。
@キルホッファー:
SQL バッチ全体は実行前に解析されるため、SQL はテーブルの選択だけでなくテーブルの更新も行うことを認識します。
編集:また、SQL は必ずしもテーブル全体をロックするとは限りません。必要な行のみをロックすることもできます。見る ここ SQL サーバーでのロックの概要については、「SQL Server でのロックの概要」を参照してください。
明示的なロックは、SQL Server によって希望よりも高い粒度にエスカレートされることがよくありますが、その代わりに、次のアプローチを試してみてはいかがでしょうか。
declare @MessageId uniqueidentifier
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId and AcknowledgedTime is null
if @@rowcount > 0
/* acknoweldge succeeded */
else
/* concurrent query acknowledged message before us,
go back and try another one */
ロックが少ないほど、同時実行性が高くなります。
本当に 1 つずつ処理する必要がありますか?SQL Server に今日の日付を持つすべての未確認メッセージを確認して返すようにするべきではないでしょうか。(もちろんすべてトランザクション内でも)
コードをトランザクションでラップすると、SQL サーバーが適切な行またはテーブルのロックを処理します。
begin transaction
--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId
commit transaction
--Select the entire message
...