MySQL 8.0 リファレンスマニュアル


MySQL 8.0 リファレンスマニュアル  /  ...  /  NDB Cluster レプリケーションの競合解決

23.6.11 NDB Cluster レプリケーションの競合解決

複数のソース (循環レプリケーションを含む) を含むレプリケーション設定を使用する場合、異なるソースがレプリカ上の同じ行を異なるデータで更新しようとする可能性があります。 NDB Cluster レプリケーションでの競合解決では、特定のソースの更新をレプリカに適用するかどうかを決定するためにユーザー定義の解決カラムを使用できるようにすることによって、このような競合を解決する手段が提供されます。

NDB Cluster (NDB$OLD(), NDB$MAX(), NDB$MAX_DELETE_WIN()) でサポートされている一部のタイプの競合解決は、このユーザー定義カラムを timestamp カラムとして実装します (ただし、このセクションで後述するように、そのタイプを TIMESTAMP にすることはできません)。 このタイプの競合解決は、常に、トランザクションごとではなく、行ごとに適用されます。 エポックベースの競合解決関数 NDB$EPOCH() および NDB$EPOCH_TRANS() は、エポックが複製された順番を比較します (このため、これらの関数はトランザクション型です)。 このセクションの後半で説明するように、競合が発生したときにレプリカの解決カラムの値を比較するには、様々な方法を使用できます。使用する方法はテーブルごとに設定できます。

更新を適用するかどうかを判断するときに解決関数で適切な選択を行えるように、解決カラムに適切な値で正しく移入されることを確認することは、アプリケーションの責任になります。

要件.  競合解消の準備は、ソースとレプリカの両方で行う必要があります。 これらのタスクは次のリストで説明します。

  • バイナリログを書き込むソースで、送信するカラム (すべてのカラム、または更新されたカラムのみ) を決定する必要があります。 MySQL Server でこれを行うには、全体的には mysqld 起動オプション --ndb-log-updated-only (このセクションの後半で説明します) を適用し、テーブル単位には mysql.ndb_replication テーブルのエントリによって実行します (ndb_replication システムテーブルを参照してください)。

    注記

    非常に大きなカラム (TEXTBLOB カラムなど) を持つテーブルをレプリケートする場合、--ndb-log-updated-only は、バイナリログのサイズを小さくし、max_allowed_packet の超過によるレプリケーションの失敗を回避するためにも役立つことがあります。

    この問題に関する詳細は、セクション17.5.1.20「レプリケーションと max_allowed_packet」を参照してください。

  • レプリカで、適用する競合解消のタイプ (「最新のタイムスタンプ優先」「同じタイムスタンプを優先」「プライマリ優先」「プライマリ受注、トランザクションの完了」またはなし) を決定する必要があります。 これは、mysql.ndb_replication システムテーブルを使用して、テーブルごとに行われます (ndb_replication システムテーブルを参照してください)。

  • NDB Cluster は、読み取りの競合検出もサポートしています。つまり、あるクラスタ内の特定の行の読み取りと別のクラスタ内の同じ行の更新または削除の間の競合を検出します。 これには、レプリカで ndb_log_exclusive_reads を 1 に設定して取得した排他的読取りロックが必要です。 競合している読み取りによって読み取られたすべての行のログが、例外テーブルに記録されます。 詳細は、読み取り競合の検出と解決を参照してください。

タイムスタンプベースの競合解決に関数 NDB$OLD()NDB$MAX()、および NDB$MAX_DELETE_WIN() を使用する場合、更新をタイムスタンプカラムとして指定するために使用されるカラムを参照するのが一般的です。 ただし、このカラムのデータ型は TIMESTAMP にしないでください。このデータ型は INT (INTEGER) または BIGINT にしてください。 また、timestamp カラムは UNSIGNED および NOT NULL にしてください。

このセクションで後述する NDB$EPOCH() および NDB$EPOCH_TRANS() 関数は、プライマリ NDB Cluster とセカンダリ NDB Cluster に適用されるレプリケーションエポックの相対的な順序を比較することで機能し、タイムスタンプは使用しません。

ソースカラムコントロール.  のイメージおよびのイメージ (すなわち、更新が適用される前と後のテーブルの状態) に関して、更新の操作を確認できます。 通常、主キーを使用してテーブルを更新する場合、ビフォアイメージは重要ではありません。ただし、更新された値をレプリカで使用するかどうかを更新ごとに決定する必要がある場合は、両方のイメージがソースバイナリログに書き込まれていることを確認する必要があります。 このセクションの後半で説明するように、これは mysqld--ndb-log-update-as-write オプションで実行されます。

重要

行全体のロギングを行うか、更新されたカラムだけのロギングを行うかは、MySQL サーバーが起動されたときに決まり、オンラインでは変更できません。異なるロギングオプションを使用して、mysqld を再起動するか、新しい mysqld インスタンスを起動する必要があります。

すべてまたは一部の行のロギング (--ndb-log-updated-only オプション)

コマンド行形式 --ndb-log-updated-only[={OFF|ON}]
システム変数 ndb_log_updated_only
スコープ グローバル
動的 はい
SET_VAR ヒントの適用 いいえ
Boolean
デフォルト値 ON

競合解決のために、行のログを取る基本的な方法は 2 つあり、その方法は mysqld--ndb-log-updated-only オプションを設定すると指定されます。

  • 行全体のログを取得します

  • 更新されたカラムデータ (すなわち、値が設定されたカラムデータ) だけのログを取ります。この値が実際に変更されたかどうかには関係しません。 これはデフォルトの動作です。

