LOCKINGリクエスト修飾子の指定によるデッドロックの最小化
特定タイプのトランザクション、あるいは非常に大きなアプリケーションや緊急のアプリケーションでは、SQL DMLリクエストの中にLOCKINGリクエスト修飾子を入れてデッドロックの機会を少なくするか防止することができます(詳細については、<Teradata Vantage™ - SQLデータ操作言語、B035-1146>を参照)。
- NOWAITオプションを使用して、ロックが直ちに認められない場合には、トランザクションをアボートします。
- 複数のトランザクションが同じ行を選択し、次に更新するときに、LOCKING ROW FOR WRITE構文を使用して、更新中のデッドロックの機会を削減します。
- 通常適用されるものよりも高い、または低いロック重大度を適用します。
LOCKING修飾子を使用した並列性の向上
システム リソースを有効に利用するために、ユーザーは、トランザクションの中でLOCKINGリクエスト修飾子を使用して、ロックの制限の度合を強めたり、弱めたりすることができます。これを行なわなければ、トランザクション リクエストの処理時に、システムによってこれらのロックが設定されます。
- LOCKINGリクエスト修飾子は、ANSI SQL-2008準拠ではありません。
- LOCKINGリクエスト修飾子は、SQLリクエストを変更することなくSQLリクエストの直前に置くか、単独で使用することができます。LOCKING句は、一般的には修飾子として使用され、その場合にはリクエストの前に置きます。例えば、次のリクエストは有効です。
LOCKING TABLE tablename FOR READ;
- 複数のLOCKINGリクエスト修飾子を、同じリクエスト内に指定することができます。
- CREATE VIEW、REPLACE VIEW、CREATE RECURSIVE VIEW、およびREPLACE RECURSIVE VIEW文を使用すれば、ビュー定義の一部分として、LOCKINGリクエスト修飾子を指定することもできます。
- LOCKINGリクエスト修飾子を使用して、リクエストが処理される前にデータベース、テーブル、または行ハッシュに設定されるロックのモードを指定することができます。LOCKINGをNOWAITオプションと一緒に指定することにより、ロックが直ちに認められない場合に、トランザクションをアボートすることができます。
- LOCKINGリクエスト修飾子は、より高いモードのロックが設定されるのを妨げることはできず、ロックされているオブジェクトには作用しません。リクエストの前にEXPLAINを入れて、各リクエストが実行されるときにロックが設定されるのを見ることができます。行ハッシュ操作のためのロックはEXPLAINレポートに記載されません。
- ロード分離テーブルがある場合、LOAD COMMITTEDロッキング修飾子を使用して、行がブロックされることなく、また同時分離変更をブロックすることなく、コミット済みの行を読み取ることができます。ロード分離テーブルについての詳細は、ロード分離を参照してください。
次の例では、LOCKINGリクエスト修飾子を使用して同時並行性を最大限にしています。
LOCKING TABLE table_a FOR READ LOCKING TABLE table_b FOR READ SELECT ... FROM table_a, table_b WHERE ...; LOCKING TABLE table_name FOR WRITE ; UPDATE ...;
動的なロックの格上げとデッドロック
最適化ルーチンが、SELECTリクエストを処理するためにプライマリ インデックスまたは固有セカンダリ インデックスを使用することにした場合、システムは行ハッシュ値にREADロックを使用します。
同じトランザクションで、同じインデックス値に基づいているDMLリクエストが後に続いている場合、システムは、そのREADロックをWRITEロックまたはEXCLUSIVEロックに格上げします。
並行トランザクションが同時に同じ行ハッシュ値でこのタイプの格上げを必要とする場合には、デッドロックになります。
例えば、2つの並行トランザクションが、次のように同じプライマリ インデックス値を使用してUPDATEがあとに続くSELECTリクエストを実行しようとしたと仮定します。この例では、ユーザーは、Teradataセッション モードで操作していると仮定しています。
ユーザー | SQLテキスト |
---|---|
A | BEGIN TRANSACTION; SELECT y FROM table_a WHERE pi = 1; UPDATE table_a SET y = 0 WHERE pi = 1; END TRANSACTION; |
B | BEGIN TRANSACTION; SELECT z FROM table_a WHERE pi = 1; UPDATE table_a SET z = 0 WHERE pi = 1; END TRANSACTION; |
この場合、user_aおよびuser_bは、SELECTの処理中に、READの同じ行ハッシュ値を共有する行に同時にアクセスすることができます。
user_aのUPDATEリクエストで、table_aの行ハッシュ値でWRITEロックをリクエストする場合、その格上げリクエストはtable_aのuser_bのREADロックが解放されるまで待ち行列で待機します。
しかし、user_bのUPDATEリクエストもその行ハッシュ値でWRITEロックをリクエストするため、user_bのREADロックは解放されず、その格上げリクエストもuser_aのREADロックが解放されるまで待ち行列で待機します。
このようなデッドロックを避けるために、必要に応じてトランザクションの前にLOCKING ROW FOR WRITE句またはLOCKING ROW FOR EXCLUSIVE句を入れることができます。
LOCKING ROWは、次の例に示すように、プライマリ インデックスまたは固有セカンダリ インデックスの制約を使用する単一のテーブルの選択リクエストだけに適しています。
ユーザー | SQLテキスト |
---|---|
A | BEGIN TRANSACTION; LOCKING ROW FOR WRITE SELECT y FROM table_a WHERE USI = 1; UPDATE table_a SET y = 0 WHERE USI = 1; END TRANSACTION; |
B | BEGIN TRANSACTION; LOCKING ROW FOR WRITE SELECT z FROM table_a WHERE USI = 1; UPDATE table_a SET z = 0 WHERE USI = 1; END TRANSACTION; |
この例では、user_aの行ハッシュ レベルのWRITEロックのリクエストは認められますが、それがuser_bのその行ハッシュ値のWRITEロックのリクエストをブロックします。user_bのトランザクションは、user_aのロックが解放されるまで待ち行列に入れられます。
user_aのロックは、トランザクション全体が完了するまで保持されます。そのため、user_bのLOCKING ROW FORのリクエストは、user_aのEND TRANSACTIONリクエストの処理が完了した後にのみ認められます。
LOCKINGリクエスト修飾子およびNOWAITオプションの使用
リクエストをロック待ち行列内で待機させることができない場合、LOCKINGリクエスト修飾子をNOWAITオプションと共に指定できます。
NOWAITは、システムがリクエストを受け取ったときに直ちに対象のオブジェクトに必要なロックを設定できない場合には、トランザクション全体が、ANSIセッション モードであっても、アボートすることを指定します。
この状況はエラーとして処理されます。トランザクションがアボートされたことがユーザーに知らされ、NOWAITが効力を発した時点までに実行された処理がロールバックされます。
ビュー定義でのLOCKINGリクエスト修飾子の指定
LOCKINGリクエスト修飾子はCREATE VIEW、REPLACE VIEW、CREATE RECUSRIVE VIEW、およびREPLACE RECURSIVE VIEW定義で指定できます。例えば、ビューはREADロックをACCESSロックに格下げできます。したがって、アドホックなユーザーがLOCKINGリクエスト修飾子の指定や誤ってトランザクション処理に影響を与えることについて心配する必要はありません。また、ユーザーは既存のレポート指向のクエリーに影響を与えることなく、基本テーブルのデータを変更することができます。
BTEQ使用時のデッドロックの回避
トランザクションの実行依頼にBTEQを使用する場合、データベースはBTEQに対してデッドロックのアボートを報告します。BTEQは、トランザクション全体ではなく、エラーの原因となったリクエストのみを再び実行依頼します(デフォルトの動作)。その結果としてトランザクションが部分的にコミットされる可能性があるため、BTEQスクリプトの作成時には、トランザクションが必ず1つのリクエストになるように注意する必要があります。例えば、BTEQの文は各行の空白を除く最後の文字としてセミコロン(;)を置くことによって終了します。したがって、BTEQは、以下の例を2つのリクエストとして認識します。
sel * from x; sel * from y;
ただし、同じ文を以下のように記述した場合、BTEQはこれらの文を1つのリクエストとして認識します。
sel * from x ; sel * from y;
次のように、BEGIN TRANSACTIONを使用して、個別のリクエストを実行するとします。
BEGIN TRANSACTION INSERT x ( 1, 2 ) ; INSERT x ( 3, 4 ) ; INSERT x ( 5, 6 ) ;
3つ目の挿入でデッドロックが発生した場合、トランザクションはロールバックされ、さらにBTEQの再試行が有効になっている場合は3つ目の挿入が暗黙的トランザクションとして再実行されます。3つ目の挿入にEND TRANSACTIONが続く場合は、失敗が発生しますが、3つ目の挿入はすでにコミットされています。