web-dev-qa-db-ja.com

SQL Serverを使用したSELECT FOR UPDATE

分離レベルREAD_COMMITTEDおよびREAD_COMMITTED_SNAPSHOT=ONのMicrosoft SQL Server 2005データベースを使用しています。

使用したい:

SELECT * FROM <tablename> FOR UPDATE

...他のデータベース接続が同じ行「FOR UPDATE」にアクセスしようとしたときにブロックするようにします。

私は試した:

SELECT * FROM <tablename> WITH (updlock) WHERE id=1

...ただし、これは「1」以外のIDを選択した場合でも、他のすべての接続をブロックします。

Oracle、DB2、MySqlで知られているSELECT FOR UPDATEを実行するための正しいヒントはどれですか?

EDIT 2009-10-03:

これらは、テーブルとインデックスを作成するステートメントです。

CREATE TABLE example ( Id BIGINT NOT NULL, TransactionId BIGINT, 
    Terminal BIGINT, Status SMALLINT );
ALTER TABLE example ADD CONSTRAINT index108 PRIMARY KEY ( Id )
CREATE INDEX I108_FkTerminal ON example ( Terminal )
CREATE INDEX I108_Key ON example ( TransactionId )

多くの並列プロセスがこれを行いますSELECT

SELECT * FROM example o WITH (updlock) WHERE o.TransactionId = ?

EDIT 2009-10-05:

より良い概要のために、私は次の表にすべての試みられた解決策を書き留めました:

メカニズム|異なる行ブロックでのSELECT |同じ行ブロックで選択
 ----------------------- + ---------------- ---------------- + -------------------------- 
 ROWLOCK |いいえ| no 
 updlock、rowlock |はい|はい
 xlock、rowlock |はい|はい
 repeatableread |いいえ| no 
 DBCC TRACEON(1211、-1)|はい|はい
 rowlock、xlock、holdlock |はい|はい
 updlock、holdlock |はい|はい
 UPDLOCK、READPAST |いいえ| no 
 
探しています|いいえ|はい
74
tangens

最近、SQL Serverが必要以上にロックするため(ページ)、 デッドロックの問題 が発生しました。あなたは本当にそれに対して何もすることはできません。今、デッドロック例外をキャッチしています...そして、代わりにOracleがあればいいのにと思います。

編集:一方、スナップショット分離を使用して、すべてではなく多くの問題を解決しています。残念ながら、スナップショット分離を使用できるようにするには、データベースサーバーで許可する必要があります。これは、顧客サイトで不必要な問題を引き起こす可能性があります。現在、デッドロック例外(もちろん発生する可能性があります)だけでなく、バ​​ックグラウンドプロセス(ユーザーが繰り返すことはできません)からトランザクションを繰り返すスナップショットの同時実行性の問題もキャッチしています。しかし、これは以前よりもはるかに優れたパフォーマンスを発揮します。

33

同様の問題があり、1行のみをロックしたい。私が知る限り、UPDLOCKオプションを使用すると、SQLSERVERは行を取得するために読み取る必要があるすべての行をロックします。そのため、行に直接アクセスするためのインデックスを定義しない場合、先行するすべての行がロックされます。あなたの例では:

idフィールドを持つTBLという名前のテーブルがあると仮定します。 id=10で行をロックします。フィールドID(または選択に関与する他のフィールド)のインデックスを定義する必要があります。

CREATE INDEX TBLINDEX ON TBL ( id )

そして、あなたが読んだ行のみをロックするクエリは次のとおりです:

SELECT * FROM TBL WITH (UPDLOCK, INDEX(TBLINDEX)) WHERE id=10.

INDEX(TBLINDEX)オプションを使用しない場合、SQLSERVERはid=10で行を見つけるためにテーブルの先頭からすべての行を読み取る必要があるため、これらの行はロックされます。

19
ManuelConde

スナップショットの分離と読み取りのブロックを同時に行うことはできません。スナップショット分離の目的は、prevent読み取りのブロックです。