通常、更新されたカラムのみのログを取るだけで十分 (しかも効率的) です。ただし、すべての行のログを取る必要がある場合、--ndb-log-updated-only0 または OFF に設定すると、これを実行できます。

--ndb-log-update-as-write オプション: 変更されたデータを更新としてログを取得

コマンド行形式 --ndb-log-update-as-write[={OFF|ON}]
システム変数 ndb_log_update_as_write
スコープ グローバル
動的 はい
SET_VAR ヒントの適用 いいえ
Boolean
デフォルト値 ON

MySQL Server の --ndb-log-update-as-write オプションの設定では、ロギングがのイメージがある状態で実行されるか、ない状態で実行されるかを指定します。 競合解消は MySQL Server 更新ハンドラで行われるため、更新が書込みではなく更新であるようにレプリケーションソースによって実行されるロギングを制御する必要があります。つまり、既存の行を置き換える場合でも、更新は既存の行の書込みではなく既存の行の変更として処理されます。 このオプションはデフォルトではオンです。つまり、更新は書き込みとして処理されます。 つまり、更新はデフォルトで、update_row イベントとしてではなくバイナリログに write_row イベントとして書き込まれます。

このオプションを無効にするには、--ndb-log-update-as-write=0 または --ndb-log-update-as-write=OFF を使用してソース mysqld を起動します。 NDB テーブルから異なるストレージエンジンを使用するテーブルに複製する場合は、これを行う必要があります。詳細は、NDB から別のストレージエンジンへのレプリケーションおよびNDB から非トランザクションストレージエンジンへのレプリケーションを参照してください。

競合解決の制御.  通常、競合解決は競合が発生する可能性のあるサーバーで有効になっています。 ロギング方法の選択と同様に、mysql.ndb_replication テーブル内のエントリによって有効化されます。

ndb_replication システムテーブル.  競合解消を有効にするには、使用する競合解消タイプおよび方法に応じて、ソースまたはレプリカ (あるいはその両方) の mysql システムデータベースに ndb_replication テーブルを作成する必要があります。 このテーブルは、ロギングと競合解決関数をテーブル単位に制御するために使用され、レプリケーションに関与する 1 つの行単位テーブルを持ちます。ndb_replication が作成され、競合を解決すべきサーバー上の制御情報が格納されます。 レプリカ上でデータをローカルに変更できる単純なソース - レプリケーション設定では、これは通常レプリカです。 双方向レプリケーションなどのより複雑なレプリケーションスキームでは、これは通常、関係するすべてのソースです。 mysql.ndb_replication の各行は複製されるテーブルに対応し、対象テーブルに対するログの取得方法と競合の解決方法 (すなわち、もし競合が発生した場合に、どの競合解決関数を使用するか) を指定します。 mysql.ndb_replication テーブルの定義は次のとおりです。

CREATE TABLE mysql.ndb_replication  (
    db VARBINARY(63),
    table_name VARBINARY(63),
    server_id INT UNSIGNED,
    binlog_type INT UNSIGNED,
    conflict_fn VARBINARY(128),
    PRIMARY KEY USING HASH (db, table_name, server_id)
)   ENGINE=NDB
PARTITION BY KEY(db,table_name);

このテーブルのカラムは、次のいくつかのパラグラフで説明します。

db.  複製されるテーブルを含むデータベースの名前です。 ワイルドカード _% のどちらか、またはその両方を、データベース名の一部として使用できます。 マッチングは、LIKE 演算子に対して実装されたマッチングに似ています。

table_name.  複製されるテーブルの名前です。 テーブル名に、ワイルドカード _% のどちらか、またはその両方を含めることができます。 マッチングは、LIKE 演算子に対して実装されたマッチングに似ています。

server_id.  テーブルが存在する MySQL インスタンス (SQL ノード) の一意のサーバー ID です。

binlog_type.  使用されるバイナリロギングのタイプです。 これは、次の表に示すように指定されます。

表 23.69 binlog_type 値 (内部値と説明を含む)

内部値 説明
0 NBT_DEFAULT サーバーのデフォルトを使用します
1 NBT_NO_LOGGING バイナリログにこのテーブルの記録を行いません
2 NBT_UPDATED_ONLY 更新された属性のみが記録されます
3 NBT_FULL 更新されない場合でも、行全体を記録します (MySQL サーバーのデフォルトの動作)
4 NBT_USE_UPDATE (NBT_UPDATED_ONLY_USE_UPDATE および NBT_FULL_USE_UPDATE の値の生成のみを行い、単独では使用しません)
5 [Not used] ---
6 NBT_UPDATED_ONLY_USE_UPDATE (NBT_UPDATED_ONLY | NBT_USE_UPDATE に相当) 値が変更されていない場合でも、更新された属性を使用します
7 NBT_FULL_USE_UPDATE (NBT_FULL | NBT_USE_UPDATE に相当) 値が変更されていない場合でも、行全体を使用します

conflict_fn.  適用される競合解決関数です。 この関数は、次のリストに示されたいずれかの関数として指定する必要があります。

これらの関数は、次のいくつかのパラグラフで説明します。

NDB$OLD(column_name).  column_name の値がソースとレプリカの両方で同じである場合、更新が適用されます。それ以外の場合、更新はレプリカに適用されず、例外がログに書き込まれます。 これは、次の擬似コードで説明します。

if (source_old_column_value == replica_current_column_value)
  apply_update();
else
  log_exception();

