TiDBの楽観的トランザクションモデル
楽観的トランザクションでは、競合する変更はトランザクションのコミットの一部として検出されます。これにより、同時実行トランザクションが同じ行をまれに変更する場合にパフォーマンスが向上します。行ロックを取得するプロセスをスキップできるためです。同時実行トランザクションが同じ行を頻繁に変更する場合(競合)、楽観的トランザクションのパフォーマンスは悲観的取引よりも悪くなる可能性があります。
楽観的トランザクションを有効にする前に、アプリケーションがステートメントCOMMITがエラーを返す可能性を正しく処理していることを確認してください。アプリケーションがこれをどのように処理するか不明な場合は、悲観的トランザクションを使用することをお勧めします。
注記:
TiDBはバージョン3.0.8以降、デフォルトで悲観的取引モード使用します。ただし、既存のクラスタをバージョン3.0.7以前から3.0.8以降にアップグレードしても、この変更は影響しません。つまり、悲観的トランザクションモードがデフォルトで使用されるのは、新しく作成されたクラスタのみです。
楽観的取引の原則
分散トランザクションをサポートするため、TiDBは楽観的トランザクションにおいて2フェーズコミット(2PC)を採用しています。その手順は以下のとおりです。
クライアントが取引を開始します。
TiDB は、現在のトランザクションの一意のトランザクション ID として、PD からタイムスタンプ (時間とともに単調増加し、グローバルに一意) を取得します。これを
start_tsと呼びます。TiDB はマルチバージョン同時実行制御を実装しているため、start_tsこのトランザクションによって取得されたデータベース スナップショットのバージョンとしても機能します。つまり、トランザクションはstart_tsのデータベースからのみデータを読み取ることができます。クライアントは読み取りリクエストを発行します。
- TiDBはPDからルーティング情報(TiKVノード間でデータがどのように分散されるか)を受け取ります。
- TiDBはTiKVからバージョン
start_tsのデータを受信する。
クライアントが書き込みリクエストを発行します。
TiDBは、書き込まれたデータが制約を満たしているかどうかを確認します(データ型が正しいこと、NOT NULL制約が満たされていることなど)。有効なデータは、TiDB内のこのトランザクションのプライベートメモリに格納されます。
クライアントはコミット要求を発行します。
TiDBは2PC方式を採用し、トランザクションの原子性を保証すると同時に、データをストア内に永続化します。
- TiDBは、書き込むデータからプライマリキーを選択します。
- TiDBはPDからリージョン分布情報を受け取り、それに応じてすべてのキーをリージョンごとにグループ化します。
- TiDBは、関係するすべてのTiKVノードに書き込み前リクエストを送信します。その後、TiKVは競合や期限切れのバージョンがないかを確認します。有効なデータはロックされます。
- TiDBはプリライトフェーズで全ての応答を受信し、プリライトは成功しました。
- TiDB は PD からコミット バージョン番号を受け取り、それを
commit_tsとマークします。 - TiDBは、プライマリキーが格納されているTiKVノードへの2回目のコミットを開始します。TiKVはデータをチェックし、書き込み前フェーズで残されたロックを解除します。
- TiDBは、第2フェーズが正常に完了したことを報告するメッセージを受信する。
TiDBは、トランザクションが正常にコミットされたことをクライアントに通知するメッセージを返します。
TiDBは、このトランザクションに残っているロックを非同期的にクリアします。
メリットとデメリット
上記のTiDBにおけるトランザクション処理から、TiDBトランザクションには以下の利点があることが明らかです。
- 理解しやすい
- 単一行トランザクションに基づくクロスノードトランザクションを実装する
- 分散型ロック管理
しかし、TiDBトランザクションには次のような欠点もあります。
- 2PCによるトランザクションレイテンシー
- タイムスタンプ割り当てを一元管理するサービスが必要
- メモリに大量のデータが書き込まれたときに発生するOOM(メモリ不足)エラー
トランザクションの再試行
注記:
バージョン8.0.0以降、システム変数
tidb_disable_txn_auto_retryは非推奨となり、TiDBは楽観的トランザクションの自動再試行をサポートしなくなりました。3 悲観的な取引モード使用することをお勧めします。楽観的的トランザクションの競合が発生した場合は、エラーを捕捉してアプリケーションでトランザクションを再試行できます。
楽観的トランザクションモデルでは、競合が激しいシナリオで書き込み競合が発生すると、トランザクションのコミットが失敗する可能性があります。バージョン3.0.8以降、TiDBはデフォルトでMySQLと同じ悲観的取引モード使用します。これは、TiDBとMySQLが書き込みタイプのSQLステートメントの実行中にロックを追加し、そのRepeatable Read分離レベルが現在の読み取りを許可するため、コミット時に例外が発生しないことを意味します。
自動再試行
注記:
- TiDB v3.0.0以降、トランザクションの自動再試行はデフォルトで無効になっています。これは、トランザクション分離レベルを損なう可能性があるためです。
- TiDB v8.0.0以降、楽観的トランザクションの自動再試行はサポートされなくなりました。
トランザクションのコミット中に書き込み競合が発生した場合、TiDB は書き込み操作を含む SQL ステートメントを自動的に再試行します。自動再試行を有効にするには、 tidb_disable_txn_auto_retry ~ OFFを設定し、再試行回数の上限をtidb_retry_limitに設定してください。
# Whether to disable automatic retry. ("on" by default)
tidb_disable_txn_auto_retry = OFF
# Set the maximum number of the retires. ("10" by default)
# When "tidb_retry_limit = 0", automatic retry is completely disabled.
tidb_retry_limit = 10
自動再試行は、セッションレベルまたはグローバルレベルのいずれかで有効にできます。
セッションレベル:
SET tidb_disable_txn_auto_retry = OFF;SET tidb_retry_limit = 10;世界レベル:
SET GLOBAL tidb_disable_txn_auto_retry = OFF;SET GLOBAL tidb_retry_limit = 10;
注記:
変数
tidb_retry_limitは、再試行の最大回数を決定します。この変数を0に設定すると、自動的にコミットされる暗黙的な単一ステートメントトランザクションを含め、どのトランザクションも自動的に再試行されません。これは、TiDBの自動再試行メカニズムを完全に無効にする方法です。自動再試行が無効になると、競合するすべてのトランザクションは、エラー(メッセージtry again laterを含む)をアプリケーションレイヤーに最速で報告します。
再試行の制限
デフォルトでは、TiDB はトランザクションを再試行しません。これは、更新の損失やREPEATABLE READ分離の破損につながる可能性があるためです。
その理由は、再試行の手順から明らかである。
- 新しいタイムスタンプを割り当てて、それを
start_tsとマークします。 - 書き込み操作を含むSQL文を再試行してください。
- 2フェーズコミットを実装する。
ステップ2では、TiDBは書き込み操作を含むSQLステートメントのみを再試行します。ただし、再試行中に、TiDBはトランザクションの開始を示す新しいバージョン番号を受け取ります。つまり、TiDBは新しいバージョンstart_tsのデータを使用してSQLステートメントを再試行します。この場合、トランザクションが他のクエリ結果を使用してデータを更新すると、分離レベルREPEATABLE READが侵害されるため、結果に矛盾が生じる可能性があります。
アプリケーションが更新の損失を許容でき、 REPEATABLE READ分離の一貫性を必要としない場合は、 tidb_disable_txn_auto_retry = OFF設定することでこの機能を有効にできます。
競合検出
分散データベースであるTiDBは、主に書き込み前段階で、TiKVレイヤーにおいてインメモリ競合検出を実行します。TiDBインスタンスはステートレスであり、互いの存在を認識しないため、自身の書き込みがクラスタ全体で競合を引き起こすかどうかを知ることはできません。したがって、競合検出はTiKVレイヤーで実行されます。
構成は以下のとおりです。
# Controls the number of slots. ("2048000" by default)
scheduler-concurrency = 2048000
さらに、TiKVはスケジューラにおける待機ラッチに費やされた時間の監視をサポートしています。

Scheduler latch wait duration値が高く、低速書き込みがない場合、現時点で多くの書き込み競合が発生していると安全に結論付けることができます。