7

おそらくmvccを永続化することで解決できます(特定のバッチのみとは対照的に:SET TRANSACTION ISOLATION LEVEL SNAPSHOT):

ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

[編集:10月14日]

これを読んだ後: SQL ServerよりもOracleの方が良い同時性? そしてこれ: http://msdn.Microsoft.com/en-us/library/ms175095.aspx

READ_COMMITTED_SNAPSHOTデータベースオプションがONに設定されている場合、オプションをサポートするために使用されるメカニズムはすぐにアクティブになります。 READ_COMMITTED_SNAPSHOTオプションを設定する場合、ALTER DATABASEコマンドを実行する接続のみがデータベースで許可されます。 ALTER DATABASEが完了するまで、データベースに他の開いている接続があってはなりません。データベースはシングルユーザーモードである必要はありません。

特定のデータベースでmssqlのMVCCを永続的にアクティブにするには、2つのフラグを設定する必要があるという結論に達しました。

ALTER DATABASE yourDbNameHere SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;
5
Michael Buen

試してください(updlock、rowlock)

5
BlueMonkMN

完全な答えは、DBMSの内部を掘り下げることができます。クエリエンジン(SQLオプティマイザによって生成されたクエリプランを実行する)の動作に依存します。

ただし、1つの可能な説明(一部のDBMSの少なくとも一部のバージョンに適用-MS SQL Serverに必ずしも適用されない)は、ID列にインデックスがないため、「WHERE id = ?」でクエリを実行しようとするプロセスその結果、テーブルの順次スキャンが実行され、その順次スキャンがプロセスが適用したロックにヒットします。 DBMSがデフォルトでページレベルのロックを適用する場合、問題が発生する可能性もあります。 1つの行をロックすると、ページ全体とそのページのすべての行がロックされます。

これをトラブルの原因として非難できる方法がいくつかあります。クエリプランを見てください。インデックスを調べます。 1ではなく1000000のIDでSELECTを試して、他のプロセスがまだブロックされているかどうかを確認します。

5

OK、1回の選択ではデフォルトで「Read Committed」トランザクション分離が使用され、ロックされてそのセットへの書き込みが停止されます。トランザクション分離レベルは次の方法で変更できます

Set Transaction Isolation Level { Read Uncommitted | Read Committed | Repeatable Read | Serializable }
Begin Tran
  Select ...
Commit Tran

これらは、SQL Server BOLで詳細に説明されています

次の問題は、ロックトランザクションで〜2500を超えるロックがあるか、「通常の」メモリの40%以上を使用すると、デフォルトでSQL Server 2K5がロックをエスカレートすることです。エスカレーションはページに進み、その後テーブルロック

「トレースフラグ」1211tを設定することにより、このエスカレーションをオフに切り替えることができます。詳細については、BOLを参照してください

3
TFD

アプリケーションロックは、「有用な」ロックエスカレーションを回避しながら、独自の粒度で独自のロックを実行する1つの方法です。 sp_getapplock を参照してください。

2
Constantin

偽の更新を作成して、行ロックを強制します。

UPDATE <tablename> (ROWLOCK) SET <somecolumn> = <somecolumn> WHERE id=1

それがあなたの列をロックしないなら、神は何をするかを知っています。

この「UPDATE」の後、SELECT (ROWLOCK)およびその後の更新を行うことができます。

2
Feu

この特定のクエリの実行中に、他のセッションが行を読み取れないようにする必要があると仮定しています...

WITH(XLOCK、READPAST)ロックヒントを使用しながらトランザクションでSELECTをラップすると、必要な結果が得られます。それらの他の同時読み取りがWITH(NOLOCK)を使用していないことを確認してください。 READPASTを使用すると、他のセッションは同じSELECTを他の行で実行できます。

BEGIN TRAN
  SELECT *
  FROM <tablename> WITH (XLOCK,READPAST) 
  WHERE RowId = @SomeId

  -- Do SOMETHING

  UPDATE <tablename>
  SET <column>=@somevalue
  WHERE RowId=@SomeId