この関数は同じ値が優先競合解決に使用されます。 このタイプの競合解決では、誤ったソースからのレプリカに更新が適用されないようにします。

重要

この関数では、ソースのビフォアイメージのカラム値が使用されます。

NDB$MAX(column_name).  ソースからの特定の行の timestamp カラム値がレプリカの値より大きい場合は適用され、それ以外の場合はレプリカに適用されません。 これは、次の擬似コードで説明します。

if (source_new_column_value > replica_current_column_value)
  apply_update();

この関数はもっとも大きいタイムスタンプが優先競合解決に使用できます。 このタイプの競合解決では、競合が発生した場合、最後に更新された行のバージョンが存続するバージョンになります。

重要

この関数では、アフターイメージのソースのカラム値が使用されます。

NDB$MAX_DELETE_WIN().  これは NDB$MAX() のバリエーションです。 削除操作に使用できるタイムスタンプがないため、NDB$MAX() を使用した削除は実際には NDB$OLD として処理されますが、一部のユースケースでは最適ではありません。 NDB$MAX_DELETE_WIN() では、ソースから取得された既存の行を追加または更新する特定の行の timestamp カラム値がレプリカの値より大きい場合、その値が適用されます。 ただし、削除操作は常に値が高いものとして処理されます。 これは、次の擬似コードで説明します。

if ( (source_new_column_value > replica_current_column_value)
        ||
      operation.type == "delete")
  apply_update();

この関数はもっとも大きいタイムスタンプ、削除が優先競合解決に使用できます。 このタイプの競合解決では、競合が発生した場合、削除された行のバージョン、または (そうでなければ) 最後に更新された行のバージョンが存続するバージョンになります。

注記

NDB$MAX() と同様に、ソースのアフターイメージのカラム値は、この関数で使用される値です。

NDB$EPOCH() および NDB$EPOCH_TRANS().  NDB$EPOCH() 関数は、レプリカクラスタでレプリケートされたエポックが適用される順序を、レプリカで発生した変更と比較して追跡します。 この相対的な順序付けを使用して、レプリカで発生した変更がローカルで発生した変更と同時に行われ、競合が発生する可能性があるかどうかを判断します。

NDB$EPOCH() の説明で従う内容のほとんどは、NDB$EPOCH_TRANS() にも適用されます。 例外は本文に記載されています。

NDB$EPOCH() は非対称であり、双方向レプリケーション構成 (active-active レプリケーションとも呼ばれる) 内のいずれかの NDB Cluster で動作します。 ここで、プライマリとして動作するクラスタと、セカンダリとして動作するもう一方のクラスタについて言及します。 プライマリ上のレプリカは競合の検出および処理を担当しますが、セカンダリ上のレプリカは競合の検出または処理には関与しません。

プライマリ上のレプリカは、競合を検出すると、これらを補正するためにイベントを独自のバイナリログに注入します。これにより、セカンダリ NDB Cluster が最終的にプライマリと再配置されるため、プライマリとセカンダリが相違しなくなります。 この補正および再編成メカニズムでは、プライマリ NDB Cluster がセカンダリとの競合を常に優先する必要があります。つまり、競合が発生した場合、プライマリ変更はセカンダリからの変更ではなく常に使用されます。 このプライマリが常に勝つルールには次の意味が込められています。

  • プライマリでコミットされたデータを変更する操作は完全に永続的であり、競合検出および解決によって元に戻されたりロールバックされることはありません。

  • プライマリから読み取られたデータには一貫性があります。 プライマリ (ローカルまたはレプリカから) でコミットされた変更は、後で元に戻されません。

  • セカンダリでデータを変更する操作は、競合が発生しているとプライマリが判断した場合、あとで取り消される可能性があります。

  • セカンダリで読み取られた各行は、常に自己矛盾がなく、各行は、セカンダリでコミットされた状態、またはプライマリでコミットされた状態のいずれかを常に反映しています。

  • セカンダリで読み取られた一連の行は、任意の時点で必ずしも一貫しているわけではありません。 NDB$EPOCH_TRANS() の場合、これは一時的な状態であり、NDB$EPOCH() の場合は、永続的な状態となることがあります。

  • 競合のない十分な長さの期間を想定すると、セカンダリ NDB Cluster 上のすべてのデータ (最終的に) はプライマリデータと整合性がとれます。

NDB$EPOCH() および NDB$EPOCH_TRANS() では、競合を検出するためにユーザースキーマを修正したり、アプリケーションを変更したりする必要はありません。 ただし、システム全体が指定された制限内で動作することを検証するために、使用するスキーマ、および使用するアクセスパターンを考慮する必要があります。

NDB$EPOCH() および NDB$EPOCH_TRANS() の各関数は、オプションのパラメータを取ることができます。これはエポックの下位 32 ビットを表すために使用するビット数で、次に示すように計算された値以下に設定する必要があります:

CEIL( LOG2( TimeBetweenGlobalCheckpoints / TimeBetweenEpochs ), 1)

これらの構成パラメータがデフォルト値 (それぞれ、2000 および 100 ミリ秒) である場合、値は 5 バイトになり、デフォルト値 (6) で十分です。ただし、ほかの値が TimeBetweenGlobalCheckpointsTimeBetweenEpochs、またはその両方に使用される場合を除きます。 値が小さすぎると誤判定となる可能性があります。一方、値が大きすぎると、データベース内に無駄なスペースが増える可能性があります。

