1

私は最近、このようなステートメントが主キーselect * from table with (rowlock updlock) where key=valueに対して実行されたときに、SQL サーバーが主キーに対してのみロックを発行することを拒否するという、かなり苛立たしい状況に遭遇しました。ここで誤解しないでください。行をロックしますが、さらに一歩進んでテーブルもロックします。

私は SQL ロックのエスカレーションについて読み、ロック ヒントで特定のインデックスを使用することを見てきましたが、何百万ものレコードと同時更新が必要なテーブルに多数のインデックスがある場合、これは実用的ではありません。それらのレコードで発生します。小さなテーブルと特定のクエリの場合、目的の動作を得ることができますが、テーブルの幅が広く (列が多く)、データを使用するプロセスが多数ある場合、この方法はうまく機能せず、実際の競合点になる可能性があります。

私が追加したいのは、行の主キーに対してロックを発行し、インデックス、テーブルスキャン、またはその他の方法が使用されるたびに、 PKLock (主キーロックの略) として新しいロックヒントサックです。行を取得すると、テーブル全体をロックする代わりに、このロックをチェックして尊重します。

このようなテーブル ロックを発行する必要がないため、DB に対するコードの並列実行の容量が大幅に増加します。

このアイデアについて検討し、潜在的な欠陥、改善できる方法、または私のジレンマを解決するために追加する必要があるその他の要素を指摘してください。

編集

@レムス

このクエリを実行すると

begin transaction
select lockname from locks  where lockname='A'
update Locks Set locked=1 where lockname='A'

そして、このクエリ:

begin transaction
select lockname from locks  where lockname='A'

どちらの例でも、トランザクションをコミットする前に行 A が返されます。これはブロックではなく、更新の背後にある読み取りです。

成功するソリューションでは、使用するインデックスを指定せずに次のことを行う必要があります。

  1. クエリ 1 の場合: A の読み取りとロック、A の更新
  2. クエリ 2 の場合: B の読み取りとロック、B の更新、クエリ 2 のコミット
  3. クエリ 2 の場合: B を読み取り、A のロックが解放されるまでブロックされる
  4. クエリ 1 の場合: クエリ 1 をコミットする
  5. クエリ 2 の場合: A の読み取りとロック、A の更新、クエリ 2 のコミット
4

2 に答える 2

3

あなたは以前にこの質問をし、答えを与えられました:スキーマとコードを修正してください。その投稿では、ロックの競合はIXロックであり、インテントロックの競合は、粒度の高いロックを示しています。これは、テーブルスキャンを示しています。ロックヒントは必要ありません。必要なのはインデックスと適切なクエリだけです。たとえば、他の質問を考えてみましょう。SQLサーバーで行レベルのロックが正しく機能していないように見えるのはなぜですか。答えは簡単です。ロックテーブルは、LockNameのクラスター化されたインデックスで整理する必要があります。

CREATE TABLE [dbo].[Locks]( 
    [LockName] [varchar](50) NOT NULL, 
    [Locked] [bit] NOT NULL, 
    CONSTRAINT [PK_Locks] PRIMARY KEY CLUSTERED  ([LockName]));
GO    

insert into Locks (LockName, Locked) values ('A', 0);
insert into Locks (LockName, Locked) values ('B', 0);
GO

1つのセッションでこれを行います:

begin transaction
update Locks 
  set Locked=1 
  output inserted.*
  where LockName = 'A';

他のセッションでは、これを行います。

begin transaction
update Locks 
  set Locked=1 
  output inserted.*
  where LockName = 'B';

更新の競合、ブロッキング、(間違った)ヒントの必要はなく、何もありません。ちょうど良いoleの正しいスキーマとクエリの設計。

ちなみに、ここで説明するロックはすでに存在し、キーロックと呼ばれます。これらは、SQLServerが動作するデフォルトの暗黙モードです。SQLServerが1秒あたり16000tpcトランザクションのTPC-Cベンチマーク数を公開できると世界でどのように想像しますか?サーバーに必要なすべての並列処理機能があります。使用方法を理解するには、1冊か2冊の本を読む必要があります。このテーマに関する文献はたくさんあります。トランザクション処理:概念と手法から始めることができます。

更新しました

begin transaction 
select lockname from locks  where lockname='A' 
update Locks Set locked=1 where lockname='A'

これは、ロックのヒントがいくつ/多様であっても、機能しません。これが、出力構文を含む更新がある理由です。

begin transaction 
update Locks 
 Set locked=1 
 output inserted.*
 where lockname='A'

これにより、最初に更新してから、更新したものを返すことが保証されます。この手法は、データベースでは、求めるセマンティクスであるリソースの取得に非常によく使用されます。実際、この手法は、リソース獲得ポスターの子であるキュー処理の基礎です。OUTPUT句の「キュー」段落を参照してください。キューには、処理するリソースのテーブルがあり、各スレッドは1つを取得してロックし、処理を開始します。

create table Resources (
   id int identity(1,1) not null,
   enqueue_time datetime not null default getutcdate(),
   is_processing bit not null default 0,
   payload xml);

create clustered index cdxResources on Resources 
  (is_processing, enqueue_time);
go   

-- enqueue:
insert into Resources (payload) values ('<do>This</do>');
insert into Resources (payload) values ('<do>That</do>');
insert into Resources (payload) values ('<do>Something</do>');
insert into Resources (payload) values ('<do>Anything</do>');

別々のセッションから、これを実行します。

--dequeue
begin transaction;
with cte as (
  select top(1) *
  from Resources with(readpast)
  where is_processing = 0
  order by enqueue_time)
update cte
  set is_processing = 1
  output inserted.*;   

各セッションがそれ自体のリソースを取得し、それをロックし、他のすべての人によってロックされているすべてのものをスキップするのがわかります。たまたま私はこのように動作するシステムを運用しており、テーブル内に500万を超えるリソース(Webサービスの支払い処理要求)があり、100個の同時プロセッサーから1秒あたり約50個のキューを取り出して処理しています(約2秒かかります)。プロセスの呼び出しごと)。ジャンクハードウェアの一部。だからそれは絶対に可能です。

于 2010-02-26T21:21:36.083 に答える
0

おそらく、ロックをまったく指定したくないのでしょうか?

http://blogs.msdn.com/davidlean/archive/2009/04/06/sql-server-nolock-hint-other-poor-ideas.aspx

于 2010-02-26T19:19:12.603 に答える