複数のソース (循環レプリケーションを含む) を含むレプリケーション設定を使用する場合、異なるソースがレプリカ上の同じ行を異なるデータで更新しようとする可能性があります。 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 システムテーブルを参照してください)。注記非常に大きなカラム (
TEXT
やBLOB
カラムなど) を持つテーブルをレプリケートする場合、--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-only
を 0
または 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. 適用される競合解決関数です。 この関数は、次のリストに示されたいずれかの関数として指定する必要があります。
NULL
: 競合解消が対応するテーブルに使用されないことを示します。
これらの関数は、次のいくつかのパラグラフで説明します。
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) で十分です。ただし、ほかの値が TimeBetweenGlobalCheckpoints
、TimeBetweenEpochs
、またはその両方に使用される場合を除きます。 値が小さすぎると誤判定となる可能性があります。一方、値が大きすぎると、データベース内に無駄なスペースが増える可能性があります。
競合する行のエントリは、このセクションの他の場所で説明されているのと同じ例外テーブルスキーマルールに従って定義されている場合、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
システム変数を各ソース (通常は各 PRIMARY
、SECONDARY
のいずれか) で適切な値に設定することで、プライマリロールとセカンダリロールが 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_TYPE
、NDB$CFT_CAUSE
または NDB$ORIG_TRANSID
を使用する場合は、必要な各カラムに接頭辞 NDB$
を使用して名前を付ける必要があります。 必要に応じて、オプションカラムを定義しない場合でも NDB$
プリフィクスを使用して必須カラムに名前を付けることができます。ただしこの場合、4 つすべての必須カラムにプリフィクスを使用して名前を付ける必要があります。
このカラムに続き、元のテーブルの主キーを構成するカラムは、元のテーブルの主キーの定義に使用される順番でコピーをしてください。 元のテーブルの主キーカラムを複製するカラムのデータ型は、元のカラムと同じ (または大きい) データ型にしてください。 主キーカラムのサブセットを使用できます。
例外テーブルでは、NDB
ストレージエンジンを使用する必要があります。 (例外テーブルで NDB$OLD()
を使用する例は、このセクションの後半で示します。)
オプションで、コピーされる主キーカラムのあとに追加カラムを定義できますが、その前に追加カラムを定義することはできません。このような追加カラムは NOT NULL
にはできません。 NDB Cluster は、次のいくつかの段落で説明する、3 つの事前定義された追加のオプションカラム NDB$OP_TYPE
、NDB$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_ROW
、UPDATE_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_EXIST
は UPDATE_ROW
および WRITE_ROW
操作の原因として報告できます。ROW_ALREADY_EXISTS
は WRITE_ROW
イベントに対して報告できます。 DATA_IN_CONFLICT
は、行ベースの競合関数が競合を検出した場合に報告されます。TRANS_IN_CONFLICT
は、トランザクション競合関数がトランザクション全体に属するすべての操作を拒否する場合に報告されます。
NDB$ORIG_TRANSID
: NDB$ORIG_TRANSID
カラム (使用する場合) には、元のトランザクションの ID が含まれます。 このカラムは次のように定義されます。
NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL
NDB$ORIG_TRANSID
は NDB
によって生成される 64 ビットの値です。 この値は、同じまたは異なる例外テーブルから同じ競合トランザクションに属する複数の例外テーブルのエントリを相互に関連付けるために使用されます。
元のテーブルの主キーの一部ではない追加の参照カラムには、
または colname
$OLD
という名前を付けることができます。colname
$NEW
は、更新および削除操作 (colname
$OLDDELETE_ROW
イベントを含む操作) で古い値を参照します。
を使用すると、挿入および更新操作、つまり colname
$NEWWRITE_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
で「もっとも大きいタイムスタンプが優先」競合解決を有効にするものとします。 これは、次のステップで実行できます。
ソース mysqld と
--ndb-log-update-as-write=OFF
が起動されていることを確認します。-
ソースで、次の
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
) の挿入と同じ効果があり、サーバーのデフォルトが使用されます。 -
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;
ここで示す順番で、次のステップが必要です。
-
まず (
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
カラムに挿入してください。 -
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_TYPE
、NDB$CFT_CAUSE
またはNDB$ORIG_TRANSID
のいずれかのカラムが含まれているため、4 つの必須カラムにはNDB$
接頭辞が必要です。 前に示したように、テーブル
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 の単一インスタンスで排他的行ロックが実行される方法に似ています。