複数のマスターが関与するレプリケーションセットアップを使用する場合 (循環レプリケーションを含みます)、異なるマスターが、異なるデータを持つスレーブ上の同じ行を更新しようとする可能性があります。MySQL Cluster レプリケーションでの競合解決は、特定のマスター上の更新をスレーブで適用すべきかどうかを判断するために使用されるユーザー定義の解決カラムを許可することで、このような競合を解決できます。
MySQL 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.4.1.20「レプリケーションと max_allowed_packet」を参照してください。
スレーブでは、どのタイプの競合解決 (「最後のタイムスタンプが優先」、「同じタイムスタンプが優先」、「プライマリが優先」、「プライマリが優先し、トランザクションを完結する」、または何もしない) を適用するかを指定する必要があります。これは、
mysql.ndb_replication
システムテーブルを使用して、テーブルごとに行われます (ndb_replication システムテーブルを参照してください)。MySQL Cluster NDB 7.4.1 以降は読み取り競合検出をサポートしています。すなわち、あるクラスタの特定の行の読み取りと、別のクラスタの同じ行の更新または削除との間の競合を検出します。これには、スレーブで
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()
関数は、プライマリおよびセカンダリ MySQL Cluster で適用されるレプリケーションエポックの相対順序を比較することで機能し、タイプスタンプを利用しません。
マスターカラムの制御
「前」のイメージおよび「後」のイメージ (すなわち、更新が適用される前と後のテーブルの状態) に関して、更新の操作を確認できます。一般的に主キーでテーブルを更新する場合、「前」のイメージはそれほど問題はありません。ただし、更新ごとにレプリケーションスレーブで更新された値を使用するかどうかを指定する必要がある場合は、両方のイメージがマスターのバイナリログに書き込まれることを確認する必要があります。このセクションの後半で説明するように、これは mysqld の --ndb-log-update-as-write
オプションで実行されます。
行全体のロギングを行うか、更新されたカラムだけのロギングを行うかは、MySQL サーバーが起動されたときに決まり、オンラインでは変更できません。異なるロギングオプションを使用して、mysqld を再起動するか、新しい mysqld インスタンスを起動する必要があります。
すべてまたは一部の行のロギング (--ndb-log-updated-only オプション)
コマンド行形式 | --ndb-log-updated-only |
---|---|
システム変数 | ndb_log_updated_only |
スコープ | グローバル |
動的 | はい |
型 | ブール |
デフォルト | ON |
競合解決のために、行のログを取る基本的な方法は 2 つあり、その方法は mysqld の --ndb-log-updated-only
オプションを設定すると指定されます。
行全体のログを取得します
更新されたカラムデータ (すなわち、値が設定されたカラムデータ) だけのログを取ります。この値が実際に変更されたかどうかには関係しません。これはデフォルトの動作です。
通常、更新されたカラムのみのログを取るだけで十分 (しかも効率的) です。ただし、すべての行のログを取る必要がある場合、--ndb-log-updated-only
を 0
または OFF
に設定すると、これを実行できます。
--ndb-log-update-as-write オプション: 変更されたデータを更新としてログを取得
コマンド行形式 | --ndb-log-update-as-write |
---|---|
システム変数 | ndb_log_update_as_write |
スコープ | グローバル |
動的 | はい |
型 | ブール |
デフォルト | ON |
MySQL Server の --ndb-log-update-as-write
オプションの設定では、ロギングが「前」のイメージがある状態で実行されるか、ない状態で実行されるかを指定します。競合解決は MySQL Server の更新ハンドラで行われるため、更新は更新であって書き込みではないようにマスターでのロギングを制御する必要があります。すなわち、更新は、新しい行の書き込みではなく、(更新が既存の行を置き換える場合でも) 既存の行の変更として処理されます。このオプションはデフォルトではオンです。つまり、更新は書き込みとして処理されます。(つまりデフォルトでは、更新は update_row
イベントとしてではなく、write_row
イベントとしてバイナリログに書き込まれます。)
オプションをオフにするには、マスターの mysqld を --ndb-log-update-as-write=0
または --ndb-log-update-as-write=OFF
で起動します。NDB テーブルから異なるストレージエンジンを使用するテーブルに複製する場合は、これを行う必要があります。詳細は、NDB から別のストレージエンジンへのレプリケーションおよびNDB から非トランザクションストレージエンジンへのレプリケーションを参照してください。
競合解決の制御
通常、競合解決は競合が発生する可能性のあるサーバーで有効になっています。ロギング方法の選択と同様に、mysql.ndb_replication
テーブル内のエントリによって有効化されます。
ndb_replication システムテーブル
競合解決を有効にするには、競合解決のタイプ、および使用する方法によってマスター、スレーブ、またはその両方の mysql
システムデータベースに ndb_replication
テーブルを作成する必要があります。このテーブルは、ロギングと競合解決関数をテーブル単位に制御するために使用され、レプリケーションに関与する 1 つの行単位テーブルを持ちます。ndb_replication
が作成され、競合を解決すべきサーバー上の制御情報が格納されます。スレーブでローカルにデータを変更することもできる単純なマスタースレーブのセットアップでは、一般的にこれはスレーブになります。より複雑なマスターマスター (2 方向) レプリケーションスキーマでは、これは関与するすべてのマスターになります。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 使用されるバイナリロギングのタイプです。これは、次の表に示すように指定されます。
値 | 内部値 | 説明 |
---|---|---|
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 (master_old_column_value == slave_current_column_value)
apply_update();
else
log_exception();
この関数は「同じ値が優先」競合解決に使用されます。このタイプの競合解決では、誤ったマスターから更新がスレーブに適用されません。
マスターの「前」のイメージのカラム値がこの関数で使用されます。
NDB$MAX(column_name) マスターからの特定の行の 「タイムスタンプ」カラム値がスレーブでの値より高い場合は、適用されます。高くない場合は、スレーブで適用されません。これは、次の擬似コードで説明します。
if (master_new_column_value > slave_current_column_value)
apply_update();
この関数は「もっとも大きいタイムスタンプが優先」競合解決に使用できます。このタイプの競合解決では、競合が発生した場合、最後に更新された行のバージョンが存続するバージョンになります。
マスターの「後」のイメージからのカラム値がこの関数で使用されます。
NDB$MAX_DELETE_WIN(column_name)
これは NDB$MAX()
のバリエーションです。タイムスタンプは削除操作に使用できないため、NDB$MAX()
を使用する削除は、実際には NDB$OLD
として処理されます。ただしユースケースによっては、これは最適ではありません。NDB$MAX_DELETE_WIN()
の場合、マスターから既存の行を追加または更新する特定行の 「タイムスタンプ」カラム値が、スレーブでの値より大きい場合に適用されます。ただし、削除操作は常に値が高いものとして処理されます。これは、次の擬似コードで説明します。
if ( (master_new_column_value > slave_current_column_value)
||
operation.type == "delete")
apply_update();
この関数は「もっとも大きいタイムスタンプ、削除が優先」競合解決に使用できます。このタイプの競合解決では、競合が発生した場合、削除された行のバージョン、または (そうでなければ) 最後に更新された行のバージョンが存続するバージョンになります。
NDB$MAX()
と同様に、マスターの「後」のイメージからのカラム値が、この関数で使用される値です。
NDB$EPOCH() および NDB$EPOCH_TRANS()
NDB$EPOCH()
関数は、複製されたエポックが、スレーブで発生した変更に関連してスレーブの MySQL Cluster で適用される順番を追跡します。この相対的な順番は、スレーブで発生した変更が、ローカルに発生した変更と同時に起こったかどうか、そのために競合が発生する可能性があるかどうかを判断するために使用されます。
NDB$EPOCH()
の説明で従う内容のほとんどは、NDB$EPOCH_TRANS()
にも適用されます。例外は本文に記載されています。
NDB$EPOCH()
は非対象であり、2 つのクラスタの循環レプリケーション構成の一方の MySQL Cluster で動作します (「アクティブアクティブ」レプリケーションと呼ぶこともあります)。ここで、プライマリとして動作するクラスタと、セカンダリとして動作するもう一方のクラスタについて言及します。プライマリのスレーブは競合の検出と処理を担います。一方、セカンダリのスレーブは競合の検出または処理には関与しません。
プライマリのスレーブが競合を検出すると、競合を相殺するためにスレーブ自身のバイナリログにイベントを挿入します。これにより、最終的にセカンダリの MySQL Cluster はプライマリに合うように再調整され、プライマリとセカンダリの不一致が回避されます。この補正と再調整のメカニズムには、プライマリの MySQL Cluster がセカンダリとの競合に常に勝る必要があります。つまり、競合が発生した場合、セカンダリからの変更ではなく、プライマリの変更が常に使用される必要があります。この「プライマリが常に勝つ」ルールには次の意味が込められています。
プライマリでいったんコミットされると、データを変更する操作は永続し、競合の検出と解決によって取り消されたり、ロールバックされたりしません。
プライマリから読み取られたデータには一貫性があります。プライマリでコミットされた変更 (ローカルまたはスレーブから) は、あとで取り消せません。
セカンダリでデータを変更する操作は、競合が発生しているとプライマリが判断した場合、あとで取り消される可能性があります。
セカンダリで読み取られた各行は、常に自己矛盾がなく、各行は、セカンダリでコミットされた状態、またはプライマリでコミットされた状態のいずれかを常に反映しています。
セカンダリで読み取られた一連の行は、任意の時点で必ずしも一貫しているわけではありません。
NDB$EPOCH_TRANS()
の場合、これは一時的な状態であり、NDB$EPOCH()
の場合は、永続的な状態となることがあります。十分な期間、競合が発生しないと、セカンダリの MySQL 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 システムテーブルを参照してください)。このシナリオでのプライマリとセカンダリの MySQL Cluster のロールは、mysql.ndb_replication
テーブルのエントリによってすべて決定されます。
NDB$EPOCH()
および NDB$EPOCH_TRANS()
で使用される競合検出アルゴリズムは非同期であるため、プライマリスレーブとセカンダリスレーブの server_id
エントリで異なる値を使用する必要があります。
MySQL Cluster NDB 7.3.6 より前では、DELETE
操作間の競合は UPDATE
操作の競合と同様に処理され、同じエポック内の競合は競合していると見なされていました。MySQL Cluster NDB 7.3.6 以降では、DELETE
操作間の競合だけでは NDB$EPOCH()
または NDB$EPOCH_TRANS()
を使用して競合を起動するには十分でなく、エポック内の相対的な配置は重要ではありません。(Bug "18454499)
NDB$EPOCH() および NDB$EPOCH_TRANS() ステータス変数
NDB$EPOCH()
および NDB$EPOCH_TRANS()
の競合検出をモニターするために、いくつかのステータス変数を使用できます。このスレーブが Ndb_conflict_fn_epoch
システムステータス変数の現在の値で最後に再起動されたあとに NDB$EPOCH()
によって競合中であると検出された行数がわかります。
Ndb_conflict_fn_epoch_trans
は NDB$EPOCH_TRANS()
によって競合中であると直接検出された行数を示します。実際に再構成された行数 (行のメンバーシップ、またはほかの競合する行と同じトランザクションの依存関係によって影響を受ける行を含みます) は、Ndb_conflict_trans_row_reject_count
によって取得されます。
詳細については、セクション18.3.4.4「MySQL Cluster のステータス変数」を参照してください。
NDB$EPOCH() の制約
NDB$EPOCH()
を使用して競合検出を実行する場合、現在、次の制約が適用されます。
TimeBetweenEpochs
(デフォルト: 100 ミリ秒) に比例する精度で、競合が MySQL Cluster のエポック境界を使用して検出されます。最小競合ウィンドウは、両方のクラスタの同じデータへの並列更新が常に競合を報告する最小時間です。これは、常にゼロでない時間であり、2 * (latency + queueing + TimeBetweenEpochs)
にほぼ比例します。これは、TimeBetweenEpochs
にデフォルトを仮定し、またクラスタ間の待機時間 (およびキューイング遅延) を無視して、最小競合ウィンドウが約 200 ミリ秒であることを意味します。この最小ウィンドウは、予期されたアプリケーション「競争」パターンを調べるときに考慮してください。NDB$EPOCH()
およびNDB$EPOCH_TRANS()
関数を使用するテーブルには、追加ストレージが必要です。関数に渡される値によって、1 行当たり 1 ビットから 32 ビットのスペースが必要です。-
削除操作の競合は、プライマリとセカンダリの間の相違につながる可能性があります。行が両方のクラスタ上で同時に削除される場合、競合は検出できますが、行が削除されるために競合は記録されません。すなわち、後続の再編成操作の伝播中にさらなる競合は検出されず、不一致になる可能性があります。
削除は外部シリアライズするか、1 つのクラスタだけにルーティングしてください。また、行の削除の間の競合を追跡できるように、このような削除および削除に続く挿入でトランザクションごとに個々の行を更新してください。これには、アプリケーションの変更が必要となる場合があります。
競合の検出に
NDB$EPOCH()
またはNDB$EPOCH_TRANS()
を使用する場合、循環「アクティブアクティブ」構成の MySQL Cluster が 2 つの場合のみがサポートされています。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()
と同じ条件と制約に従い、さらにバージョン 2 のバイナリログ行イベントが使用されます (--log-bin-use-v1-row-events
は 0 に等しい)。これにより、バイナリログ内のイベント当たり 2 バイトのストレージオーバーヘッドが加わります。また、すべてのトランザクション ID をセカンダリのバイナリログに記録する必要があり (--ndb-log-transaction-id
オプション)、このため、可変のオーバーヘッド (行当たり最大 13 バイト) がさらに加わります。
NDB$EPOCH() および NDB$EPOCH_TRANS()を参照してください。
NULL 対応するテーブルに競合解決を使用しないことを示します。
ステータス情報
サーバーステータス変数 Ndb_conflict_fn_max
は、mysqld が最後に起動されてから、「もっとも大きいタイムスタンプが優先」競合解決によって現在の SQL ノードに行が適用されなかった回数のカウントを示します。
指定された mysqld が最後に再起動されたあとに「同じタイムスタンプが優先」競合解決の結果として行が挿入されなかった回数は、グローバルステータス変数 Ndb_conflict_fn_old
によって取得されます。Ndb_conflict_fn_old
をインクリメントすること以外に、このセクションの後半の説明のように、使用されなかった行の主キーは 例外テーブルに挿入されます。
競合解決の例外テーブル
NDB$OLD()
競合解決関数を使用するには、このタイプの競合解決が使用される各 NDB
テーブルに対応する例外テーブルも作成する必要があります。これは、NDB$EPOCH()
または NDB$EPOCH_TRANS()
を使用する場合にも当てはまります。このテーブルの名前は、競合解決が適用されるテーブルの名前に文字列 $EX
を付加した名前です。(たとえば、元のテーブルの名前が mytable
である場合、対応する例外テーブルの名前は mytable$EX
になります。)MySQL Cluster NDB 7.4.1 より前では、このテーブルは次のように作成されます。
CREATE TABLE original_table$EX (
server_id INT UNSIGNED,
master_server_id INT UNSIGNED,
master_epoch BIGINT UNSIGNED,
count INT UNSIGNED,
original_table_pk_columns,
[additional_columns,]
PRIMARY KEY(server_id, master_server_id, master_epoch, count)
) ENGINE=NDB;
MySQL Cluster NDB 7.4.1 以降では、例外のタイプ、原因、および元のトランザクションに関する情報を提供するオプションカラムを含む、拡張した例外テーブル定義をサポートしています。これらのバージョンでは、例外テーブルを作成する構文は次のとおりです。
CREATE TABLE original_table$EX (
[NDB$]server_id INT UNSIGNED,
[NDB$]master_server_id INT UNSIGNED,
[NDB$]master_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$]master_server_id, [NDB$]master_epoch, [NDB$]count)
) ENGINE=NDB;
最初の 4 つのカラムが必須です。最初の 4 つのカラムの名前、および元のテーブルの主キーカラムに一致するカラムの名前は重要ではありません。ただし、わかりやすさと一貫性のため、server_id
、master_server_id
、master_epoch
、および count
のカラムについてはここで示した名前を使用し、元のテーブルの主キーのカラムに一致するカラムについては元のテーブルと同じ名前を使用することをお勧めします。
MySQL Cluster NDB 7.4.1 以降では、例外テーブルがこのセクションの後半で説明した複数のオプションカラム NDB$OP_TYPE
、NDB$CFT_CAUSE
、または NDB$ORIG_TRANSID
を使用している場合、各必須カラムもプリフィクス NDB$
を使用して名前を付ける必要があります。必要に応じて、オプションカラムを定義しない場合でも NDB$
プリフィクスを使用して必須カラムに名前を付けることができます。ただしこの場合、4 つすべての必須カラムにプリフィクスを使用して名前を付ける必要があります。
このカラムに続き、元のテーブルの主キーを構成するカラムは、元のテーブルの主キーの定義に使用される順番でコピーをしてください。元のテーブルの主キーカラムを複製するカラムのデータ型は、元のカラムと同じ (または大きい) データ型にしてください。MySQL Cluster NDB 7.3 以前では、例外テーブルの主キーはカラム対カラムで再作成する必要があります。MySQL Cluster NDB 7.4.1 以降では、主キーカラムのサブセットを使用することも可能です。
使用する MySQL Cluster のバージョンにかかわらず、例外テーブルは NDB
ストレージエンジンを使用する必要があります。(例外テーブルで NDB$OLD()
を使用する例は、このセクションの後半で示します。)
オプションで、コピーされる主キーカラムのあとに追加カラムを定義できますが、その前に追加カラムを定義することはできません。このような追加カラムは NOT NULL
にはできません。MySQL Cluster NDB 7.4.1 以降では、事前定義の 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 ビットの値です。この値は、同じまたは異なる例外テーブルから同じ競合トランザクションに属する複数の例外テーブルのエントリを相互に関連付けるために使用されます。
MySQL Cluster NDB 7.4.1 以降では、更新および削除の操作 (すなわち、DELETE_ROW
イベントを含む操作です) で、元のテーブルの主キーの一部ではない追加参照カラムの名前を
または colname
$OLD
. colname
$NEW
参照の古い値にできます。colname
$OLD
は挿入および更新の操作 (言い換えると、colname
$NEWWRITE_ROW
イベント、UPDATE_ROW
イベント、または両方のタイプのイベントを使用する操作です) で新しい値の参照に使用できます。競合中の操作は特定の非主キー参照カラムに値は指定されませんが、例外テーブルの行には NULL
またはそのカラムに対して定義されたデフォルト値のいずれかが格納されます。
mysql.ndb_replication
テーブルは、データテーブルがレプリケーション用にセットアップされたときに読み取られるため、複製されるテーブルに対応する行は、複製されるテーブルが作成される前に mysql.ndb_replication
に挿入する必要があります。
例
この例では、セクション18.6.5「レプリケーションのための MySQL Cluster の準備」およびセクション18.6.6「MySQL Cluster レプリケーションの起動 (レプリケーションチャネルが 1 つ)」で説明したとおり、すでに MySQL Cluster レプリケーションセットアップが正常に機能しているものとします。
NDB$MAX() の例
「タイムスタンプ」 としてカラム mycol
を使用して、テーブル test.t1
で「もっとも大きいタイムスタンプが優先」競合解決を有効にするものとします。これは、次のステップで実行できます。
--ndb-log-update-as-write=OFF
でマスターの mysqld を起動したことを確認します。-
マスターで、
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
のもっとも大きい値を持つ行のバージョンがスレーブに書き込まれます。
ほかの binlog_type
オプション (NBT_UPDATED_ONLY_USE_UPDATE
など) は、コマンド行オプションを使用するのではなく、ndb_replication
テーブルを使用してマスターでロギングを制御するために使用してください。
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 SMALLINT UNSIGNED, master_server_id INT UNSIGNED, master_epoch BIGINT UNSIGNED, count BIGINT UNSIGNED, a INT UNSIGNED NOT NULL, b CHAR(25) NOT NULL, [additional_columns,] PRIMARY KEY(server_id, master_server_id, master_epoch, count) ) ENGINE=NDB;
MySQL Cluster NDB 7.4.1 以降では、特定の競合に対するタイプ、原因、および元のトランザクション ID に関する情報のカラムを加えることができます。元のテーブルのすべての主キーカラムに一致するカラムを提供する必要もありません。これらのバージョンでは、次のように例外テーブルを作成できます。
CREATE TABLE test.t2$EX ( NDB$server_id SMALLINT UNSIGNED, NDB$master_server_id INT UNSIGNED, NDB$master_epoch BIGINT UNSIGNED, NDB$count BIGINT 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$master_server_id, NDB$master_epoch, NDB$count) ) ENGINE=NDB;
少なくとも 1 つのカラム
NDB$OP_TYPE
、NDB$CFT_CAUSE
、またはNDB$ORIG_TRANSID
をテーブル定義に含めたため、4 つの必須カラムにNDB$
プリフィクスが必要です。 前に示したように、テーブル
test.t2
を作成します。
NDB$OLD()
を使用して競合解決を実行するテーブルごとに、これらのステップに従う必要があります。このようなテーブルごとに、mysql.ndb_replication
に対応する行があり、複製されているテーブルと同じデータベースに例外テーブルがある必要があります。
読み取り競合の検出と解決
MySQL Cluster NDB 7.4.1 以降では、読み取り操作の追跡をサポートしています。これにより、あるクラスタの特定の行の読み取りと、別のクラスタの同じ行の更新または削除との間で競合を管理することが、循環レプリケーションセットアップで可能になります。この例では、employee
および department
テーブルを使用してシナリオをモデル化します。このシナリオでは、ある従業員がある部門から別の部門にマスタークラスタ (以降、クラスタ A と呼びます) 上で移動し、一方、スレーブクラスタ (以降、B と呼びます) は従業員の前の部門の従業員数をインターリーブされたトランザクションで更新します。
データテーブルは次の 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$master_server_id INT UNSIGNED,
NDB$master_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$master_server_id, NDB$master_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
) と更新操作の間で発生しているためです。MySQL Cluster NDB 7.4.1 以降では、スレーブクラスタで 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 |
...
+-------+-------------+-------------------+
読み取りの追跡は、存在する行だけに基づいて行われます。特定条件の追跡に基づく読み取りは、検出された行だけと競合し、インターリーブされたトランザクションで挿入された行とは競合しません。これは、MySQL Cluster の 1 つのインスタンスで排他的行ロックが実行される方法に類似しています。