web-dev-qa-db-ja.com

MySQLでのトリガーvsストアドプロシージャのパフォーマンス

ここのDBA.StackExchangeに関する投稿( レコードのリビジョン番号を維持するためのトリガーのベストプラクティスは何ですか? )は、MySQLのパフォーマンスに関する興味深い質問(少なくとも、私にとって興味深い)を生み出しました。

コンテキストは、更新される各行のレコードをテーブルに挿入することです。行が更新される前に、以前の値を保存してから、列の1つ(「バージョン」列)を増分します。

トリガー内でこれを行うと、うまく機能します。 MySQLの場合、トリガーは 行ごと なので、簡単に解決できます。現在テーブルにあるデータを選択し、それをロギングテーブルに挿入して、新しいデータの「バージョン」列を更新します。

ただし、このロジックをストアドプロシージャに移動することは可能です。その場合、挿入を実行してから、テーブルの「バージョン」列をインクリメントします。すべてがベースに設定されます。

したがって、この挿入を実行する場合、セットベースのストアドプロシージャアプローチまたはトリガーベースのアプローチを使用した方がパフォーマンスが向上しますか?

この質問はMySQLに関するものです(行ごとのトリガーがあるため)。ただし、他の行ごとのトリガーDBMSにも当てはまります。

11
Richard

簡単にするために、トリガーは、データベースの変更を追跡するためのあらゆる方法を実装するための方法です。ただし、トリガーを使用すると、内部で何が起こるかを認識する必要があります。

MySQLストアドプロシージャプログラミング によると、256ページの「トリガーオーバーヘッド」の下には次のように書かれています。

トリガーは、必要に応じて、適用するDMLステートメントにオーバーヘッドを追加することを覚えておくことが重要です。実際のオーバーヘッドの量はトリガーの性質によって異なりますが、---すべてのMySQLトリガーがFOR EACH ROWを実行するため--オーバーヘッドは、大量の行を処理するステートメントに対して急速に蓄積する可能性があります。したがって、トリガーに高価なSQLステートメントや手続きコードを配置することは避けてください。

トリガーオーバーヘッドの詳しい説明は、529〜531ページに記載されています。そのセクションの結論は次のとおりです。

ここでの教訓は次のとおりです。トリガーコードはDMLステートメントの影響を受ける行ごとに1回実行されるため、トリガーは簡単にDMLパフォーマンスの最も重要な要素になる可能性があります。トリガー本体内のコードは、できるだけ軽量にする必要があります。特に、トリガー内のSQLステートメントは、可能な限りインデックスでサポートする必要があります。

本に記載されていないのは、トリガーを使用する際のもう1つの要因です。監査ロギングに関しては、データのログイン先に注意してください。 MyISAMテーブルにログを記録することを選択した場合、MyISAMテーブルへの各INSERTは、INSERT中にフルテーブルロックを生成するためです。これは、高トラフィック、高トランザクション環境で深刻なボトルネックになる可能性があります。さらに、トリガーがInnoDBテーブルに対するものであり、トリガー内からMyISAMの変更をログに記録すると、これはACIDコンプライアンスを密かに無効にします(つまり、ブロックトランザクションを自動コミット動作に減らします)。これはロールバックできません。

InnoDBテーブルでトリガーを使用して変更をロギングする場合

  • ログに記録するテーブルもInnoDBです
  • 自動コミットがオフになっています
  • START TRANSACTION ... COMMIT/ROLLBACKブロックを完全に設定している

このようにして、監査ログはメインテーブルと同様にCOMMIT/ROLLBACKの恩恵を受けることができます。

ストアドプロシージャの使用に関しては、追跡されているテーブルに対してDMLのすべてのポイントでストアドプロシージャを入念に呼び出す必要があります。何万行ものアプリケーションコードに直面しても、ロギングの変更を見逃してしまいがちです。そのようなコードをトリガーに配置すると、それらすべてのDMLステートメントを見つける必要がなくなります。

警告

トリガーの複雑さによっては、それでもボトルネックになる可能性があります。監査ログのボトルネックを軽減したい場合は、何かできることがあります。ただし、インフラストラクチャを少し変更する必要があります。

一般的なハードウェアを使用して、さらに2つのDBサーバーを作成します。

これにより、監査ログによるメインデータベース(MD)での書き込みI/Oが削減されます。これを実現する方法は次のとおりです。

手順01)メインデータベースでバイナリログをオンにします。

ステップ02)安価なサーバーを使用して、バイナリログを有効にしてMySQL(MDと同じバージョン)をセットアップします。これはDMになります。 MDからDMへのレプリケーションをセットアップします。

ステップ03)2番目の安価なサーバーを使用して、バイナリログを無効にしてMySQL(MDと同じバージョン)をセットアップします。 -replicate-do-table を使用するように各監査テーブルを設定します。これはAUになります。 DMからAUへのレプリケーションをセットアップします。

ステップ04)mysqldumpでMDからテーブル構造を作成し、DMおよびAUにロードします。

ステップ05)MDのすべての監査テーブルを変換してBLACKHOLEストレージエンジンを使用する

ステップ06)DMおよびAUのすべてのテーブルを変換して、BLACKHOLEストレージエンジンを使用する

ステップ07)MyISAMストレージエンジンを使用するようにAUのすべての監査テーブルを変換する

それが終わったら

  • DMはMDから複製し、バイナリログにのみ記録します
  • すべての監査テーブルで -replicate-do-table フィルターを使用すると、AUはDMから複製します

これにより、監査情報が別のDBサーバーに格納され、MDで通常発生する書き込みI/Oの低下が軽減されます。

7
RolandoMySQLDBA

この更新を一括で実行する方法を次に示します。

この例では

  • table_AにはPRIMARY KEY IDがあります
  • PRIMARY KEYとしてidのみを使用して、table_A_Keys2Updateというテーブルを作成します。
  • 更新する必要があることがわかっているtable_AのIDをtable_A_Keys2Updateに入力します

Table_A_Keys2Updateを作成するには、次の手順を実行します。

CREATE TABLE table_A_Keys2Update SELECT id FROM table_A;
ALTER TABLE table_A_Keys2Update ADD PRIMARY KEY (id);

リビジョン番号をインクリメントする必要があるIDをtable_A_Keys2Updateに入力した後、次のUPDATE JOINを実行して、IDがtable_Aとtable_A_Keys2Updateの両方にあるすべての行のリビジョン番号をインクリメントします。

UPDATE table_A A INNER JOIN table_A_Keys2Update B USING (id)
SET A.revision = A.revision + 1;

この1行のクエリで、トリガーとストアドプロシージャを置き換えることができます。

必要に応じて、この1つのクエリをストアドプロシージャに配置し、必要に応じて呼び出すことができます。

1
RolandoMySQLDBA