Documentation Home
MySQL 5.6 リファレンスマニュアル
Download this Manual
PDF (US Ltr) - 26.8Mb
PDF (A4) - 26.9Mb
HTML Download (TGZ) - 7.2Mb
HTML Download (Zip) - 7.2Mb


18.6.11 MySQL Cluster レプリケーションの競合解決

複数のマスターが関与するレプリケーションセットアップを使用する場合 (循環レプリケーションを含みます)、異なるマスターが、異なるデータを持つスレーブ上の同じ行を更新しようとする可能性があります。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 システムテーブルを参照してください)。

    注記

    非常に大きなカラム (TEXTBLOB カラムなど) を持つテーブルを複製する場合、--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-only0 または 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) で十分です。ただし、ほかの値が TimeBetweenGlobalCheckpointsTimeBetweenEpochs、またはその両方に使用される場合を除きます。値が小さすぎると誤判定となる可能性があります。一方、値が大きすぎると、データベース内に無駄なスペースが増える可能性があります。

このセクションの別のところで説明するように、例外テーブルが同じ例外テーブルのスキーマルールに従って定義された場合、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_transNDB$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_idmaster_server_idmaster_epoch、および count のカラムについてはここで示した名前を使用し、元のテーブルの主キーのカラムに一致するカラムについては元のテーブルと同じ名前を使用することをお勧めします。

MySQL Cluster NDB 7.4.1 以降では、例外テーブルがこのセクションの後半で説明した複数のオプションカラム NDB$OP_TYPENDB$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_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 ビットの値です。この値は、同じまたは異なる例外テーブルから同じ競合トランザクションに属する複数の例外テーブルのエントリを相互に関連付けるために使用されます。

MySQL Cluster NDB 7.4.1 以降では、更新および削除の操作 (すなわち、DELETE_ROW イベントを含む操作です) で、元のテーブルの主キーの一部ではない追加参照カラムの名前を colname$OLD または colname$NEW. colname$OLD 参照の古い値にできます。colname$NEW は挿入および更新の操作 (言い換えると、WRITE_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もっとも大きいタイムスタンプが優先競合解決を有効にするものとします。これは、次のステップで実行できます。

  1. --ndb-log-update-as-write=OFF でマスターの mysqld を起動したことを確認します。

  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 のもっとも大きい値を持つ行のバージョンがスレーブに書き込まれます。

注記

ほかの 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;

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

  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 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_TYPENDB$CFT_CAUSE、または NDB$ORIG_TRANSID をテーブル定義に含めたため、4 つの必須カラムに NDB$ プリフィクスが必要です。

  3. 前に示したように、テーブル 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 つのインスタンスで排他的行ロックが実行される方法に類似しています。