競合する行のエントリは、このセクションの他の場所で説明されているのと同じ例外テーブルスキーマルールに従って定義されている場合、NDB$EPOCH()NDB$EPOCH_TRANS() の両方によって関連する例外テーブルに挿入されます (NDB$OLD(column_name) を参照)。 使用するデータテーブルを作成する前に、例外テーブルを作成する必要があります。

NDB$EPOCH() および NDB$EPOCH_TRANS() は、このセクションで説明したほかの競合検出関数と同様に、mysql.ndb_replication テーブルに関連するエントリを含めることで起動されます (ndb_replication システムテーブルを参照してください)。 このシナリオでのプライマリ NDB Cluster とセカンダリ NDB Cluster の役割は、mysql.ndb_replication テーブルエントリによって完全に決定されます。

NDB$EPOCH() および NDB$EPOCH_TRANS() で使用される競合検出アルゴリズムは非対称であるため、プライマリレプリカとセカンダリレプリカの server_id エントリには異なる値を使用する必要があります。

NDB$EPOCH() または NDB$EPOCH_TRANS() を使用して競合をトリガーするには、DELETE 操作間の競合のみでは不十分であり、エポック内の相対的な配置は関係ありません。 (Bug #18459944)

競合検出ステータス変数.  競合検出の監視には、いくつかのステータス変数を使用できます。 このレプリカが Ndb_conflict_fn_epoch システムステータス変数の現在の値から最後に再起動されてから、NDB$EPOCH() によって競合が検出された行数を確認できます。

Ndb_conflict_fn_epoch_trans には、NDB$EPOCH_TRANS() によって直接競合が検出された行数が用意されています。 Ndb_conflict_fn_epoch2 および Ndb_conflict_fn_epoch2_trans には、NDB$EPOCH2() および NDB$EPOCH2_TRANS() によって競合が検出された行数がそれぞれ表示されます。 実際に再編成された行の数 (他の競合する行と同じトランザクションへのメンバーシップまたは同じトランザクションへの依存関係の影響を含む) は、Ndb_conflict_trans_row_reject_count によって指定されます。

詳細については、セクション23.3.3.9.3「NDB Cluster ステータス変数」を参照してください。

NDB$EPOCH() の制約.  NDB$EPOCH() を使用して競合検出を実行する場合、現在次の制限が適用されます:

  • NDB Cluster のエポック境界を使用して競合が検出され、TimeBetweenEpochs に比例した粒度 (デフォルト) : 100 milliseconds). 最小競合ウィンドウは、両方のクラスタの同じデータへの並列更新が常に競合を報告する最小時間です。 これは、常にゼロでない時間であり、2 * (latency + queueing + TimeBetweenEpochs) にほぼ比例します。 これは、TimeBetweenEpochs にデフォルトを仮定し、またクラスタ間の待機時間 (およびキューイング遅延) を無視して、最小競合ウィンドウが約 200 ミリ秒であることを意味します。 この最小ウィンドウは、予期されたアプリケーション競争パターンを調べるときに考慮してください。

  • NDB$EPOCH() および NDB$EPOCH_TRANS() 関数を使用するテーブルには、追加ストレージが必要です。関数に渡される値によって、1 行当たり 1 ビットから 32 ビットのスペースが必要です。

  • 削除操作の競合は、プライマリとセカンダリの間の相違につながる可能性があります。 行が両方のクラスタ上で同時に削除される場合、競合は検出できますが、行が削除されるために競合は記録されません。 つまり、後続の再編成操作の伝播中にさらに競合が検出されないため、相違が発生する可能性があります。

    削除は外部シリアライズするか、1 つのクラスタだけにルーティングしてください。 また、行の削除の間の競合を追跡できるように、このような削除および削除に続く挿入でトランザクションごとに個々の行を更新してください。 これには、アプリケーションの変更が必要となる場合があります。

  • 競合検出に NDB$EPOCH() または NDB$EPOCH_TRANS() を使用している場合、現在、発生 active-active 構成内の 2 つの NDB Cluster のみがサポートされます。

  • BLOB または TEXT カラムを持つテーブルは、現在 NDB$EPOCH() または NDB$EPOCH_TRANS() ではサポートされていません。

NDB$EPOCH_TRANS().  NDB$EPOCH_TRANS() は、NDB$EPOCH() 関数を拡張します。 プライマリがすべてに優先ルール (NDB$EPOCH() および NDB$EPOCH_TRANS()を参照してください) を使用する同じ方法で競合が検出され、処理されますが、競合が発生した同じトランザクションで更新されたその他の行も競合していると見なす、という条件が追加されます。 つまり、NDB$EPOCH() がセカンダリの競合している各行を再編成するのに対して、NDB$EPOCH_TRANS() は競合しているトランザクションを再編成します。

また、競合しているトランザクションへの依存が検出されたトランザクションも競合していると見なされ、これらの依存関係はセカンダリクラスタのバイナリログの内容によって判断されます。 バイナリログにはデータ変更操作だけ (挿入、更新、削除) が含まれるため、重複するデータの変更だけがトランザクション間の依存関係の判断に使用されます。

NDB$EPOCH_TRANS() には、NDB$EPOCH() と同じ条件および制限が適用され、さらに、すべてのトランザクション ID がセカンダリバイナリログ (--ndb-log-transaction-id オプション) に記録され、可変オーバーヘッド (行あたり最大 13 バイト) が追加される必要があります。 非推奨の log_bin_use_v1_row_events システム変数 (デフォルトは OFF) は、NDB$EPOCH_TRANS() を使用した ON に設定しないでください。

NDB$EPOCH() および NDB$EPOCH_TRANS()を参照してください。