COMMIT
2
ewoo

使用してみてください:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

これにより、ロックが排他的になり、トランザクションの間、ロックが保持されます。

1
RMorrisey

行ロックの問題をまったく別の方法で解決しました。私は、SQLサーバーがそのようなロックを満足のいく方法で管理できないことに気付きました。ミューテックスを使用して、プログラムの観点からこれを解決することを選択しました... waitForLock ... releaseLock ...

1
jessn

この記事 によると、解決策はWITH(REPEATABLEREAD)ヒントを使用することです。

1
erikkallen

コミット時に例外を処理し、トランザクションを繰り返す必要があります。

1
user205622

質問-このケースはロックエスカレーションの結果であることが証明されています(つまり、ロックエスカレーションイベントのプロファイラーでトレースする場合、それは間違いなくブロッキングの原因となっていますか?)その場合、ロックのエスカレーションを防ぐためにインスタンスレベルでトレースフラグを有効にすることにより、完全な説明と(やや極端な)回避策があります。 http://support.Microsoft.com/kb/3236 トレースフラグ1211を参照してください

しかし、それは意図しない副作用を持つ可能性があります。

行を意図的にロックし、長期間ロックしたままにする場合、トランザクションに内部ロックメカニズムを使用することは(少なくともSQL Serverでは)最良の方法ではありません。 SQL Serverの最適化はすべて、短いトランザクションを対象としています-取得、更新、取得。それがそもそもロックのエスカレーションの理由です。

したがって、トランザクションロックの代わりに長期間行を「チェックアウト」することが目的の場合は、値と単純なol 'updateステートメントを含む列を使用して、行にロックされているかどうかのフラグを立てることをお勧めします。

1
onupdatecascade

すべてのクエリを再検討してください。SELECTFOR UPDATEを持っている同じテーブルから、ROWLOCK/FOR UPDATEヒントなしで選択するクエリがあるかもしれません。


MSSQLは、多くの場合、これらの行ロックをページレベルのロックにエスカレートします(クエリしているフィールドにインデックスがない場合は、テーブルレベルのロックも)。これを参照してください 説明 。 FOR UPDATEを要求するため、トランザクションレベル(財務、在庫など)の堅牢性が必要であると想定できます。したがって、そのサイトのアドバイスはあなたの問題には当てはまりません。 MSSQLがロックをエスカレートする理由にすぎません。


MSSQL 2005以降を使用している場合、それらはMVCCベースです。ROWLOCK/ UPDLOCKヒントを使用した行レベルのロックで問題はないはずです。ただし、既にMSSQL 2005以降を使用している場合は、インデックスがある場合はWHERE句のフィールドをチェックして、ロックをエスカレートする場合、FOR UPDATEする同じテーブルをクエリするクエリをいくつかチェックしてみてください。


PS.
PostgreSQLを使用していますが、MVCC for FOR UPDATEも使用しています。同じ問題は発生しません。ロックエスカレーションはMVCCが解決するものです。そのため、MSSQL 2005がフィールドのインデックスを持たないWHERE句を使用してテーブルのロックをエスカレートする場合は驚くでしょう。それでもMSSQL 2005の場合(ロックエスカレーション)が当てはまる場合は、WHERE句のフィールドにインデックスがある場合はチェックしてみてください。

免責事項:MSSQLの最後の使用はバージョン2000のみです。

1
Michael Buen

READPASTを試しましたか?

テーブルをキューのように扱うとき、UPDLOCKとREADPASTを一緒に使用しました。

0
Gratzy

最初にこの行を簡単に更新してみてください(実際にデータを変更せずに)。その後、更新用に選択された行を続行できます。

UPDATE dbo.Customer SET FieldForLock = FieldForLock WHERE CustomerID = @CustomerID
/* do whatever you want */

編集:もちろんトランザクションでラップする必要があります

編集2:別の解決策は、シリアライズ可能分離レベルを使用することです

0
Vladimir