はじめに
リレーショナルデータベースは、トランザクションの実装方法(Atomicity、Consistency、Isolation、およびDurability)でACIDプロパティに従います。複数のトランザクションによってデータが変更されたり、最終的な結果に一貫性が失われたりしないようにするには、分離が必要です。操作が分離されたままであることを保証するために、SQLServerはロックメカニズムを適用します。
ロックモードと階層
同時実行制御のためのSQLServerのメカニズムが関係しています。ロック待機やデッドロックなどの観点からパフォーマンスを最適化するには、特定のシナリオに基づいて決定を下す必要があります。
SQL Serverでは、ロックはさまざまな方法で、いくつかのレベルの粒度で保持できます。ロックモードは特定の方法であり、そのレベルはロック階層です。
図1は、デフォルトのトランザクション分離レベル(READ COMMITTED)でSQLServerで使用できるロックモードを示しています。
ロックエスカレーションの概要
SQL Serverは、いくつかのレベルでリソースをロックできます。それは、ワークロードの性質に応じた最も効率的な動作に依存します。表1に、ロックできるリソースを示します。
- より詳細なレベルのロック(行レベルのロックなど)により、同時実行性が高くなり、ブロッキングが少なくなります。
- より高いレベルのロック(テーブルレベルのロックなど)は、同時実行性を減らします。実際のステートメントの長さによっては、より多くのブロッキングが発生する可能性があります。
SQL Serverは、内部メトリックに従って必要なロックレベルを選択します。
ロックエスカレーションは、ロックがより細かいレベルの粒度からより粗いレベルに変換されるときに発生します。
たとえば、行ロックをテーブルロックに変換します(表1を参照)。
リソース | 説明 |
RID | ヒープ内の単一の行をロックするために使用される行識別子。 |
キー | シリアル化可能なトランザクションでキー範囲を保護するために使用されるインデックス内の行ロック。 |
ページ | データページやインデックスページなど、データベース内の8キロバイト(KB)ページ。 |
範囲 | データページやインデックスページなど、8ページの連続したグループ。 |
HoBT | ヒープまたはBツリー。ロックは、クラスター化インデックスを持たないテーブル内のBツリー(インデックス)またはヒープデータページを保護しています。 |
表 | すべてのデータとインデックスを含むテーブル全体。 |
ファイル | データベースファイル。 |
アプリケーション | アプリケーション指定のリソース。 |
メタデータ | メタデータロック。 |
ALLOCATION_UNIT | アロケーションユニット。 |
データベース | データベース全体。 |
ロックエスカレーションの理由
SQL Serverのロックは、非常に高額になる可能性があります。 Lock Managerが取得するロックごとに、SQL Serverはメモリ(64バイトまたは128バイト)を予約する必要があります。量は、それぞれ32ビットシステムと64ビットシステムのどちらを扱っているかによって異なります。
テーブルの行ロックの数が増えると、SQLServerはますます多くのメモリを取得する必要があります。したがって、他のプロセスはメモリ不足で不足しています。
行ロックとページロックを単一のテーブル(オブジェクト)レベルのロックに変換することは理にかなっています。そのテーブルのロックの数が5000を超えると発生します。
妥協は、トランザクションプロセスの他のセッションでテーブル全体を使用できなくなったときに発生します。
ロックエスカレーションのデモンストレーション
リスト1のコードを使用して、ロックのエスカレーションを示すことができます。
まず、表について少し説明しましょう。 Production.ProductsI は約7777行を運ぶ比較的小さなテーブルです。建物の要素は、101回複製された77行の同じセットです。リスト1のコードは、同じ更新ステートメントの3つのバージョンで構成されており、それぞれがトランザクションで囲まれています。
-- Listing 1: Demonstrating Lock Escalation
-- Update very few rows
BEGIN TRAN
use TSQLV4
GO
UPDATE Production.ProductsI SET unitprice='100.00'
WHERE unitprice='18.00';
ROLLBACK
-- Update a large number of rows
BEGIN TRAN
use TSQLV4
GO
UPDATE Production.ProductsI SET unitprice='100.00'
WHERE unitprice>'18.00';
ROLLBACK
-- Update over 5000 rows
BEGIN TRAN
use TSQLV4
GO
UPDATE Production.ProductsI SET unitprice='100.00';
ROLLBACK
より明確にするために、リスト1の内容を分類します。
その前に、リスト2 –TSQLV4データベースに保持されているロックを表示するクエリを見てみましょう。
最初のアクションは、リスト1aを実行することです。次に、リスト2を使用して、シナリオでLockManagerがどのようにロックを実行するかを調べます。ロールバックステートメントを発行せずにリスト1aを実行します。このようにして、リスト2のクエリでロックをキャプチャできるように、ロックを十分に長く保持します。
-- Listing 1a: Demonstrating Lock Escalation
-- Update very few rows
BEGIN TRAN
use TSQLV4
GO
UPDATE Production.ProductsI SET unitprice='100.00'
WHERE unitprice='18.00';
ROLLBACK
-- Listing 2: Displaying Locks Held in Database TSQLV4
USE TSQLV4
GO
SELECT
resource_type
, DB_NAME (resource_database_id) database_name
--, OBJECT_NAME(resource_associated_entity_id) resource_name
, request_mode
, request_type
, request_status
, request_reference_count
, request_session_id
, resource_associated_entity_id
, OBJECT_NAME(resource_associated_entity_id) [object_name] --small obj ids
, getuser.login_name
FROM sys.dm_tran_locks
CROSS APPLY dmv.dbo.getuser(request_session_id) as getuser
WHERE DB_NAME (resource_database_id)='TSQLV4';
リスト1aのクエリを実行し、リスト2のクエリを使用してロックを確認すると、SQLServerは図2に示す結果を返します。
テーブルの404行にはunitprice=’18.00’ 。 Lock Managerは、これらの行を、必要なレベルの他のロックとともにロックします。これにより、図2の行数は467になります。
-- Listing 1b: Demonstrating Lock Escalation
-- Update a large number of rows
BEGIN TRAN
use TSQLV4
GO
UPDATE Production.ProductsI SET unitprice='100.00'
WHERE unitprice>'18.00';
ROLLBACK
リスト1bのクエリを実行すると、同様の動作が見られます。今回は4406行を扱っています。これは、unitprice>18.00のテーブルProduction.ProductIの行数を反映しています。
-- Listing 1c: Demonstrating Lock Escalation
-- Update over 5000 rows
BEGIN TRAN
use TSQLV4
GO
UPDATE Production.ProductsI SET unitprice='100.00';
ROLLBACK
さらに進んでリスト1cのコードを実行すると、別の動作が表示されます(図4を参照)。
リスト1cは、Production.ProductIテーブルのすべての7777行を更新しようとしています。 SQL Serverは、非常に多くの行をロックすることは、分離を保証するのにもはや効率的ではないと判断します。代わりに、テーブル全体がロックされます。
ロックエスカレーションの詳細
テーブルロックは、他のセッションがトランザクション期間中その行を変更できないことを意味します。これは、ブロックしているセッションがテーブル内のすべての行を操作しない場合でも発生する可能性があります。
他の要因がSQLServerでのロックの取得方法とエスカレーション方法に影響を与える可能性があることにも言及する価値があります。これらは、構成された分離レベル、インデックス付け、およびトレースフラグです。
トレースフラグT1211およびT1224を適用して、ロックエスカレーションを完全に無効にすることができます。次のコードを使用して、特定のテーブルに対してロックエスカレーションを無効または有効にすることもできます。
-- Listing 5: Disable and Enable Lock Escalation
ALTER TABLE Production.ProductsI SET (LOCK_ESCALATION=DISABLE);
ALTER TABLE Production.ProductsI SET (LOCK_ESCALATION=TABLE);
テーブル全体のロックに関連するブロッキングを減らすために、これを実行することをお勧めします。メモリへの影響があるため、一時的な対策として検討する必要があります。
結論
SQL Serverは、ロックエスカレーションを使用して、サーバーリソースに対するより詳細なロックの影響を制御します。これらのロックの発生方法(行ロック、ページロック、オブジェクトロックなど)を表示するには、sys.dm_tran_locks動的管理ビューにクエリを実行します。ロックエスカレーションに加えて、ロックに関する多くの情報を提供します。
Lock Managerの動作を操作することは可能ですが、細心の注意を払って操作することが不可欠です。また、そのような変更を行うことを目的とした取り組みがパフォーマンスに与える影響を正確に把握することも重要です。
参照
- Korotkevitch、D.、2016年。ProSQLServerの内部。フロリダ:Dmitri Korotkevitch
- Sys.dm_tran_locksを使用したロックシナリオ
- トランザクションのロックと行のバージョン管理ガイド