ステータス情報.  サーバーステータス変数 Ndb_conflict_fn_max は、mysqld が最後に起動されてから、もっとも大きいタイムスタンプが優先競合解決によって現在の SQL ノードに行が適用されなかった回数のカウントを示します。

指定された mysqld が最後に再起動されたあとに同じタイムスタンプが優先競合解決の結果として行が挿入されなかった回数は、グローバルステータス変数 Ndb_conflict_fn_old によって取得されます。 Ndb_conflict_fn_old をインクリメントすること以外に、このセクションの後半の説明のように、使用されなかった行の主キーは 例外テーブルに挿入されます。

NDB$EPOCH2().  NDB$EPOCH2() 関数は NDB$EPOCH() と似ていますが、NDB$EPOCH2() には双方向レプリケーショントポロジでの削除処理が用意されています。 このシナリオでは、ndb_slave_conflict_role システム変数を各ソース (通常は各 PRIMARYSECONDARY のいずれか) で適切な値に設定することで、プライマリロールとセカンダリロールが 2 つのソースに割り当てられます。 これが行われると、セカンダリによる変更はプライマリによってセカンダリに反映され、セカンダリは条件付きで適用されます。

NDB$EPOCH2_TRANS().  NDB$EPOCH2_TRANS() は、NDB$EPOCH2() 関数を拡張します。 競合は同じ方法で検出および処理され、プライマリロールとセカンダリロールがレプリケートクラスタに割り当てられますが、競合が発生した同じトランザクションで更新された他の行も競合とみなされる追加の条件があります。 つまり、NDB$EPOCH2() ではセカンダリで競合する個々の行が再編成され、NDB$EPOCH_TRANS() では競合するトランザクションが再編成されます。

NDB$EPOCH() および NDB$EPOCH_TRANS() では、最後に変更されたエポックごとに行ごとに指定されたメタデータを使用して、セカンダリからの受信レプリケートされた行変更がローカルでコミットされた変更と並行しているかどうかをプライマリで判断します。同時変更は競合しているとみなされ、セカンダリの例外テーブルの更新および再編成が行われます。 プライマリで行が削除されると問題が発生するため、レプリケートされた操作が競合するかどうかを判断するために使用可能な最終変更エポックはなくなります。つまり、競合する削除操作は検出されません。 これにより相違が生じる可能性があります。たとえば、一方のクラスタでの削除は、他方での削除および挿入と並行しているため、NDB$EPOCH() および NDB$EPOCH_TRANS() の使用時に削除操作を一方のクラスタにのみルーティングできます。

NDB$EPOCH2() では、削除 - 削除の競合を無視し、潜在的な結果の相違を回避することで、削除された行に関する情報を PRIMARY に格納するという問題は回避されます。 これは、セカンダリに正常に適用され、セカンダリからセカンダリにレプリケートされた操作を反映することで実現されます。 セカンダリに戻ると、プライマリから発生した操作によって削除されたセカンダリに操作を再適用するために使用できます。

NDB$EPOCH2() を使用する場合、セカンダリはプライマリから削除を適用し、反映された操作によってリストアされるまで新しい行を削除することに注意する必要があります。 理論上、セカンダリでの後続の挿入または更新はプライマリからの削除と競合しますが、この場合、クラスタ間の相違を防ぐために、これを無視してセカンダリを win に許可することを選択します。 つまり、削除後、プライマリは競合を検出せず、すぐにセカンダリの次の変更を採用します。 このため、セカンダリ状態は最終 (安定) 状態に進むと、複数の前のコミット済状態に再アクセスでき、これらの一部が表示される場合があります。

また、セカンダリからプライマリにすべての操作を反映すると、プライマリログバイナリログのサイズが増加し、帯域幅、CPU 使用率およびディスク I/O が必要になることにも注意してください。

セカンダリでの反映された操作の適用は、セカンダリのターゲット行の状態によって異なります。 セカンダリに反映された変更が適用されるかどうかは、Ndb_conflict_reflected_op_prepare_count および Ndb_conflict_reflected_op_discard_count のステータス変数をチェックすることで追跡できます。 適用される変更の数は、これらの値の違いにすぎません (Ndb_conflict_reflected_op_prepare_count は常に Ndb_conflict_reflected_op_discard_count 以上であることに注意してください)。

イベントは、次の両方の条件に該当する場合にのみ適用されます:

  • 行 (イベントのタイプに従って存在するかどうか) の存在。 削除および更新操作では、行がすでに存在している必要があります。 挿入操作の場合、行は存在していない必要があります。

  • 行はプライマリによって最後に変更されました。 反映された操作の実行によって変更が行われた可能性があります。

両方の条件が満たされない場合、反映された操作はセカンダリによって破棄されます。

競合解決の例外テーブル.  NDB$OLD() の競合解消機能を使用するには、このタイプの競合解消を使用する各 NDB テーブルに対応する例外テーブルも作成する必要があります。 これは、NDB$EPOCH() または NDB$EPOCH_TRANS() を使用する場合にも当てはまります。 このテーブルの名前は、競合解決が適用されるテーブルの名前に文字列 $EX を付加した名前です。 (たとえば、元のテーブルの名前が mytable である場合、対応する例外テーブルの名前は mytable$EX になります。) 例外テーブルを作成する構文は、次のとおりです:

