高スループットで信頼性の高い大規模データベース アプリケーション システムを実装したい場合、他のデータベースから MySQL に移行したい場合、MySQL のパフォーマンスを調整したい場合、ロックとトランザクション モデルを学習してマスターすることは非常に役立ちます。 InnoDB の。
記事ディレクトリ
14.7.1 InnoDB のロック
このセクションでは、InnoDB のさまざまなタイプのロックについて説明します。
通りすがりの友達、スターでサポートするのを手伝ってください: https://github.com/cncounter/translation/
共有ロックと排他ロック
InnoDB は、共有ロック (略して S ロック) と排他的ロック (略して X ロック) の 2 つのタイプを含む標準の行レベルのロックを実装します。「排他ロック」は「相互排他ロック」と呼ばれることもあります。
- 共有ロック (S): ロックを保持しているトランザクションがこの行を読み取ることを許可します。
- 排他的ロック (X): ロックを保持しているトランザクションがこの行を更新または削除できるようにします。
トランザクションがrow の共有ロック ( )T1
を保持している場合、次のように、別のトランザクションが row のロックを要求します。r
S
T2
r
T2
ロックが要求された場合はS
、すぐにロックを許可できます。結果は次のようになります。T1
両方とも rowのロックT2
を保持します。r
S
T2
ロックが要求された場合X
、すぐにロックを許可することはできないため、キューに入れて待機する必要があります。
トランザクションがrowT1
のr
排他ロック ( ) を保持している場合X
、別のトランザクションT2
によるr
row のロックの要求はすぐには許可されません。この時点で、トランザクションは行のロックを解放するまでT2
待つ必要があります。T1
r
インテントロック
InnoDB は複数粒度のロック ( multiple granularity locking
) をサポートしており、行ロックとテーブル ロックを共存させることができます。
たとえば、LOCK TABLES ... WRITE
このステートメントは、指定された table に排他ロック (ロック) を設定しますX
。
複数の粒度レベルでのロックを実用化するために、InnoDB は [Intent Lock] ( intention lock
) または [Intent Lock] を使用します。
インテンション ロックはテーブル レベルのロック (テーブル レベル ロック) であり、現在のトランザクションが後でテーブル内の行に対してどのタイプのロックを実行するかを示します (共有ロックまたは排他ロックが必要かどうか)。
インテント ロックには 2 つのタイプがあります。
共享意向锁
(IS
、intention shared lock
): トランザクションがテーブル内の特定の行に共有ロックを設定しようとしていることを示します。排他意向锁
(IX
、intention exclusive lock
): トランザクションがテーブル内の特定の行に排他ロックを設定することを意図していることを示します。
たとえば、ロックSELECT ... LOCK IN SHARE MODE
が設定されIS
、ロックSELECT ... FOR UPDATE
が設定されますIX
。
インテントロックのプロトコルは次のとおりです。
- トランザクションがテーブル内の行の共有ロックを取得するには、まず
IS
そのテーブルのロック、またはより制限的なロックを取得する必要があります。 - トランザクションがテーブル内の行の排他ロックを取得するには、まず
IX
そのテーブルのロックを取得する必要があります。
インテント ロックと他のロックの互換性は次のように要約されます。
X |
IX |
S |
IS |
|
---|---|---|---|---|
X |
対立 | 対立 | 対立 | 対立 |
IX |
対立 | 兼容 |
対立 | 兼容 |
S |
対立 | 対立 | 兼容 |
兼容 |
IS |
対立 | 兼容 |
兼容 |
兼容 |
要求されたロックが既存のロックと互換性がある場合、要求されたトランザクションは直ちに許可されます
が、既存のロックと競合する場合、ロックは許可されません。要求側のトランザクションは、競合するロックが解放されるまで待つ必要があります。
ロック要求が既存のロックと競合し、「デッドロック」が発生するために許可できない場合は、エラーが直接報告されます。
インテント ロックは、(たとえば、フル テーブル リクエストを除く)LOCK TABLES ... WRITE
他のリクエストをブロックしません。インテント ロックの主な目的は、トランザクションがテーブル内の行をロックしていること、またはテーブル内の行をロックしようとしていることを示すことです。
SHOW ENGINE INNODB STATUS
ステートメントと出力トランザクション情報ではInnoDB monitor
、インテント ロックは次のようになります。
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
レコードロック
レコード ロック (レコード ロック)。インデックス レコード (インデックス レコード) に対するロックでもあります。たとえば、SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
このステートメントは、他のトランザクションがt.c1
値 の行を挿入/更新/削除できないようにします10
。
レコード ロックは、インデックスが定義されていないテーブルであっても、常にインデックス レコードをロックします。インデックス セットがないテーブルの場合、InnoDB は自動的に非表示のクラスター化インデックス (clustered index
クラスター化インデックスとも呼ばれます) を作成し、このインデックスを使用してレコード ロックを実行します。詳細については、「クラスター化インデックスとセカンダリインデックス」を参照してください。
SHOW ENGINE INNODB STATUS
ステートメントおよび出力トランザクション情報ではInnoDB monitor
、レコード ロックは次のようになります。
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc ''O;;
2: len 7; hex b60000019d0110; asc ;;
ギャップロック
ギャップロック:
- インデックスレコード間のギャップをロックすることです。
- または、最初のインデックス レコードの前のギャップをロックします。
- または、最後のインデックス レコードの後のギャップをロックします。
たとえば、この範囲内のすべての値間のギャップがロックされているため、値が列に存在するかどうかに関係なく、他のトランザクションはこの値を列に挿入SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
できなくなります。15
t.c1
ギャップは、単一のインデックス値、複数のインデックス値にまたがるか、空の場合もあります。
ギャップ ロックはパフォーマンスと同時実行性の間のトレードオフであり、特定のトランザクション分離レベルでのみ使用されます。
一意のインデックスを使用して一意の行をロックするステートメントの場合、ギャップ ロックは必要ありません。(複数の列から構成されるユニークインデックスで、検索条件に一部の列のみが含まれる場合もギャップロックが発生します。) 例えば、列
にid
ユニークインデックスがある場合、次のSQL文はid = 100
他のセッションが前のギャップに改行を挿入するかどうかに関係なく、行のレコード ロック:
SELECT * FROM child WHERE id = 100;
id
列にインデックスがない場合、または一意のインデックスではない場合、ステートメントは前のギャップをまとめてロックします。
異なるトランザクションが同じギャップに対して競合するロックを保持する可能性があることに注意してください。たとえば、トランザクション A はギャップ上で共有ギャップ ロック (ギャップ S ロック) を保持でき、トランザクション B は同じギャップ上で排他的ギャップ ロック (ギャップ X ロック) を保持できます。競合するギャップ ロックが許可される理由は、レコードがインデックスからパージされる場合、そのレコード上のさまざまなトランザクションによって保持されているギャップ ロックをマージする必要があるためです。
InnoDB のギャップ ロックは「純粋に抑制的」であり、その唯一の目的は、他のトランザクションがギャップに挿入されるのを防ぐことです。ギャップ ロックは共存できます。1 つのトランザクションによって保持されているギャップ ロックは、他のトランザクションによる同じギャップのロックを妨げません。また、共有ギャップ ロックと排他的ギャップ ロックの間に違いはありません。これらは互いに競合するものではなく、同じ効果をもたらします。
ギャップ ロックは明示的に無効にすることができます。これは、トランザクション分離レベルが に設定されているREAD COMMITTED
か、システム変数が有効になっている(廃止された) 場合に発生します。innodb_locks_unsafe_for_binlog
ギャップ ロックは、検索およびインデックス スキャン中は無効になり、外部キー制約チェックと重複キー チェックにのみ使用されます。
READ COMMITTED
分離レベルを使用するか、 を有効にするinnodb_locks_unsafe_for_binlog
と、他の影響もあります。MySQL は、WHERE
条件の計算が完了すると、一致しない行のレコード ロックを直ちに解放します。このステートメントに対して、InnoDB は「半一貫性読み取り (半一貫性)」を実行し、コミットされた最新バージョンを MySQL に返し、MySQL が行が のUPDATE
条件に一致するかどうかを判断できるようにします。UPDATE
WHERE
プロキーロック
Next-Key ロックは、インデックス レコード ロックと以前のギャップ ロックを組み合わせたものです。
InnoDB 行レベルのロックが実行される方法は、インデックスの検索またはスキャン時に、見つかったインデックス レコードに共有ロックまたはミューテックス ロックが設定されることです。したがって、行レベルのロックは本質的にはインデックス レコード ロックです。インデックス レコードに対する一時的なキー ロックは、そのインデックス レコードに先行する「ギャップ」にも影響します。つまり、プロキーロック=レコードロック+ギャップロックとなります。
セッション (セッション) がR
インデックス レコードに対して共有一時キー ロックまたは排他的一時キー ロックを保持している場合、インデックスのソート方向に従って、他のセッションは前のR
ギャップに新しいインデックス レコードを挿入できません。
インデックスに10
、11
、13
、および20
これらの 4 つの値が含まれているとします。このとき、インデックスのキー ロックには次の間隔 (間隔) を含めることができます。
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
このうち、括弧は終点を除く開区間を示し、角括弧は終点を含む閉区間を示します。
間隔の最後のグループ、キー ロックの範囲は、現在の最大値より大きく、正の無限大まで (正の無限大) です。Infinity には実際のインデックス レコードは含まれていません。実際、この一時キー ロックは現在の最大インデックス値以降のギャップをロックするだけです。
デフォルトでは、InnoDB はREPEATABLE READ
トランザクション分離レベルで実行されます。この分離レベルでは、InnoDB は検索とインデックス スキャンにキー ロックを使用して、ファントム行を防ぎます。
SHOW ENGINE INNODB STATUS
ステートメントによって出力されるトランザクション情報と一時InnoDB monitor
キーロックは次のようになります。
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc ''O;;
2: len 7; hex b60000019d0110; asc ;;
インテントロックを挿入
Insert Intention Lock (Insert Intention Lock) は、新しい行を挿入する前に操作によってINSERT
設定されるギャップ ロックです。このロックは挿入の意図信号を示し、実行方法は次のとおりです。複数のトランザクションが同じギャップにレコードを挿入したい場合、それらが同じ位置にない限り、ブロックしたり待機したりする必要はありません。
たとえば、インデックスには と の 2 つの値があり4
ます7
。2 つのトランザクションがあり、それぞれ と を挿入するときに5
、6
挿入される行の排他ロックを取得する前に、各トランザクションは挿入意図ロックを使用して と の間の4
ギャップ7
をロックしますが、特定の行は矛盾していない。
次の例は、トランザクションがレコードを挿入するための排他ロックを取得する前に、挿入意図のロックを取得することを示しています。この例には、A と B という 2 つのクライアントが含まれます。
A
90
クライアントは 2 つのインデックス レコード (と)を含むテーブルを作成し102
、トランザクションを開始して、ID が 100 を超えるインデックス レコードに排他ロックを設定します。排他ロックには、レコード 102 の前にギャップ ロックが含まれています。
# A客户端
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id |
+-----+
| 102 |
+-----+
クライアント B はトランザクションを開始した後、ギャップにレコード 101 を挿入しようとします。排他的ロックの取得を待機している間、挿入インテント ロックが最初に取得されます。
# B客户端
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
SHOW ENGINE INNODB STATUS
ステートメントと出力トランザクション情報にInnoDB monitor
、次のようなインテント ロックを挿入します。
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000066; asc f;;
1: len 6; hex 000000002215; asc "" ;;
2: len 7; hex 9000000172011c; asc r ;;...
自己増加ロック
自己増加ロック ( Lock)は、レコードを挿入する必要があるトランザクションによって取得される、列を持つテーブルであるAUTO-INC
特別なテーブル レベルのロックです。AUTO_INCREMENT
最も単純なシナリオでは、トランザクションがテーブルに値を挿入している場合、他のトランザクションはテーブルに新しい値を挿入する前にそのトランザクションが完了するのを待つ必要があるため、最初のトランザクションによって挿入された行は継続的な自動インクリメントプライマリを取得します。キーの値。
innodb_autoinc_lock_mode
自己増加アルゴリズムを制御するために使用されるオプション。予測可能な自動インクリメント シーケンスを選択するか、挿入操作の高い同時実行パフォーマンスを選択するかを検討してください。
詳細については、セクション14.6.1.6「InnoDB での AUTO_INCREMENT 処理」を参照してください。
空間インデックスで使用される述語ロック
InnoDB は地理空間列のインデックスをサポートしますSPATIAL
。詳細については、「空間分析の最適化」を参照してください。
インデックス レコードをロックする場合、一時キー ロックはトランザクション分離レベルをSPATIAL
適切にサポートしません。キューブには絶対的な並べ替え順序がないため、誰が「次の」キーであるかを知る方法はありません。REPEATABLE READ
SERIALIZABLE
トランザクション分離レベル でインデックスを持つテーブルをサポートするためにSPATIAL
、InnoDB は述語ロック (Predicate Lock) を使用します。
SPATIAL
インデックス レコードには MBR 値 (最小境界四角形、最小境界四角形) が含まれるため、InnoDB は MBR 値と一致するインデックス レコードに述語ロックを設定して、インデックスに対する一貫した読み取りを強制します。他のトランザクションは、クエリ条件に一致する行を挿入または変更できません。
14.7.2 InnoDB トランザクション モデル
InnoDB のトランザクション モデルは、マルチバージョン データベースの最良の特性と従来の 2 フェーズ ロックを組み合わせることを目的としています。
デフォルトでは、InnoDB は行レベルのロックを使用し、Oracle データベースと同様に、非ロックの一貫した読み取り方式でクエリを実行します。
InnoDB のロック情報はスペースを節約する方法で保存されるため、ロックのエスカレーションは必要ありません。
InnoDB メモリを使い果たすことなく、複数のユーザーが InnoDB テーブル内の各行、または任意の数の行をロックすることをサポートします。
14.7.2.1 トランザクション分離レベル
トランザクションの分離はデータベースの基本的な機能です。分離はACID
中I
です。分離レベルは、複数のトランザクションが同時に変更され、同時にクエリされる場合のパフォーマンス、信頼性、一貫性、再現性のバランスを調整するために使用される構成可能な項目です。
InnoDB は、「SQL:1992 Standard」で定義されている 4 つのトランザクション分離レベルをサポートしています。
READ UNCOMMITTED
(コミットされていない読み取り)、READ COMMITTED
(コミットされた読み取り)、REPEATABLE READ
(反復読み取り)、InnoDB のデフォルトの分離レベル。SERIALIZABLE
(連載)。
SET TRANSACTION
ユーザーは、ステートメントを使用するだけで、現在のセッションの分離レベルを変更したり (自分のセッションの可視性を制御したり)、後続のすべての接続の分離レベルを変更したりすることができます。
サーバーのデフォルトの分離レベルを設定するには、--transaction-isolation
コマンドラインまたは構成ファイルでオプションを使用します。分離レベルの設定の詳細については、「SET TRANSACTION ステートメント」を参照してください。
InnoDB は、トランザクション分離レベルごとに異なるロック戦略を使用します。
- デフォルトレベルは、仕様が重要な重要なデータ処理などの
REPEATABLE READ
一貫性を実現するために使用できます。ACID
READ COMMITTED
バッチ レポートなどのシナリオでは、一貫性制約を緩和することもできますREAD UNCOMMITTED
。現時点では、正確な一貫性と再現可能な結果は、ロックのオーバーヘッドを削減することよりも重要ではありません。SERIALIZABLE
はよりも制限が厳しくREPEATABLE READ
、主にトランザクションなどの特殊なケース、または同時実行性やデッドロックの問題のトラブルシューティングやトラブルシューティングなどのシナリオで使用されますXA
。
MySQL のさまざまなトランザクション分離レベルについては、以下で詳しく説明します。最初に最も一般的に使用される分離レベルを取り上げ、最後に最も使用頻度の低い分離レベルを取り上げます。
REPEATABLE READ
[Repeatable Read] は、InnoDB のデフォルトの分離レベルです。反復可能な読み取り分離レベル、最初の読み取り時に作成されたスナップショットを使用した、同じトランザクション内の一貫した読み取り。
これは、SELECT
同じトランザクション内で複数の通常ステートメント (非ロック) が実行された場合、それらのステートメント間の整合性SELECT
が保証されることを意味します。
詳細については、「14.7.2.3 非ロック一貫性読み取り」を参照してください。
UPDATE
ステートメント、DELETE
ステートメント、およびロック読み取り (ロック読み取り、つまりSELECT ... FOR UPDATE
orステートメント)の場合SELECT ... LOCK IN SHARE MODE
、使用されるロックは、フィルター条件が一意のインデックスを使用するか範囲条件を使用するかによって決まります。
-
一意のインデックスを使用する一意のクエリ条件の場合、InnoDB は、以前のギャップではなく、見つかったインデックス レコードのみをロックします。
-
他のクエリ条件の場合、InnoDB はスキャンされたインデックス範囲をロックし、他のセッションがギャップ ロックやキー ロックを通じてこの範囲に新しい値を挿入するのを防ぎます。ギャップ ロックと隣接キー ロックの詳細については、前のコンテンツ「14.7.1 InnoDB のロック」を参照してください。
READ COMMITTED
[コミットされた読み取り] 分離レベルでは、同じトランザクション内であっても、一貫した読み取りごとに独自の新しいスナップショットが設定され、読み取られます。一貫性のある読み取りについては、「14.7.2.3 非ロックの一貫性のある読み取り」を参照してください。
read(SELECT ... FOR UPDATE
またはSELECT ... LOCK IN SHARE MODE
)UPDATE
ステートメントとDELETE
ステートメントをロックする場合、InnoDB は現時点ではインデックス レコードのみをロックし、インデックス レコード間のギャップはロックしないため、他のトランザクションはロックされたレコードの隣に新しいレコードを挿入できます。現時点では、ギャップ ロックは外部キー制約チェックと重複キー チェックにのみ使用されます。
ギャップ ロックを無効にすると、他のセッションがギャップに新しい行を挿入する可能性があるため、ファントムの問題が発生する可能性があります。ファントム読み取りの詳細については、「14.7.4 ファントム行」を参照してください。
READ COMMITTED
分離レベルは行ベースのバイナリログのみをサポートします。READ COMMITTED
と一緒に使用するとbinlog_format=MIXED
、サーバーは行ベースのバイナリログに自動的に切り替わります。
を使用するとREAD COMMITTED
、他の効果もあります。
UPDATE
ANDステートメントの場合DELETE
、InnoDB は更新または削除が必要な行のロックのみを保持します。WHERE
MySQL は条件を計算した後、一致しない行のレコード ロックを解放します。これによりデッドロックの可能性が大幅に減りますが、デッドロックが発生する可能性は依然としてあります。
このステートメントでUPDATE
は、行がロックされている場合、InnoDB は半一貫性読み取り (「半一貫性」読み取り) を実行し、コミットされた最新バージョンを MySQL に返し、行が条件を満たしているかどうかを MySQL に判断させUPDATE
ますWHERE
。行が一致する場合 (更新が必要であることを示す)、MySQL はその行を再度読み取り、今度は InnoDB がその行をロックするか、上記のロックが最初に解放されるのを待ちます。
以下の例を参照してください。この表から始めましょう。
CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
COMMIT;
この場合、インデックスがないため、非表示のクラスター化インデックスがクエリおよびインデックス スキャン中にレコード ロックとして使用されます。
セッション A が次のステートメントを通じて更新操作を実行するとします。
# 会话A
START TRANSACTION;
UPDATE t SET b = 5 WHERE b = 3;
この時点では、セッション A はトランザクションをコミットしていないため、2 番目のセッション B が次のステートメントを通じて更新操作を実行します。
# 会话B
UPDATE t SET b = 4 WHERE b = 2;
InnoDB を実行するUPDATE
と、まず、読み取った行ごとに排他ロック (排他ロック) を設定し、次に行を変更する必要があるかどうかを判断します。InnoDB を変更する必要がない場合、行のロックは解放されます。それ以外の場合、InnoDB はトランザクションが終了するまで行ロックを保持します。これは、以下に示すようにトランザクションの処理に影響します。
デフォルトのREPEATABLE READ
分離レベルが使用されていると仮定すると、最初の分離レベルはUPDATE
まずスキャンする各行に X ロックを設定し、それらのロックを解放しません。
# REPEATABLE READ 隔离级别; 会话A
x-lock(1,2); retain x-lock
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); retain x-lock
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); retain x-lock
最初のUPDATE
行はすべての行のロックを保持しているため、2 番目の行はUPDATE
ロックの取得を直ちにブロックし、UPDATE
最初の行がコミットまたはロールバックするまで続行できません。
# REPEATABLE READ 隔离级别; 会话B
x-lock(1,2); block and wait for first UPDATE to commit or roll back
分離レベルが使用される場合READ COMMITTED
、最初にUPDATE
スキャンによって読み取られた各行に対して X ロックを取得し、次に変更する必要のない行に対して X ロックを解放します。
# READ COMMITTED 隔离级别; 会话A
x-lock(1,2); unlock(1,2)
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); unlock(3,2)
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); unlock(5,2)
2 番目の場合UPDATE
、InnoDB は半一貫性読み取り (「半一貫性」読み取り) を実行し、コミットされた最新バージョンを MySQL に返し、行が次の条件を満たすかどうかを MySQL に判断させUPDATE
ますWHERE
。
# READ COMMITTED 隔离级别; 会话B
x-lock(1,2); update(1,2) to (1,4); retain x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); retain x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); retain x-lock
ただし、WHERE
インデックス付きカラムが条件に含まれており、InnoDB がそのインデックスを使用する場合、レコード ロックを取得して保持するときにインデックス付きカラムのみが考慮されます。
次の例では、最初の例では、UPDATE
すべての行に対してX ロックを取得して保持しますb = 2
。
2 番目は、UPDATE
b 列に定義されたインデックスも使用されるため、同じレコードに対して X ロックを取得しようとするとブロックされます。
CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2,3),(2,2,4);
COMMIT;
# 会话 A
START TRANSACTION;
UPDATE t SET b = 3 WHERE b = 2 AND c = 3;
# 会话 B
UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
READ COMMITTED
分離レベルの使用はinnodb_locks_unsafe_for_binlog
基本的にオプション [このオプションは廃止されました] の設定と同じですが、いくつかの違いがあります。
innodb_locks_unsafe_for_binlog
すべてのセッションに影響するグローバル設定であり、分離レベルはすべてのセッションに対してグローバルに設定することも、セッションごとに個別に設定することもできます。innodb_locks_unsafe_for_binlog
サーバーの起動時にのみ設定できますが、分離レベルは起動時に設定することも、実行時に変更することもできます。
したがって、よりも便利で柔軟ですREAD COMMITTED
。innodb_locks_unsafe_for_binlog
READ UNCOMMITTED
[Read Uncommitted] 分離レベルでは、SELECT
ステートメントは非ロック方式で実行されますが、以前のバージョンの行が使用される可能性があります。したがって、この分離レベルを使用すると、読み出しの一貫性が保証されなくなり、この現象をダーティリード(ダーティリード)と呼びます。それ以外の場合、この分離レベルは に似ていますREAD COMMITTED
。
SERIALIZABLE
[シリアル化] この分離レベルも同様でREPEATABLE READ
、無効にするとautocommit
、InnoDB はSELECT
すべての通常のステートメントを暗黙的に に変換しますSELECT ... LOCK IN SHARE MODE
。
自動コミット ( autocommit
) が有効な場合は、SELECT
トランザクション内で単独で実行されます。したがって、これは読み取り専用とみなされ、一貫した非ロック読み取りとして実行される場合は、他のトランザクションをブロックすることなくシリアル化できます。
他のトランザクションが選択した行を変更する間、通常のステートメントを強制的にブロックして待機させる場合は、SELECT
無効にしますautocommit
。
14.7.2.2 自動コミット、コミット、ロールバック
InnoDB では、すべてのユーザー アクティビティはトランザクション内で実行されます。自動コミット モード ( autocommit
) が有効な場合、各 SQL ステートメントはそれ自体でトランザクションを形成します。
MySQL の新しいセッション接続はデフォルトで自動的にコミットされ、SQL ステートメントがエラーを生成しない場合は、実行後に自動的にコミットされます。
特定の SQL ステートメントがエラーを返した場合、そのエラーに応じてコミットするかロールバックするかが決定されます。詳細については、「InnoDB エラー処理」を参照してください。
START TRANSACTION
自動コミットが有効になっているセッションでは、明示的なORステートメントで始まりORステートメントBEGIN
で終わる複数ステートメントのトランザクションも実行できます。詳細については、「START TRANSACTION、COMMIT、および ROLLBACK ステートメント」を参照してください。COMMIT
ROLLBACK
によって自動コミット モードが無効になっている場合SET autocommit = 0
、セッションには常にオープンなトランザクションが存在します。COMMIT
orROLLBACK
ステートメントは現在のトランザクションを終了し、新しいトランザクションを開始します。
自動コミット モードが無効になっているセッションの場合、接続が切断された場合、または明示的にトランザクションをコミットせずにセッションが終了した場合、MySQL はトランザクションのロールバックを実行します。
COMMIT
一部のステートメントは、そのようなステートメントの前にステートメントが自動的に追加されたかのように、トランザクションを暗黙的に終了します。詳細については、「暗黙的なコミットを引き起こすステートメント」を参照してください。
COMMIT
現在のトランザクションによって行われた変更を永続化し、他のセッションに表示できるようにする必要があることを示します。このステートメントは、ROLLBACK
現在のトランザクションの変更をキャンセルします。COMMIT
そしてROLLBACK
両方とも、現在のトランザクション中に設定されたすべての InnoDB ロックを解放します。
DML 操作グループとトランザクション
MySQL データベースのクライアント接続では、自動送信モードがデフォルトで有効になっており、各 SQL ステートメントは実行後に自動的に送信されます。他のデータベース システムを使用したことがあるユーザーは、この操作モードに慣れていない可能性があります。これは、一連の DML ステートメントを実行してから、まとめて送信またはロールバックする方法が一般的であるためです。
複数ステートメントのトランザクションを使用するには:
- 自動コミット モードはステートメントによってオフにすることができ、
SET autocommit = 0
適切なタイミングでCOMMIT
またはでトランザクションを終了しますROLLBACK
。 - 自動コミット状態では、 または
START TRANSACTION
でトランザクションを開始し、COMMIT
またはで終了できますROLLBACK
。
次の例は 2 つのトランザクションを示しています。このうち、最初のトランザクションはコミットされ、2 番目のトランザクションはロールバックされます。
# 连接数据库
shell> mysql test
実行されたSQL:
-- 建表
mysql> CREATE TABLE customer (a INT, b CHAR (20), INDEX (a));
Query OK, 0 rows affected (0.00 sec)
mysql> -- 在 autocommit 开启的状态下, 启动事务:
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (10, 'Heikki');
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> -- 关闭 autocommit 状态; 启动事务:
mysql> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (15, 'John');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO customer VALUES (20, 'Paul');
Query OK, 1 row affected (0.00 sec)
mysql> DELETE FROM customer WHERE b = 'Heikki';
Query OK, 1 row affected (0.00 sec)
mysql> -- 回滚事务:
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM customer;
+------+--------+
| a | b |
+------+--------+
| 10 | Heikki |
+------+--------+
1 row in set (0.00 sec)
mysql>
クライアントプログラミング言語でのトランザクション
PHP、Perl DBI、JDBC、ODBC、または標準 C 呼び出しインターフェイスなどの MySQL クライアント API では、通常の SQL ステートメント (および)COMMIT
と同様に、トランザクション制御ステートメント ( など) を文字列として MySQL サーバーに送信できます。特定の API は、トランザクションをコミットおよびロールバックするための関数/メソッドも個別に提供します。SELECT
INSERT
14.7.2.3 非ロック一貫性読み取り
一貫した読み取りとは、InnoDB がマルチバージョン テクノロジーを通じてクエリに対して特定の時点でデータベース スナップショットを提示することを意味します。クエリでは、この時点より前にコミットされたすべてのトランザクションの変更を確認できますが、この時点以降に新しいトランザクションまたはコミットされていないトランザクションによって行われた変更は確認できません。
例外は、クエリでは同じトランザクション内で以前に実行されたステートメントによって行われた変更を確認できることです。この例外により、いくつかの例外が発生します。テーブル内の一部の行を更新すると、SELECT
その行は更新されてからの最新バージョンが表示されますが、他の行には古いバージョンが表示されたままになる可能性があります。他のセッションもこのテーブルを更新した場合、この例外は、存在しないある種の状態が表示される可能性があることを意味します。
これがデフォルトのREPEATABLE READ
分離レベルの場合、同じトランザクション内のすべての一貫した読み取りは、トランザクションが最初に読み取られたときに作成されたスナップショットを読み取ります。現在のトランザクションをコミットしてから、新しいクエリ ステートメントを実行して最新のデータ スナップショットを取得できます。
分離レベルを使用するとREAD COMMITTED
、トランザクション内のすべての一貫した読み取りで、独自の新しいスナップショットが設定され、読み取られます。
READ COMMITTED
と のREPEATABLE READ
分離レベルでは、 SELECT
InnoDB がステートメントを処理するためのデフォルト モードは一貫性読み取りです。
一貫した読み取りでは、読み取り中のテーブルにロックが設定されないため、他のセッションは読み取り中にそれらのテーブルを自由に変更できます。
デフォルトのREPEATABLE READ
分離レベルを使用すると、通常のSELECT
一貫性のある読み取りを実行するときに、InnoDB は現在のトランザクションの時点を指定し、この時点に基づいてトランザクション内のすべてのクエリでどのデータを参照できるかを決定します。この特定の時点以降、別のトランザクションがデータ行を削除してコミットした場合、現在のトランザクションはこの行が削除されたことを認識しません。挿入操作と更新操作は同じ方法で処理されます。
ヒント
データベース状態のスナップショットは、トランザクション内のステートメントに適用できますSELECT
が、DML ステートメント (追加、削除、変更) には必ずしも適用できるわけではありません。
いくつかの行が挿入または変更され、トランザクションが後でコミットされる場合、別のREPEATABLE READ
レベルの同時トランザクションで実行されたDELETE
ORステートメントUPDATE
は、これらのコミットされたばかりの行に影響を与える可能性がありますが、そのセッションは読み取り時にこれらの行を参照できない可能性があります。 。トランザクションが別のトランザクションによってコミットされた行を更新または削除すると、それらの変更は現在のトランザクションに表示されるようになります。
たとえば、次のシナリオがあります。
# 场景1
SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
-- Returns 0: 没有匹配的行. ::: 查不到
DELETE FROM t1 WHERE c1 = 'xyz';
-- 删除了被另一个事务提交的某些行... ::: 但确实会删除
# 场景2
SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
-- Returns 0: 没有匹配的行.::: 查不到
UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
-- Affects 10 rows: 比如, 另一个事务 txn2 刚刚提交了 10 行 'abc' 值. ::: 但确实更新了...
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: 本事务 txn1 这时候可以看到刚刚更新的行.
SELECT
トランザクションをコミットしてから、別のORステートメントを実行してSTART TRANSACTION WITH CONSISTENT SNAPSHOT
、時点を進めることができます。
これは、マルチバージョン同時実行制御と呼ばれます。
次の例では、セッション B がトランザクションをコミットし、セッション A 自体がトランザクションをコミットした後でのみ、A は B の新しい行を参照できます。これは、この時点では A の時点が B のコミットよりも新しいためです。
Session A Session B
SET autocommit=0; SET autocommit=0;
时间序
| SELECT * FROM t;
| empty set
| INSERT INTO t VALUES (1, 2);
|
v SELECT * FROM t;
empty set
COMMIT;
SELECT * FROM t;
empty set
COMMIT;
SELECT * FROM t;
---------------------
| 1 | 2 |
---------------------
データベースの最新の状態 (最新の状態) を表示したい場合は、READ COMMITTED
分離レベルを使用するか、ロック読み取りを使用できます。
SELECT * FROM t LOCK IN SHARE MODE;
分離レベルを使用する場合READ COMMITTED
、トランザクション内の各一貫した読み取りは、独自の新しいスナップショットを設定して読み取ります。
SELECT ステートメントでは、最新の行を含むトランザクションが終了するまでLOCK IN SHARE MODE
ロック read: がブロックされる可能性があります。14.7.2.4 読み取りのロックSELECT
を参照してください。
一貫性のある読み取りは、特定の DDL ステートメントをサポートしません。
DROP TABLE
MySQL は削除されたテーブルを使用できず、InnoDB がこのテーブルを破棄したため、整合性読み取りは一時的に有効になりません。ALTER TABLE
この操作では元のテーブルの一時コピーが作成され、一時コピーの作成後に元のテーブルが削除されるため、この操作では一貫性読み取りを有効にすることはできません。トランザクション内で一貫性のある読み取りが再実行されると、新しいテーブルのデータ行は表示されません。これは、トランザクションがスナップショットを取得したときにこれらの行が存在しなかったためです。この場合、「テーブル定義が変更されました。トランザクションを再試行してください」というエラー メッセージが返されますER_TABLE_DEF_CHANGED
。
動作は、 、 、FOR UPDATE
など、 またはを指定しないさまざまなクエリでは異なります。LOCK IN SHARE MODE
INSERT INTO ... SELECT
UPDATE ... (SELECT)
CREATE TABLE ... SELECT
- デフォルトでは、InnoDB はこれらのステートメントでより強力なロックを使用しますが、
SELECT
一部のステートメントは、同じトランザクション内であっても、一貫した読み取りセットのように動作しREAD COMMITTED
、独自の新しいスナップショットを読み取ります。 - この場合に非ロック読み取りを実行するには、
innodb_locks_unsafe_for_binlog
このオプションを有効にし、トランザクション分離レベルをREAD UNCOMMITTED
、READ COMMITTED
、またはに設定してREPEATABLE READ
、データ行の読み取り中のロックを回避します。
14.7.2.4 ロックされた読み取り
トランザクション内で最初にデータがクエリされ、次に関連データが挿入または更新される場合、従来のSELECT
ステートメントでは十分な保護が提供されません。
他のトランザクションは、検索したばかりの行を更新または削除することができます。
InnoDB は、追加のセキュリティを提供できる 2 種類のロック読み取り (ロック読み取り) をサポートしています。
-
SELECT ... LOCK IN SHARE MODE
読み取られたすべての行に共有ロックを設定します。他のセッションはこれらの行を読み取ることができますが、現在のトランザクションが終了するまで変更することはできません。クエリ時に、まだコミットされていない他のトランザクションによって一部の行が変更された場合、現在のクエリはそれらのトランザクションが終了するまでブロックされ、その後は最新の値が使用されます。
-
SELECT ... FOR UPDATE
検索で見つかったインデックス レコードの場合、データ行および関連するすべてのインデックス エントリは、それらの行に対してステートメントが実行されたかのようにロックされます
UPDATE
。SELECT ... LOCK IN SHARE MODE
特定の分離レベルでの変更、読み取りへの使用、さらには読み取り操作を含む他のトランザクションはブロックされます。一貫した読み取りでは、読み取りビュー内のレコードに設定されているロックは無視されます。(古いバージョンのデータ行はロックできないため、レコードと UNDO ログのメモリ コピーを通じて再構築されます)。
このタイプの句は、単一のテーブルであるか複数のテーブルであるかに関係なく、ツリー構造またはグラフ構造のデータを処理する場合に非常に役立ちます。まずそれを走査してから、レコードの一部を変更します。
トランザクションがコミットまたはロールバックされると、LOCK IN SHARE MODE
およびによってFOR UPDATE
設定されたロックが解放されます。
知らせ
読み取りのロックは、自動コミットが無効になっている場合にのみ可能です。(通常は
START TRANSACTION
ステートメントまたは設定を使用してautocommit=0
自動コミットを無効にします)
ネストされたステートメント クエリを実行する場合、サブクエリでロックされた読み取りも指定されていない限り、外側のクエリ内のロックされた読み取りはサブクエリのデータ行をロックしません。たとえば、次のステートメントはt2
テーブル内の行をロックしません。
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;
t2
テーブル内の行をロックするには、サブクエリ内でロック読み取りも必要です。
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;
ロック読み取りの使用例
例 1:
child
テーブルに新しい行を挿入する必要があるとします。ただしparent
、対応するレコードがテーブル内に存在することを確認してください。アプリケーション コードでは、次の一連の操作によって参照整合性を確保できます。
まず、一貫した読み取りを使用してテーブルがクエリされ、parent
親レコードが存在するかどうかが確認されます。child
これにより、テーブルへのデータの安全な挿入が保証されますか? いいえ、他のセッションが私たちの知らないSELECT
間にテーブルINSERT
内parent
のデータ行を削除する可能性があるためです。
LOCK IN SHARE MODE
この潜在的なバグを回避するには、次のように実行できますSELECT
。
SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;
LOCK IN SHARE MODE
クエリが親レコードを返した後、そのレコードをテーブル'Jones'
に追加してトランザクションをコミットしても安全です。他のトランザクションがテーブル内の対応するデータ行の排他ロックを取得しようとすると、それらはブロックされるため、続行する前に操作が完了するまで待つ必要があります。つまり、2 つのテーブル内のデータがロックされるまで待つ必要がありますchild
。parent
一貫した状態にあること。
例 2:
別の例として、CHILD_CODES
テーブル内に整数の counter_field フィールドがあり、child
テーブル内の各レコードに一意の ID を割り当てるために使用されます。
現在のカウンタ値を読み取るために一貫性読み取りモードまたは共有モードを使用することはできません。その場合、複数のクライアントが同じ値を参照することになり、2 つのトランザクションが同じ ID でデータを追加しようとすると、重複キー エラー (重複キー エラー) が発生します。 。
このシナリオでは、LOCK IN SHARE MODE
これは良い解決策ではなく、複数のユーザーが同時にカウンターを読み取ると、少なくとも 1 人のユーザーがカウンターの更新時にデッドロック状態になってしまいます。
カウンタを読み取ってインクリメントするには、FOR UPDATE
カウンタをインクリメントする前にカウンタのロックされた読み取りが実行されます。例えば:
SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;
SELECT ... FOR UPDATE
利用可能な最新のデータが読み取られ、読み取られた各行に排他ロックが設定されます。したがって、UPDATE
ステートメントが設定したものと同じロックを設定します。
この例は、SELECT ... FOR UPDATE
仕組みを説明するためのものです。MySQL では、一意の識別子を生成するタスクは、実際には 1 つのクエリで完了できます。
UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();
SELECT
このステートメントは、現在のセッションに基づいて ID 情報のみをクエリします。しかも表を一切読まずに。
14.7.3 InnoDB のさまざまな SQL ステートメントによって設定されるロック
読み取りUPDATE
およびDELETE
ステートメントをロックすると、通常、スキャンされたインデックス レコードにレコード ロックが設定されます。
SQL ステートメントにWHERE
条件が含まれているかどうかに関係なく、SQL ステートメントはロックされます。InnoDB は正確な WHERE 条件を記録しないため、スキャンしたインデックス範囲のみを知っています。一般に、一時的なキー ロック ( next-key lock
) が使用され、他のトランザクションが前のギャップ (ギャップ) に新しい行を挿入するのをブロック (ブロック) できます。もちろん、ギャップ ロック ( gap locking
) を明示的に無効にすることも可能です。そうすれば、隣接するキー ロックは使用されなくなります。詳細については、前のセクション「14.7.1 InnoDB のロック」を参照してください。
トランザクション分離レベルは、使用されるロックにも影響します (セクション14.7.2.1「トランザクション分離レベル」を参照) 。
検索でセカンダリ インデックスが使用され、排他ロックが設定される場合、InnoDB は対応するクラスター化インデックス レコードも取得してロックします。
インデックスが見つからない場合、MySQL はフル テーブル スキャン (テーブル全体をスキャン) を実行し、テーブル内の各行がロックされるため、他のセッションによるテーブルへのすべての挿入がブロックされます。したがって、クエリの実行時に多くの不要な行をスキャンする必要がないように、適切なインデックスを作成することが非常に重要です。
さまざまな SQL ステートメントに対して InnoDB によって設定されるロックは次のとおりです。
-
SELECT ... FROM
一貫した読み取り、読み取りはスナップショットであり、通常はロックされず、トランザクション分離レベルのみがSERIALIZABLE
ロックされます。レベルの場合SERIALIZABLE
、検索されたインデックス レコードに共有一時キー ロックが設定されます。ただし、一意のインデックスを使用して一意の行をクエリする SQL ステートメントの場合、設定する必要があるインデックス レコード ロックは 1 つだけです。 -
SELECT ... FOR UPDATE
または の場合、SELECT ... LOCK IN SHARE MODE
スキャンされた行はロックされますが、結果セットにない行は通常、すぐに解放されます (WHERE
句内のフィルター条件を満たさないなど)。ただし、場合によっては、クエリの実行中に結果行と元のデータ ソース間の接続が失われる可能性があるため、行ロックがすぐに解放されないことがあります。たとえば、UNION
ステートメントでは、テーブル内でスキャン (およびロック) された行が、結果セットを満たすかどうかを計算する前に一時テーブルに挿入される場合があります。この場合、一時テーブルの行と元のテーブルの行の間の関係が失われるため、クエリの実行が終了するまで行ロックは解放されません。 -
SELECT ... LOCK IN SHARE MODE
検索で見つかったインデックス レコードに共有一時キー ロックを設定します。ただし、一意のインデックスを通じて一意の行を取得する場合は、単一のインデックス レコードをロックするだけで済みます。 -
SELECT ... FOR UPDATE
検索で見つかった各レコードに排他的キーロックを設定します。例外は、一意のインデックスを介して一意の行を検索するステートメントであり、ロックする必要があるインデックス レコードは 1 つだけです。検索で見つかったインデックス レコードについては、
SELECT ... FOR UPDATE
他のセッションの実行がブロックされSELECT ... LOCK IN SHARE MODE
、特定の分離レベルのトランザクションによるデータの読み取りがブロックされます。一貫性のある読み取りでは、読み取りビュー内のレコードに設定されているロックは無視されます。 -
UPDATE ... WHERE ...
検索で見つかった各レコードに排他的キーロックを設定します。例外は、一意のインデックスを介して一意の行を検索するステートメントであり、ロックする必要があるインデックス レコードは 1 つだけです。 -
クラスター化インデックス レコードが変更されると
UPDATE
、影響を受けるセカンダリ インデックス レコードに対して暗黙的なロックがかかります。新しいセカンダリ インデックス レコードを挿入する前および新しいセカンダリ インデックス レコードの挿入中に重複チェックを実行すると、UPDATE
影響を受けるセカンダリ インデックス レコードに共有ロックも設定されます。 -
DELETE FROM ... WHERE ...
検索で見つかった各レコードに排他的キーロックを設定します。例外は、一意のインデックスを介して一意の行を検索するステートメントであり、ロックする必要があるインデックス レコードは 1 つだけです。 -
INSERT
挿入された行に排他ロックを設定します。一時的なキー ロック (つまり、ギャップ ロックなし) ではなく、インデックス レコード ロックを使用しても、他のセッションが先行するギャップに新しい行を挿入することは妨げられません。新しい行を挿入する前に、挿入意図のギャップ ロックが設定されます。挿入意図を通知します。複数のトランザクションが同じインデックス ギャップに新しいレコードを挿入したい場合、それが同じスロットでない限り、待つ必要はありません。インデックス レコードの値がそれぞれ
4
とであると仮定します7
。それぞれ 5 と 6 の 2 つの値を挿入する 2 つのトランザクションがある場合、排他ロックを取得する前に、各トランザクションはまず挿入意図ロックを設定して 4 と 7 の間のギャップをロックしますが、その間のブロックはありません。なぜなら、行が矛盾しないからです。重複キー エラーが発生した場合、重複したインデックス レコードに共有ロックが設定されます。別のセッションが排他ロックを取得し、複数のセッションが同じ行を挿入しようとすると、この共有ロックによりデッドロックが発生する可能性があります。これは、別のセッションに参加して行が削除されたときに発生します。たとえば、InnoDB テーブルは
t1
次の構造を持っています。CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
次の操作を順番に実行する 3 つのセッションがあると仮定します。
セッション 1:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 2:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 3:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 1:
ROLLBACK;
セッション 1 の最初の操作では、行の排他ロックを取得します。セッション 2 とセッション 3 の両方の操作で重複キー エラーが生成され、行の共有ロックが要求されます。セッション 1 がロールバックすると、行の排他ロックが解放され、セッション 2 と 3 によって共有ロックのキューに入れられた要求が許可されます。このとき、セッション 2 とセッション 3 はデッドロックになります。相手が共有ロックを保持しているため、どちらのセッションも行の排他ロックを取得できません。
テーブルにキー値を持つ行が含まれており、3 つのセッションが次の順序で実行される場合にも、
1
同様の状況が発生します。セッション 1:
START TRANSACTION; DELETE FROM t1 WHERE i = 1;
セッション 2:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 3:
START TRANSACTION; INSERT INTO t1 VALUES(1);
セッション 1:
COMMIT;
セッション 1 の最初の操作では、行の排他ロックを取得します。セッション 2 とセッション 3 の両方の操作で重複キー エラーが発生し、行の共有ロックが要求されます。セッション 1 がコミットすると、行の排他ロックが解放され、キューに入れられたリクエストに対してセッション 2 とセッション 3 の共有ロックが付与されます。このとき、セッション 2 とセッション 3 はデッドロックになります。相手が共有ロックを保持しているため、どちらのセッションも行の排他ロックを取得できません。
-
INSERT ... ON DUPLICATE KEY UPDATE
単純なINSERT
ステートメントとは異なり、重複キー エラーが発生すると、更新される行に共有ロックではなく排他ロックが設定されます。排他的インデックス レコード ロックは、重複する主キー値に使用されます。重複する一意のキー値に排他的な一時キー ロックを設定します。 -
一意のキーに競合がない場合、 は と同じように
REPLACE
処理されますINSERT
。競合がある場合、置き換えられる回線に排他的キーロックが設定されます。 -
INSERT INTO T SELECT ... FROM S WHERE ...
T
挿入された各行に排他的レコード ロック (ギャップ ロックなし) を設定します。トランザクション分離レベルが であるかREAD COMMITTED
、トランザクション分離レベルがSERIALIZABLE
有効ではないが有効な場合innodb_locks_unsafe_for_binlog
、InnoDB は S テーブルで一貫した読み取り (ロックなし) を検索します。他のケースでは、InnoDB は行S
に共有一時キー ロックを設定します。ロックを設定する必要があるのはなぜですか? その理由は、ロールフォワード リカバリにステートメント ベースの bin-log を使用する場合、それぞれをまったく同じ方法で実行する必要があるためです。元の操作 SQL ステートメント。CREATE TABLE ... SELECT ...
SELECT
共有隣接キー ロックを使用するか、同様に一貫した読み取りを使用して実行しますINSERT ... SELECT
。または
SELECT
を使用して構築すると、 InnoDB はテーブルの行に共有一時キー ロックを設定します。REPLACE INTO t SELECT ... FROM s WHERE ...
UPDATE t ... WHERE col IN (SELECT ... FROM s ...)
s
-
指定された
AUTO_INCREMENT
属性列を持つテーブルのデータを初期化するとき、InnoDB は関連付けられたインデックスの末尾に排他ロックを設定します。の
innodb_autoinc_lock_mode=0
場合、InnoDB は特殊なテーブル ロック モードを使用します。AUTO-INC
このモードでは、自動インクリメント カウンターにアクセスするには、ロックを取得して、現在の SQL ステートメント (トランザクション全体ではなく) が終了するまで保持する必要があります。テーブル ロックが保持されている間はAUTO-INC
、他のクライアントはテーブルに挿入できません。
の一括挿入でもinnodb_autoinc_lock_mode=1
、同じ動作が発生します。
テーブルレベルのロックAUTO-INC
とinnodb_autoinc_lock_mode=2
を併用することはできません。詳細については、「InnoDB での AUTO_INCREMENT 処理」を参照してください。InnoDB は、以前に初期化された
AUTO_INCREMENT
列値を取得するときにロックを設定しません。 -
制約が定義されている場合
FOREIGN KEY
、すべての挿入、更新、削除で制約をチェックし、共有レコード ロックを設定する必要があります。InnoDB は、制約チェックが失敗した場合にもロックを設定します。 -
LOCK TABLES
テーブル ロックを設定しますが、InnoDB よりも高いレベルの MySQL ロックを設定します。これがデフォルト値である場合innodb_table_locks = 1
、autocommit = 0
InnoDB はテーブル ロックを認識でき、MySQL レベルは行レベルのロックも認識します。そうしないと、InnoDB の自動デッドロック検出は、そのようなテーブル ロックに関係するデッドロックを検出できません。同様に、この場合、上位層の MySQL は行レベルのロックを認識せず、他のセッションの行レベルのロックを保持するテーブルにテーブル ロックを設定する可能性があります。ただし、 「14.7.5.2 デッドロックの検出」で説明されているように、これはトランザクションの整合性には影響しません。
-
デフォルトの場合
innodb_table_locks=1
、LOCK TABLES
各テーブルで 2 つのロックが取得されます。MySQL レベルのテーブル ロックに加えて、InnoDB テーブル ロックも取得されます。MySQL 4.1.2 より前のバージョンは InnoDB テーブル ロックを取得しません。古いバージョンの動作は、 をinnodb_table_locks=0
指定することでシミュレートできます。InnoDB テーブル ロックが取得されていない場合、テーブル内のレコードが他のトランザクションによってロックされていれば、LOCK TABLES
実行は成功します。MySQL 5.7 では、明示的にロックされたテーブル
innodb_table_locks=0
に対しては機能しませんLOCK TABLES ... WRITE
。ただし、読み取りモードのテーブル ロック、および によって暗黙的にトリガーされる読み取り/書き込みロックLOCK TABLES ... READ
(トリガーなど) は機能します。LOCK TABLES ... WRITE
-
トランザクションがコミットまたは中止されると、トランザクションが保持しているすべての InnoDB ロックが解放されます。したがって
autocommit=1
、このモードでは、LOCK TABLES
取得した InnoDB テーブル ロックがすぐに解放されるため、InnoDB テーブルで実行することは意味がありません。 -
LOCK TABLES
暗黙的なUNLOCK TABLES` が実行されるため、トランザクションの実行中に他のテーブルをロックすることはできませんCOMMIT' 和
。
14.7.4 ファントム行
同一トランザクション内で、異なる時点で同じクエリ文を実行すると、異なる結果セットが得られる現象をファントムリーディング(ファントム問題)と呼びます。
例: 同じSELECT
ステートメントが 2 回実行されますが、2 番目のクエリは最初のクエリより 1 行多い行を返します。この場合、この行は「ファントム行 (Phantom Row)」になります。
child
テーブルの列にid
インデックスがあると仮定して、100
より大きい ID 値を持つすべての行をクエリし、後で更新できるようにロックします。
SELECT * FROM child WHERE id > 100 FOR UPDATE;
このクエリは、 より大きい ID 値を持つ最初のレコードから100
インデックスをスキャンします。テーブル内に、スキャン範囲内にid 値が90
sum であるデータの行が 2 つある場合、ギャップがロック ( to ) されていない場合、他のセッションはテーブルに id 値を持つ新しい行を挿入する可能性があります。同じトランザクション内で同じステートメントが再度実行されると、クエリによって返された結果に ID を持つ新しい行が表示されます。これはファントム行です。このような行がデータ項目とみなされる場合、この新しいファントム データはトランザクション分離原則に違反します。つまり、読み取られたデータはトランザクションの実行中に変更できません。102
90
102
101
SELECT
101
ファントム読み取りを防ぐために、InnoDB はインデックス行ロックとギャップ ロックを組み合わせた「ネクストキー ロック」と呼ばれるアルゴリズムを使用します。InnoDB の行レベル ロックの実行方法は、インデックスの検索またはスキャン時に、見つかったインデックス レコードに共有ロックまたは排他ロックが設定されることです。したがって、行レベルのロックは本質的にはインデックス レコードのロックです。
さらに、インデックス レコードに対する一時的なキー ロックは、そのインデックス レコードに先行する「ギャップ」にも影響します。つまり、即時キー ロックは、インデックス レコード ロックにインデックス レコードの前のギャップ ロックを加えたものになります。セッションがR
インデックス レコードに一時キー ロック (共有ロックまたは排他ロック) を設定すると、他のセッションはR
インデックスのソート順に従って直前のギャップに新しいインデックス レコードを挿入できなくなります。
InnoDB がインデックスをスキャンするとき、インデックス内の最後のレコードの後のギャップもロックする場合があります。前の例では、この状況を示しました。テーブルに 100 を超えるレコードが挿入されるのを防ぐためにid
、InnoDB によって設定されたロックにはid
102 以降のギャップ ロックが含まれています。
一時キー ロックを使用して一意性チェックを実装することもできます。共有モードでデータを読み取るときに、挿入する行に重複が見つからない場合は、読み取り時に一時キー ロックが設定され、他のセッションが妨げられるため、行を安全に挿入できます。後で重複を挿入しないようにします。実際、一時的なキー ロックは、テーブルに存在しないコンテンツを「ロック」できます。
ギャップ ロックを無効にする方法については、「14.7.1 InnoDB のロック」を参照してください。ただし、ギャップ ロックを無効にすると、他のセッションがギャップに新しい行を挿入する可能性があるため、ファントム読み取りの問題が発生する可能性があります。
14.7.5 InnoDB のデッドロック
デッドロックとは、複数のトランザクションが相互に必要なロックを保持しているために実行を継続できない状況を指します。それらはすべてリソースを待っているため、保持しているロックを解放する人はいません。
たとえば、UPDATE
や などのステートメントでSELECT ... FOR UPDATE
複数のテーブルや複数の行をロックする場合、逆の順序で実行するとデッドロックが発生する可能性があります。
タイミングの問題により、SQL ステートメントがインデックス範囲をロックしたり、ギャップをロックしたりする必要がある場合、各トランザクションがロックの一部のみを取得すると、デッドロックも発生します。
デッドロックの例については、次のサブセクション「14.7.5.1 InnoDB デッドロックの例」を参照してください。
デッドロックの可能性を減らすには:
- トランザクションを使用し、明細書は使用しないようにしてください
LOCK TABLES
。 - トランザクションの開始時間が長くなりすぎないように、挿入または更新を実行するトランザクションを十分に小さくします。
- 異なるトランザクションが複数のテーブルまたは広範囲の行を更新する場合は、各トランザクションの操作順序を同じにしてください。
SELECT ... FOR UPDATE
およびステートメントでUPDATE ... WHERE
使用される列にインデックスを作成します。
デッドロックの可能性は分離レベルの影響を受けません。分離レベルは読み取り操作の動作のみを変更しますが、デッドロックは書き込み操作によって発生するためです。
デッドロックの回避方法およびデッドロック状態からの回復方法については、「14.7.5.3 デッドロックの軽減とデッドロックの対処方法」を参照してください。
デッドロック検出が有効になっている場合 (デフォルトで有効)、InnoDB はデッドロックが発生した場所を自動的に検出し、トランザクションの 1 つ (ビクティム、ビクティムと呼ばれます) を自動的にロールバックします。innodb_deadlock_detect
このオプションで自動デッドロック検出が無効になっている場合、InnoDB はinnodb_lock_wait_timeout
指定されたタイムアウト期間までにのみトランザクションをロールバックできます。アプリケーションのロジックが完全に正しい場合でも、トランザクションの再試行などの状況に対処する必要があります。このコマンドを使用して、SHOW ENGINE INNODB STATUS
最新のデッドロックされたトランザクションを表示できます。デッドロックが頻繁に発生する場合、トランザクション構造の調整が必要な場合、またはエラー処理が必要な場合は、mysqld
起動パラメータのオプションを指定して、innodb_print_all_deadlocks
すべてのデッドロック関連情報をエラー ログに出力できます。
自動デッドロック検出と処理を実行する方法については、「14.7.5.2 デッドロック検出」を参照してください。
14.7.5.1 InnoDB デッドロックの例
次の例は、デッドロックが発生したときに何が起こるかを示しています。この例には、A と B という 2 人のクライアントが関係しています。
まず、クライアント A はテーブルを作成し、データを挿入して、トランザクションを開始します。トランザクションでは、クライアント A はS
共有モードでクエリを実行して行のロックを取得します。
# 客户端A
mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
+------+
| i |
+------+
| 1 |
+------+
次に、クライアント B はトランザクションを開始し、テーブルからこの行を削除しようとします。
# 客户端B
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> DELETE FROM t WHERE i = 1;
X
削除操作にはロックの取得が必要です。ただし、このロックはX
クライアント A が保持するS
ロックと互換性がないため、すぐには承認できず、行に追加する必要があるロック要求はキューに入れられるため、クライアント B はブロックされます。
次に、クライアント A もテーブルから行を削除しようとします。
# 客户端A
mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
クライアント A は行を削除する前にロックを取得する必要があるため、ここでデッドロックが発生していることがわかりますX
。しかし、クライアント B がX
ロックを要求し、クライアント AS
がロックを解放するのを待っているため、クライアント A のX
ロック要求は許可されません。
さらに、これはクライアント B によって最初に要求されたロックであるX
ため、A によって保持されているロックをロックS
にアップグレードすることはできません。X
その結果、InnoDB によってクライアントの 1 つがエラーを生成し、保持しているロックが解放されます。クライアントから返されるエラー メッセージは次のようなものです。
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
次に、別のクライアントのロック要求が許可されて実行され、テーブルから行が削除されます。
14.7.5.2 デッドロックの検出
deadlock detection
InnoDB はデフォルトでデッドロック検出 ( ) を有効にし、トランザクションによって生成された「デッドロック」を自動的に検出し、1 つ以上のトランザクションを自動的にロールバックしてデッドロック状態を解消します。
InnoDB は、より小さいトランザクションのロールバックを選択しようとします。トランザクションのサイズに関しては、挿入、更新、削除された行の数によって異なります。
デフォルトでは、 InnoDB がテーブル ロックを認識するinnodb_table_locks = 1
とautocommit = 0
、上位層の MySQL も行レベルのロックを認識できます。
そうしないと、MySQL ステートメントによって設定されたテーブル ロックや他のストレージ エンジンによって設定されたロックが関係してLOCK TABLES
いる場合、InnoDB はデッドロックを自動的に検出できません。この種の状況は、システム変数innodb_lock_wait_timeout
で設定されたタイムアウト期間によってのみ解決できます。
InnoDB モニターの出力LATEST DETECTED DEADLOCK
セクションに次の情報が含まれている場合: 「ロック テーブル待機グラフの検索が深すぎるか長すぎます。トランザクションに従ってロールバックします。」は、待機リスト内のトランザクションの数が制限 200 に達したことを意味します。200 を超えるトランザクションの待機リストはデッドロックとみなされ、待機リストを確認しようとしているトランザクションはロールバックされます。待機リスト内のトランザクションが 100 万を超えるロックを保持しており、チェックするロック スレッドがある場合にも、同じエラーが発生する可能性があります。
デッドロックを回避するためにデータベース操作を実行する方法については、以前の「14.7.5 InnoDB のデッドロック」を参照してください。
デッドロック検出を無効にする
innodb_deadlock_detect
デッドロック検出は、オプションを使用して無効にすることができます。
同時実行性の高いシステムでは、複数のスレッドが同じロックを待機すると、デッドロックの検出により応答が遅くなる可能性があります。場合によっては、innodb_lock_wait_timeout
自動デッドロック検出よりも、トランザクション ロールバックの指定されたタイムアウトに依存する方が効率的である場合があります。
14.7.5.3 デッドロック確率の低減、デッドロックエラーの処理
前のセクション「14.7.5.2 デッドロック検出」で紹介した概念に基づいています。ここでは、データベース操作を整理してデッドロックを最小限に抑える方法と、アプリケーションでのデッドロック エラーをフォローアップする方法を説明します。
デッドロックはトランザクション リレーショナル データベースの典型的な問題ですが、デッドロックが頻繁に発生して一部のトランザクションが実行できない場合を除き、それほどひどいものではありません。
通常、デッドロック エラーによりトランザクションがロールバックされると、アプリケーションはトランザクションを再実行する必要があります (一部のビジネスは手動でトリガーできます)。
InnoDB は行レベルの自動ロックを使用します。単一行のデータを挿入または削除するトランザクションでも、デッドロックが発生する可能性があります。これらの操作は実際には「アトミック」操作ではないため、(1 つ以上の) インデックス レコードに対応する行を挿入または削除すると、データベースは自動的にロックされます。
以下に説明する技術的手段を使用して、デッドロックに対処し、デッドロックの可能性を減らすことができます。
-
このコマンドを使用する
SHOW ENGINE INNODB STATUS
と、いつでも最近のデッドロックの原因を確認できます。デッドロックを回避するためにアプリケーションを調整するのに役立ちます。 -
デッドロック警告が頻繁に発生する場合は、
innodb_print_all_deadlocks
構成オプションを有効にして、より多くの DEBUG 情報を収集します。各デッドロックに関する情報を MySQL の「エラー ログ」に出力します。デバッグ後は、必ずこのオプションを無効にしてください。 -
デッドロックによりトランザクションが失敗した場合は、必ず再実行してください。デッドロックはひどいものではありません。通常はもう一度試してください。
-
競合の可能性を減らすために、トランザクションを小さくし、トランザクションの継続時間を短くします。
-
データベースに変更を加えた後は、競合の可能性を減らすために、タイムリーにトランザクションをコミットしてください。特に、
mysql
トランザクションをコミットせずに、長時間の対話セッションを開いたままにしないでください。 -
ロック読み取り (
SELECT ... FOR UPDATE
またはSELECT ... LOCK IN SHARE MODE
) を使用している場合は、より低い分離レベル (例: ) に切り替えてみてくださいREAD COMMITTED
。 -
1 つのトランザクションで複数のテーブルまたは複数のデータ セットを変更する場合は、毎回一貫した順序でこれらの操作を実行します。こうすることで、トランザクションは明確に定義されたキューを形成でき、デッドロックが発生しません。たとえば、複数の場所に分散した INSERT、UPDATE、および DELETE ステートメントを記述する代わりに、データベース操作を特定の関数またはサービス メソッドにカプセル化したり、保存サービスを呼び出したりします。
-
インデックスを適切に追加します。このようにすると、SQL クエリは少数のインデックス レコードをスキャンするだけで済み、ロックされたレコードが少なくなります。
EXPLAIN SELECT
MySQL サーバーが SQL クエリを実行するためにデフォルトで使用するインデックスを決定するために使用できます。 -
ロックを減らします。古いバージョンのスナップショットからデータを読み取ることができる場合は、
FOR UPDATE
orLOCK IN SHARE MODE
句を使用する必要はありません。デッドロックが頻繁に発生する場合は、READ COMMITTED
同じトランザクション内のすべての一貫した読み取りが独自の新しいスナップショットから読み取られるため、分離レベルを使用することをお勧めします。 -
少なくとも、テーブルレベルのロックを使用してトランザクションをシリアル化します。InnoDB などのトランザクションをサポートするストレージ エンジンで
LOCK TABLES
使用する正しい方法は、まずトランザクションをSET autocommit = 0
(ではなくSTART TRANSACTION
) オープンし、次にトランザクション内で実行しLOCK TABLES
、トランザクションが明示的にコミットされた後に呼び出しますUNLOCK TABLES
。たとえば、t2
テーブルからデータを読み取り、テーブルに書き込む必要がある場合はt1
、次の順序で実行できます。SET autocommit=0; LOCK TABLES t1 WRITE, t2 READ; ... do something with tables t1 and t2 here ... COMMIT; UNLOCK TABLES;
テーブルレベルのロックにより、他のセッションがこのテーブルを同時に更新するのを防ぐことができ、デッドロックが回避されますが、高負荷システムの応答時間は遅くなります。
-
トランザクションをシリアル化するもう 1 つの方法は、1 行のデータのみを含む補助「セマフォ」テーブルを作成することです。各トランザクションは、他のテーブルの読み取りまたは書き込みの前に、このデータ行を更新します。これにより、すべてのトランザクションが確実にシリアルに実行されるようになります。この場合、このシリアル化操作は行レベルのロックに対応するため、InnoDB のデッドロック検出アルゴリズムも有効になることに注意してください。MySQL テーブル ロックを使用する場合、デッドロックの問題はタイムアウトによってのみ解決できます。
関連リンク
ここに友達がいる場合は、Star でのサポートにご協力ください: https://github.com/cncounter/translation/