小さい頃はエラ呼吸

いつのまにやら肺で呼吸をしています。


SQLServerではNOLOCKを使用していても「ロック要求がタイムアウトしました。」のエラーになることがある

SQLServerのNOLOCKロックヒント

SQLServerのNOLOCKロックヒントは、テーブルやレコードに対してロックをかけません。このため、別プロセスによってロックがかかっていたとしてもダーティリードして、データを読み取ることができます。

SQL Server の場合、既定の分離レベルの Read Committed が指定された SELECT ステートメントでは、行に対して読み取り時に S ロックが取得され、解放されます。これにより、分離レベルが適用されますが、S ロックが必要な行に互換性のないロックが存在する場合に、SELECT ステートメントが待機することを意味します。NOLOCK ヒントが指定されている場合、SELECT 操作では S ロックが取得されずにデータが読み取られます。操作は成功しますが、この場合、SELECT ステートメントはコミットされないデータも読み取ります。
ロック ヒント (SQL Server Compact Edition) はてなブックマーク - ロック ヒント (SQL Server Compact Edition)

スキーマロック

ただし、テーブルのスキーマ(テーブル構造)に対して低レベルのロックがかかります。

たとえば、あるユーザがテーブルの削除や変更などを行っている最中に、別のユーザがそのテーブルを読み取ろうとすると、正しく読み取ることができない可能性があります。このため、SQLServerではスキーマ(テーブルの構造)の変更中には、他のプロセスから操作されるのを防止するため、スキーマ更新ロック(Sch-M)をかけます。

SELECT文にNOLOCKを指定した場合でも、スキーマ共有ロック(Sch-S)という低レベルのロックがかかります。スキーマ共有ロック(Sch-S)同士が競合した場合はエラーとはなりませんが、スキーマ更新ロック(Sch-M)とスキーマ共有ロック(Sch-S)が競合した場合にはエラーとなります。このため、テーブルの削除や変更中にNOLOCK指定のselect文を発行すると、「ロック要求がタイムアウトしました。」のエラーになることがあります。

検証

1.SQLServerに任意のデータベースとテーブルを作成します。

    • データベース名:test
    • テーブル名:Table_1

2.SQLServer Management Studioでtestデータベースに対して以下のクエリを発行します。このクエリはトランザクション中にTable1を削除して、その後1分間waitします。

use test;
BEGIN TRANSACTION
DROP TABLE Table_1;
-- 1分間waitする
WAITFOR DELAY '00:01'

3.2のクエリを実行中に、testデータベースに対してselect文(NOLOCK)を実行します。

use test;
SET LOCK_TIMEOUT 0;
select * from Table_1 WITH(NOLOCK);

4.すると、3のクエリ側は以下のようなエラーになります。

メッセージ 1222、レベル 16、状態 51、行 3
ロック要求がタイムアウトしました。

スキーマ更新ロックの発生タイミング

このスキーマ更新ロック(Sch-M)はテーブルの削除や変更時だけでなく、SQLServerが内部で統計情報を更新するときにも発生します。

スキーマ ロック
SQL Server は 2 種類のスキーマ ロックを使用しており、SQL Server がテーブルの統計情報を更新するときにその両方が使用されます。
SQL Server の統計保守機能 (Autostats) はてなブックマーク - SQL Server の統計保守機能 (Autostats)

このため、何らかのタイミングでNOLOCK指定をしているSELECT文でもロック要求タイムアウトのエラーが返ってくることがあるため、注意が必要です。
アプリケーション側では、NOLOCKだからロック要求タイムアウトエラーにならないと決めつけず、必ずロック要求タイムアウトエラーが返ってきた場合の例外処理を組み込むようにしていたほうが安全です。