CREATE TABLE original_table$EX  (
    [NDB$]server_id INT UNSIGNED,
    [NDB$]source_server_id INT UNSIGNED,
    [NDB$]source_epoch BIGINT UNSIGNED,
    [NDB$]count INT UNSIGNED,

    [NDB$OP_TYPE ENUM('WRITE_ROW','UPDATE_ROW', 'DELETE_ROW',
      'REFRESH_ROW', 'READ_ROW') NOT NULL,]
    [NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',
      'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL,]
    [NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL,]

    original_table_pk_columns,

    [orig_table_column|orig_table_column$OLD|orig_table_column$NEW,]

    [additional_columns,]

    PRIMARY KEY([NDB$]server_id, [NDB$]source_server_id, [NDB$]source_epoch, [NDB$]count)
) ENGINE=NDB;

最初の 4 つのカラムが必須です。 最初の 4 つのカラムの名前と元のテーブルの主キーカラムと一致するカラムは重要ではありません。ただし、わかりやすく一貫性のある理由から、ここに示す名前を server_id, source_server_id, source_epoch および count カラムに使用し、元のテーブルの主キーと一致するカラムに元のテーブルと同じ名前を使用することをお薦めします。

例外テーブルで、このセクションの後半で説明するオプションのカラム NDB$OP_TYPENDB$CFT_CAUSE または NDB$ORIG_TRANSID を使用する場合は、必要な各カラムに接頭辞 NDB$ を使用して名前を付ける必要があります。 必要に応じて、オプションカラムを定義しない場合でも NDB$ プリフィクスを使用して必須カラムに名前を付けることができます。ただしこの場合、4 つすべての必須カラムにプリフィクスを使用して名前を付ける必要があります。

このカラムに続き、元のテーブルの主キーを構成するカラムは、元のテーブルの主キーの定義に使用される順番でコピーをしてください。 元のテーブルの主キーカラムを複製するカラムのデータ型は、元のカラムと同じ (または大きい) データ型にしてください。 主キーカラムのサブセットを使用できます。

例外テーブルでは、NDB ストレージエンジンを使用する必要があります。 (例外テーブルで NDB$OLD() を使用する例は、このセクションの後半で示します。)

オプションで、コピーされる主キーカラムのあとに追加カラムを定義できますが、その前に追加カラムを定義することはできません。このような追加カラムは NOT NULL にはできません。 NDB Cluster は、次のいくつかの段落で説明する、3 つの事前定義された追加のオプションカラム NDB$OP_TYPENDB$CFT_CAUSE、および NDB$ORIG_TRANSID をサポートしています。

NDB$OP_TYPE: このカラムを使用して、競合の原因となっている操作のタイプを取得できます。 このカラムを使用する場合、ここで示すように定義します。

NDB$OP_TYPE ENUM('WRITE_ROW', 'UPDATE_ROW', 'DELETE_ROW',
    'REFRESH_ROW', 'READ_ROW') NOT NULL

WRITE_ROWUPDATE_ROW、および DELETE_ROW 操作タイプは、ユーザー起動の操作を表します。 REFRESH_ROW 操作は、競合を検出したクラスタから元のクラスタに戻されたトランザクションを相殺するときに、競合解決によって生成される操作です。 READ_ROW 操作は、排他的行ロックを使用して定義される、ユーザー起動の読み取り追跡操作です。

NDB$CFT_CAUSE: 登録された競合の原因を示すオプションのカラム NDB$CFT_CAUSE を定義できます。 このカラムは、使用する場合、ここで示すように定義されます。

NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',
    'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL

ROW_DOES_NOT_EXISTUPDATE_ROW および WRITE_ROW 操作の原因として報告できます。ROW_ALREADY_EXISTSWRITE_ROW イベントに対して報告できます。 DATA_IN_CONFLICT は、行ベースの競合関数が競合を検出した場合に報告されます。TRANS_IN_CONFLICT は、トランザクション競合関数がトランザクション全体に属するすべての操作を拒否する場合に報告されます。

NDB$ORIG_TRANSID: NDB$ORIG_TRANSID カラム (使用する場合) には、元のトランザクションの ID が含まれます。 このカラムは次のように定義されます。

NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL

NDB$ORIG_TRANSIDNDB によって生成される 64 ビットの値です。 この値は、同じまたは異なる例外テーブルから同じ競合トランザクションに属する複数の例外テーブルのエントリを相互に関連付けるために使用されます。

元のテーブルの主キーの一部ではない追加の参照カラムには、colname$OLD または colname$NEW という名前を付けることができます。colname$OLD は、更新および削除操作 (DELETE_ROW イベントを含む操作) で古い値を参照します。colname$NEW を使用すると、挿入および更新操作、つまり WRITE_ROW イベント、UPDATE_ROW イベントまたは両方のタイプのイベントを使用した操作で新しい値を参照できます。 競合する操作で主キーではない特定の参照カラムの値が指定されていない場合、例外テーブルの行には、NULL またはそのカラムに定義されているデフォルト値が含まれます。

重要

mysql.ndb_replication テーブルは、データテーブルがレプリケーション用にセットアップされたときに読み取られるため、複製されるテーブルに対応する行は、複製されるテーブルが作成されるmysql.ndb_replication に挿入する必要があります。

次の例では、セクション23.6.5「NDB Cluster のレプリケーションの準備」 および セクション23.6.6「NDB Cluster レプリケーションの開始 (シングルレプリケーションチャネル)」 で説明されているように、NDB Cluster レプリケーションがすでに機能していることを前提としています。

NDB$MAX() の例.  タイムスタンプ としてカラム mycol を使用して、テーブル test.t1もっとも大きいタイムスタンプが優先競合解決を有効にするものとします。 これは、次のステップで実行できます。

  1. ソース mysqld--ndb-log-update-as-write=OFF が起動されていることを確認します。

  2. ソースで、次の INSERT ステートメントを実行します:

    INSERT INTO mysql.ndb_replication
        VALUES ('test', 't1', 0, NULL, 'NDB$MAX(mycol)');

    server_id に 0 を挿入すると、このテーブルにアクセスするすべての SQL ノードが競合解決を使用します。 特定の mysqld だけで競合解決を使用する場合は、実際のサーバー ID を使用します。

    binlog_type カラムに NULL を挿入すると、0 (NBT_DEFAULT) の挿入と同じ効果があり、サーバーのデフォルトが使用されます。

  3. test.t1 テーブルを作成します。

    CREATE TABLE test.t1 (
        columns
        mycol INT UNSIGNED,
        columns
    ) ENGINE=NDB;

    これで、このテーブルで更新が実行されると、競合解消が適用され、mycol の最大値を持つ行のバージョンがレプリカに書き込まれます。

注記

コマンドラインオプションを使用するのではなく、ndb_replication テーブルを使用してソースのロギングを制御するには、NBT_UPDATED_ONLY_USE_UPDATE としてのその他の binlog_type オプションを使用する必要があります。

NDB$OLD() の例.  NDB テーブル (ここで定義されたテーブルなど) が複製中であり、このテーブルへの更新に同じタイムスタンプが優先競合解決を有効にするものとします。

CREATE TABLE test.t2  (
    a INT UNSIGNED NOT NULL,
    b CHAR(25) NOT NULL,
    columns,
    mycol INT UNSIGNED NOT NULL,
    columns,
    PRIMARY KEY pk (a, b)
)   ENGINE=NDB;

ここで示す順番で、次のステップが必要です。

  1. まず (test.t2 を作成する前に)、ここで示すように mysql.ndb_replication テーブルに行を挿入する必要があります。

    INSERT INTO mysql.ndb_replication
        VALUES ('test', 't2', 0, NULL, 'NDB$OLD(mycol)');

    binlog_type カラムに可能な値は、このセクションの前半に示しています。 値 'NDB$OLD(mycol)'conflict_fn カラムに挿入してください。

  2. test.t2 に対応する例外テーブルを作成します。 ここに示すテーブル作成ステートメントには、必要なすべてのカラムが含まれます。これらのカラムの後、およびテーブル主キーの定義の前に、追加のカラムを宣言する必要があります。

    CREATE TABLE test.t2$EX  (
        server_id INT UNSIGNED,
        source_server_id INT UNSIGNED,
        source_epoch BIGINT UNSIGNED,
        count INT UNSIGNED,
        a INT UNSIGNED NOT NULL,
        b CHAR(25) NOT NULL,
    
        [additional_columns,]
    
        PRIMARY KEY(server_id, source_server_id, source_epoch, count)
    )   ENGINE=NDB;

    特定の競合のタイプ、原因および元のトランザクション ID に関する情報を示す追加のカラムを含めることができます。 元のテーブルのすべての主キーカラムに一致するカラムを提供する必要もありません。 つまり、次のような例外テーブルを作成できます:

    CREATE TABLE test.t2$EX  (
        NDB$server_id INT UNSIGNED,
        NDB$source_server_id INT UNSIGNED,
        NDB$source_epoch BIGINT UNSIGNED,
        NDB$count INT UNSIGNED,
        a INT UNSIGNED NOT NULL,
    
        NDB$OP_TYPE ENUM('WRITE_ROW','UPDATE_ROW', 'DELETE_ROW',
          'REFRESH_ROW', 'READ_ROW') NOT NULL,
        NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',
          'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL,
        NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL,
    
        [additional_columns,]
    
        PRIMARY KEY(NDB$server_id, NDB$source_server_id, NDB$source_epoch, NDB$count)
    )   ENGINE=NDB;
    注記

    テーブル定義に NDB$OP_TYPENDB$CFT_CAUSE または NDB$ORIG_TRANSID のいずれかのカラムが含まれているため、4 つの必須カラムには NDB$ 接頭辞が必要です。

  3. 前に示したように、テーブル test.t2 を作成します。

NDB$OLD() を使用して競合解決を実行するテーブルごとに、これらのステップに従う必要があります。 このようなテーブルごとに、mysql.ndb_replication に対応する行があり、複製されているテーブルと同じデータベースに例外テーブルがある必要があります。

読み取り競合の検出と解決.  NDB Cluster は読み取り操作の追跡もサポートしているため、循環レプリケーション設定で、あるクラスタ内の特定の行の読み取りと別のクラスタ内の同じ行の更新または削除との間の競合を管理できます。 この例では、レプリカクラスタ (B) がインターリーブされたトランザクションで従業員の前部門の従業員数を更新している間に、employee および department テーブルを使用して、従業員がソースクラスタ上のある部門から別の部門に異動されるシナリオをモデル化します (これ以降、クラスタ A と呼びます)。

データテーブルは次の SQL ステートメントで作成されました。

# Employee table
CREATE TABLE employee (
    id INT PRIMARY KEY,
    name VARCHAR(2000),
    dept INT NOT NULL
)   ENGINE=NDB;

# Department table
CREATE TABLE department (
    id INT PRIMARY KEY,
    name VARCHAR(2000),
    members INT
)   ENGINE=NDB;

2 つのテーブルの内容は、次の SELECT ステートメントの出力 (一部) に示される行を含みます。

mysql> SELECT id, name, dept FROM employee;
+---------------+------+
| id   | name   | dept |
+------+--------+------+
...
| 998  |  Mike  | 3    |
| 999  |  Joe   | 3    |
| 1000 |  Mary  | 3    |
...
+------+--------+------+

mysql> SELECT id, name, members FROM department;
+-----+-------------+---------+
| id  | name        | members |
+-----+-------------+---------+
...
| 3   | Old project | 24      |
...
+-----+-------------+---------+

4 つの必須カラム (これらはこのテーブルの主キーに使用されます)、操作タイプと原因用のオプションカラム、および元のテーブルの主キーカラムを含んだ、ここで示した SQL ステートメントで作成された例外テーブルをすでに使用しているものとします。

CREATE TABLE employee$EX  (
    NDB$server_id INT UNSIGNED,
    NDB$source_server_id INT UNSIGNED,
    NDB$source_epoch BIGINT UNSIGNED,
    NDB$count INT UNSIGNED,

    NDB$OP_TYPE ENUM( 'WRITE_ROW','UPDATE_ROW', 'DELETE_ROW',
                      'REFRESH_ROW','READ_ROW') NOT NULL,
    NDB$CFT_CAUSE ENUM( 'ROW_DOES_NOT_EXIST',
                        'ROW_ALREADY_EXISTS',
                        'DATA_IN_CONFLICT',
                        'TRANS_IN_CONFLICT') NOT NULL,

    id INT NOT NULL,

    PRIMARY KEY(NDB$server_id, NDB$source_server_id, NDB$source_epoch, NDB$count)
)   ENGINE=NDB;

2 つのクラスタ上で同時に 2 つのトランザクションが発生するものとします。 クラスタ A では、新しい部門を作成してから、従業員番号 999 をその部門に移動します。次の SQL ステートメントを使用します。

BEGIN;
  INSERT INTO department VALUES (4, "New project", 1);
  UPDATE employee SET dept = 4 WHERE id = 999;
COMMIT;

同時にクラスタ B では、次に示すように、別のトランザクションが employee から読み取ります。

BEGIN;
  SELECT name FROM employee WHERE id = 999;
  UPDATE department SET members = members - 1  WHERE id = 3;
commit;

競合しているトランザクションは、通常は競合解決メカニズムで検出されません。これは、競合が読み取り (SELECT) と更新操作の間で発生しているためです。 この問題を回避するには、レプリカクラスタで SET ndb_log_exclusive_reads = 1 を実行します。 この方法で排他読取りロックを取得すると、ソースで読み取られた行にレプリカクラスタで競合解決が必要であるというフラグが付けられます。 これらのトランザクションのロギングの前にこの方法で排他読取りを有効にすると、クラスタ B での読取りが追跡され、解決のためにクラスタ A に送信されます。その後、従業員行での競合が検出され、クラスタ B でのトランザクションが中断されます。

競合は、ここで示すように (クラスタ A にある) 例外テーブルに READ_ROW 操作として登録されます (操作タイプの説明については、競合解決の例外テーブルを参照してください)。

mysql> SELECT id, NDB$OP_TYPE, NDB$CFT_CAUSE FROM employee$EX;
+-------+-------------+-------------------+
| id    | NDB$OP_TYPE | NDB$CFT_CAUSE     |
+-------+-------------+-------------------+
...
| 999   | READ_ROW    | TRANS_IN_CONFLICT |
+-------+-------------+-------------------+

読み取り操作で検出された、存在するどの行にもフラグが付けられます。 すなわち、ここで示すように、クラスタ A での更新と、同時に発生したトランザクションで同じテーブルからのクラスタ B での複数行の読み取りとの間で競合の影響を調べることで、例外テーブルに同じ競合に起因する複数の行のログが取られる場合があります。 ここで、クラスタ A で実行されるトランザクションを示します。

BEGIN;
  INSERT INTO department VALUES (4, "New project", 0);
  UPDATE employee SET dept = 4 WHERE dept = 3;
  SELECT COUNT(*) INTO @count FROM employee WHERE dept = 4;
  UPDATE department SET members = @count WHERE id = 4;
COMMIT;

同時に、ここで示すステートメントを含むトランザクションがクラスタ B で実行されます。

SET ndb_log_exclusive_reads = 1;  # Must be set if not already enabled
...
BEGIN;
  SELECT COUNT(*) INTO @count FROM employee WHERE dept = 3 FOR UPDATE;
  UPDATE department SET members = @count WHERE id = 3;
COMMIT;

この場合、ここで示すように、2 番目のトランザクションの SELECT の中の WHERE 条件に一致する 3 つすべての行が読み取られ、例外テーブルでフラグが付けられます。

mysql> SELECT id, NDB$OP_TYPE, NDB$CFT_CAUSE FROM employee$EX;
+-------+-------------+-------------------+
| id    | NDB$OP_TYPE | NDB$CFT_CAUSE     |
+-------+-------------+-------------------+
...
| 998   | READ_ROW    | TRANS_IN_CONFLICT |
| 999   | READ_ROW    | TRANS_IN_CONFLICT |
| 1000  | READ_ROW    | TRANS_IN_CONFLICT |
...
+-------+-------------+-------------------+

読み取りの追跡は、存在する行だけに基づいて行われます。 特定条件の追跡に基づく読み取りは、検出された行だけと競合し、インターリーブされたトランザクションで挿入された行とは競合しません。 これは、NDB Cluster の単一インスタンスで排他的行ロックが実行される方法に似ています。


関連キーワード:  NDB, テーブル, 競合, カラム, ndb, ndbinfo, 更新, 解決, 操作, TRANS