TiDB 楽観的トランザクションモデル
楽観的トランザクションでは、競合する変更はトランザクション コミットの一部として検出されます。これにより、行ロックを取得するプロセスをスキップできるため、同時トランザクションが同じ行を頻繁に変更しない場合のパフォーマンスが向上します。同時トランザクションが同じ行を頻繁に変更する場合 (競合)、楽観的トランザクションのパフォーマンスは悲観的な取引よりも低下する可能性があります。
楽観的トランザクションを有効にする前に、アプリケーションがCOMMIT
ステートメントがエラーを返す可能性があることを正しく処理していることを確認してください。アプリケーションがこれをどのように処理するか不明な場合は、代わりに悲観的トランザクションを使用することをお勧めします。
注記:
v3.0.8 以降、TiDB はデフォルトで悲観的トランザクションモードを使用します。ただし、既存のクラスターを v3.0.7 以前から v3.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(メモリ不足)が発生する
トランザクションの再試行
注記:
v8.0.0 以降、
tidb_disable_txn_auto_retry
システム変数は非推奨となり、TiDB は楽観的トランザクションの自動再試行をサポートしなくなりました。 悲観的なトランザクションモードを使用することをお勧めします。楽観的トランザクションの競合が発生した場合は、エラーをキャプチャして、アプリケーションでトランザクションを再試行できます。
楽観的トランザクション モデルでは、競合が激しいシナリオで書き込み間の競合が発生するため、トランザクションのコミットに失敗する可能性があります。TiDB はデフォルトで楽観的同時実行制御を使用しますが、MySQL は悲観的同時実行制御を適用します。つまり、MySQL は書き込みタイプの SQL ステートメントの実行中にロックを追加し、その Repeatable Read 分離レベルでは現在の読み取りが許可されるため、コミットで例外が発生することはありません。アプリケーションの適応の難しさを軽減するために、TiDB は内部再試行メカニズムを提供します。
自動再試行
トランザクションのコミット中に書き込み間の競合が発生した場合、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
が高く、低速書き込みがない場合、現時点で書き込み競合が多数発生していると安全に結論付けることができます。