第 8 章 最適化

目次

8.1 最適化の概要
8.2 SQL ステートメントの最適化
8.2.1 SELECT ステートメントの最適化
8.2.2 DML ステートメントの最適化
8.2.3 データベース権限の最適化
8.2.4 INFORMATION_SCHEMA クエリーの最適化
8.2.5 その他の最適化のヒント
8.3 最適化とインデックス
8.3.1 MySQL のインデックスの使用の仕組み
8.3.2 主キーの使用
8.3.3 外部キーの使用
8.3.4 カラムインデックス
8.3.5 マルチカラムインデックス
8.3.6 インデックスの使用の確認
8.3.7 InnoDB および MyISAM インデックス統計コレクション
8.3.8 B ツリーインデックスとハッシュインデックスの比較
8.4 データベース構造の最適化
8.4.1 データサイズの最適化
8.4.2 MySQL データ型の最適化
8.4.3 多数のテーブルの最適化
8.4.4 MySQL が内部一時テーブルを使用する仕組み
8.5 InnoDB テーブルの最適化
8.5.1 InnoDB テーブルのストレージレイアウトの最適化
8.5.2 InnoDB トランザクション管理の最適化
8.5.3 InnoDB ロギングの最適化
8.5.4 InnoDB テーブルの一括データロード
8.5.5 InnoDB クエリーの最適化
8.5.6 InnoDB DDL 操作の最適化
8.5.7 InnoDB ディスク I/O の最適化
8.5.8 InnoDB 構成変数の最適化
8.5.9 多くのテーブルのあるシステムに対する InnoDB の最適化
8.6 MyISAM テーブルの最適化
8.6.1 MyISAM クエリーの最適化
8.6.2 MyISAM テーブルの一括データロード
8.6.3 REPAIR TABLE ステートメントの速度
8.7 MEMORY テーブルの最適化
8.8 クエリー実行プランの理解
8.8.1 EXPLAIN によるクエリーの最適化
8.8.2 EXPLAIN 出力フォーマット
8.8.3 EXPLAIN EXTENDED 出力フォーマット
8.8.4 クエリーパフォーマンスの推定
8.8.5 クエリーオプティマイザの制御
8.9 バッファリングとキャッシュ
8.9.1 InnoDB バッファープール
8.9.2 MyISAM キーキャッシュ
8.9.3 MySQL クエリーキャッシュ
8.9.4 プリペアドステートメントおよびストアドプログラムのキャッシュ
8.10 ロック操作の最適化
8.10.1 内部ロック方法
8.10.2 テーブルロックの問題
8.10.3 同時挿入
8.10.4 メタデータのロック
8.10.5 外部ロック
8.11 MySQL サーバーの最適化
8.11.1 システム要素およびスタートアップパラメータのチューニング
8.11.2 サーバーパラメータのチューニング
8.11.3 ディスク I/O の最適化
8.11.4 メモリーの使用の最適化
8.11.5 ネットワークの使用の最適化
8.11.6 スレッドプールプラグイン
8.12 パフォーマンスの測定 (ベンチマーク)
8.12.1 式と関数の速度の測定
8.12.2 MySQL ベンチマークスイート
8.12.3 独自のベンチマークの使用
8.12.4 performance_schema によるパフォーマンスの測定
8.12.5 スレッド情報の検査

この章では、MySQL のパフォーマンスを最適化する方法について説明し、例を示します。最適化には、いくつかのレベルでの構成、チューニング、およびパフォーマンスの測定が含まれます。業務の役割 (開発者、データベース管理者、または両方の組み合わせ) に応じて、個々の SQL ステートメント、アプリケーション全体、単一のデータベースサーバー、または複数のネットワーク接続されたデータベースサーバーのレベルで最適化できます。プロアクティブにパフォーマンスを事前に計画する場合や、または問題の発生後に、構成やコードの問題のトラブルシューティングを行う場合があります。CPU やメモリーの使用を最適化することで、スケーラビリティーを向上し、データベースを低下させず、より多くの負荷を処理させることもできます。

8.1 最適化の概要

データベースのパフォーマンスは、テーブル、クエリー、構成設定など、データベースレベルの複数の要因に依存します。これらのソフトウェア構造は、ハードウェアレベルでの CPU および I/O 操作につながり、それらを最小限にし、可能なかぎり効率的にする必要があります。データベースのパフォーマンスを行う際は、ソフトウェア側の高レベルのルールとガイドラインについて学び、時計を使ってパフォーマンスを測定することから始めます。熟練するにつれ、内部で起こっていることについて詳しく学習し、CPU サイクルや I/O 操作などの測定を開始します。

一般的なユーザーの目標は、既存のソフトウェアやハードウェア構成から、最高のデータベースパフォーマンスを得ることです。上級ユーザーは、MySQL ソフトウェア自体を改善する機会を見つけたり、独自のストレージエンジンやハードウェアアプライアンスを開発して、MySQL エコシステムを拡張したりします。

データベースレベルでの最適化

データベースアプリケーションを高速にすることにおいてもっとも重要な要素は、その基本設計です。

  • テーブルは適切に構築されていますか。特に、カラムに適切なデータ型があり、各テーブルに、作業の種類に適切なカラムがありますか。たとえば、頻繁な更新を実行するアプリケーションでは、多くの場合に少数のカラムのある多数のテーブルを使用し、大量のデータを解析するアプリケーションでは、多くの場合に多数のカラムのある少数のテーブルを使用します。

  • クエリーを効率的にするため、適切なインデックスが設定されていますか。

  • テーブルごとに適切なストレージエンジンを使用しており、使用している各ストレージエンジンの長所と機能を生かしていますか。特に、InnoDB などのトランザクションストレージエンジンまたは MyISAM などの非トランザクションストレージエンジンの選択は、パフォーマンスとスケーラビリティーにきわめて重要な場合があります。

    注記

    MySQL 5.5 以上では、InnoDB は新しいテーブルのデフォルトのストレージエンジンです。実際に、高度な InnoDB パフォーマンス機能は、InnoDB テーブルが、特にビジーなデータベースに対して、多くの場合に単純な MyISAM テーブルよりパフォーマンスが優れていることを意味します。

  • 各テーブルは適切な行フォーマットを使用していますか。この選択は、テーブルに使用されるストレージエンジンによっても異なります。特に、圧縮テーブルは使用するディスク領域が減るため、データの読み取りと書き込みに必要なディスク I/O も少なくなります。圧縮は、InnoDB テーブルでのあらゆる種類のワークロードと、読み取り専用 MyISAM テーブルに使用できます。

  • アプリケーションでは、適切なロック戦略を使用していますか。たとえば、データベース操作を同時に実行できるように、可能なかぎり共有アクセスを許可したり、重要な操作が最優先されるように、適切な場合に排他的アクセスを要求したりするなどです。ここでも、ストレージエンジンの選択が重要です。InnoDB ストレージエンジンは、ユーザーが関与せずに、ほとんどのロックの問題を処理するため、データベースの同時実行性を向上し、コードの実験やチューニングの量を削減できます。

  • キャッシュに使用されるメモリー領域がすべて正しくサイズ設定されていますか。つまり、頻繁にアクセスされるデータを保持するために十分な大きさがありながらも、物理メモリーをオーバーロードし、ページングを発生させるほど大きくしません。構成する主なメモリー領域は、InnoDB バッファープール、MyISAM キーキャッシュ、MySQL クエリーキャッシュです。

ハードウェアレベルでの最適化

データベースがビジーになるほど、どんなデータベースアプリケーションも最終的にハードウェアの制限に達します。データベース管理者は、アプリケーションをチューニングするか、サーバーを再構成してこれらのボトルネックを回避できるかどうか、または追加のハードウェアリソースが必要かどうかを評価する必要があります。システムボトルネックは一般に次の原因から発生します。

  • ディスクシーク。ディスクがデータを検索するには時間がかかります。最新のディスクでは、通常この平均時間が 10 ms 未満であるため、理論的には 1 秒間に約 100 シーク実行できることになります。この時間は、新しいディスクでは徐々に改善されますが、1 つのテーブルに対して最適化することはきわめて困難です。シーク時間を最適化する方法は、複数のディスクにデータを分散することです。

  • ディスクの読み取りと書き込み。ディスクが正しい位置にある場合に、データを読み取りまたは書き込みする必要があります。最新のディスクでは、1 つのディスクで少なくとも 10 - 20M バイト/秒のスループットを実現します。これは、複数のディスクから並列で読み取ることができるため、シークより最適化が簡単です。

  • CPU サイクル。データがメインメモリー内にある場合、結果を得るために、それを処理する必要があります。メモリーの量と比較して大きなテーブルを使用することは、もっとも一般的な制限要因になります。しかし、小さいテーブルでは、通常速度は問題になりません。

  • メモリー帯域幅。CPU で、CPU キャッシュに収められるより多くのデータを必要とする場合、メインメモリーの帯域幅がボトルネックになります。これは、ほとんどのシステムでまれなボトルネックですが、認識しておくべきです。

移植性とパフォーマンスのバランス

ポータブル MySQL プログラムで、パフォーマンス指向の SQL 拡張を使用するには、ステートメント内の MySQL 固有のキーワードを コメント区切り文字で囲むことができます。ほかの SQL サーバーはコメントにされたキーワードを無視します。コメントの作成については、セクション9.6「コメントの構文」を参照してください。

8.2 SQL ステートメントの最適化

インタプリタから直接発行されるか、API によって内部で送信されるかに関係なく、データベースアプリケーションのコアロジックは SQL ステートメントによって実行されます。このセクションのチューニングのガイドラインは、あらゆる種類の MySQL アプリケーションの高速化に役立ちます。このガイドラインでは、データを読み取りおよび書き込みする SQL 操作、一般的な SQL 操作の内部オーバーヘッド、およびデータベースモニタリングなどの特定のシナリオで使われる操作について説明します。

8.2.1 SELECT ステートメントの最適化

SELECT ステートメントの形式のクエリーは、データベースのすべてのルックアップ操作を実行します。動的 Web ページの 1 秒未満の応答時間を達成するためでも、または巨大な夜間のレポートを生成するための時間から数時間を取り除くためでも、これらのステートメントのチューニングは最優先です。

8.2.1.1 SELECT ステートメントの速度

クエリーの最適化の主な考慮事項は次のとおりです。

  • 遅い SELECT ... WHERE クエリーを高速化するため、最初に確認することは、インデックスを追加できるかどうかです。WHERE 句で使用するカラムにインデックスをセットアップし、評価、フィルタリング、および最終的な結果の取得を高速化します。無駄なディスク領域を避けるため、アプリケーションで使用される多くの関連クエリーを高速化する少数のインデックスのセットを構築します。

    インデックスは、結合外部キーなどの機能を使用して、さまざまなテーブルを参照するクエリーに特に重要です。EXPLAIN ステートメントを使用して、SELECT に使用するインデックスを判断できます。セクション8.3.1「MySQL のインデックスの使用の仕組み」およびセクション8.8.1「EXPLAIN によるクエリーの最適化」を参照してください。

  • 過度な時間がかかる関数呼び出しなどのクエリーの部分を特定し、チューニングします。クエリーの構築の仕方によっては、関数が結果セットのすべての行に対して 1 回ずつ、さらにはテーブル内のすべての行に対して 1 回ずつ呼び出されるなど、大幅に非効率性を拡大させていることがあります。

  • 特に大きなテーブルの場合に、クエリーでの完全テーブルスキャンの回数を最小にします。

  • ANALYZE TABLE ステートメントを定期的に使用して、テーブル統計を最新に維持し、オプティマイザが、効率的な実行プランを立てるために必要な情報が得られるようにします。

  • チューニング技法、インデックス作成技法、および各テーブルのストレージエンジンに固有の構成パラメータについて学習します。InnoDBMyISAM のどちらでも、クエリーの高いパフォーマンスを可能にし、維持するための一連のガイドラインがあります。詳細については、セクション8.5.5「InnoDB クエリーの最適化」およびセクション8.6.1「MyISAM クエリーの最適化」を参照してください。

  • 特に、MySQL 5.6.4 以上では、セクション14.13.14「InnoDB の読み取り専用トランザクションの最適化」の技法を使用して、InnoDB テーブルの単一クエリートランザクションを最適化できます。

  • 特にオプティマイザで同じ変換の一部を自動的に実行する場合、理解が困難になるようなクエリーの変換を避けます。

  • いずれかの基本ガイドラインによって、パフォーマンスの問題が簡単に解決されない場合、EXPLAIN プランを読み、インデックス、WHERE 句、結合句などを調整して、特定のクエリーの内部の詳細を調査します。(ある程度の専門技術に達している場合は、EXPLAIN プランを読むことがすべてのクエリーの最初の手順になると考えられます。)

  • MySQL がキャッシュに使用するメモリー領域のサイズとプロパティーを調整します。InnoDBバッファープールMyISAM キーキャッシュ、および MySQL クエリーキャッシュの効率的な使用によって、2 回目以降、メモリーから結果が取得されるため、繰り返しのクエリーの実行が高速化します。

  • キャッシュメモリー領域を使用して高速に実行するクエリーでも、必要なキャッシュメモリーを減らして、アプリケーションがよりスケーラブルになるように、さらに最適化できます。スケーラビリティーは、パフォーマンスを大幅に低下させずに、アプリケーションでより多くの同時ユーザー、大きなリクエストなどを処理できることを意味します。

  • クエリーの速度が、テーブルに同時にアクセスしているほかのセッションによって影響を受ける可能性があるロックの問題を処理します。

8.2.1.2 MySQL の WHERE 句の最適化の方法

このセクションでは、WHERE 句の処理で実行可能な最適化について説明します。例では SELECT ステートメントを使用していますが、DELETE および UPDATE ステートメント内の WHERE 句にも同じ最適化を適用します。

注記

MySQL オプティマイザへの取り組みは継続中であるため、MySQL が実行する最適化のすべてをここで説明しているわけではありません。

読みやすさを犠牲にしても、算術演算を高速化するように、クエリーを書き換えたいと考えがちです。MySQL では同様の最適化を自動的に実行するため、多くの場合にこの作業を回避でき、クエリーを理解しやすく、保守しやすい形式のままにしておくことができます。MySQL によって実行される最適化の一部を次に示します。

  • 不要なかっこの削除:

     ((a AND b) AND c OR (((a AND b) AND (c AND d))))
    -> (a AND b AND c) OR (a AND b AND c AND d)
  • 定数畳み込み:

     (a<b AND b=c) AND a=5
    -> b>5 AND b=c AND a=5
  • 定数条件の削除 (定数畳み込みのために必要です):

     (B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6)
    -> B=5 OR B=6
  • インデックスによって使用される定数式は 1 回だけ評価されます。

  • WHERE を使用しない単一テーブルの COUNT(*) は、MyISAM テーブルと MEMORY テーブルのテーブル情報から直接取得されます。これは、1 つだけのテーブルで使用された場合に、NOT NULL 式にも実行されます。

  • 無効な定数式の早期の検出。MySQL は一部の SELECT ステートメントが実行不可能であることをすみやかに検出し、行を返しません。

  • GROUP BY または集約関数 (COUNT()MIN() など) を使用しない場合、HAVINGWHERE とマージされます。

  • 結合内の各テーブルについて、テーブルの高速の WHERE 評価を取得し、可能なかぎり早く行をスキップするために、より単純な WHERE が構築されます。

  • クエリー内のほかのすべてのテーブルの前に、まず、すべての定数テーブルが読み取られます。定数テーブルは次のいずれかです。

    • 空白のテーブルまたは 1 行のテーブル。

    • PRIMARY KEY または UNIQUE インデックスでの WHERE 句で使用されるテーブル。ここではすべてのインデックス部分が定数式と比較され、NOT NULL として定義されます。

    次のテーブルはすべて定数テーブルとして使用されます。

    SELECT * FROM t WHERE primary_key=1;
    SELECT * FROM t1,t2 WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
  • テーブルを結合するための最適な結合の組み合わせは、すべての可能性を試してみることで見つかります。ORDER BY および GROUP BY 句内のすべてのカラムが同じテーブルにある場合、結合する際に最初にそのテーブルが選ばれます。

  • ORDER BY 句と別の GROUP BY 句がある場合、または、ORDER BY または GROUP BY に結合キュー内の最初のテーブルと異なるテーブルのカラムが含まれている場合は、一時テーブルが作成されます。

  • SQL_SMALL_RESULT オプションを使用すると、MySQL ではインメモリー一時テーブルが使用されます。

  • オプティマイザがテーブルスキャンを使用する方が効率的であると判断しないかぎり、各テーブルインデックスがクエリーされ、最適なインデックスが使用されます。かつて、スキャンは、最適なインデックスがテーブルの 30% 超にまたがっているかどうかに基づいて使用されていましたが、固定のパーセンテージによって、インデックスを使用するか、スキャンを使用するかの選択が決定されなくなりました。現在のオプティマイザは複雑になり、テーブルサイズ、行数、I/O ブロックサイズなどの追加の要因に基づいて推定します。

  • 場合によって、MySQL はデータファイルを参照しなくてもインデックスから行を読み取ることができます。インデックスから使用されるすべてのカラムが数値の場合、クエリーの解決にインデックスツリーのみが使用されます。

  • 各行が出力される前に、HAVING 句に一致しないものはスキップされます。

きわめて高速なクエリーのいくつかの例:

SELECT COUNT(*) FROM tbl_name;
SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;
SELECT MAX(key_part2) FROM tbl_name WHERE key_part1=constant;
SELECT ... FROM tbl_name ORDER BY key_part1,key_part2,... LIMIT 10;
SELECT ... FROM tbl_name ORDER BY key_part1 DESC, key_part2 DESC, ... LIMIT 10;

MySQL は、インデックス設定されたカラムが数値であるとして、インデックスツリーのみを使用して、次のクエリーを解決します。

SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;
SELECT COUNT(*) FROM tbl_name WHERE key_part1=val1 AND key_part2=val2;
SELECT key_part2 FROM tbl_name GROUP BY key_part1;

次のクエリーは、個別のソーティングパスを使用せずに、インデックスを使用して、ソート順で行を取得します。

SELECT ... FROM tbl_name ORDER BY key_part1,key_part2,... ;
SELECT ... FROM tbl_name ORDER BY key_part1 DESC, key_part2 DESC, ... ;

8.2.1.3 range の最適化

range アクセスメソッドは単一のインデックスを使用して、1 つまたは複数のインデックス値間隔の中に含まれるテーブル行のサブセットを取得します。これは、シングルパートまたはマルチパートインデックスに使用できます。次のセクションでは、WHERE 句から間隔を抽出する方法について詳しく説明します。

8.2.1.3.1 シングルパートインデックスの range アクセスメソッド

シングルパートインデックスでは、インデックス値間隔は WHERE 句内の対応する条件によって便利に表すことができるため、間隔よりも範囲条件について説明します。

シングルパートインデックスの範囲条件の定義は次のとおりです。

  • BTREEHASH の両方のインデックスで、=<=>IN()IS NULL、または IS NOT NULL 演算子を使用した場合、キーパートと定数値の比較は範囲条件です。

  • さらに、BTREE インデックスでは、><>=<=BETWEEN!=、または <> 演算子、または LIKE への引数が、ワイルドカード文字で始まっていない定数文字列である場合の LIKE 比較を使用した場合に、キーパートと定数値の比較は範囲条件です。

  • すべての種類のインデックスで、OR または AND で組み合わされた複数の範囲条件は、1 つの範囲条件を形成します。

先述の定数値とは次のいずれかを意味します。

  • クエリー文字列からの定数

  • 同じ結合からの const または system テーブルのカラム

  • 非相関サブクエリーの結果

  • 以前の型の部分式からのみ構成された式

以下に WHERE 句内で範囲条件を使用したクエリーのいくつかの例を示します。

SELECT * FROM t1 WHERE key_col > 1 AND key_col < 10;
SELECT * FROM t1 WHERE key_col = 1 OR key_col IN (15,18,20);
SELECT * FROM t1 WHERE key_col LIKE 'ab%' OR key_col BETWEEN 'bar' AND 'foo';

定数伝播フェーズ中に、一部の非定数値が定数に変換されることがあります。

MySQL は可能なインデックスごとに、WHERE 句から範囲条件を抽出しようとします。抽出プロセス時に、範囲条件の構築に使用できない条件はドロップされ、重複する範囲を生成する条件は組み合わされて、空の範囲を生成する条件は削除されます。

key1 がインデックス設定されたカラムで nonkey がインデックス設定されていない、次のステートメントを考慮します。

SELECT * FROM t1 WHERE (key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR (key1 < 'bar' AND nonkey = 4) OR (key1 < 'uux' AND key1 > 'z');

キー key1 の抽出プロセスは次のとおりです。

  1. 元の WHERE 句から始めます。

    (key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
    (key1 < 'bar' AND nonkey = 4) OR
    (key1 < 'uux' AND key1 > 'z')
  2. nonkey = 4key1 LIKE '%b' は、範囲スキャンに使用できないため、削除します。それらを削除する正しい方法は、範囲スキャンの実行時に一致する行を見落とさないように、それらを TRUE で置き換えることです。TRUE で置き換えると、次のようになります。

    (key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
    (key1 < 'bar' AND TRUE) OR
    (key1 < 'uux' AND key1 > 'z')
  3. 常に true または false である条件を縮小します。

    • (key1 LIKE 'abcde%' OR TRUE) は常に true です

    • (key1 < 'uux' AND key1 > 'z') は常に false です

    これらの条件を定数で置き換えると、次のようになります。

    (key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)

    不要な TRUE および FALSE 定数を削除すると、次のようになります。

    (key1 < 'abc') OR (key1 < 'bar')
  4. 重複する間隔を 1 つに組み合わせて、範囲スキャンに使用される最終的な条件が生成されます。

    (key1 < 'bar')

一般に (前の例で示したように)、範囲スキャンに使用される条件は、WHERE 句より制限がゆるくなります。MySQL は、範囲条件を満たすが、完全な WHERE 句でない行をフィルタ処理する追加のチェックを実行します。

範囲条件抽出アルゴリズムは、任意の深さのネストの AND/OR 構造を処理でき、その出力は WHERE 句内の条件が存在する順番に依存しません。

現在、MySQL では、空間インデックスに対して、range アクセスメソッドの複数の範囲のマージをサポートしていません。この制限を回避するには、同じ SELECT ステートメントで UNION を使用できますが、ただし、各空間述語は、別の SELECT に入れます。

8.2.1.3.2 マルチパートインデックスの range アクセスメソッド

マルチパートインデックスの範囲条件は、シングルパートインデックスの範囲条件の拡張です。マルチパートインデックスの範囲条件は、インデックス行を 1 つまたは複数のキータプル間隔内に入るように制限します。キータプル間隔は、インデックスからの順序付けを使用して、キータプルのセットに定義されます。

たとえば、key1(key_part1key_part2key_part3) として定義されたマルチパートインデックスと、キー順で示された次のキータプルのセットを考慮します。

key_part1key_part2key_part3 NULL 1 'abc' NULL 1 'xyz' NULL 2 'foo' 1 1 'abc' 1 1 'xyz' 1 2 'abc' 2 1 'aaa'

条件 key_part1 = 1 は次の間隔を定義します。

(1,-inf,-inf) <= (key_part1,key_part2,key_part3) < (1,+inf,+inf)

間隔は前のデータセットの 4、5、6 番目のタプルをカバーし、range アクセスメソッドで使用できます。

対照的に、条件 key_part3 = 'abc' は単一の間隔を定義せず、range アクセスメソッドで使用できません。

次の説明では、マルチパートインデックスに対して、範囲条件がどのように作用するかを詳しく示します。

  • HASH インデックスでは、同一の値を含む各間隔を使用できます。これは次の形式の条件に対してのみ、間隔を生成できることを意味します。

    key_part1cmpconst1AND key_part2cmpconst2AND ...
    AND key_partNcmpconstN;

    ここで、const1const2、… は定数で、cmp は、=<=>、または IS NULL 比較演算子のいずれかで、条件はすべてのインデックスパートをカバーします。(つまり、N パートインデックスの各パートに 1 つずつ N 条件があります。)たとえば、次は 3 パート HASH インデックスの範囲条件です。

    key_part1 = 1 AND key_part2 IS NULL AND key_part3 = 'foo'

    何を定数とみなすかの定義については、「セクション8.2.1.3.1「シングルパートインデックスの range アクセスメソッド」」を参照してください。

  • BTREE インデックスでは、各条件で =<=>IS NULL><>=<=!=<>BETWEEN、または LIKE 'pattern' (ここで 'pattern' はワイルドカードで始まらない) を使用して、キーパートと定数値を比較する、AND で組み合わされた条件に、間隔を使用できます。条件に一致するすべての行を含む単一のキータプルを判断できる場合にかぎり、1 つの間隔を使用できます (または <> または != を使用する場合は 2 つの間隔)。

    オプティマイザは、比較演算子が =<=>、または IS NULL である場合にかぎり、追加のキーパートを使用して、間隔を判断しようとします。演算子が ><>=<=!=<>BETWEEN、または LIKE の場合、オプティマイザはそれを使用しますが、追加のキーパートは考慮しません。次の式では、オプティマイザは最初の比較からの = を使用します。さらに 2 番目の比較からの >= も使用しますが、それ以上のキーパートを考慮せず、間隔の構築に 3 番目の比較を使用しません。

    key_part1 = 'foo' AND key_part2 >= 10 AND key_part3 > 10

    単一の間隔は次のとおりです。

    ('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf)

    作成された間隔に初期条件よりも多い行が含まれる可能性があります。たとえば、前の間隔は値 ('foo', 11, 0) を含みますが、これは元の条件を満たしません。

  • 間隔内に含まれる行セットをカバーする条件が OR で組み合わされている場合、それらは、それらの間隔の和集合内に含まれる行セットをカバーする条件を形成します。条件が AND で組み合わされている場合、それらは間隔の共通集合内に含まれる行セットを対象とする条件を形成します。たとえば、2 パートインデックスでのこの条件の場合:

    (key_part1 = 1 AND key_part2 < 2) OR (key_part1 > 5)

    間隔は次のとおりです。

    (1,-inf) < (key_part1,key_part2) < (1,2)
    (5,-inf) < (key_part1,key_part2)

    この例で、1 行目の間隔は、左境界に 1 つのキーパートを使用し、右境界に 2 つのキーパートを使用しています。2 行目の間隔は 1 つのキーパートのみを使用しています。EXPLAIN 出力の key_len カラムは、使用されたキープリフィクスの最大長を示しています。

    場合によって、key_len はキーパートが使用されたことを示しますが、それが予期したものではないことがあります。key_part1key_part2NULL になることがあるとします。次に、key_len カラムに、次の条件の 2 つのキーパート長が表示されます。

    key_part1 >= 1 AND key_part2 < 2

    しかし、実際は条件が次に変換されます。

    key_part1 >= 1 AND key_part2 IS NOT NULL

セクション8.2.1.3.1「シングルパートインデックスの range アクセスメソッド」」では、単一パートインデックスで、範囲条件の間隔を組み合わせたり、削除したりするために、どのように最適化が実行されるかを説明しています。マルチパートインデックスでの範囲条件にも類似の手順が実行されます。

8.2.1.3.3 複数値比較の等価範囲の最適化

col_name がインデックス設定されたカラムである次の式を考慮します。

col_name IN(val1, ..., valN)col_name = val1 OR ... OR col_name = valN

col_name が複数の値のいずれかと等しい場合に、各式は true になります。これらの比較は等価範囲比較です (ここで範囲は単一の値です)。オプティマイザは、次のように等価範囲比較の対象とする行の読み取りのコストを推定します。

  • col_name に一意のインデックスがある場合、指定した値を持つことができる行は多くても 1 つであるため、各範囲の行の見積もりは 1 です。

  • そうでない場合は、オプティマイザは、インデックスのダイブまたはインデックス統計を使用して、各範囲の行数を推定できます。

インデックスダイブでは、オプティマイザは範囲の両端でダイブを作成し、範囲内の行数を見積もりとして使用します。たとえば、式 col_name IN (10, 20, 30) には 3 つの等価範囲があり、オプティマイザは範囲あたり 2 つのダイブを作成して、行の見積もりを生成します。ダイブのペアごとに、指定した値を持つ行数の見積もりを生成します。

インデックスダイブは、正確な行見積もりを提供しますが、式内の比較値の数が増えるほど、オプティマイザの行見積もりの生成に時間がかかるようになります。インデックス統計の使用は、インデックスダイブより正確ではありませんが、大きな値リストの場合に、行見積もりが高速になります。

eq_range_index_dive_limit システム変数を使用して、オプティマイザが行の見積もり戦略を別の戦略に切り替える値の数を構成できます。統計の使用を無効にして、常にインデックスダイブを使用するには、eq_range_index_dive_limit を 0 に設定します。最大 N 個の等価範囲の比較にインデックスダイブの使用を許可するには、eq_range_index_dive_limitN + 1 に設定します。

eq_range_index_dive_limit は MySQL 5.6.5 以降で使用できます。5.6.5 より前では、オプティマイザは eq_range_index_dive_limit=0 と同等のインデックスダイブを使用します。

最適な推定を行うためにテーブルインデックス統計を更新するには、ANALYZE TABLE を使用します。

8.2.1.4 インデックスマージの最適化

インデックスマージメソッドは、複数の range スキャンによって、行を取得しそれらの結果を 1 つにマージするために使用されます。このマージによって、その基盤となるスキャンの和集合、共通集合、または共通集合の和集合を生成できます。このアクセスメソッドは、1 つのテーブルからのインデックススキャンをマージします。複数のテーブルにわたるスキャンはマージしません。

EXPLAIN 出力では、インデックスマージメソッドは type カラムに index_merge と表示されます。この場合、key カラムには使用されたインデックスのリストが含まれ、key_len にはそれらのインデックスの最長のキーパートのリストが含まれます。

例:

SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;
SELECT * FROM tbl_name WHERE (key1 = 10 OR key2 = 20) AND non_key=30;
SELECT * FROM t1, t2 WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%') AND t2.key1=t1.some_col;
SELECT * FROM t1, t2 WHERE t1.key1=1 AND (t2.key1=t1.some_col OR t2.key2=t1.some_col2);

インデックスマージメソッドにはいくつかのアクセスアルゴリズムがあります (EXPLAIN 出力の Extra フィールドで確認されます)。

  • Using intersect(...)

  • Using union(...)

  • Using sort_union(...)

次のセクションでは、これらのメソッドについて詳しく説明します。

注記

インデックスマージ最適化アルゴリズムには次の既知の不具合があります。

  • クエリーに AND/OR の深いネストのある複雑な WHERE 句があり、MySQL が最適なプランを選択しない場合、次の同一律を使用して、項を分配してみてください。

    (x AND y) OR z = (x OR z) AND (y OR z)
    (x OR y) AND z = (x AND z) OR (y AND z)
  • インデックスマージは全文インデックスには適用できません。将来の MySQL リリースでこれらを扱うように、それを拡張する予定です。

  • MySQL 5.6.6 より前では、一部のキーに対して範囲スキャンが使用可能な場合、オプティマイザはインデックスマージ和集合またはインデックスマージソート和集合アルゴリズムを使用することを考慮しません。たとえば、次のクエリーを考慮します。

    SELECT * FROM t1 WHERE (goodkey1 < 10 OR goodkey2 < 20) AND badkey < 30;

    このクエリーでは、2 つのプランが使用可能です。

    • (goodkey1 < 10 OR goodkey2 < 20) 条件を使用したインデックスマージスキャン。

    • badkey < 30 条件を使用した範囲スキャン。

    ただし、オプティマイザは 2 つめのプランしか考慮しません。

インデックスマージアクセスメソッドの可能性のあるさまざまなバリアントとその他のアクセスメソッドとの選択は、使用可能な各種オプションのコスト見積もりに基づきます。

8.2.1.4.1 インデックスマージ共通集合アクセスアルゴリズム

このアクセスアルゴリズムは、WHERE 句が、AND で結合されたさまざまなキーに対する複数の範囲条件に変換され、各条件が次のいずれかである場合に採用できます。

  • この形式では、インデックスには正確に N 個のパートがあります (つまり、すべてのインデックスパートがカバーされます)。

    key_part1=const1 AND key_part2=const2 ... AND key_partN=constN
  • InnoDB テーブルの主キーに対する範囲条件。

例:

SELECT * FROM innodb_table WHERE primary_key < 10 AND key_col1=20;
SELECT * FROM tbl_name WHERE (key1_part1=1 AND key1_part2=2) AND key2=2;

インデックスマージ共通集合アルゴリズムは、使用されたすべてのインデックスの同時スキャンを実行し、マージされたインデックススキャンから受け取る行シーケンスの共通集合を生成します。

クエリーに使用されているすべてのカラムが、使用されるインデックスによってカバーされている場合、完全なテーブル行は取得されません (この場合、EXPLAIN 出力の Extra フィールドに Using index が含まれます)。次はそのようなクエリーの例です。

SELECT COUNT(*) FROM t1 WHERE key1=1 AND key2=1;

使用されるインデックスで、クエリーに使用されているすべてのカラムがカバーされない場合、使用されているすべてのキーの範囲条件が満たされている場合にのみ、完全な行が取得されます。

マージされた条件のいずれかが InnoDB テーブルの主キーに対する条件である場合、それは行の取得には使用されませんが、ほかの条件を使用して取得された行をフィルタ処理するために使用されます。

8.2.1.4.2 インデックスマージ和集合アクセスアルゴリズム

このアルゴリズムの適用基準はインデックスマージメソッド共通集合アルゴリズムの場合と似ています。このアルゴリズムは、テーブルの WHERE 句が、OR で組み合わされたさまざまなキーに対する複数の範囲条件に変換されており、各条件が次のいずれかである場合に採用できます。

  • この形式では、インデックスには正確に N 個のパートがあります (つまり、すべてのインデックスパートがカバーされます)。

    key_part1=const1 AND key_part2=const2 ... AND key_partN=constN
  • InnoDB テーブルの主キーに対する範囲条件。

  • インデックスマージメソッド共通集合アルゴリズムを適用できる条件。

例:

SELECT * FROM t1 WHERE key1=1 OR key2=2 OR key3=3;
SELECT * FROM innodb_table WHERE (key1=1 AND key2=2) OR (key3='foo' AND key4='bar') AND key5=5;
8.2.1.4.3 インデックスマージソート和集合アクセスアルゴリズム

このアクセスアルゴリズムは、WHERE 句が、OR で組み合わされた複数の範囲条件に変換されているが、インデックスマージメソッド和集合アルゴリズムを適用できない場合に採用されます。

例:

SELECT * FROM tbl_name WHERE key_col1 < 10 OR key_col2 < 20;
SELECT * FROM tbl_name WHERE (key_col1 > 10 OR key_col2 = 20) AND nonkey_col=30;

ソート和集合アルゴリズムと和集合アルゴリズムの違いは、ソート和集合アルゴリズムでは、行を返す前にまずすべての行の行 ID をフェッチし、それらをソートする必要があることです。

8.2.1.5 エンジンコンディションプッシュダウンの最適化

この最適化は、インデックスが設定されていないカラムと定数との直接比較の効率性を向上します。このような場合、条件が評価のためにストレージエンジンにプッシュダウンされます。この最適化は、NDB ストレージエンジンでのみ使用できます。

MySQL Cluster では、この最適化によって、クラスタのデータノードとクエリーを発行した MySQL Server 間で、ネットワーク経由で一致しない行を送る必要性をなくすことができ、それを使用した場合のクエリーを、コンディションプッシュダウンが可能であっても使用しない場合より、5 - 10 倍高速化できます。

MySQL Cluster テーブルが次のように定義されているとします。

CREATE TABLE t1 ( a INT, b INT, KEY(a)
) ENGINE=NDB;

コンディションプッシュダウンは、インデックスが設定されていないカラムと定数との比較を含む、ここに示すようなクエリーで使用できます。

SELECT a, b FROM t1 WHERE b = 10;

コンディションプッシュダウンの使用は、EXPLAIN の出力で確認できます。

mysql> EXPLAIN SELECT a,b FROM t1 WHERE b = 10\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ALL
possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 10 Extra: Using where with pushed condition

ただし、コンディションプッシュダウンは、これらの 2 つのクエリーのいずれかと一緒に使用することはできません

SELECT a,b FROM t1 WHERE a = 10;
SELECT a,b FROM t1 WHERE b + 1 = 10;

カラム a にインデックスが存在するため、コンディションプッシュダウンは最初のクエリーには適用できません。(インデックスアクセスメソッドの方が効率的であるため、コンディションプッシュダウンよりも優先して選択されます。) インデックスが設定されていないカラム b を含む比較は間接的であるため、2 つめのクエリーにコンディションプッシュダウンを採用することはできません。(ただし、WHERE 句内で b + 1 = 10b = 9 にまとめる場合はコンディションプッシュダウンを適用できます。)

> または < 演算子を使用して、インデックス設定されたカラムを定数と比較する場合にもコンディションプッシュダウンを採用できます。

mysql> EXPLAIN SELECT a, b FROM t1 WHERE a < 2\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: range
possible_keys: a key: a key_len: 5 ref: NULL rows: 2 Extra: Using where with pushed condition

コンディションプッシュダウンでサポートされるその他の比較には、次が含まれます。

  • column [NOT] LIKE pattern

    pattern は、照合するパターンを含む文字列リテラルである必要があります。構文については、セクション12.5.1「文字列比較関数」を参照してください。

  • column IS [NOT] NULL

  • column IN (value_list)

    value_list の各項目は定数のリテラル値である必要があります。

  • column BETWEEN constant1 AND constant2

    constant1constant2 はそれぞれ、定数のリテラル値である必要があります。

前のリストのすべての場合で、条件をカラムと定数との 1 つ以上の直接比較の形式に変換できます。

エンジンコンディションプッシュダウンはデフォルトで有効です。サーバーの起動時にそれを無効にするには、optimizer_switch システム変数を設定します。たとえば、my.cnf ファイルで、次の行を使用します。

[mysqld]
optimizer_switch=engine_condition_pushdown=off

実行時に、次のようにコンディションプッシュダウンを有効にします。

SET optimizer_switch='engine_condition_pushdown=off';

制限  エンジンコンディションプッシュダウンには次の制限があります。

  • コンディションプッシュダウンは、NDB ストレージエンジンによってのみサポートされます。

  • カラムは定数とのみ比較できますが、これには、定数値に評価される式も含まれます。

  • 比較に使用されるカラムは、BLOB 型または TEXT 型のいずれかであってはいけません。

  • カラムと比較される文字列値は、カラムと同じ照合順序を使用する必要があります。

  • 結合は直接サポートされていません。複数のテーブルを含む条件は、可能な場合に個別にプッシュされます。実際にプッシュダウンされる条件を判断するには、EXPLAIN EXTENDED を使用します。

8.2.1.6 インデックスコンディションプッシュダウンの最適化

インデックスコンディションプッシュダウン (ICP) は、MySQL がインデックスを使用してテーブルから行を取得する場合の最適化です。ICP を使用しない場合、ストレージエンジンはインデックスをトラバースして、ベーステーブル内で行を検索し、MySQL Server に返し、MySQL Server が行に対して WHERE 条件を評価します。ICP を有効にすると、インデックスからのフィールドだけを使用して WHERE 条件の部分を評価できる場合は、MySQL Server はこの WHERE 条件の部分をストレージエンジンにプッシュダウンします。ストレージエンジンは、インデックスエントリを使用して、プッシュされたインデックス条件を評価し、これが満たされている場合にのみ、テーブルから行を読み取ります。ICP は、ストレージエンジンがベーステーブルにアクセスする必要がある回数と、MySQL サーバーがストレージエンジンにアクセスする必要がある回数を削減できます。

インデックスコンディションプッシュダウン最適化は、完全なテーブル行にアクセスする必要がある場合に、rangerefeq_ref、および ref_or_null アクセスメソッドで使用されます。この戦略は、InnoDB テーブルと MyISAM テーブルに使用できます。(インデックスコンディションプッシュダウンは、MySQL 5.6 ではパーティション化されたテーブルでサポートされていません。この問題は MySQL 5.7 で解決されています。)ただし、InnoDB テーブルの場合、ICP はセカンダリインデックスにのみ使用されます。ICP の目標は、完全なレコードの読み取りの回数を減らし、それによって IO 操作を減らすことです。InnoDB のクラスタ化されたインデックスの場合、完全なレコードはすでに InnoDB バッファーに読み込まれています。この場合に ICP を使用しても IO は削減されません。

この最適化の仕組みを確認するには、まずインデックスコンディションプッシュダウンが使用されない場合に、インデックススキャンがどのように進められるかを考察します。

  1. まず、インデックスタプルを読み取り、次にそのインデックスタプルを使用して、完全なテーブル行を見つけて読み取ることで、次の行を取得します。

  2. このテーブルに適用される WHERE 条件の部分をテストします。テスト結果に基づいて行を受け入れるか、拒否します。

インデックスコンディションプッシュダウンが使用される場合、代わりにスキャンは次のように進められます。

  1. 次の行のインデックスタプルを取得します (ただし完全なテーブル行ではありません)。

  2. このテーブルに適用され、インデックスカラムのみを使用してチェックできる WHERE 条件の部分をテストします。条件が満たされている場合、次の行のインデックスタプルに進みます。

  3. 条件が満たされている場合、インデックスタプルを使用して、完全なテーブル行を見つけて読み取ります。

  4. このテーブルに適用される WHERE 条件の残りの部分をテストします。テスト結果に基づいて行を受け入れるか、拒否します。

インデックスコンディションプッシュダウンが使用されると、EXPLAIN 出力の Extra カラムに Using index condition と表示されます。完全なテーブル行を読み取る必要がある場合に適用されないため、Index only は表示されません。

人とその住所に関する情報を格納するテーブルがあり、そのテーブルに、INDEX (zipcode, lastname, firstname) と定義されたインデックスがあるとします。ある個人の zipcode 値を知っているが、名前が確かでない場合に、次のように検索できます。

SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';

MySQL はインデックスを使用して、zipcode='95054' を持つ人をスキャンします。2 番目の部分 (lastname LIKE '%etrunia%') は、スキャンする必要がある行数を制限するために使用できないため、インデックスコンディションプッシュダウンを使用しない場合に、このクエリーでは zipcode='95054' を持つすべての人の完全なテーブル行を取得する必要があります。

インデックスコンディションプッシュダウンを使用すると、MySQL は完全なテーブル行を読み取る前に、lastname LIKE '%etrunia%' 部分をチェックします。これにより、lastname 条件に一致しないすべてのインデックスタプルに対応する完全な行の読み取りが避けられます。

インデックスコンディションプッシュダウンはデフォルトで有効です。これは、optimizer_switch システム変数で index_condition_pushdown フラグを設定することで制御できます。セクション8.8.5.2「切り替え可能な最適化の制御」を参照してください。

8.2.1.7 インデックス拡張の使用

InnoDB は、自動的に各セカンダリインデックスに主キーカラムを追加して、それを拡張します。このテーブル定義について考えます。

CREATE TABLE t1 ( i1 INT NOT NULL DEFAULT 0, i2 INT NOT NULL DEFAULT 0, d DATE DEFAULT NULL, PRIMARY KEY (i1, i2), INDEX k_d (d)
) ENGINE = InnoDB;

このテーブルでは、カラム (i1, i2) に主キーを定義しています。さらに、カラム (d) にセカンダリインデックス k_d を定義していますが、内部で InnoDB はこのインデックスを拡張し、それをカラム (d, i1, i2) として処理します。

MySQL 5.6.9 より前では、オプティマイザは拡張セカンダリインデックスの使用方法や使用するかどうかを判断する際に、その主キーカラムを考慮しません。5.6.9 以降、オプティマイザは主キーカラムを考慮するようになったため、より効率的なクエリー実行プランやパフォーマンスの向上につながる可能性があります。

オプティマイザは、refrange、および index_merge インデックスアクセス、ルースインデックススキャン、結合とソートの最適化、および MIN()/MAX() 最適化に拡張セカンダリインデックスを使用できます。

次の例に、オプティマイザが拡張セカンダリインデックスを使用するかどうかによって、実行プランにどのような影響を与えるか示します。これらの行に t1 が移入されているとします。

INSERT INTO t1 VALUES
(1, 1, '1998-01-01'), (1, 2, '1999-01-01'),
(1, 3, '2000-01-01'), (1, 4, '2001-01-01'),
(1, 5, '2002-01-01'), (2, 1, '1998-01-01'),
(2, 2, '1999-01-01'), (2, 3, '2000-01-01'),
(2, 4, '2001-01-01'), (2, 5, '2002-01-01'),
(3, 1, '1998-01-01'), (3, 2, '1999-01-01'),
(3, 3, '2000-01-01'), (3, 4, '2001-01-01'),
(3, 5, '2002-01-01'), (4, 1, '1998-01-01'),
(4, 2, '1999-01-01'), (4, 3, '2000-01-01'),
(4, 4, '2001-01-01'), (4, 5, '2002-01-01'),
(5, 1, '1998-01-01'), (5, 2, '1999-01-01'),
(5, 3, '2000-01-01'), (5, 4, '2001-01-01'),
(5, 5, '2002-01-01');

ここで次のクエリーを考慮します。

EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'

この例では、主キーがカラム (i1, i2) で構成され、クエリーで i2 を参照していないため、オプティマイザは主キーを使用できません。代わりに、オプティマイザは (d) に対してセカンダリインデックス k_d を使用でき、実行プランは拡張インデックスを使用するかどうかによって異なります。

オプティマイザがインデックス拡張を考慮しない場合、それはインデックス k_d(d) のみとして扱います。クエリーの EXPLAIN では次の結果が生成されます。

mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref
possible_keys: PRIMARY,k_d key: k_d key_len: 4 ref: const rows: 5 Extra: Using where; Using index

オプティマイザがインデックス拡張を考慮する場合、それはインデックス k_d(d, i1, i2) として扱います。この場合、それは左端のインデックスプリフィクス (d, i1) を使用して、より適切な実行プランを生成できます。

mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref
possible_keys: PRIMARY,k_d key: k_d key_len: 8 ref: const,const rows: 1 Extra: Using index

どちらの場合も key は、オプティマイザがセカンダリインデックス k_d を使用することを示しますが、EXPLAIN 出力には、拡張インデックスの使用による次のような改善が示されます。

  • key_len は 4 バイトから 8 バイトになり、キールックアップでカラム d だけでなく、di1 も使用されていることを示しています。

  • キールックアップで 1 つではなく 2 つのキーパートが使用されるため、ref 値が const から const,const に変更されています。

  • rows 数は 5 から 1 に減少し、InnoDB が結果を生成するために調査する必要がある行数が少なくなることを示しています。

  • Extra 値が Using where; Using index から Using index に変更されています。このことは、データ行のカラムを参照せずに、インデックスのみを使用して、行を読み取れることを意味します。

拡張インデックスの使用のオプティマイザの動作の違いは、SHOW STATUS でも確認できます。

FLUSH TABLE t1;
FLUSH STATUS;
SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01';
SHOW STATUS LIKE 'handler_read%'

前のステートメントには FLUSH TABLEFLUSH STATUS が含まれ、テーブルキャッシュをフラッシュし、ステータスカウンタをクリアします。

インデックス拡張を使用しないと、SHOW STATUS は次の結果を生成します。

+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 5 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+

インデックス拡張を使用すると、SHOW STATUS は次の結果を生成します。Handler_read_next 値が 5 から 1 に減少し、インデックスをより効率的に使用していることを示しています。

+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 1 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+

optimizer_switch システム変数の use_index_extensions フラグにより、InnoDB テーブルのセカンダリインデックスの使用方法を判断する際に、オプティマイザが主キーカラムを考慮するかどうかを制御できます。デフォルトで、use_index_extensions は有効です。インデックス拡張の使用を無効にするとパフォーマンスが向上するかどうかを確認するには、次のステートメントを使用します。

SET optimizer_switch = 'use_index_extensions=off';

オプティマイザによるインデックス拡張の使用は、インデックス (16) のキーパートの数と最大キー長 (3072 バイト) への通常の制限によります。

8.2.1.8 IS NULL の最適化

MySQL は、col_name=constant_value に対して使用できる同じ最適化を col_nameIS NULL に対しても実行できます。たとえば、MySQL は、インデックスと範囲を使用して、IS NULL を含む NULL を検索できます。

例:

SELECT * FROM tbl_name WHERE key_col IS NULL;
SELECT * FROM tbl_name WHERE key_col <=> NULL;
SELECT * FROM tbl_name WHERE key_col=const1 OR key_col=const2 OR key_col IS NULL;

WHERE 句に、NOT NULL として宣言されているカラムの col_nameIS NULL 条件が含まれている場合、その式は最適化により除去されます。この最適化は、とにかくカラムで NULL が生成される可能性がある場合には行われません。たとえば、LEFT JOIN の右側のテーブルから取得されている場合です。

MySQL は、解決済みのサブクエリーで一般的な形式である col_name = expr OR col_name IS NULL の組み合わせを最適化することもできます。この最適化が使用された場合、EXPLAINref_or_null と示されます。

この最適化は、任意のキーパートに対して 1 つの IS NULL を処理できます。

テーブル t2 のカラム a および b にインデックスがあるとして、最適化されるクエリーのいくつかの例:

SELECT * FROM t1 WHERE t1.a=expr OR t1.a IS NULL;
SELECT * FROM t1, t2 WHERE t1.a=t2.a OR t2.a IS NULL;
SELECT * FROM t1, t2 WHERE (t1.a=t2.a OR t2.a IS NULL) AND t2.b=t1.b;
SELECT * FROM t1, t2 WHERE t1.a=t2.a AND (t2.b=t1.b OR t2.b IS NULL);
SELECT * FROM t1, t2 WHERE (t1.a=t2.a AND t2.a IS NULL AND ...) OR (t1.a=t2.a AND t2.a IS NULL AND ...);

ref_or_null はまずリファレンスキーの読み取りを行い、次に NULL キー値のある行の個別の検索を実行します。

この最適化では、1 つの IS NULL レベルしか処理できません。次のクエリーでは、MySQL は式 (t1.a=t2.a AND t2.a IS NULL) に対してのみキールックアップを使用し、b に対してはキーパートを使用できません。

SELECT * FROM t1, t2 WHERE (t1.a=t2.a AND t2.a IS NULL) OR (t1.b=t2.b AND t2.b IS NULL);

8.2.1.9 LEFT JOIN および RIGHT JOIN の最適化

MySQL は次のように A LEFT JOIN B join_condition を実装します。

  • テーブル B は、テーブル AA が依存するすべてのテーブルに依存して設定されます。

  • テーブル A は、LEFT JOIN 条件で使用されるすべてのテーブル (B を除く) に依存して設定されます。

  • LEFT JOIN 条件は、テーブル B からの行の取得方法を決定するために使用されます。(言い換えると、WHERE 句内のすべての条件が使用されません)。

  • テーブルは常にそれが依存するすべてのテーブルのあとに読み取られることを除き、すべての標準の結合最適化が実行されます。循環依存関係がある場合、MySQL はエラーを発行します。

  • すべての標準 WHERE 最適化が実行されます。

  • AWHERE 句に一致する行があるが、BON 条件に一致する行がない場合、すべてのカラムが NULL に設定された追加の B 行が生成されます。

  • LEFT JOIN を使用して、一部のテーブルに存在しない行を検索し、WHERE 部分の col_name IS NULL のテストを実行した場合 (ここで col_nameNOT NULL と宣言されているカラム)、MySQL は LEFT JOIN 条件に一致する 1 つの行が見つかったあとに、それ以上の行 (の特定のキーの組み合わせ) の検索を停止します。

RIGHT JOIN の実装は、テーブルの役割が逆の LEFT JOIN の場合と類似しています。

結合オプティマイザは、テーブルを結合すべき順序を計算します。LEFT JOIN または STRAIGHT_JOIN によって強制されるテーブル読み取り順序は、確認するテーブル配列が少なくなるため、結合オプティマイザがはるかに高速にその作業を実行するのに役立ちます。これは、次のような種類のクエリーを実行する場合、LEFT JOIN によって d の前に b を読み取るように強制されるため、MySQL がその完全スキャンを実行することを意味します。

SELECT * FROM a JOIN b LEFT JOIN c ON (c.key=a.key) LEFT JOIN d ON (d.key=a.key) WHERE b.key=d.key;

この例の修正は、abFROM 句内に示される順序を逆にすることです。

SELECT * FROM b JOIN a LEFT JOIN c ON (c.key=a.key) LEFT JOIN d ON (d.key=a.key) WHERE b.key=d.key;

LEFT JOIN では、生成された NULL 行に対して、WHERE 条件が常に false である場合、LEFT JOIN は通常の結合に変更されます。たとえば、t2.column1NULL であった場合、次のクエリーの WHERE 句は false になります。

SELECT * FROM t1 LEFT JOIN t2 ON (column1) WHERE t2.column2=5;

そのため、クエリーを通常の結合に変換しても問題ありません。

SELECT * FROM t1, t2 WHERE t2.column2=5 AND t1.column1=t2.column1;

これにより、テーブル t1 の前にテーブル t2 を使用することがより適切なクエリー計画になる場合に、MySQL はそれを実行できるため、高速化できます。テーブルの結合順序についてヒントを提供するには、STRAIGHT_JOIN を使用します。(セクション13.2.9「SELECT 構文」を参照してください。)

8.2.1.10 Nested Loop 結合アルゴリズム

MySQL は、Nested Loop アルゴリズムまたはそのバリエーションを使用してテーブル間の結合を実行します。

Nested Loop 結合アルゴリズム

単純な Nested Loop Join (NLJ) アルゴリズムは、ループ内の最初のテーブルから行を一度に 1 つずつ読み取り、各行を、結合の次のテーブルを処理するネストしたループに渡します。このプロセスは、結合するテーブルが残っている回数だけ繰り返されます。

3 つのテーブル t1t2、および t3 間の結合が、次の結合型を使用して実行されるとします。

Table Join Type
t1 range
t2 ref
t3 ALL

単純な NLJ アルゴリズムを使用した場合、結合は次のように処理されます。

for each row in t1 matching range { for each row in t2 matching reference key { for each row in t3 { if row satisfies join conditions, send to client } }
}

NLJ アルゴリズムでは、外側のループから内側のループに、一度に 1 つずつ行を渡すため、一般に内側のループで処理されるテーブルを何回も読み取ります。

Block Nested Loop 結合アルゴリズム

Block Nested-Loop (BNL) 結合アルゴリズムは、外側のループで読み取られた行のバッファリングを使用して、内側のループでテーブルを読み取る必要がある回数が削減されます。たとえば、バッファーに 10 行が読み込まれ、このバッファーが次の内側のループに渡される場合、内側のループで読み取られる各行をバッファー内のすべての 10 行と比較できます。これにより、内部テーブルを読み取る必要のある回数が大幅に減少します。

MySQL は、次の条件下で結合バッファリングを使用します。

  • join_buffer_size システム変数によって各結合バッファーのサイズが決まります。

  • 結合バッファリングは、結合の型が ALL または index である (つまり、使用できるキーがなく、データ行またはインデックス行の完全スキャンがそれぞれ実行される場合) か、または range である場合に使用できます。MySQL 5.6 では、セクション8.2.1.14「Block Nested Loop 結合と Batched Key Access 結合」に説明するように、バッファリングの使用が外部結合に適用できるように拡張されています。

  • バッファリング可能な結合ごとに 1 つのバッファーが割り当てられるため、特定のクエリーが、複数の結合バッファーを使用して処理されることがあります。

  • ALL 型または index 型であっても、最初の非定数テーブルには結合バッファーが割り当てられません。

  • 結合バッファーは、結合の実行前に割り当てられ、クエリーの完了後に解放されます。

  • 結合バッファーには、行全体ではなく、結合に関連するカラムだけが格納されます。

NLJ アルゴリズム (バッファリングなし) で先述した結合の例では、結合は結合バッファリングを使用すると、次のように実行されます。

for each row in t1 matching range { for each row in t2 matching reference key { store used columns from t1, t2 in join buffer if buffer is full { for each row in t3 { for each t1, t2 combination in join buffer { if row satisfies join conditions, send to client } } empty buffer } }
}
if buffer is not empty { for each row in t3 { for each t1, t2 combination in join buffer { if row satisfies join conditions, send to client } }
}

S が結合バッファー内の格納される各 t1t2 の組み合わせのサイズであり、C がバッファー内の組み合わせの数である場合、テーブル t3 がスキャンされる回数は:

(S * C)/join_buffer_size + 1

join_buffer_size が前のすべての行の組み合わせを保持できるだけの大きさになる時点まで、join_buffer_size の値が大きくなるほど、t3 スキャンの回数は減少します。その時点では、さらに大きくしても速度は向上しなくなります。

8.2.1.11 ネストした結合の最適化

結合を表す構文では、ネストした結合を使用できます。次の説明は、セクション13.2.9.2「JOIN 構文」に説明する結合構文について言及しています。

table_factor の構文は SQL 標準と比較して拡張されています。後者は table_reference のみを受け付け、かっこ内のそれらのリストは受け付けません。これは、table_reference 項目のリストの各カンマを内部結合と同等とみなす場合、保守的な拡張です。例:

SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)

次と同等です。

SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)

MySQL では、CROSS JOININNER JOIN と構文上同等です (それらは相互に置き換え可能です)。標準 SQL では、それらは同等ではありません。INNER JOINON 句と一緒に使用します。CROSS JOIN はそうでない場合でも使用できます。

一般に、内部結合操作のみを含む結合式内のかっこは無視できます。かっこを削除し、操作を左側にグループ化すると、結合式は:

t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL) ON t1.a=t2.a

次の式に変換されます。

(t1 LEFT JOIN t2 ON t1.a=t2.a) LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL

まだ、2 つの式は同等ではありません。これを確認するには、テーブル t1t2t3 が次の状態であるとします。

  • テーブル t1 には行 (1)(2) が含まれます

  • テーブル t2 には行 (1,101) が含まれます

  • テーブル t3 には行 (101) が含まれます

この場合、最初の式は行 (1,1,101,101)(2,NULL,NULL,NULL) を含む結果セットを返し、2 番目の式は行 (1,1,101,101)(2,NULL,NULL,101) を返します。

mysql> SELECT * -> FROM t1 -> LEFT JOIN -> (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL) -> ON t1.a=t2.a;+------+------+------+------+
| a | a | b | b |
+------+------+------+------+
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | NULL |
+------+------+------+------+
mysql> SELECT * -> FROM (t1 LEFT JOIN t2 ON t1.a=t2.a) -> LEFT JOIN t3 -> ON t2.b=t3.b OR t2.b IS NULL;+------+------+------+------+
| a | a | b | b |
+------+------+------+------+
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | 101 |
+------+------+------+------+

次の例では、外部結合操作が内部結合操作と一緒に使用されています。

t1 LEFT JOIN (t2, t3) ON t1.a=t2.a

その式は次の式に変換できません。

t1 LEFT JOIN t2 ON t1.a=t2.a, t3.

指定されたテーブル状態では、次の 2 つの式は異なる行セットを返します。

mysql> SELECT * -> FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a;+------+------+------+------+
| a | a | b | b |
+------+------+------+------+
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | NULL |
+------+------+------+------+
mysql> SELECT * -> FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3;+------+------+------+------+
| a | a | b | b |
+------+------+------+------+
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | 101 |
+------+------+------+------+

したがって、外部結合演算子を含む結合式のかっこを省略すると、元の式の結果セットが変わることがあります。

正確に言えば、左外部結合操作の右オペランドと右結合操作の左オペランドのかっこを無視することはできません。言い換えれば、外部結合操作の内部テーブル式のかっこを無視することはできません。ほかのオペランド (外部テーブルのオペランド) のかっこは無視できます。

次の式:

(t1,t2) LEFT JOIN t3 ON P(t2.b,t3.b)

は次の式と同等です。

t1, t2 LEFT JOIN t3 ON P(t2.b,t3.b)

任意のテーブル t1,t2,t3 と属性 t2.b および t3.b に対する任意の条件 P の場合。

結合式 (join_table) の結合操作の実行順序が左から右でない場合は常に、ネストした結合と呼びます。次のクエリーを考慮します。

SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a WHERE t1.a > 1
SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a WHERE (t2.b=t3.b OR t2.b IS NULL) AND t1.a > 1

それらのクエリーは次のネストした結合が含まれるとみなされます。

t2 LEFT JOIN t3 ON t2.b=t3.b
t2, t3

最初のクエリーでは、左結合操作によってネストした結合が形成され、2 番目のクエリーでは、内部結合操作によってそれが形成されます。

最初のクエリーでは、かっこを省略できます。結合式の文法構造によって結合操作の実行の同じ順序が決定されます。2 番目のクエリーでは、かっこを省略できますが、それらがなくてもここの結合式は一義的に解釈できます。(ここの拡張構文では、2 番目のクエリーの (t2, t3) のかっこは必要ですが、理論上はなくても解析できます。LEFT JOINON が式 (t2,t3) の左と右の区切り文字の役割を果たすため、クエリーの構文構造が一義的になります。)

前の例でこれらの点を説明します。

  • 内部結合のみを含む (外部結合を含まない) 結合式の場合、かっこは削除できます。かっこを削除して、左から右に評価できます (実際には、任意の順序でテーブルを評価できます)。

  • 一般に、外部結合、または内部結合と混在した外部結合の場合には、同じことが当てはまりません。かっこの削除によって、結果が変わることがあります。

ネストした外部結合を含むクエリーは内部結合を含むクエリーと同じパイプライン方式で実行されます。正確には、Nested Loop 結合アルゴリズムのバリエーションが利用されます。Nested Loop 結合がクエリーを実行する際に利用するアルゴリズムスキーマを思い出してください。たとえば、次の形式の 3 つのテーブル T1,T2,T3 に対する結合クエリーがあるとします。

SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2) INNER JOIN T3 ON P2(T2,T3) WHERE P(T1,T2,T3).

ここでは、P1(T1,T2)P2(T3,T3) が何らかの結合条件 (式での) で、P(T1,T2,T3) はテーブル T1,T2,T3 のカラムに対する条件です。

Nested Loop 結合アルゴリズムでは、このクエリーを次のように実行します。

FOR each row t1 in T1 { FOR each row t2 in T2 such that P1(t1,t2) { FOR each row t3 in T3 such that P2(t2,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } }
}

表記 t1||t2||t3 は、t1t2、および t3 のカラムを連結させて行が構築されることを意味します。次のいくつかの例では、行名が表示される場所の NULL は、その行の各カラムに NULL が使用されることを意味します。たとえば、t1||t2||NULL は、行 t1t2 のカラムと、t3 の各カラムの NULL を連結させて行が構築されることを意味します。

ここで、ネストした外部結合のあるクエリーを考慮しましょう。

SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON P2(T2,T3)) ON P1(T1,T2) WHERE P(T1,T2,T3).

このクエリーでは、Nested Loop パターンを変更して、次を取得します。

FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) { BOOL f2:=FALSE; FOR each row t3 in T3 such that P2(t2,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f2=TRUE; f1=TRUE; } IF (!f2) { IF P(t1,t2,NULL) { t:=t1||t2||NULL; OUTPUT t; } f1=TRUE; } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } }
}

一般に、外部結合操作の最初の内部テーブルのネストしたループでは、ループの前にオフにされ、ループのあとにチェックされるフラグが導入されます。フラグは、外部テーブルの現在行で、内側オペランドを表すテーブルからの一致が見つかったときにオンにされます。ループサイクルの最後でフラグがまだオフの場合は、外部テーブルの現在行で一致が見つかりませんでした。この例では、行が内部テーブルのカラムの NULL 値で補完されます。結果の行は、出力の最終チェックまたは次のネストしたループに渡されますが、行が、埋め込まれたすべての外部結合の結合条件を満たしている場合に限られます。

ここでの例では、次の式で表された外部結合テーブルが埋め込まれています。

(T2 LEFT JOIN T3 ON P2(T2,T3))

内部結合を含むクエリーでは、オプティマイザは次のようなネストしたループの異なる順序を選択することがあります。

FOR each row t3 in T3 { FOR each row t2 in T2 such that P2(t2,t3) { FOR each row t1 in T1 such that P1(t1,t2) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } }
}

外部結合を含むクエリーでは、オプティマイザは外部テーブルのループが内部テーブルのループの前に実行される順序のみを選択できます。つまり、外部結合を含むクエリーでは、1 つだけのネスト順序しか使用できません。次のクエリーでは、オプティマイザは 2 つの異なるネストを評価します。

SELECT * T1 LEFT JOIN (T2,T3) ON P1(T1,T2) AND P2(T1,T3) WHERE P(T1,T2,T3)

ネストは次のようになります。

FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) { FOR each row t3 in T3 such that P2(t1,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f1:=TRUE } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } }
}

および:

FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t3 in T3 such that P2(t1,t3) { FOR each row t2 in T2 such that P1(t1,t2) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f1:=TRUE } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } }
}

両方のネストで、T1 は外部結合で使用されているため、外側のループで処理される必要があります。T2T3 は内部結合で使用されているため、その結合は内側のループで処理される必要があります。ただし、結合は内部結合であるため、T2T3 はどちらの順序でも処理できます。

内部結合の Nested Loop アルゴリズムについて説明した際に、クエリー実行のパフォーマンスに与える影響が大きい場合があるという詳細については省きました。いわゆるプッシュダウン 条件については説明しませんでした。たとえば、WHERE 条件 P(T1,T2,T3) を論理積標準形によって表現できるとします。

P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3).

この場合、MySQL は実際に内部結合を含むクエリーの実行に、次の Nested Loop スキーマを使用します。

FOR each row t1 in T1 such that C1(t1) { FOR each row t2 in T2 such that P1(t1,t2) AND C2(t2) { FOR each row t3 in T3 such that P2(t2,t3) AND C3(t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } }
}

等位項 C1(T1)C2(T2)C3(T3) がそれぞれ、もっとも内側のループから、評価可能なもっとも外側のループまで押し出されることがわかります。C1(T1) がきわめて制限の強い条件である場合、このコンディションプッシュダウンによって、テーブル T1 から内側ループに渡される行数が大幅に少なくなることがあります。結果として、クエリーの実行時間が大幅に短縮される可能性があります。

外部結合を含むクエリーでは、外部テーブルの現在行で内部テーブルに一致があることが見つかったあとにのみ、WHERE 条件がチェックされます。そのため、内側のネストしたループからのプッシュ条件の最適化は、外部結合を含むクエリーには直接適用できません。ここでは、一致が検出されたときにオンにされるフラグによって保護された、条件付きプッシュダウン述語を導入する必要があります。

次の外部結合のある例の場合:

P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)

保護されたプッシュダウン条件を使用した Nested Loop スキーマは次のようになります。

FOR each row t1 in T1 such that C1(t1) { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) AND (f1?C2(t2):TRUE) { BOOL f2:=FALSE; FOR each row t3 in T3 such that P2(t2,t3) AND (f1&&f2?C3(t3):TRUE) { IF (f1&&f2?TRUE:(C2(t2) AND C3(t3))) { t:=t1||t2||t3; OUTPUT t; } f2=TRUE; f1=TRUE; } IF (!f2) { IF (f1?TRUE:C2(t2) && P(t1,t2,NULL)) { t:=t1||t2||NULL; OUTPUT t; } f1=TRUE; } } IF (!f1 && P(t1,NULL,NULL)) { t:=t1||NULL||NULL; OUTPUT t; }
}

一般に、プッシュダウン述語は P1(T1,T2)P(T2,T3) などの結合条件から抽出できます。この場合、プッシュダウン述語は、対応する外部結合操作によって生成される NULL が補完された行の述語のチェックを妨げるフラグによっても保護されます。

ここで、ある内部テーブルから、同じネストした結合内の別の内部テーブルへのキーによるアクセスは、それが WHERE 条件からの述語によって引き起こされている場合に、禁止されます。(この例では、条件付きキーアクセスを使用できますが、この手法はまだ MySQL 5.6 に採用されていません。)

8.2.1.12 外部結合の単純化

クエリーの FROM 句内のテーブル式は、多くの場合単純化されます。

パーサー段階で、右外部結合操作を含むクエリーは、左結合操作のみを含む同等のクエリーに変換されます。一般的な場合、変換は次のルールに従って実行されます。

(T1, ...) RIGHT JOIN (T2,...) ON P(T1,...,T2,...) =
(T2, ...) LEFT JOIN (T1,...) ON P(T1,...,T2,...)

形式 T1 INNER JOIN T2 ON P(T1,T2) のすべての内部結合式は、WHERE 条件に (または埋め込まれる結合の結合条件が存在する場合は、それに) 等位項として結合されるリスト T1,T2P(T1,T2) によって、置き換えられます。

オプティマイザは、外部結合操作を含む結合クエリーのプランを評価する際、そのような各操作で、外部テーブルが内部テーブルより前にアクセスされるプランのみを考慮に入れます。そのようなプランのみ、Nested Loop スキーマによって、外部結合操作を含むクエリーを実行できるため、オプティマイザのオプションが制限されます。

次の形式のクエリーがあるとします。

SELECT * T1 LEFT JOIN T2 ON P1(T1,T2) WHERE P(T1,T2) AND R(T2)

テーブル T2 の一致する行数を大幅に狭める R(T2) を使用しています。クエリーをそのまま実行した場合、オプティマイザは、テーブル T2 の前にテーブル T1 にアクセスする以外に選択肢がなく、きわめて非効率的な実行プランにつながる可能性があります。

さいわい、MySQL では、WHERE 条件が NULL を受け付けない場合に、それらのクエリーを外部結合操作を含まないクエリーに変換します。条件は、操作のために構築された NULL で補完された行に対し、FALSE または UNKNOWN に評価する場合に、外部結合操作に対して NULL を受け付けないと呼ばれます。

したがって、この外部結合の場合:

T1 LEFT JOIN T2 ON T1.A=T2.A

次のような条件は NULL を受け付けません。

T2.B IS NOT NULL,
T2.B > 3,
T2.C <= T1.C,
T2.B < 2 OR T2.C > 1

次のような条件は NULL を受け付けます。

T2.B IS NULL,
T1.B < 3 OR T2.B IS NOT NULL,
T1.B < 3 OR T2.B > 3

条件が外部結合操作に対して NULL を受け付けるかどうかをチェックする一般的なルールは単純です。条件は次の場合に NULL を受け付けます。

  • その形式が A IS NOT NULL で、A がいずれかの内部テーブルの属性である場合

  • いずれかの引数が NULL である場合に、UNKNOWN に評価する内部テーブルへの参照を含む述語である場合

  • NULL を受け付けない条件を等位項として含む論理積である場合

  • NULL を受け付けない条件の論理和である場合。

条件は、クエリー内で、ある外部結合操作に対しては NULL を受け付けないが、ほかの外部結合操作に対しては NULL を受け付ける場合があります。次のクエリーで:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A LEFT JOIN T3 ON T3.B=T1.B WHERE T3.C > 0

WHERE 条件は、2 番目の外部結合操作に対しては NULL を受け付けませんが、最初の外部結合操作に対しては NULL を受け付けます。

WHERE 条件がクエリーの外部結合操作に対して NULL を受け付けない場合、外部結合操作は内部結合操作に置き換えられます。

たとえば、前のクエリーは次のクエリーに置き換えられます。

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A INNER JOIN T3 ON T3.B=T1.B WHERE T3.C > 0

元のクエリーでは、オプティマイザは、1 つのアクセス順序 T1,T2,T3 のみと互換性のあるプランを評価します。置換先のクエリーでは、さらにアクセスシーケンス T3,T1,T2 も考慮します。

ある外部結合操作の変換によって、別の操作の変換がトリガーされることがあります。そのため、次のクエリー:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A LEFT JOIN T3 ON T3.B=T2.B WHERE T3.C > 0

は、まず次のクエリーに変換されます。

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A INNER JOIN T3 ON T3.B=T2.B WHERE T3.C > 0

これは次のクエリーと同等です。

SELECT * FROM (T1 LEFT JOIN T2 ON T2.A=T1.A), T3 WHERE T3.C > 0 AND T3.B=T2.B

ここで、条件 T3.B=T2.B は NULL を受け付けず、外部結合をまったく含まないクエリーを取得するため、残りの外部結合操作を内部結合に置き換えることができます。

SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3 WHERE T3.C > 0 AND T3.B=T2.B

場合によっては、埋め込まれた外部結合操作を置き換えることに成功しても、埋め込む外部結合を変換できない場合があります。次のクエリー:

SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON T3.B=T2.B) ON T2.A=T1.A WHERE T3.C > 0

は次に変換されます。

SELECT * FROM T1 LEFT JOIN (T2 INNER JOIN T3 ON T3.B=T2.B) ON T2.A=T1.A WHERE T3.C > 0,

それは埋め込む外部結合操作を含む形式にのみ書き換えることができます。

SELECT * FROM T1 LEFT JOIN (T2,T3) ON (T2.A=T1.A AND T3.B=T2.B) WHERE T3.C > 0.

クエリーに埋め込まれた外部結合操作を変換しようとする場合、WHERE 条件と一緒に埋め込む外部結合に対して、結合条件を考慮する必要があります。次のクエリーで:

SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON T3.B=T2.B) ON T2.A=T1.A AND T3.C=T1.C WHERE T3.D > 0 OR T1.D > 0

WHERE 条件は埋め込まれた外部結合に対しては NULL を受け付けますが、埋め込む外部結合 T2.A=T1.A AND T3.C=T1.C の結合条件は NULL を受け付けません。そのため、クエリーは次に変換できます。

SELECT * FROM T1 LEFT JOIN (T2, T3) ON T2.A=T1.A AND T3.C=T1.C AND T3.B=T2.B WHERE T3.D > 0 OR T1.D > 0

8.2.1.13 Multi-Range Read の最適化

セカンダリインデックスでの範囲スキャンを使用して行を読み取ると、テーブルが大きく、ストレージエンジンのキャッシュに格納されていない場合、ベーステーブルへのランダムディスクアクセスが多発する結果になることがあります。Disk-Sweep Multi-Range Read (MRR) 最適化を使用すると、MySQL は、最初にインデックスだけをスキャンし、該当する行のキーを収集することによって、範囲スキャンのランダムディスクアクセスの回数を軽減しようとします。続いてキーがソートされ、最後に主キーの順序を使用してベーステーブルから行が取得されます。Disk-Sweep MRR の目的は、ランダムディスクアクセスの回数を減らし、その代わりに、ベーステーブルデータの順次スキャンを増やすことです。

Multi-Range Read の最適化には、次のメリットがあります。

  • MRR により、データ行はインデックスタプルに基づいて、ランダムな順序ではなく、順次アクセスできます。サーバーはクエリー条件を満たすインデックスタプルセットを取得し、それらをデータ行 ID 順に従ってソートし、ソートされたタプルを使用して、データ行を順番に取得します。これにより、データアクセスの効率が向上し、負荷が軽減されます。

  • MRR により、範囲インデックススキャンや結合属性にインデックスを使用する等価結合などの、インデックスタプル経由でのデータ行へのアクセスを必要とする操作のキーアクセスのリクエストのバッチ処理が可能になります。MRR はインデックス範囲のシーケンスを反復処理して、対象のインデックスタプルを取得します。これらの結果が累積されると、それらは対応するデータ行にアクセスするために使用されます。データ行の読み取りを開始する前に、すべてのインデックスタプルを取得する必要はありません。

次のシナリオでは、MRR の最適化に利益がある場合について説明しています。

シナリオ A: インデックス範囲スキャンと等価結合操作で、InnoDB テーブルと MyISAM テーブルに対して MRR を使用できます。

  1. インデックスタプルの一部はバッファーに累積されます。

  2. バッファー内のタプルはそれらのデータ行 ID によってソートされます。

  3. データ行には、ソートされたインデックスタプルシーケンスに従ってアクセスされます。

シナリオ B: 複数範囲インデックススキャンで、または属性によって等価結合を実行する際に、NDB テーブルに対して、MRR を使用できます。

  1. 単一キー範囲の可能性のある範囲の一部は、クエリーが送信される中央ノード上のバッファーに累積されます。

  2. 範囲はデータ行にアクセスする実行ノードに送信されます。

  3. アクセスされた行はパッケージに格納され、中央ノードに返送されます。

  4. 受け取ったデータ行を含むパッケージはバッファーに入れられます。

  5. データ行がバッファーから読み取られます。

MRR が使用された場合は、EXPLAIN 出力の Extra カラムに Using MRR と示されます。

InnoDBMyISAM は、クエリー結果を生成するために完全なテーブル行にアクセスする必要がない場合、MRR を使用しません。これは、(カバーするインデックス経由で) インデックスタプル内の情報に完全に基づいて結果を生成できる場合であり、MRR にメリットはありません。

MRR を使用でき、(key_part1, key_part2) にインデックスがあると想定するクエリーの例:

SELECT * FROM t WHERE key_part1 >= 1000 AND key_part1 < 2000 AND key_part2 = 10000;

インデックスは (key_part1, key_part2) 値のタプルから構成され、最初に key_part1 によって、次に key_part2 によって順序付けされます。

MRR を使用しないと、インデックススキャンでは、インデックスタプル内の key_part2 値に関係なく、1000 から最大 2000 の key_part1 範囲のすべてのインデックスタプルがカバーされます。スキャンは範囲内のタプルに 10000 以外の key_part2 値が含まれるかぎり、追加の作業を実行します。

MRR を使用すると、スキャンが key_part1 の 1 つの値 (1000、1001、...、1999) に 1 つずつ、複数の範囲に分割されます。これらの各スキャンは、key_part2 = 10000 のタプルのみを検索する必要があります。インデックスに key_part2 が 10000 でない多数のタプルが含まれる場合、MRR により、読み取られるインデックスタプルが大幅に少なくなります。

これを間隔表記を使用して表すには、非 MRR スキャンで、key_part2 = 10000 のタプルでない多数のタプルが含まれる可能性のあるインデックス範囲 [{1000, 10000}, {2000, MIN_INT}) を調査する必要があります。MRR スキャンでは、key_part2 = 10000 のタプルのみを含む複数の単一ポイント間隔 [{1000, 10000}]、...、[{1999, 10000}] を調査します。

2 つの optimizer_switch システム変数フラグは、MRR 最適化の使用へのインタフェースを提供します。mrr フラグは MRR を有効にするかどうかを制御します。mrr が有効である (on) 場合、mrr_cost_based フラグは、オプティマイザが MRR を使用するか使用しないかをコストベースで選択しようと試みる (on) か、または可能なかぎり MRR を使用する (off) かどうかを制御します。デフォルトで、mrronmrr_cost_basedon です。セクション8.8.5.2「切り替え可能な最適化の制御」を参照してください。

MRR では、ストレージエンジンが、そのバッファーに割り当てることができるメモリーの量のガイドラインとして、read_rnd_buffer_size システム変数の値を使用します。エンジンは最大 read_rnd_buffer_size バイトを使用して、単一のパスで処理する範囲の数を判断します。

8.2.1.14 Block Nested Loop 結合と Batched Key Access 結合

MySQL 5.6 では、結合したテーブルと結合バッファーの両方へのインデックスアクセスを使用する、Batched Key Access (BKA) 結合アルゴリズムが使用できるようになりました。BKA アルゴリズムは、ネストした外部結合を含む、内部結合、外部結合、および準結合操作をサポートします。BKA には、テーブルスキャンの効率性の向上による結合パフォーマンスの改善というメリットもあります。さらに、以前内部結合にのみ使用されていた Block Nested Loop (BNL) 結合アルゴリズムが拡張され、ネストした外部結合を含む、外部結合と準結合操作にも採用できます。

次のセクションでは、元の BNL アルゴリズムの拡張の基礎にある結合バッファー管理、拡張 BNL アルゴリズム、および BKA アルゴリズムについて説明します。準結合戦略については、「セクション8.2.1.18.1「準結合変換によるサブクエリーの最適化」」を参照してください。

8.2.1.14.1 Block Nested Loop および Batched Key Access アルゴリズムの結合バッファー管理

MySQL 5.6 では、MySQL Server は、内部テーブルへのインデックスアクセスなしの内部結合だけでなく、外部結合と、サブクエリーのフラット化のあとに見られる準結合も実行するために結合バッファーを使用できます。さらに、内部テーブルへのインデックスアクセスがある場合、結合バッファーを効率的に使用できます。

結合バッファー管理コードは、目的の行カラムの値を格納する際に、結合バッファー領域を少し効率的に利用します。行カラムの値が NULL の場合に行カラムにバッファー内の追加バイトを割り当てず、VARCHAR 型の値には最小数のバイトが割り当てられます。

コードでは、標準と増分の 2 つの種類のバッファーをサポートします。結合テーブル t1t2 に結合バッファー B1 が使用されており、この操作の結果が結合バッファー B2 を使用して、テーブル t3 と結合されるとします。

  • 標準結合バッファーには、各結合オペランドからのカラムが格納されます。B2 が標準結合バッファーである場合、B2 に入れられる各行 r は、B1 からの行 r1 のカラムと、テーブル t2 からの一致する行 r2 の対象のカラムから構成されます。

  • 増分結合バッファーには、2 つめの結合オペランドによって生成されるテーブルの行からのカラムのみが格納されます。つまり、それは 1 つめのオペランドバッファーからの行の増分になります。B2 が増分結合バッファーである場合、それには、B1 からの行 r1 へのリンクとともに、行 r2 の対象のカラムが格納されます。

増分結合バッファーは常に、前の結合操作からの結合バッファーに相対的な増分になるため、最初の結合操作からのバッファーは常に標準バッファーになります。直前の例では、テーブル t1 および t2 を結合するために使用されるバッファー B1 は標準バッファーである必要があります。

結合操作に使用される増分バッファーの各行には、結合されるテーブルからの行の対象カラムのみが格納されます。これらのカラムには、最初の結合オペランドによって生成されたテーブルからの一致する行の対象カラムへの参照が追加されます。増分バッファー内の複数の行から、カラムが前の結合バッファーに格納されている同じ行 r を参照できます。ただし、これらのすべての行が行 r に一致する場合にかぎります。

増分バッファーにより、前の結合操作で使用されたバッファーからのカラムのコピーの頻度を少なくできます。これにより、一般に、最初の結合オペランドによって生成された行が 2 つめの結合オペランドによって生成される複数の行に一致する可能性があるため、バッファー領域が節約されます。最初のオペランドからの行のコピーを何回も行う必要がありません。さらに、増分バッファーにより、コピー時間の短縮のため、処理時間も節約されます。

MySQL 5.6.3 現在、optimizer_switch システム変数の block_nested_loop および batched_key_access フラグによって、オプティマイザがどのように Block Nested Loop 結合アルゴリズムと Batched Key Access 結合アルゴリズムを使用するかを制御します。デフォルトで、block_nested_looponbatched_key_accessoff です。セクション8.8.5.2「切り替え可能な最適化の制御」を参照してください。

MySQL 5.6.3 より前では、optimizer_join_cache_level システム変数によって、結合バッファー管理を制御します。この変数の指定可能な値とそれらの意味については、セクション5.1.4「サーバーシステム変数」の説明を参照してください。

準結合戦略については、「セクション8.2.1.18.1「準結合変換によるサブクエリーの最適化」」を参照してください。

8.2.1.14.2 外部結合と準結合の Block Nested Loop アルゴリズム

MySQL 5.6 では、BNL アルゴリズムの元の実装が、外部結合および準結合操作をサポートするように拡張されています。

結合バッファーを使用して、これらの操作が実行されると、バッファーに入れられた各行に一致フラグが付加されます。

結合バッファーを使用して、外部結合操作が実行された場合、2 つめのオペランドによって生成されたテーブルの各行で、結合バッファー内の各行に対する一致がチェックされます。一致が見つかると、新しく拡張された行が形成され (元の行に 2 つめのオペランドからのカラムを追加)、残りの結合操作によるさらなる拡張のために送られます。さらに、バッファー内の一致した行の一致フラグが有効にされます。結合されるテーブル内のすべての行が調査されたあとに、結合バッファーがスキャンされます。有効にされた一致フラグがないバッファーからの各行は、NULL の補完 (2 つめのオペランドの各カラムの NULL 値) によって拡張され、残りの結合操作によるさらなる拡張のために送られます。

MySQL 5.6.3 現在、optimizer_switch システム変数の block_nested_loop フラグによって、オプティマイザが Block Nested Loop アルゴリズムを使用する方法を制御します。デフォルトで、block_nested_loopon です。セクション8.8.5.2「切り替え可能な最適化の制御」を参照してください。

MySQL 5.6.3 より前では、optimizer_join_cache_level システム変数によって、結合バッファー管理を制御します。この変数の指定可能な値とそれらの意味については、セクション5.1.4「サーバーシステム変数」の説明を参照してください。

EXPLAIN 出力で、Extra 値に Using join buffer (Block Nested Loop) が含まれ、type 値が ALLindex、または range の場合に、テーブルへの BNL の使用が示されます。

準結合戦略については、「セクション8.2.1.18.1「準結合変換によるサブクエリーの最適化」」を参照してください。

8.2.1.14.3 Batched Key Access 結合

MySQL 5.6.3 では Batched Key Access (BKA) 結合アルゴリズムと呼ばれるテーブルの結合の方法を実装しています。BKA は、2 つめの結合オペランドによって生成されるテーブルへのインデックスアクセスがある場合に適用できます。BNL 結合アルゴリズムと同様、BKA 結合アルゴリズムでは、結合バッファーを使用して、結合操作の最初のオペランドによって生成された行の対象カラムを累積します。次に、BKA アルゴリズムは、バッファー内のすべての行に対し、結合されるテーブルにアクセスするためのキーを構築し、これらのキーをインデックスルックアップのために、データベースエンジンに一括で送信します。キーは、Multi-Range Read (MRR) インタフェース経由で、エンジンに送信されます (セクション8.2.1.13「Multi-Range Read の最適化」を参照してください)。キーの送信後、MRR エンジン関数は最適な方法で、インデックス内のルックアップを実行し、これらのキーによって見つかった結合されたテーブルの行をフェッチし、BKA 結合アルゴリズムに一致する行の提供を開始します。一致する各行は結合バッファー内の行への参照が組み合わされます。

BKA が使用される場合、join_buffer_size の値によって、ストレージエンジンへの個々のリクエストでのキーのバッチの大きさが定義されます。バッファーが大きいほど、結合操作の右側テーブルへの順次アクセスが増え、パフォーマンスを著しく向上させることができます。

BKA を使用するには、optimizer_switch システム変数の batched_key_access フラグが on に設定されている必要があります。BKA では MRR を使用するため、mrr フラグも on に設定されている必要があります。現在、MRR のコスト見積もりはきわめて悲観的です。したがって、BKA を使用するには、mrr_cost_basedoff にする必要もあります。次の設定によって、BKA が有効になります。

mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

MRR 関数が実行される 2 つのシナリオがあります。

  • 最初のシナリオは、InnoDBMyISAM などの従来のディスクベースのストレージエンジンで使用されます。これらのエンジンでは通常、結合バッファーからのすべての行のキーが一度に MRR インタフェースに送信されます。エンジン固有の MRR 関数は、送信されたキーのインデックスルックアップを実行し、それらから行 ID (または主キー) を取得して、BKA アルゴリズムからのリクエストによって、これらの選択されたすべての行 ID の行を 1 つずつフェッチします。各行は、結合バッファー内の一致した行へのアクセスを可能にするアソシエーション参照とともに返されます。行は MRR 関数によって最適な方法でフェッチされます。それらは、行 ID (主キー) 順でフェッチされます。これにより、読み取りがランダムな順序ではなく、ディスク順になるため、パフォーマンスが向上します。

  • 2 つめのシナリオは、NDB などのリモートストレージエンジンで使用されます。結合バッファーからの行の一部のキーのパッケージが、それらのアソシエーションとともに、MySQL Server (SQL ノード) によって、MySQL Cluster データノードに送信されます。返信で、SQL ノードは、対応するアソシエーションが組み合わされた一致する行のパッケージ (または複数のパッケージ) を受け取ります。BKA 結合アルゴリズムでは、これらの行を取得し、新しく結合された行を構築します。次に、新しいキーセットがデータノードに送信され、返されたパッケージからの行が新しい結合された行の構築に使用されます。このプロセスは、結合バッファーからの最後のキーがデータノードに送信され、SQL ノードがこれらのキーに一致するすべての行を受け取り、結合するまで、続行されます。これにより、SQL ノードによってデータノードに送信されるキーを含むパッケージが少なくなることは、結合操作を実行するために、それとデータノード間のラウンドトリップが少なくなることを意味するため、パフォーマンスが向上します。

最初のシナリオでは、結合バッファーの一部がインデックスルックアップによって選択され、MRR 関数へのパラメータとして渡される行 ID (主キー) を格納するために予約されます。

結合バッファーからの行に対して構築されるキーを格納するための特別なバッファーはありません。代わりに、バッファー内の次の行のキーを構築する関数が、MRR 関数へのパラメータとして渡されます。

EXPLAIN 出力で、Extra 値に Using join buffer (Batched Key Access) が含まれ、type 値が ref または eq_ref の場合に、テーブルへの BKA の使用が示されます。

8.2.1.15 ORDER BY の最適化

場合によって、MySQL は、インデックスを使用して、特別なソートを行わずに ORDER BY 句を満たすことができます。

インデックスのすべての未使用の部分とすべての特別な ORDER BY カラムが WHERE 句内で定数であるかぎり、ORDER BY がインデックスに完全に一致しない場合でもインデックスを使用できます。次のクエリーではインデックスを使用して ORDER BY 部分を解決します。

SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;
SELECT * FROM t1 WHERE key_part1 = constant ORDER BY key_part2;
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
SELECT * FROM t1 WHERE key_part1 = 1 ORDER BY key_part1 DESC, key_part2 DESC;
SELECT * FROM t1 WHERE key_part1 > constant ORDER BY key_part1 ASC;
SELECT * FROM t1 WHERE key_part1 < constant ORDER BY key_part1 DESC;
SELECT * FROM t1 WHERE key_part1 = constant1 AND key_part2 > constant2 ORDER BY key_part2;

場合によって、MySQL は WHERE 句に一致する行を見つけるためにインデックスを使用しても、ORDER BY を解決するために、インデックスを使用できないことがあります。これらの例には、次のようなものが含まれます。

  • さまざまなキーに対して ORDER BY を使用します。

    SELECT * FROM t1 ORDER BY key1, key2;
  • キーの連続しない部分に対して ORDER BY を使用します。

    SELECT * FROM t1 WHERE key2=constant ORDER BY key_part2;
  • ASCDESC を混在させます。

    SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
  • 行をフェッチするために使用されるキーが ORDER BY で使用されるキーと同じでありません。

    SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
  • キーカラム名以外の項を含む式で ORDER BY を使用します。

    SELECT * FROM t1 ORDER BY ABS(key);
    SELECT * FROM t1 ORDER BY -key;
  • 多数のテーブルを結合しようとしており、ORDER BY 内のカラムがすべて、行の取得に使用される最初の非定数テーブルからのものとはかぎりません。(これは EXPLAIN 出力で、const 結合型を持たない最初のテーブルです。)

  • ORDER BY 式と GROUP BY 式が異なります。

  • ORDER BY 句に指定されたカラムのプリフィクスのみにインデックスを設定しています。この場合、インデックスを使用してソート順序を完全には解決できません。たとえば、CHAR(20) カラムがあり、その先頭の 10 バイトだけにインデックスを設定している場合、インデックスで、10 バイト目を越える値を区別できないため、filesort が必要になります。

  • 使用されたテーブルインデックスの種類が、行を順番に格納しません。たとえば、これは、MEMORY テーブルの HASH インデックスに当てはまります。

インデックスをソートに使用できるかどうかは、カラムエイリアスの使用によって影響を受けることがあります。カラム t1.a にインデックスが設定されているとします。次のステートメントでは、選択リスト内のカラム名は a です。これは t1.a を指しているため、ORDER BY 内の a への参照にはインデックスを使用できます。

SELECT a FROM t1 ORDER BY a;

次のステートメントでも、選択リスト内のカラム名は a ですが、これはエイリアス名です。これは ABS(a) を指しているため、ORDER BY 内の a への参照にはインデックスを使用できません。

SELECT ABS(a) AS a FROM t1 ORDER BY a;

次のステートメントでは、ORDER BY は、選択リスト内のカラムの名前でない名前を参照しています。ただし、t1 には a というカラムがあるため、ORDER BY はそれを使用し、インデックスを使用できます。(当然ながら、結果のソート順序は、ABS(a) の順序とはまったく異なる可能性があります。)

SELECT ABS(a) AS b FROM t1 ORDER BY a;

デフォルトで、MySQL はすべての GROUP BY col1, col2, ... クエリーを、ORDER BY col1, col2, ... とクエリーに指定したかのように、ソートします。同じカラムリストを含む明示的な ORDER BY 句が含まれている場合、ソート処理は引き続き行われますが、速度の低下なく、MySQL が最適化によってそれを除去します。クエリーに GROUP BY が含まれているが、結果のソートのオーバーヘッドを避けたい場合は、ORDER BY NULL を指定することでソートを抑止できます。例:

INSERT INTO foo
SELECT a, COUNT(*) FROM bar GROUP BY a ORDER BY NULL;
注記

MySQL 5.6 における暗黙の GROUP BY ソートへの依存は、非推奨になっています。グループ化された結果の特定のソート順序を実現するには、明示的な ORDER BY 句を使用することをお勧めします。GROUP BY ソートは、たとえば、オプティマイザがもっとも効率的であると考えるどのような方法でも、グループ化を指示できるようにしたり、ソートオーバーヘッドを回避したりするためなどに、今後のリリースで変更される可能性のある MySQL 拡張機能です。

EXPLAIN SELECT ... ORDER BY を使用すると、MySQL がインデックスを使用してクエリーを解決できるかどうかを確認できます。Extra カラムに Using filesort と表示された場合、それはできません。「セクション8.8.1「EXPLAIN によるクエリーの最適化」」を参照してください。filesort は、MEMORY ストレージエンジンによって使用されるものと似た固定長の行ストレージフォーマットを使用します。VARCHAR などの可変長型は、固定長を使用して格納されます。

MySQL には、結果をソートして取得するために 2 つの filesort アルゴリズムがあります。元のメソッドは ORDER BY カラムだけを使用します。変更されたメソッドは ORDER BY カラムだけでなく、クエリーによって参照されるすべてのカラムを使用します。

どちらの filesort アルゴリズムを使用するかはオプティマイザが選択します。通常は変更されたアルゴリズムが使用されますが、BLOB カラムや TEXT カラムが含まれる場合を除きます。その場合には元のアルゴリズムが使用されます。どちらのアルゴリズムでも、ソートバッファーサイズは sort_buffer_size システム変数値です。

元の filesort アルゴリズムは次のように動作します。

  1. キーに従って、またはテーブルスキャンによって、すべての行を読み取ります。WHERE 句に一致しない行をスキップします。

  2. 行ごとに、ソートバッファーに値のペア (ソートキー値と行 ID) を格納します。

  3. すべてのペアがソートバッファーに収まる場合は、一時ファイルが作成されません。そうでない場合は、ソートバッファーがいっぱいになると、メモリー内でそれに対して qsort (quicksort) が実行され、それが一時ファイルに書き込まれます。ソートされたブロックへのポインタを保存します。

  4. すべての行が読み取られるまで、前の手順を繰り返します。

  5. 別の一時ファイルで、最大 MERGEBUFF (7) 領域の 1 つのブロックへのマルチマージを実行します。最初のファイルのすべてのブロックが 2 番目のファイルに格納されるまで、この処理を繰り返します。

  6. 残りが MERGEBUFF2 (15) ブロックより少なくなるまで、次を繰り返します。

  7. 最後のマルチマージで、行 ID (値のペアの最後の部分) のみが結果ファイルに書き込まれます。

  8. 結果ファイルで、行 ID を使用して、ソートされた順序で行を読み取ります。これを最適化するには、行 ID の大きなブロックを読み取り、それらをソートして、それらを使用して、ソートされた順序で行を行バッファーに読み込みます。行バッファーサイズは read_rnd_buffer_size システム変数値です。この手順のコードは sql/records.cc ソースファイルにあります。

このアプローチの問題の 1 つは、WHERE 句の評価時に 1 回と値のペアのソート後にもう 1 回と、2 回行を読み取ることです。さらに、1 回目は行が連続してアクセスされても (テーブルスキャンを実行する場合など)、2 回目はそれらがランダムにアクセスされます。(ソートキーは順序付けされますが、行の位置は順序付けされません。)

変更された filesort アルゴリズムには、行を 2 回読み取ることを回避する最適化が組み込まれています。それは、ソートキー値を記録しますが、行 ID の代わりに、クエリーで参照されているカラムを記録します。変更された filesort アルゴリズムは次のように動作します。

  1. WHERE 句に一致する行を読み取ります。

  2. 行ごとに、ソートキー値とクエリーで参照されるカラムから構成される値のタプルを記録します。

  3. ソートバッファーがいっぱいになると、メモリー内でソートキー値によってタプルをソートし、それを一時ファイルに書き込みます。

  4. 一時ファイルのマージソート後、ソートされた順序で行を取得しますが、2 回目はテーブルにアクセスするのではなく、ソートされたタプルから直接必要なカラムを読み取ります。

変更された filesort アルゴリズムを使用すると、タプルが元のメソッドで使用されるペアより長くなり、ソートバッファーに収まるそれらの数が少なくなります。その結果、追加の I/O によって、変更されたアプローチの方が速くなるのではなく、遅くなる可能性があります。速度の低下を防ぐため、オプティマイザはソートタプル内の追加のカラムの合計サイズが max_length_for_sort_data システム変数の値を超えない場合にのみ、変更されたアルゴリズムを使用します。(この変数の値を著しく高く設定すると、高いディスクアクティビティーと低い CPU アクティビティーの組み合わせが見られます。)

filesort が実行されると、EXPLAIN 出力で、Extra カラムに Using filesort が含まれます。さらに、オプティマイザトレース出力には filesort_summary ブロックが含まれます。例:

"filesort_summary": { "rows": 100, "examined_rows": 100, "number_of_tmp_files": 0, "sort_buffer_size": 25192, "sort_mode": "<sort_key, additional_fields>"
}

sort_mode 値は、使用されたアルゴリズムとソートバッファー内のタプルの内容に関する情報を提供します。

  • <sort_key, rowid>: ソートバッファータプルには、ソートキー値と元のテーブル行の行 ID が含まれます。タプルはソートキー値でソートされ、行 ID は、テーブルからの行の読み取りに使用されます。

  • <sort_key, additional_fields>: ソートバッファータプルには、ソートキー値とクエリーで参照されているカラムが含まれます。タプルはソートキー値でソートされ、カラム値は、タプルから直接読み取られます。

オプティマイザのトレースについては、「MySQL Internals: Tracing the Optimizer」を参照してください。

テーブル t1 に、4 つの VARCHAR カラム abc、および d があり、オプティマイザはこのクエリーに filesort を使用するとします。

SELECT * FROM t1 ORDER BY a, b;

クエリーは ab でソートしますが、すべてのカラムを返すため、クエリーによって参照されているカラムは abc、および d です。オプティマイザがどの filesort アルゴリズムを選択するかによって、クエリーは次のように実行されます。

元のアルゴリズムの場合、ソートバッファータプルの内容は次のようになります。

(fixed size a value, fixed size b value,
row ID into t1)

オプティマイザは固定サイズ値でソートします。ソート後、オプティマイザは、順番にタプルを読み取り、各タプル内の行 ID を使用して、t1 から行を読み取り、選択リストカラム値を取得します。

変更されたアルゴリズムの場合、ソートバッファータプルの内容は次のようになります。

(fixed size a value, fixed size b value,
a value, b value, c value, d value)

オプティマイザは固定サイズ値でソートします。ソート後、オプティマイザは、順番にタプルを読み取り、abc、および d の値を使用して、t1 を再度読み取ることなく、選択リストカラム値を取得します。

filesort が使用されない遅いクエリーでは、max_length_for_sort_datafilesort がトリガーされる適切な値まで小さくしてみてください。

ORDER BY 速度を向上するには、MySQL で、追加のソートフェーズではなく、インデックスを使用させることができるかどうかをチェックします。これが不可能な場合は、次の戦略を試すことができます。

  • sort_buffer_size 変数値を増やします。

  • read_rnd_buffer_size 変数値を増やします。

  • カラムに格納された値を保持するために必要なだけの大きさでカラムを宣言することにより、行あたりに使用する RAM を減らします。たとえば、値が 16 文字を超えることがない場合は、CHAR(16) の方が CHAR(200) よりも適切です。

  • tmpdir システム変数を変更して、大量の空き領域のある専用ファイルシステムを指すようにします。変数値には、ラウンドロビン方式で使用される複数のパスをリストできます。この機能を使用して、複数のディレクトリに負荷を分散できます。パスは UNIX ではコロン文字 (:)、Windows ではセミコロン文字 (;) で区切るようにしてください。パスには、同じディスク上の異なるパーティションではなく、異なる物理ディスクにあるファイルシステム内のディレクトリを指定してください。

ORDER BY にインデックスが使用されないが、LIMIT 句も存在する場合、オプティマイザはマージファイルの使用を避け、メモリー内で行をソートできます。詳細は、セクション8.2.1.19「LIMIT クエリーの最適化」を参照してください。

8.2.1.16 GROUP BY の最適化

GROUP BY 句を満たすもっとも一般的な方法は、テーブル全体をスキャンし、各グループのすべての行が連続する新しい一時テーブルを作成することであり、それにより、この一時テーブルを使用してグループを見つけて、集約関数 (ある場合) を適用できます。場合によって、MySQL はインデックスアクセスを使用することで、それよりはるかに適切に実行し、一時テーブルの作成を回避できます。

GROUP BY にインデックスを使用するためのもっとも重要な前提条件は、すべての GROUP BY カラムが同じインデックスから属性を参照することと、インデックスがそのキーを正しい順序で格納する (たとえば、これは BTREE インデックスで、HASH インデックスではありません) ことです。一時テーブルの使用をインデックスアクセスに置き換えられるかどうかは、クエリー内でインデックスのどの部分が使用されているか、その部分に指定された条件、および選択された集約関数にもよります。

次のセクションで詳しく説明するように、インデックスアクセスによって GROUP BY クエリーを実行する方法は 2 つあります。最初の方法では、グループ化操作はすべての範囲述語 (ある場合) とともに適用されます。2 つめの方法では、まず範囲スキャンを実行し、次に結果タプルをグループ化します。

MySQL では、GROUP BY はソートに使用されるため、サーバーはグループ化に ORDER BY 最適化を適用することもあります。セクション8.2.1.15「ORDER BY の最適化」を参照してください。

8.2.1.16.1 ルースインデックススキャン

GROUP BY を処理するもっとも効率的な方法は、インデックスを使用してグループ化するカラムを直接取得することです。このアクセスメソッドでは、MySQL はキーが順序付けられている、インデックス型のプロパティーを使用します。(たとえば、BTREE)。このプロパティーにより、インデックス内のすべての WHERE 条件を満たすキーを考慮する必要なく、インデックス内のルックアップグループを使用できます。このアクセスメソッドはインデックス内のキーの一部だけを考慮するため、ルースインデックススキャンと呼ばれています。WHERE 句がない場合、ルースインデックススキャンでは、グループの数だけキーを読み取りますが、これはすべてのキーの数よりもはるかに少ないことがあります。WHERE 句に範囲述語が含まれる場合 (セクション8.8.1「EXPLAIN によるクエリーの最適化」range 結合型の説明を参照してください)、ルースインデックススキャンでは範囲条件を満たす各グループの最初のキーをルックアップし、再度最小限の数のキーを読み取ります。これは次の条件の下で可能です。

  • クエリーが単一テーブルに対するものです。

  • GROUP BY はインデックスの左端のプリフィクスを形成するカラムのみを指定し、ほかのカラムは指定しません。(GROUP BY の代わりに、クエリーに DISTINCT 句がある場合、個々のすべての属性がインデックスの左端のプリフィクスを形成するカラムを参照します。)たとえば、テーブル t1(c1,c2,c3) にインデックスがある場合、クエリーに GROUP BY c1, c2, がある場合に、ルースインデックススキャンを適用できます。クエリーに GROUP BY c2, c3 (カラムは左端のプリフィクスでない) または GROUP BY c1, c2, c4 (c4 はインデックス内にない) がある場合は適用できません。

  • 選択リスト (ある場合) で使用されている集約関数が、MIN()MAX() だけであり、それらはすべて同じカラムを参照します。カラムはインデックス内にある必要があり、GROUP BY にあるカラムを追跡する必要があります。

  • クエリーで参照された GROUP BY からの部分以外のインデックスの部分は、定数である必要があります (つまり、定数と同等のもので参照されている必要があります) が、MIN() または MAX() 関数の引数を除きます。

  • インデックス内のカラムの場合、プリフィクスだけでなく、完全なカラム値にインデックスが設定されている必要があります。たとえば、c1 VARCHAR(20), INDEX (c1(10)) では、インデックスはルースインデックススキャンに使用できません。

ルースインデックススキャンをクエリーに適用できる場合、EXPLAIN 出力で、Extra カラムに Using index for group-by と示されます。

テーブル t1(c1,c2,c3,c4) にインデックス idx(c1,c2,c3) があると仮定します。ルースインデックススキャンアクセスメソッドは、次のクエリーに使用できます。

SELECT c1, c2 FROM t1 GROUP BY c1, c2;
SELECT DISTINCT c1, c2 FROM t1;
SELECT c1, MIN(c2) FROM t1 GROUP BY c1;
SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;
SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;

次に示す理由により、以下のクエリーはこのクイック選択メソッドで実行できません。

  • MIN() または MAX() 以外の集約関数があります。

    SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
  • GROUP BY 句内のカラムがインデックスの左端のプリフィクスを形成していません。

    SELECT c1, c2 FROM t1 GROUP BY c2, c3;
  • クエリーは GROUP BY 部分のあとに続くキーの部分を参照し、そこに定数と同等のものがありません。

    SELECT c1, c3 FROM t1 GROUP BY c1, c2;

    クエリーに WHERE c3 = const が含まれる場合、ルースインデックススキャンを使用できます。

ルースインデックススキャンアクセスメソッドは、選択リスト内で、すでにサポートされている MIN() および MAX() 参照に加えて、ほかの形式の集約関数参照にも適用できます。

  • AVG(DISTINCT)SUM(DISTINCT)、および COUNT(DISTINCT) がサポートされています。AVG(DISTINCT)SUM(DISTINCT) は 1 つの引数をとります。COUNT(DISTINCT) には複数のカラム引数を指定できます。

  • クエリーに GROUP BY または DISTINCT 句があってはいけません。

  • ここでも先述したルーススキャンの制限が適用されます。

テーブル t1(c1,c2,c3,c4) にインデックス idx(c1,c2,c3) があると仮定します。ルースインデックススキャンアクセスメソッドは、次のクエリーに使用できます。

SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1;
SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;

ルースインデックススキャンは次のクエリーに適用できません。

SELECT DISTINCT COUNT(DISTINCT c1) FROM t1;
SELECT COUNT(DISTINCT c1) FROM t1 GROUP BY c1;
8.2.1.16.2 タイトインデックススキャン

タイトインデックススキャンは、クエリー条件によって、フルインデックススキャンまたは範囲インデックススキャンのいずれかになります。

ルースインデックススキャンの条件が満たされていなくても、GROUP BY クエリーの一時テーブルの作成を回避できる場合があります。WHERE 句に範囲条件がある場合、このメソッドはこれらの条件を満たすキーだけを読み取ります。そうでない場合は、インデックススキャンを実行します。このメソッドは WHERE 句によって定義された各範囲内のすべてのキーを読み取るか、または範囲条件がなければインデックス全体をスキャンするため、タイトインデックススキャンと呼んでいます。タイトインデックススキャンでは、範囲条件を満たすすべてのキーが見つかったあとにのみ、グループ化操作が実行されます。

このメソッドが機能するためには、クエリー内のすべてのカラムに、GROUP BY キーの前にくるか、または間の部分にあるキーの部分を参照する定数同等条件があれば十分です。同等条件からの定数は、インデックスの完全なプリフィクスを形成できるように、検索キーのギャップを埋めます。これらのインデックスのプリフィクスは、インデックスルックアップに使用できます。GROUP BY 結果のソートが必要で、インデックスのプリフィクスである検索キーを形成できる場合、順序付けされたインデックス内のプリフィクスによる検索で、すでにすべてのキーが順番に取得されているため、MySQL は余分なソート操作も避けられます。

テーブル t1(c1,c2,c3,c4) にインデックス idx(c1,c2,c3) があると仮定します。次のクエリーは、前述のルースインデックススキャンアクセスメソッドでは機能しませんが、タイトインデックススキャンアクセスメソッドでは機能します。

  • GROUP BY にはギャップがありますが、条件 c2 = 'a' によってカバーされます。

    SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
  • GROUP BY は、キーの最初の部分から開始されませんが、その部分に対して定数を与える条件があります。

    SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;

8.2.1.17 DISTINCT の最適化

ORDER BY と組み合わされた DISTINCT では多くの場合に一時テーブルが必要です。

DISTINCT では GROUP BY を使用できるため、MySQL が ORDER BY または HAVING 句内の選択したカラムの部分でないカラムをどのように処理するかを学んでください。セクション12.19.3「MySQL での GROUP BY の処理」を参照してください。

ほとんどの場合、DISTINCT 句は GROUP BY の特殊な例と考えることができます。たとえば、次の 2 つのクエリーは同等です。

SELECT DISTINCT c1, c2, c3 FROM t1
WHERE c1 > const;
SELECT c1, c2, c3 FROM t1
WHERE c1 > const GROUP BY c1, c2, c3;

この同等性のため、GROUP BY クエリーに適用できる最適化は DISTINCT 句のあるクエリーにも適用できます。そのため、DISTINCT クエリー最適化の可能性の詳細については、セクション8.2.1.16「GROUP BY の最適化」を参照してください。

LIMIT row_countDISTINCT と組み合わせた場合、MySQL は row_count 固有の行が見つかるとただちに停止します。

クエリーに指定されたすべてのテーブルのカラムを使用しない場合、MySQL は最初の一致が見つかるとただちに未使用テーブルのスキャンを停止します。次の例では、t1t2 の前に使用され (これは、EXPLAIN で確認できます)、MySQL は t2 (t1 内の特定の行の) で、最初の行を見つけると、t2 からの読み取りを停止します。

SELECT DISTINCT t1.a FROM t1, t2 where t1.a=t2.a;

8.2.1.18 サブクエリーの最適化

MySQL クエリーオプティマイザには、サブクエリーの評価に使用できるさまざまな戦略があります。IN (または =ANY) サブクエリーの場合、オプティマイザには次の選択肢があります。

  • 準結合

  • 実体化

  • EXISTS 戦略

NOT IN (または <>ALL) サブクエリーの場合、オプティマイザには次の選択肢があります。

  • 実体化

  • EXISTS 戦略

次のセクションでは、これらの最適化戦略について詳しく説明します。

8.2.1.18.1 準結合変換によるサブクエリーの最適化

MySQL 5.6.5 現在、オプティマイザは、このセクションで説明するように、準結合戦略を使用して、サブクエリーの実行を改善します。

2 つのテーブル間の内部結合の場合、結合は、他方のテーブルに一致がある回数だけ、一方のテーブルから 1 行を返します。ただし、問題によっては、重要な情報は一致の数ではなく、一致があるかどうかだけの場合があります。コースカリキュラムのクラスとクラス名簿 (各クラスに登録されている生徒) をそれぞれ一覧表示する classroster というテーブルがあるとします。実際に生徒が登録されているクラスを一覧表示するには、次の結合を使用できます。

SELECT class.class_num, class.class_name
FROM class INNER JOIN roster
WHERE class.class_num = roster.class_num;

ただし、結果には、登録された生徒ごとに、各クラスが 1 回ずつ一覧表示されます。ここでの問題では、これは不要な情報の重複です。

class_numclass テーブル内の主キーとすると、重複の抑制は、SELECT DISTINCT を使用して実現できますが、あとで重複を除去するためにだけ、まずすべての一致する行を生成することは非効率的です。

同じ重複のない結果は、次のサブクエリーを使用して取得できます。

SELECT class_num, class_name
FROM class
WHERE class_num IN (SELECT class_num FROM roster);

ここで、オプティマイザは IN 句に roster テーブルから各クラス番号のインスタンスを 1 つだけ返すサブクエリーが必要であることを認識できます。この場合、クエリーは準結合として実行できます。つまり、roster 内の行に一致する class 内の各行のインスタンスを 1 つだけ返す操作です。

MySQL 5.6.6 より前では、外部クエリーの指定は、単純なテーブルスキャンやカンマ構文を使用した内部結合に制限されており、ビュー参照は不可能でした。5.6.6 現在、外部クエリー指定で外部結合および内部結合構文を使用でき、テーブル参照がベーステーブルでなければいけないという制限はなくなりました。

MySQL では、サブクエリーは準結合として扱われるために、次の条件を満たしている必要があります。

  • それは、おそらく AND 式内の項として、WHERE 句または ON 句のトップレベルに表示される IN (または =ANY) サブクエリーである必要があります。例:

    SELECT ...
    FROM ot1, ...
    WHERE (oe1, ...) IN (SELECT ie1, ... FROM it1, ... WHERE ...);

    ここで、ot_iit_i は、クエリーの外側部分と内側部分のテーブルを表し、oe_iie_i は、外部テーブルと内部テーブル内のカラムを参照する式を表します。

  • それは UNION コンストラクトのない単一の SELECT である必要があります。

  • それには GROUP BY または HAVING 句または集約関数が含まれていてはなりません。

  • それには、LIMIT を使用した ORDER BY があってはなりません。

  • 外部テーブルとおよび内部テーブルの合計数が結合で許可されている最大テーブル数より少なくなければなりません。

サブクエリーは相関する場合と相関しない場合があります。LIMIT と同様に、ORDER BY も使用しなければ、DISTINCT を使用できます。

サブクエリーが先述の条件を満たしている場合、MySQL はそれを準結合に変換し、次の戦略からコストに基づいた選択を行います。

  • サブクエリーを結合に変換するか、テーブルプルアウトを使用して、クエリーをサブクエリーテーブルと外部テーブル間の内部結合として実行します。テーブルプルアウトは、テーブルをサブクエリーから外部クエリーに引き出します。

  • 重複の除去: 準結合を結合のように実行し、一時テーブルを使用して、重複レコードを削除します。

  • FirstMatch: 行の組み合わせの内部テーブルをスキャンし、指定した値グループの複数のインスタンスがある場合、それらすべてを返すのではなく、1 つを選択します。これはスキャンを「ショートカット」し、不要な行の生成をなくします。

  • LooseScan: 各サブクエリーの値グループから単一の値を選択できるようにするインデックスを使用して、サブクエリーテーブルをスキャンします。

  • サブクエリーをインデックス付きの一時テーブルに実体化し、その一時テーブルを使用して、結合を実行します。インデックスは重複の削除に使用されます。さらに、インデックスはあとで一時テーブルと外部テーブルを結合する際のルックアップにも使用されることがあります。そうでない場合はテーブルがスキャンされます。

重複の除去を除いて、これらの各戦略を有効または無効にするには、optimizer_switch システム変数を使用します。semijoin フラグは準結合を使用するかどうかを制御します。これが on に設定されている場合、firstmatchloosescan、および materialization フラグによって、使用可能な準結合戦略を詳細に制御できます。これらのフラグはデフォルトで on です。セクション8.8.5.2「切り替え可能な最適化の制御」を参照してください。

準結合戦略の使用は、EXPLAIN 出力で次のように示されます。

  • 準結合されたテーブルは、外側の選択に表示されます。EXPLAIN EXTENDEDSHOW WARNINGS には書き換えられたクエリーが示され、準結合構造が表示されます。ここから、準結合から引き出されたテーブルについての情報を得ることができます。サブクエリーが準結合に変換された場合、サブクエリー述語がなくなっており、そのテーブルと WHERE 句が外部クエリー結合リストと WHERE 句にマージされたことがわかります。

  • 重複の除去のための一時テーブルの使用は、Extra カラムの Start temporaryEnd temporary によって示されます。引き出されておらず、Start temporaryEnd temporary によってカバーされる EXPLAIN 出力行の範囲内にあるテーブルは、一時テーブルにそれらの rowid が格納されます。

  • Extra カラムの FirstMatch(tbl_name) は結合のショートカットを示します。

  • Extra カラムの LooseScan(m..n) は LooseScan 戦略の使用を示します。mn はキーパート番号です。

  • MySQL 5.6.7 現在、実体化のための一時テーブルの使用は、MATERIALIZEDselect_type 値のある行と、<subqueryN>table 値のある行によって示されます。

    MySQL 5.6.7 より前では、実体化のための一時テーブルの使用は、Extra カラムに、単一のテーブルが使用された場合 Materialize によって示され、複数のテーブルが使用された場合 Start materializeEnd materialize によって示されます。Scan が存在する場合、テーブルの読み取りに一時テーブルインデックスは使用されません。そうでない場合は、インデックスルックアップが使用されます。

8.2.1.18.2 サブクエリー実体化によるサブクエリーの最適化

MySQL 5.6.5 現在、オプティマイザは、サブクエリー処理の効率向上を可能にする戦略として、サブクエリー実体化を使用します。

実体化を使用しない場合、オプティマイザは、非相関サブクエリーを相関サブクエリーとして書き換えることがあります。たとえば、次の IN サブクエリーは非相関です (where_condition には t2 からのカラムのみが含まれ、t1 からは含まれません)。

SELECT * FROM t1
WHERE t1.a IN (SELECT t2.b FROM t2 WHERE where_condition);

オプティマイザはこれを EXISTS 相関サブクエリーとして書き換えることがあります。

SELECT * FROM t1
WHERE EXISTS (SELECT t2.b FROM t2 WHERE where_condition AND t1.a=t2.b);

一時テーブルを使用したサブクエリー実体化により、そのような書き換えを回避し、外部クエリーの行ごとに 1 回ではなく、1 回だけサブクエリーを実行させることができます。実体化は、通常メモリー内に一時テーブルとしてサブクエリー結果を生成することによって、クエリー実行を高速化します。MySQL ははじめてサブクエリー結果を必要としたときに、その結果を一時テーブルに実体化します。あとで結果が必要になったときに、MySQL は再度一時テーブルを参照します。テーブルはルックアップを高速にし、負荷を軽減するため、ハッシュインデックスによってインデックス設定されます。このインデックスは一意で、重複がないため、テーブルを小さくします。

サブクエリー実体化では、可能なかぎりインメモリー一時テーブルを使用しようとし、テーブルが大きくなりすぎた場合、ディスク上のストレージに戻ります。セクション8.4.4「MySQL が内部一時テーブルを使用する仕組み」を参照してください。

MySQL で使用されるサブクエリー実体化では、optimizer_switch システム変数の materialization フラグが on である必要があります。その後実体化は、任意の場所 (選択リスト、WHEREONGROUP BYHAVING、または ORDER BY 内) に存在する、次のいずれかのユースケースに分類される述語のサブクエリー述語に適用されます。

  • 外側の式 oe_i または内側の式 ie_i が NULL 可能でない場合に、述語はこの形式になります。N には 1 以上を指定できます。

    (oe_1, oe_2, ..., oe_N) [NOT] IN (SELECT ie_1, i_2, ..., ie_N ...)
  • 単一の外側の式 oe と内側の式 ie がある場合に、述語はこの形式になります。式は NULL 可能にできます。

    oe [NOT] IN (SELECT ie ...)
  • 述語は IN または NOT INUNKNOWN (NULL) の結果は FALSE の結果と同じ意味になります。

次の例に、UNKNOWN および FALSE 述語評価の同等性の要件が、サブクエリー実体化を使用できるかどうかにどのように影響するかを示します。サブクエリーが非相関になるように、where_conditiont2 からのカラムのみが含まれ、t1 からは含まれないとします。

このクエリーは実体化の対象になります。

SELECT * FROM t1
WHERE t1.a IN (SELECT t2.b FROM t2 WHERE where_condition);

ここでは、IN 述語が UNKNOWN を返すか、FALSE を返すかは問題ではありません。どちらも t1 からの行はクエリー結果に含まれません。

サブクエリー実体化が使用されない例は、t2.b が NULL 可能カラムである次のクエリーです。

SELECT * FROM t1
WHERE (t1.a,t1.b) NOT IN (SELECT t2.a,t2.b FROM t2 WHERE where_condition);

クエリーで EXPLAIN を使用すると、オプティマイザがサブクエリー実体化を使用しているかどうかの何らかの指示が得られます。実体化を使用しないクエリー実行と比較して、select_typeDEPENDENT SUBQUERY から SUBQUERY に変更されることがあります。これは、外部行ごとに 1 回実行されるサブクエリーの場合、実体化によってサブクエリーが 1 回だけ実行されるようにできることを示します。さらに、EXPLAIN EXTENDED の場合、次の SHOW WARNINGS によって表示されるテキストには materializematerialize および materialized-subquery (MySQL 5.6.6 より前では materialized subselect) が含まれます。

8.2.1.18.3 FROM 句内のサブクエリー (派生テーブル) の最適化

MySQL 5.6.3 現在、オプティマイザは FROM 句内のサブクエリー (つまり派生テーブル) をより効率的に処理します。

  • FROM 句内のサブクエリーの実体化は、クエリー実行中にその内容が必要になるまで延期されるので、パフォーマンスが向上します。

    • これまで、FROM 句内のサブクエリーは EXPLAIN SELECT ステートメントに対して実体化されていました。これにより、EXPLAIN の目的がクエリーを実行することではなく、クエリー計画情報を取得することであっても、SELECT が部分的に実行されました。この実体化は行われなくなったため、EXPLAIN はそのようなクエリーに対して高速化しています。

    • EXPLAIN 以外のクエリーでは、実体化の遅延によって、それをまったく実行する必要がなくなることがあります。FROM 句内のサブクエリーの結果を別のテーブルに結合するクエリーを考慮します。オプティマイザはその他方のテーブルを最初に処理し、それが行を返さないことがわかると、それ以上結合を実行する必要はないため、オプティマイザはサブクエリーの実体化を完全にスキップできます。

  • クエリー実行中に、オプティマイザは派生テーブルにインデックスを追加して、そこからの行の取得を高速化できます。

SELECT クエリーの FROM 句にサブクエリーが表示される、次の EXPLAIN ステートメントを考慮します。

EXPLAIN SELECT * FROM (SELECT * FROM t1);

オプティマイザは、SELECT 実行中に結果が必要になるまで、サブクエリーの実体化を遅延させて、それを回避します。この例では、クエリーが実行されないため、結果が必要になることはありません。

実行されるクエリーの場合でも、サブクエリー実体化の遅延によって、オプティマイザは実体化を完全に避けられることがあります。FROM 句内のサブクエリーの結果を別のテーブルに結合する次のクエリーを考慮します。

SELECT * FROM t1 JOIN (SELECT t2.f1 FROM t2) AS derived_t2 ON t1.f2=derived_t2.f1 WHERE t1.f1 > 0;

最適化によって t1 が最初に処理され、WHERE 句で空の結果が生成された場合、結合は空である必要があり、サブクエリーは実体化される必要はありません。

最悪の場合 (派生テーブルが実体化される)、追加の作業が実行されないため、クエリーの実行に MySQL 5.6.3 より前と同じ時間がかかります。最善の場合 (派生テーブルが実体化されない)、実体化の実行に必要な時間の分だけ、クエリーの実行が速くなります。

FROM 句内のサブクエリーに実体化が必要な場合、オプティマイザは、実体化されたテーブルにインデックスを追加して、結果へのアクセスを高速化できます。そのようなインデックスによって、テーブルに ref アクセスできる場合、クエリー実行中に読み取る必要があるデータの量を大幅に削減できます。次のクエリーを考慮してください。

SELECT * FROM t1 JOIN (SELECT * FROM t2) AS derived_t2 ON t1.f1=derived_t2.f1;

オプティマイザは、derived_t2 のカラム f1 に対してインデックスを構築することで、最低コストの実行プランでの ref アクセスの使用が可能になる場合に、そのようにします。インデックスの追加後、オプティマイザは、実体化された派生テーブルをインデックス付きの通常のテーブルと同じように扱うことができ、生成されたインデックスから同様の利点が得られます。インデックス作成のオーバーヘッドは、インデックスを使用しないクエリー実行のコストと比較して無視できます。ref アクセスが、ほかのアクセスメソッドよりコストが高くなる場合は、インデックスが作成されず、オプティマイザは何も失いません。

8.2.1.18.4 EXISTS 戦略によるサブクエリーの最適化

IN 演算子を使用して (または、同等の =ANY を使用して) サブクエリーの結果をテストする比較に、特定の最適化を適用できます。このセクションでは、これらの最適化について、特に NULL 値が存在する課題に関して説明します。説明の最後の部分では、オプティマイザを支援するためにユーザーが実行できることに関する提案も紹介します。

次のようなサブクエリーの比較を考慮します。

outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)

MySQL は外側から内側にクエリーを評価します。つまり、まず外側の式 outer_expr の値を取得してから、サブクエリーを実行し、それによって生成される行を取得します。

内側の式 inner_exprouter_expr と等しい行だけが目的の行であることをサブクエリーに通知することは、かなり役に立つ最適化です。これを実行するには、適切な等式をサブクエリーの WHERE 句にプッシュダウンします。つまり、この比較は次のように変換されます。

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr)

変換後、MySQL はプッシュダウンされた等式を使用して、サブクエリーの評価時に検査する必要のある行数を制限できます。

より一般的には、N 個の値と N 値の行を返すサブクエリーとの比較は、同じ変換の対象になります。oe_iie_i が対応する外側と内側の式の値を表す場合、次のサブクエリー比較は:

(oe_1, ..., oe_N) IN (SELECT ie_1, ..., ie_N FROM ... WHERE subquery_where)

次のようになります。

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND oe_1 = ie_1 AND ... AND oe_N = ie_N)

以下の説明では、簡単にするために、1 組の外側と内側の式の値があると仮定します。

先述の変換には制限があります。これは、可能性のある NULL 値を無視する場合にかぎり有効です。つまり、プッシュダウン戦略は、次の 2 つの条件が両方とも true であるかぎり機能します。

  • outer_exprinner_exprNULL にできません。

  • FALSE サブクエリー結果と NULL を区別する必要はありません。(サブクエリーが WHERE 句内の OR または AND 式の一部である場合に、MySQL はユーザーが気にしないものと想定します。)

これらの条件の一方または両方が成立しない場合、最適化は複雑になります。

outer_exprNULL 以外の値であることがわかっているが、サブクエリーは outer_expr = inner_expr となるような行を生成しないものとします。その場合、outer_expr IN (SELECT ...) は次のように評価されます。

  • inner_exprNULL である行を SELECT が生成する場合は NULL

  • SELECTNULL 以外の値のみを生成するかまたは何も生成しない場合は FALSE

この状況では、outer_expr = inner_expr である行を探すアプローチは有効でなくなります。そのような行を探すことは必要ですが、何も見つからない場合には、inner_exprNULL となる行も探します。大ざっぱに言うと、サブクエリーは次のように変換できます。

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND (outer_expr=inner_expr OR inner_expr IS NULL))

追加の IS NULL 条件を評価する必要性は、MySQL に ref_or_null アクセスメソッドがある理由です。

mysql> EXPLAIN -> SELECT outer_expr IN (SELECT t2.maybe_null_key -> FROM t2, t3 WHERE ...) -> FROM t1;
*************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1
...
*************************** 2. row *************************** id: 2 select_type: DEPENDENT SUBQUERY table: t2 type: ref_or_null
possible_keys: maybe_null_key key: maybe_null_key key_len: 5 ref: func rows: 2 Extra: Using where; Using index
...

unique_subquery および index_subquery サブクエリー固有のアクセスメソッドには or NULL バリアントもあります。ただし、それらは EXPLAIN の出力に表示されないため、EXPLAIN EXTENDED のあとに SHOW WARNINGS を付けて使用する必要があります (警告メッセージの checking NULL に注意してください)。

mysql> EXPLAIN EXTENDED -> SELECT outer_expr IN (SELECT maybe_null_key FROM t2) FROM t1\G*************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1
...
*************************** 2. row *************************** id: 2 select_type: DEPENDENT SUBQUERY table: t2 type: index_subquery
possible_keys: maybe_null_key key: maybe_null_key key_len: 5 ref: func rows: 2 Extra: Using index
mysql> SHOW WARNINGS\G*************************** 1. row *************************** Level: Note Code: 1003
Message: select (`test`.`t1`.`outer_expr`, (((`test`.`t1`.`outer_expr`) in t2 on maybe_null_key checking NULL))) AS `outer_expr IN (SELECT maybe_null_key FROM t2)` from `test`.`t1`

追加の OR ... IS NULL 条件によってクエリーの実行は多少複雑になり、サブクエリー内の最適化の一部も適用できなくなりますが、通常これは許容できます。

outer_exprNULL になる可能性がある場合、状況ははるかに悪くなります。不明な値としての NULL の SQL の解釈によると、NULL IN (SELECT inner_expr ...) は次のように評価されるはずです。

  • SELECT が何らかの行を生成する場合は NULL

  • SELECT が行を生成しない場合は FALSE

正しい評価には、SELECT がとにかく何らかの行を生成したかどうかを確認できるようにする必要があるため、outer_expr = inner_expr をサブクエリーにプッシュダウンすることはできません。等式をプッシュダウンできないと、多くの実際のサブクエリーが非常に遅くなるため、これは問題になります。

基本的に、outer_expr の値に応じて、サブクエリーを実行するさまざまな方法が存在する必要があります。

オプティマイザは速度よりも SQL 準拠を選択するため、outer_exprNULL になる可能性を考慮します。

outer_exprNULL の場合、次の式を評価するには、SELECT を実行して何らかの行が生成されるかどうかを判断する必要があります。

NULL IN (SELECT inner_expr FROM ... WHERE subquery_where)

ここで、先述の種類のプッシュダウンされた等式を使用せずに、元の SELECT を実行する必要があります。

一方、outer_exprNULL でない場合、次の比較が絶対に必要です:

outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)

この比較をプッシュダウンされた条件を使用する式に変換

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr)

この変換を行わないと、サブクエリーが遅くなります。条件をサブクエリーにプッシュダウンするかどうかのジレンマを解決するには、条件をトリガー関数にラップします。したがって、次の形式の式は:

outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)

次のように変換されます。

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND trigcond(outer_expr=inner_expr))

より一般的には、サブクエリーの比較が外側の式と内側の式の複数のペアに基づく場合、変換は次の比較をします。

(oe_1, ..., oe_N) IN (SELECT ie_1, ..., ie_N FROM ... WHERE subquery_where)

さらに、それを次の式に変換します。

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND trigcond(oe_1=ie_1) AND ... AND trigcond(oe_N=ie_N) )

trigcond(X) は、次の値に評価される特殊な関数です。

  • リンクされた外側の式 oe_iNULL でない場合は X

  • リンクされた外側の式 oe_iNULL の場合は TRUE

トリガー関数は、CREATE TRIGGER で作成する種類のトリガーではありません

trigcond() 関数にラップされた等式は、クエリーオプティマイザにとって最高の述語ではありません。ほとんどの最適化では、クエリーの実行時にオンまたはオフになる可能性のある述語を処理できないため、trigcond(X) をすべて不明な関数であるとみなし、無視します。現時点では、トリガー等式は次の最適化で使用できます。

  • 参照の最適化: trigcond(X=Y [OR Y IS NULL]) を使用して、refeq_ref、または ref_or_null テーブルアクセスを構築できます。

  • インデックスルックアップベースのサブクエリー実行エンジン: trigcond(X=Y) を使用して、unique_subquery または index_subquery アクセスを構築できます。

  • テーブル条件ジェネレータ: サブクエリーが複数のテーブルの結合である場合、トリガー条件は可能なかぎり早く確認されます。

オプティマイザがトリガー条件を使用して、何らかの種類のインデックスルックアップベースのアクセスを作成する場合 (上記リストの最初の 2 項目に関して)、条件がオフである場合のフォールバック戦略が必要です。このフォールバック戦略は常に同じで、フルテーブルスキャンを実行します。EXPLAIN の出力で、フォールバックは Extra カラムに Full scan on NULL key と表示されます。

mysql> EXPLAIN SELECT t1.col1, -> t1.col1 IN (SELECT t2.key1 FROM t2 WHERE t2.col2=t1.col2) FROM t1\G*************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1 ...
*************************** 2. row *************************** id: 2 select_type: DEPENDENT SUBQUERY table: t2 type: index_subquery
possible_keys: key1 key: key1 key_len: 5 ref: func rows: 2 Extra: Using where; Full scan on NULL key

EXPLAIN EXTENDED に続いて SHOW WARNINGS を実行すると、トリガー条件を確認できます。

*************************** 1. row *************************** Level: Note Code: 1003
Message: select `test`.`t1`.`col1` AS `col1`, <in_optimizer>(`test`.`t1`.`col1`, <exists>(<index_lookup>(<cache>(`test`.`t1`.`col1`) in t2 on key1 checking NULL where (`test`.`t2`.`col2` = `test`.`t1`.`col2`) having trigcond(<is_not_null_test>(`test`.`t2`.`key1`))))) AS `t1.col1 IN (select t2.key1 from t2 where t2.col2=t1.col2)` from `test`.`t1`

トリガー条件を使用すると、パフォーマンスに多少の影響があります。現在 NULL IN (SELECT ...) 式では、以前に実行されなかった (遅い) フルテーブルスキャンが行われる可能性があります。これは正しい結果を得るための代価です (トリガー条件戦略の目的は、速度ではなく適合性を向上させることでした)。

複数テーブルサブクエリーでは、外側の式が NULL である場合に、結合オプティマイザが最適化を行わないため、NULL IN (SELECT ...) の実行が特に遅くなります。それは、左辺が NULL の場合のサブクエリーの評価はめったにないものと想定しています (そうでないことを示す統計があっても)。一方、外側の式が NULL になる可能性があっても実際にそうなることがない場合、パフォーマンスの低下はありません。

クエリーオプティマイザでクエリーがより適切に実行されるようにするには、次のヒントを使用してください。

  • カラムが実際に NOT NULL である場合は、そのように宣言します。(これにより、カラムの条件テストを簡単にすることで、オプティマイザのほかの側面にも役立ちます。)

  • NULLFALSE サブクエリー結果を区別する必要がない場合、遅い実行パスを簡単に回避できます。次のような比較を置き換えます。

    outer_expr IN (SELECT inner_expr FROM ...)

    次の式で:

    (outer_expr IS NOT NULL) AND (outer_expr IN (SELECT inner_expr FROM ...))

    これにより、MySQL は式の結果が明確になるとただちに AND 部分の評価を停止するため、NULL IN (SELECT ...) が評価されることはなくなります。

subquery_materialization_cost_based により、サブクエリー実体化と IN -> EXISTS サブクエリー変換の選択を制御できます。セクション8.8.5.2「切り替え可能な最適化の制御」を参照してください。

8.2.1.19 LIMIT クエリーの最適化

結果セットから指定した数の行のみが必要な場合、結果セット全体をフェッチして、余分なデータを破棄するのではなく、クエリーで LIMIT 句を使用します。

MySQL は LIMIT row_count 句があり HAVING 句のないクエリーを最適化することがあります。

  • LIMIT で少数の行のみを選択すると、MySQL では、通常フルテーブルスキャンを実行するより望ましい特定の場合に、インデックスが使用されます。

  • ORDER BY とともに LIMIT row_count を使用した場合、MySQL では、結果全体をソートするのではなく、ソートされた結果の最初の row_count 行が見つかるとすぐにソートを終了します。インデックスを使用して順序付けが行われている場合、これはきわめて高速になります。filesort を実行する必要がある場合、最初の row_count を見つける前に、LIMIT 句を使用しないクエリーに一致するすべての行が選択され、それらのほとんどまたはすべてがソートされます。初期の行が見つかったら、MySQL は結果セットの残りをすべてソートしません。

    この動作をはっきり示している現象の 1 つは、このセクションで後述するように、LIMIT を付けるか付けないかで ORDER BY クエリーは異なる順序で行を返す場合があることです。

  • LIMIT row_countDISTINCT と組み合わせた場合、MySQL は row_count 固有の行が見つかるとただちに停止します。

  • 場合によって、GROUP BY はキーを順番に読み取り (またはキーのソートを実行し)、次にキー値が変わるまでサマリーを計算して解決できます。この場合、LIMIT row_count は不要な GROUP BY 値を計算しません。

  • MySQL は必要な数の行をクライアントに送信するとただちに、SQL_CALC_FOUND_ROWS が使用されていないかぎり、クエリーを中止します。

  • LIMIT 0 は迅速に空のセットを返します。これは、クエリーの妥当性のチェックに役立つことがあります。いずれかの MySQL API を使用している場合、それは結果カラムの型の取得にも使用できます。この技法は mysql クライアントプログラムでは機能せず、そのような場合には、単に Empty set を表示します。代わりに、この目的では SHOW COLUMNS または DESCRIBE を使用します。

  • サーバーは、クエリーを解決するために一時テーブルを使用する場合、LIMIT row_count 句を使用して、必要な領域の量を計算します。

複数の行の ORDER BY カラムに同一の値がある場合、サーバーは自由にそれらの行を任意の順序で返しますが、その実行は実行プラン全体によって異なることがあります。言い換えると、それらの行のソート順序は、順序付けされていないカラムに関して決定的ではありません。

実行プランに影響する 1 つの要素は LIMIT であるため、LIMIT を付けるか付けないかで ORDER BY クエリーは異なる順序で行を返すことがあります。category カラムによってソートされるが、id および rating カラムに関して非決定的である次のクエリーを考慮します。

mysql> SELECT * FROM ratings ORDER BY category;+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
| 2 | 3 | 5.0 |
| 7 | 3 | 2.7 |
+----+----------+--------+

LIMIT を含めると、各 category 値内の行の順序に影響することがあります。たとえば、これは有効なクエリー結果です。

mysql> SELECT * FROM ratings ORDER BY category LIMIT 5;+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 4 | 2 | 3.5 |
| 3 | 2 | 3.7 |
| 6 | 2 | 3.5 |
+----+----------+--------+

各ケースで、行は ORDER BY カラムによってソートされますが、SQL 標準で必要とされるのはこれだけです。

LIMIT を使用してもしなくても同じ行順序を確保することが重要な場合は、ORDER BY 句に順序を決定的にする追加カラムを含めます。たとえば、id 値が一意である場合、指定した category 値の行を id 順で表示させるようにソートできます。

mysql> SELECT * FROM ratings ORDER BY category, id;+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
| 2 | 3 | 5.0 |
| 7 | 3 | 2.7 |
+----+----------+--------+
mysql> SELECT * FROM ratings ORDER BY category, id LIMIT 5;+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
+----+----------+--------+

MySQL 5.6.2 時点で、オプティマイザは次の形式のクエリー (およびサブクエリー) をより効率的に処理します。

SELECT ... FROM single_table ... ORDER BY non_index_column [DESC] LIMIT [M,]N;

この種のクエリーは、大きな結果セットの数行だけを表示する Web アプリケーションで一般的なものです。例:

SELECT col1, ... FROM t1 ... ORDER BY name LIMIT 10;
SELECT col1, ... FROM t1 ... ORDER BY RAND() LIMIT 15;

ソートバッファーには、sort_buffer_size のサイズが入ります。N 行 (M が指定された場合は M+N 行) のソート要素が、ソートバッファーに収まるほど小さい場合、サーバーはマージファイルの使用を回避し、ソートバッファーを優先度キューとして扱うことでメモリー内で完全にソートを実行できます。

  • テーブルをスキャンし、キュー内のソート順で選択された各行から選択リストカラムを挿入します。キューがいっぱいになった場合、ソート順で最後の行を押し出します。

  • キューから最初の N 行を返します。(M が指定されている場合、最初の M 行をスキップし、次の N 行を返します。)

以前、サーバーはソートにマージファイルを使用して、この操作を実行していました。

  • テーブルをスキャンし、テーブルの最後まで次の手順を繰り返します。

    • ソートバッファーがいっぱいになるまで、行を選択します。

    • バッファー内の最初の N 行 (M が指定された場合は M+N 行) をマージファイルに書き込みます。

  • マージファイルをソートして、最初の N 行を返します。(M が指定されている場合、最初の M 行をスキップし、次の N 行を返します。)

テーブルスキャンのコストは、キュー方法でもマージファイル方法でも同じであるため、オプティマイザはその他のコストに基づいて、方法を選択します。

  • キュー方法では、キューに行を順番に挿入するために多くの CPU を必要とします

  • マージファイル方法では、ファイルの書き込みと読み取りの I/O コストとそれをソートするための CPU コストがあります

オプティマイザは、N の特定の値と行サイズのこれらの要素のバランスを考慮します。

8.2.1.20 フルテーブルスキャンを回避する方法

MySQL がフルテーブルスキャンを使用してクエリーを解決する場合、EXPLAIN からの出力には type カラムに ALL と示されます。これは通常は次の条件で発生します。

  • テーブルがきわめて小さいため、キールックアップで煩わされるよりもテーブルスキャンを実行する方が速くなります。これは、10 行未満の行や短い行長のテーブルによくあります。

  • インデックスが設定されたカラムに対して、ON または WHERE 句に使用可能な制限がありません。

  • インデックスが設定されたカラムと定数値を比較していて、MySQL が (インデックスツリーに基づいて) その定数がテーブルのきわめて大きい部分をカバーしており、テーブルスキャンが高速に行われると計算しました。セクション8.2.1.2「MySQL の WHERE 句の最適化の方法」を参照してください。

  • 別のカラム経由で、カーディナリティーが低い (多数の行がキー値に一致する) キーを使用しています。この場合、MySQL は、キーを使用して、多数のキールックアップが実行され、テーブルスキャンが高速であると想定します。

小さいテーブルでは、テーブルスキャンは多くの場合に適切であり、実行の影響は無視できます。大きいテーブルでは、オプティマイザがテーブルスキャンを誤って選択しないように、次の技法を試してください。

  • ANALYZE TABLE tbl_name を使用して、スキャンされるテーブルのキー分布を更新します。セクション13.7.2.1「ANALYZE TABLE 構文」を参照してください。

  • スキャンされるテーブルに FORCE INDEX を使用して、MySQL に、テーブルスキャンは指定したインデックスを使用するのと比較して著しく負荷が大きいことを伝えます。

    SELECT * FROM t1, t2 FORCE INDEX (index_for_column) WHERE t1.col_name=t2.col_name;

    セクション13.2.9.3「インデックスヒントの構文」を参照してください。

  • --max-seeks-for-key=1000 オプションを使用して mysqld を開始するか、または SET max_seeks_for_key=1000 を使用して、オプティマイザに、キースキャンでは 1,000 より多くのキーシークは発生しないと想定するように伝えます。セクション5.1.4「サーバーシステム変数」を参照してください。

8.2.2 DML ステートメントの最適化

このセクションでは、データ操作言語 (DML) ステートメントの INSERTUPDATE、および DELETE を高速化する方法について説明します。従来の OLTP アプリケーションと最近の Web アプリケーションは一般に多くの小さな DML 操作を実行し、そこでは並列性が不可欠です。データ分析およびレポートアプリケーションは一般に、同時に多くの行に影響する DML 操作を実行しますが、ここでの主な考慮事項は大量のデータを書き込み、インデックスを最新に維持するための I/O です。大量のデータの挿入と更新 (業界では ETL (extract-transform-load) と呼ばれる) では、INSERTUPDATE、および DELETE ステートメントの効果を模倣する、その他の SQL ステートメントや外部コマンドを使用することがあります。

8.2.2.1 INSERT ステートメントの速度

挿入の速度を最適化するには、多くの小さな操作を 1 つの大きな操作に組み合わせます。理想的には、単一の接続を作成し、多くの新しい行のデータを一度に送信し、すべてのインデックスの更新と一貫性チェックを最後まで延期します。

行の挿入に必要な時間は、次の要因によって決まります。ここでの数はおよその割合を示しています。

  • 接続: (3)

  • サーバーへのクエリーの送信: (2)

  • クエリーの解析: (2)

  • 行の挿入: (1 ×行サイズ)

  • インデックスの挿入: (1 ×インデックス数)

  • クローズ: (1)

これには、テーブルを開く初期オーバーヘッドを考慮に入れていません。これは同時実行クエリーごとに 1 回実行されます。

テーブルのサイズによって、log N だけインデックスの挿入が遅くなります (B ツリーインデックスであるとして)。

次の方法を使用して、挿入を高速化できます。

  • 同じクライアントから同時に多数の行を挿入する場合は、複数の VALUES リストで INSERT ステートメントを使用して、同時に複数の行を挿入します。これは、個別の単一行の INSERT ステートメントを使用するより、大幅に (場合によっては数倍) 速くなります。空ではないテーブルにデータを追加する場合は、データの挿入をさらに速くするために、bulk_insert_buffer_size 変数を調整できます。セクション5.1.4「サーバーシステム変数」を参照してください。

  • テキストファイルからテーブルをロードする場合は LOAD DATA INFILE を使用します。通常、これは INSERT ステートメントを使用する場合より、20 倍速くなります。セクション13.2.6「LOAD DATA INFILE 構文」を参照してください。

  • カラムにデフォルト値があることを利用します。挿入する値がデフォルト値と異なる場合にのみ、明示的に値を挿入します。これにより、MySQL が実行する必要がある解析が減り、挿入速度が向上します。

  • InnoDB テーブルに固有のヒントについては、セクション8.5.4「InnoDB テーブルの一括データロード」を参照してください。

  • MyISAM テーブルに固有のヒントについては、セクション8.6.2「MyISAM テーブルの一括データロード」を参照してください。

8.2.2.2 UPDATE ステートメントの速度

更新ステートメントは、SELECT クエリーと同様に最適化されますが、書き込みの追加のオーバーヘッドがあります。書き込みの速度は更新されるデータの量と更新されるインデックス数によって異なります。変更がないインデックスは更新されません。

更新を速くするもう 1 つの方法は、更新を遅延して、あとで 1 行で多くの更新を実行することです。複数の更新をまとめて実行することで、テーブルをロックした場合に、一度に 1 つずつ実行するよりはるかに高速になります。

動的な行フォーマットを使用する MyISAM テーブルの場合、行を長い合計長に更新すると、行が分割されることがあります。頻繁にこれを実行する場合は、ときどき OPTIMIZE TABLE を使用することがきわめて重要になります。セクション13.7.2.4「OPTIMIZE TABLE 構文」を参照してください。

8.2.2.3 DELETE ステートメントの速度

MyISAM テーブル内の個々の行を削除するために必要な時間は、インデックスの数に正確に比例します。行をもっと速く削除するには、key_buffer_size システム変数を増やして、キーキャッシュのサイズを大きくできます。セクション8.11.2「サーバーパラメータのチューニング」を参照してください。

MyISAM テーブルからすべての行を削除するには、TRUNCATE TABLE tbl_name の方が DELETE FROM tbl_name より速くなります。切り捨て操作はトランザクションセーフではありません。アクティブなトランザクションやアクティブなテーブルロックの途中で試みるとエラーが発生します。セクション13.1.33「TRUNCATE TABLE 構文」を参照してください。

8.2.3 データベース権限の最適化

権限のセットアップが複雑であるほど、すべての SQL ステートメントに適用されるオーバーヘッドが大きくなります。GRANT ステートメントによって確立された権限を簡単にすることで、クライアントがステートメントを実行するときの MySQL の権限チェックのオーバーヘッドを軽減できます。たとえば、テーブルレベルやカラムレベルの権限を付与しない場合、サーバーは tables_privcolumns_priv テーブルの内容をチェックする必要はなくなります。同じように、どのアカウントにもリソース制限を設けない場合、サーバーはリソースのカウントを実行する必要がありません。ステートメント処理の負荷が著しく高い場合は、簡略化した付与構造を使用して、権限チェックのオーバーヘッドを軽減することを考慮してください。

8.2.4 INFORMATION_SCHEMA クエリーの最適化

データベースをモニターするアプリケーションでは、INFORMATION_SCHEMA テーブルを頻繁に使用することがあります。INFORMATION_SCHEMA テーブルに対する特定の種類のクエリーは、高速に実行するように最適化できます。この目標は、ファイル操作 (ディレクトリのスキャンやテーブルファイルを開くなど) を最小限にし、これらの動的テーブルを構成する情報を収集することです。これらの最適化は、INFORMATION_SCHEMA テーブルの検索にどのような照合順序が使われるかに影響します。詳細は、セクション10.1.7.9「照合順序と INFORMATION_SCHEMA 検索」を参照してください。

1) WHERE 句のデータベース名とテーブル名には定数のルックアップ値を使用してみます

この原則は次のように活用できます。

  • データベースやテーブルをルックアップするには、リテラル値、定数を返す関数、スカラーサブクエリーなど、定数に評価される式を使用します。

  • 一致するデータベースディレクトリ名を見つけるためにデータディレクトリのスキャンが必要になるため、非定数のデータベース名ルックアップ値を使用する (またはルックアップ値を使用しない) クエリーを避けます。

  • データベース内では、一致するテーブルファイルを見つけるためにデータベースディレクトリのスキャンが必要になるため、非定数のテーブル名ルックアップ値を使用する (またはルックアップ値を使用しない) クエリーを避けます。

この原則は、定数のルックアップ値によって、サーバーがディレクトリスキャンを回避できるカラムを示している次の表で示されている INFORMATION_SCHEMA テーブルに適用されます。たとえば、TABLES から選択する場合は、WHERE 句で TABLE_SCHEMA に定数のルックアップ値を使用すると、データディレクトリのスキャンを回避できます。

テーブルデータディレクトリスキャンを避けるために指定するカラムデータベースディレクトリスキャンを避けるために指定するカラム
COLUMNSTABLE_SCHEMATABLE_NAME
KEY_COLUMN_USAGETABLE_SCHEMATABLE_NAME
PARTITIONSTABLE_SCHEMATABLE_NAME
REFERENTIAL_CONSTRAINTSCONSTRAINT_SCHEMATABLE_NAME
STATISTICSTABLE_SCHEMATABLE_NAME
TABLESTABLE_SCHEMATABLE_NAME
TABLE_CONSTRAINTSTABLE_SCHEMATABLE_NAME
TRIGGERSEVENT_OBJECT_SCHEMAEVENT_OBJECT_TABLE
VIEWSTABLE_SCHEMATABLE_NAME

特定の定数のデータベース名に制限されたクエリーの利点は、指定したデータベースディレクトリのみをチェックするだけで済むことです。例:

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test';

リテラルのデータベース名 test を使用すると、データベースがいくつあるかに関係なく、サーバーは test データベースディレクトリだけをチェックできます。対照的に、次のクエリーでは、パターン 'test%' に一致するデータベース名を特定するために、データディレクトリのスキャンが必要であるため、効率が低下します。

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA LIKE 'test%';

特定の定数のテーブル名に制限されたクエリーの場合、対応するデータベースディレクトリ内の指定したテーブルのみをチェックするだけで済みます。例:

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1';

リテラルのテーブル名 t1 を使用すると、test データベースにテーブルがいくつあるかに関係なく、サーバーは t1 テーブルのファイルだけをチェックできます。対照的に、次のクエリーでは、パターン 't%' に一致するテーブル名を特定するために、test データベースディレクトリのスキャンが必要です。

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME LIKE 't%';

次のクエリーでは、パターン 'test%' に一致するデータベース名を特定するためにデータディレクトリをスキャンする必要があり、一致するデータベースごとに、パターン 't%' に一致するテーブル名を特定するためにデータベースディレクトリをスキャンする必要があります。

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test%' AND TABLE_NAME LIKE 't%';

2) 開く必要のあるテーブルファイルの数が最小になるクエリーを書きます

特定の INFORMATION_SCHEMA テーブルカラムを参照するクエリーでは、開く必要のあるテーブルファイルの数を最小にするいくつかの最適化を使用できます。例:

SELECT TABLE_NAME, ENGINE FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test';

この場合、サーバーがデータベースディレクトリをスキャンしてデータベース内のテーブルの名前を特定したら、さらにファイルシステムをルックアップしなくても、それらの名前を使用できるようになります。したがって、TABLE_NAME はファイルを開く必要はありません。ENGINE (ストレージエンジン) の値は、テーブルの .frm ファイルを開くことで特定でき、.MYD.MYI などのほかのテーブルファイルにアクセスすることはありません。

MyISAM テーブルの INDEX_LENGTH など、一部の値では .MYD または .MYI ファイルも開く必要があります。

ファイルオープンの最適化の種類は、次のように表されます。

  • SKIP_OPEN_TABLE: テーブルファイルを開く必要はありません。データベースディレクトリをスキャンすることによって、クエリー内ですでに情報を使用できるようになっています。

  • OPEN_FRM_ONLY: テーブルの .frm ファイルのみを開く必要があります。

  • OPEN_TRIGGER_ONLY: テーブルの .TRG ファイルのみを開く必要があります。

  • OPEN_FULL_TABLE: 最適化されていない情報のルックアップ。.frm.MYD、および .MYI ファイルを開く必要があります。

次のリストに、上記の最適化の種類がどのように INFORMATION_SCHEMA テーブルカラムに適用されるかを示します。指定されていないテーブルとカラムには、最適化が適用されません。

  • COLUMNS: OPEN_FRM_ONLY がすべてのカラムに適用されます

  • KEY_COLUMN_USAGE: OPEN_FULL_TABLE がすべてのカラムに適用されます

  • PARTITIONS: OPEN_FULL_TABLE がすべてのカラムに適用されます

  • REFERENTIAL_CONSTRAINTS: OPEN_FULL_TABLE がすべてのカラムに適用されます

  • STATISTICS:

    カラム最適化の種類
    TABLE_CATALOGOPEN_FRM_ONLY
    TABLE_SCHEMAOPEN_FRM_ONLY
    TABLE_NAMEOPEN_FRM_ONLY
    NON_UNIQUEOPEN_FRM_ONLY
    INDEX_SCHEMAOPEN_FRM_ONLY
    INDEX_NAMEOPEN_FRM_ONLY
    SEQ_IN_INDEXOPEN_FRM_ONLY
    COLUMN_NAMEOPEN_FRM_ONLY
    COLLATIONOPEN_FRM_ONLY
    CARDINALITYOPEN_FULL_TABLE
    SUB_PARTOPEN_FRM_ONLY
    PACKEDOPEN_FRM_ONLY
    NULLABLEOPEN_FRM_ONLY
    INDEX_TYPEOPEN_FULL_TABLE
    COMMENTOPEN_FRM_ONLY
  • TABLES:

    カラム最適化の種類
    TABLE_CATALOGSKIP_OPEN_TABLE
    TABLE_SCHEMASKIP_OPEN_TABLE
    TABLE_NAMESKIP_OPEN_TABLE
    TABLE_TYPEOPEN_FRM_ONLY
    ENGINEOPEN_FRM_ONLY
    VERSIONOPEN_FRM_ONLY
    ROW_FORMATOPEN_FULL_TABLE
    TABLE_ROWSOPEN_FULL_TABLE
    AVG_ROW_LENGTHOPEN_FULL_TABLE
    DATA_LENGTHOPEN_FULL_TABLE
    MAX_DATA_LENGTHOPEN_FULL_TABLE
    INDEX_LENGTHOPEN_FULL_TABLE
    DATA_FREEOPEN_FULL_TABLE
    AUTO_INCREMENTOPEN_FULL_TABLE
    CREATE_TIMEOPEN_FULL_TABLE
    UPDATE_TIMEOPEN_FULL_TABLE
    CHECK_TIMEOPEN_FULL_TABLE
    TABLE_COLLATIONOPEN_FRM_ONLY
    CHECKSUMOPEN_FULL_TABLE
    CREATE_OPTIONSOPEN_FRM_ONLY
    TABLE_COMMENTOPEN_FRM_ONLY
  • TABLE_CONSTRAINTS: OPEN_FULL_TABLE がすべてのカラムに適用されます

  • TRIGGERS: OPEN_TRIGGER_ONLY がすべてのカラムに適用されます

  • VIEWS:

    カラム最適化の種類
    TABLE_CATALOGOPEN_FRM_ONLY
    TABLE_SCHEMAOPEN_FRM_ONLY
    TABLE_NAMEOPEN_FRM_ONLY
    VIEW_DEFINITIONOPEN_FRM_ONLY
    CHECK_OPTIONOPEN_FRM_ONLY
    IS_UPDATABLEOPEN_FULL_TABLE
    DEFINEROPEN_FRM_ONLY
    SECURITY_TYPEOPEN_FRM_ONLY
    CHARACTER_SET_CLIENTOPEN_FRM_ONLY
    COLLATION_CONNECTIONOPEN_FRM_ONLY

3) EXPLAIN を使用して、サーバーがクエリーに INFORMATION_SCHEMA 最適化を使用できるかどうかを判断します

これは特に、複数のデータベースの情報を検索し、長時間かかり、パフォーマンスに影響を与える可能性のある INFORMATION_SCHEMA クエリーに適用されます。先述の最適化のうち、サーバーが INFORMATION_SCHEMA クエリーの評価に使用できるものがあれば、EXPLAIN の出力の Extra 値に示されます。次の例は、Extra 値に表示されることが予想される情報の種類を示しています。

mysql> EXPLAIN SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE -> TABLE_SCHEMA = 'test' AND TABLE_NAME = 'v1'\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: VIEWS type: ALL
possible_keys: NULL key: TABLE_SCHEMA,TABLE_NAME key_len: NULL ref: NULL rows: NULL Extra: Using where; Open_frm_only; Scanned 0 databases

定数のデータベースルックアップ値およびテーブルルックアップ値を使用すると、サーバーはディレクトリスキャンを回避できます。VIEWS.TABLE_NAME の参照では、.frm ファイルのみを開く必要があります。

mysql> EXPLAIN SELECT TABLE_NAME, ROW_FORMAT FROM INFORMATION_SCHEMA.TABLES\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: TABLES type: ALL
possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: NULL Extra: Open_full_table; Scanned all databases

ルックアップ値が指定されていない (WHERE 句がない) ため、サーバーはデータディレクトリと各データベースディレクトリをスキャンする必要があります。このようにして特定された各テーブルについて、テーブル名と行フォーマットが選択されます。TABLE_NAME では、さらにテーブルファイルを開く必要はありません (SKIP_OPEN_TABLE 最適化が適用されます)。ROW_FORMAT では、すべてのテーブルファイルを開く必要があります (OPEN_FULL_TABLE が適用されます)。EXPLAINOPEN_FULL_TABLE (SKIP_OPEN_TABLE より負荷が大きいため) をレポートします。

mysql> EXPLAIN SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES -> WHERE TABLE_SCHEMA = 'test'\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: TABLES type: ALL
possible_keys: NULL key: TABLE_SCHEMA key_len: NULL ref: NULL rows: NULL Extra: Using where; Open_frm_only; Scanned 1 database

テーブル名のルックアップ値が指定されていないため、サーバーは test データベースディレクトリをスキャンする必要があります。TABLE_NAME カラムと TABLE_TYPE カラムには、それぞれ SKIP_OPEN_TABLE 最適化と OPEN_FRM_ONLY 最適化が適用されます。EXPLAINOPEN_FRM_ONLY (これの方が負荷が大きいため) をレポートします。

mysql> EXPLAIN SELECT B.TABLE_NAME -> FROM INFORMATION_SCHEMA.TABLES AS A, INFORMATION_SCHEMA.COLUMNS AS B -> WHERE A.TABLE_SCHEMA = 'test' -> AND A.TABLE_NAME = 't1' -> AND B.TABLE_NAME = A.TABLE_NAME\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: A type: ALL
possible_keys: NULL key: TABLE_SCHEMA,TABLE_NAME key_len: NULL ref: NULL rows: NULL Extra: Using where; Skip_open_table; Scanned 0 databases
*************************** 2. row *************************** id: 1 select_type: SIMPLE table: B type: ALL
possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: NULL Extra: Using where; Open_frm_only; Scanned all databases; Using join buffer

最初の EXPLAIN 出力行の場合: 定数のデータベースルックアップ値およびテーブルルックアップ値により、サーバーは TABLES の値のディレクトリスキャンを回避できます。TABLES.TABLE_NAME の参照には、さらにテーブルファイルは必要ありません。

2 つめの EXPLAIN 出力行の場合 : COLUMNS テーブルのすべての値が OPEN_FRM_ONLY ルックアップであるため、COLUMNS.TABLE_NAME では、.frm ファイルを開く必要があります。

mysql> EXPLAIN SELECT * FROM INFORMATION_SCHEMA.COLLATIONS\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: COLLATIONS type: ALL
possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: NULL Extra:

この場合、COLLATIONS は最適化を使用できる INFORMATION_SCHEMA テーブルのいずれでもないため、最適化は適用されません。

8.2.5 その他の最適化のヒント

このセクションでは、クエリー処理速度を向上するためのさまざまな多くのヒントを示します。

  • 接続のオーバーヘッドを回避するには、データベースに対して永続的な接続を使用します。永続的な接続を使用できないため、データベースに対して多くの新しい接続を開始する場合、thread_cache_size 変数の値の変更が必要になることがあります。セクション8.11.2「サーバーパラメータのチューニング」を参照してください。

  • すべてのクエリーがテーブル内に作成したインデックスを実際に使用していることを常に確認します。MySQL では、EXPLAIN ステートメントでこれを実行できます。「セクション8.8.1「EXPLAIN によるクエリーの最適化」」を参照してください。

  • 頻繁に更新される MyISAM テーブルに対する複雑な SELECT クエリーを避け、リーダーとライターの競合のために発生するテーブルロックの問題を回避するようにしてください。

  • MyISAM は同時挿入をサポートしています。テーブルのデータファイルの途中に空きブロックがなければ、ほかのスレッドがテーブルから読み取るのと同時に新しい行をそれに INSERT できます。これを実行できることが重要な場合、行の削除を避けるようにテーブルを使用することを考慮してください。別の可能性は、テーブルの大量の行を削除したあとに OPTIMIZE TABLE を実行して、テーブルをデフラグすることです。この動作は concurrent_insert 変数の設定によって変更されます。行を削除したテーブルにも新しい行を強制的に追加 (したがって同時挿入を許可) できます。セクション8.10.3「同時挿入」を参照してください。

  • ARCHIVE テーブルで発生したデータ圧縮問題を修正するには、OPTIMIZE TABLE を使用できます。セクション15.5「ARCHIVE ストレージエンジン」を参照してください。

  • 通常 expr1expr2、... の順で行を取得する場合は、ALTER TABLE ... ORDER BY expr1, expr2, ... を使用します。テーブルを大幅に変更したあとにこのオプションを使用することで、パフォーマンスを向上できることがあります。

  • 場合によって、ほかのカラムの情報に基づいてハッシュされたカラムを導入することが役立つ場合があります。このカラムが短く、十分に一意で、インデックスが設定されている場合は、多数のカラムに広範なインデックスを使用するより大幅に高速化できる可能性があります。MySQL では、この追加カラムをきわめて簡単に使用できます。

    SELECT * FROM tbl_name WHERE hash_col=MD5(CONCAT(col1,col2)) AND col1='constant' AND col2='constant';
  • 頻繁に変更される MyISAM テーブルでは、すべての可変長カラム (VARCHARTEXT、および BLOB) を避けるようにします。テーブルに 1 つしか可変長カラムが含まれていない場合でも、テーブルは動的行フォーマットを使用します。第15章「代替ストレージエンジンを参照してください。

  • 一般に、行が大きくなるためだけに、1 つのテーブルを異なるテーブルに分割することは有益ではありません。行へのアクセスで、もっとも大きくパフォーマンスに打撃を与えるものは、行の先頭バイトを見つけるために必要なディスクシークです。データが見つかったあとは、ほとんどの最新のディスクで、大多数のアプリケーションに十分な速度で行全体を読み取ることができます。テーブルを分割することがかなりの違いをもたらす状況は、固定の行サイズに変更できる動的行フォーマットを使用している MyISAM テーブルの場合か、またはテーブルを著しく頻繁にスキャンする必要があるが、ほとんどのカラムには必要でない場合だけです。第15章「代替ストレージエンジンを参照してください。

  • 多数の行の情報に基づいたカウントなど、結果を頻繁に計算する必要がある場合、新しいテーブルを導入し、リアルタイムでカウンタを更新する方が望ましいことがあります。次のような形式の更新はきわめて高速です。

    UPDATE tbl_name SET count_col=count_col+1 WHERE key_col=constant;

    これは、テーブルレベルのロック (単一ライターと複数リーダー) しかない MyISAM のような MySQL ストレージエンジンを使用する場合に、きわめて重要です。また、この場合に行ロックマネージャーが実行する必要があることは少ないため、ほとんどのデータベースシステムでパフォーマンスが向上します。

  • 大きなログテーブルから統計を収集する必要がある場合は、ログテーブル全体をスキャンするのではなく、サマリーテーブルを使用します。サマリーの管理は、ライブで統計を計算しようとする場合よりはるかに高速になるはずです。状況が変わった (ビジネス上の決定に応じて) 場合、ログから新しいサマリーテーブルを再生成する方が、実行中のアプリケーションを変更するより早いです。

  • 可能であれば、統計レポートに必要なデータが、ライブデータから定期的に生成されるサマリーテーブルからのみ作成されるライブまたは統計として、レポートを分類します。

  • カラムにデフォルト値があることを利用します。挿入する値がデフォルト値と異なる場合にのみ、明示的に値を挿入します。これにより、MySQL が実行する必要がある解析が減り、挿入速度が向上します。

  • 状況によっては、データを BLOB カラムにパックし、格納すると便利です。この場合、情報をパックおよびアンパックするコードをアプリケーションに追加する必要がありますが、これにより、特定の段階で大量のアクセスを省略できます。これは、行とカラムのテーブル構造にうまく準拠していないデータがある場合に実用的です。

  • 通常、すべてのデータを非冗長に維持しようとしてください (データベース理論で第 3 正規形と呼ばれるものを順守します)。ただし、高速化を図るために、情報を複製したり、サマリーテーブルを作成したりすることが有利になる状況もあります。

  • ストアドルーチンや UDF (ユーザー定義関数) は特定のタスクでパフォーマンスの向上に適切な方法である場合があります。詳しくは、セクション20.2「ストアドルーチン (プロシージャーと関数) の使用」およびセクション24.3「MySQL への新しい関数の追加」を参照してください。

  • アプリケーションでクエリーや応答をキャッシュしてから、多くの挿入や更新をまとめて実行することによって、パフォーマンスを向上できます。データベースシステムで MySQL のようにテーブルロックをサポートしている場合、これはすべての更新後にインデックスキャッシュが 1 回だけフラッシュされるようにするために役立つはずです。同様の結果を得るために、MySQL のクエリーキャッシュを利用することもできます。セクション8.9.3「MySQL クエリーキャッシュ」を参照してください。

  • 1 つの SQL ステートメントで多数の行を格納するには、複数行の INSERT ステートメントを使用します。(これは比較的移植可能な技法です。)

  • 大量のデータをロードするには LOAD DATA INFILE を使用します。これは INSERT ステートメントを使用するより高速になります。

  • テーブルの各行を 1 つの一意の値で識別できるように、AUTO_INCREMENT カラムを使用します。

  • ときどき OPTIMIZE TABLE を使用して、動的形式の MyISAM テーブルによる断片化を回避します。セクション15.2.3「MyISAM テーブルのストレージフォーマット」を参照してください。

  • 可能であれば、MEMORY テーブルを使用して、高速化を図ります。セクション15.3「MEMORY ストレージエンジン」を参照してください。Web ブラウザで Cookie が有効にされていないユーザーに対して最後に表示されたバナーに関する情報など、頻繁にアクセスされる非クリティカルデータには MEMORY テーブルが役立ちます。ユーザーセッションも、揮発状態データを処理するために、多くの Web アプリケーション環境で使用できるもう 1 つの代替方法です。

  • Web サーバーでは、イメージとその他のバイナリアセットが通常、ファイルとして格納されているはずです。つまり、データベース内にはファイル自体ではなく、ファイルへの参照のみを格納します。ほとんどの Web サーバーは、データベースコンテンツよりファイルのキャッシュに優れているため、ファイルの使用は一般に高速です。

  • 対応するカラムに基づいた結合が速くなるように、異なるテーブル内の同一の情報を持つカラムは同一のデータ型を持つように宣言するべきです。

  • カラム名が簡単になるようにします。たとえば、customer というテーブルでは customer_name ではなく name のカラム名を使用します。名前をほかの SQL サーバーに移植できるようにするため、18 文字より短くすることを考慮します。

  • 実際に高速化が必要である場合、別の SQL サーバーがサポートするデータストレージの低レベルインタフェースを調べます。たとえば、MySQL MyISAM ストレージエンジンに直接アクセスすることによって、SQL インタフェースを使用する場合と比較して 2 倍から 5 倍の速度の向上が得られる可能性があります。これを実行可能にするには、データがアプリケーションと同じサーバー上にある必要があり、通常 1 プロセスのみからアクセスするようにしてください (外部ファイルロックは非常に遅いため)。これらの問題は、MySQL サーバーに低レベルの MyISAM コマンドを導入することで解消できます (これが、必要に応じてパフォーマンスを向上させる 1 つの簡単な方法になります)。データベースインタフェースを慎重に設計することで、この種類の最適化をきわめて簡単にサポートできるはずです。

  • 数値データを使用している場合、多くの場合にテキストファイルにアクセスするより、ライブ接続を使用して、データベースから情報にアクセスする方が高速です。データベース内の情報はテキストファイルよりコンパクトなフォーマットで格納される可能性が高いため、それへのアクセスにかかわるディスクアクセスが少なくなります。さらに、テキストファイルを解析して、行とカラムの境界を見つける必要がないため、アプリケーション内のコードも節約できます。

  • レプリケーションは、特定の操作でパフォーマンスの向上を実現できます。クライアントの取得をレプリケーションサーバー間で分散して、負荷を分割できます。バックアップを作成する間のマスターの速度低下を避けるため、スレーブサーバーを使用して、バックアップを作成できます。第17章「レプリケーションを参照してください。

  • DELAY_KEY_WRITE=1 テーブルオプションを使用して MyISAM テーブルを宣言すると、テーブルが閉じられるまで、ディスクにフラッシュされないため、インデックスの更新が速くなります。短所は、そのようなテーブルが開いている間に、何かによってサーバーが強制終了させられた場合に、--myisam-recover-options オプションを使用してサーバーを実行するか、サーバーを再起動する前に myisamchk を実行して、テーブルが問題ないことを確認する必要があることです。(ただし、この場合でも、キー情報は常にデータ行から生成できるため、DELAY_KEY_WRITE を使用しても何も失われないはずです。)

  • SELECT ステートメントの優先度を挿入より高くしたい場合、サポートされる非トランザクションテーブルに、INSERT LOW_PRIORITY を使用します。

  • キューに割り込んで先に取得されるようにするには、サポートされる非トランザクションテーブルに SELECT HIGH_PRIORITY を使用します。つまり、書き込みの実行を待機している別のクライアントがある場合でも、SELECT が実行されます。

    LOW_PRIORITYHIGH_PRIORITY はテーブルレベルのロックのみを使用する非トランザクションストレージエンジンにのみ効果があります。

8.3 最適化とインデックス

SELECT 操作のパフォーマンスを向上する最善の方法は、クエリーでテストされる 1 つ以上のカラムにインデックスを作成することです。インデックスエントリは、テーブル行へのポインタのように動作し、クエリーが WHERE 句の条件に一致する行を迅速に特定し、それらの行のほかのカラム値を取得できます。すべての MySQL データ型にインデックスを設定できます。

クエリーで使用されている可能なすべてのカラムにインデックスを作成しようとしがちですが、不要なインデックスは領域を無駄にし、MySQL が使用するインデックスを判断するための時間を無駄にします。各インデックスを更新する必要があるため、インデックスは挿入、更新、削除のコストも追加します。最適なインデックスのセットを使用して、高速のクエリーを実現するために、適切なバランスを見つける必要があります。

8.3.1 MySQL のインデックスの使用の仕組み

インデックスは特定のカラム値のある行をすばやく見つけるために使用されます。インデックスがないと、MySQL は関連する行を見つけるために、先頭行から始めてテーブル全体を読み取る必要があります。テーブルが大きいほど、このコストが大きくなります。テーブルに問題のカラムのインデックスが含まれている場合、MySQL はすべてのデータを調べる必要なく、データファイルの途中のシークする位置をすばやく特定できます。これはすべての行を順次読み取るよりはるかに高速です。

ほとんどの MySQL インデックス (PRIMARY KEYUNIQUEINDEX、および FULLTEXT) は B ツリーに格納されます。例外: 空間データ型のインデックスは R ツリーを使用します。MEMORY テーブルはハッシュインデックスもサポートします。InnoDBFULLTEXT インデックスの逆のリストを使用します。

一般に、インデックスは次の説明に示すように使われます。ハッシュインデックス (MEMORY テーブルで使用されているような) に固有の特性については、セクション8.3.8「B ツリーインデックスとハッシュインデックスの比較」で説明しています。

MySQL はこれらの操作にインデックスを使用します。

  • WHERE 句に一致する行をすばやく見つけるため。

  • 行を考慮に入れないようにするため。複数のインデックスから選択する場合、MySQL は通常最小数の行を見つけるインデックス (もっとも選択的なインデックス) を使用します。

  • テーブルにマルチカラムインデックスがある場合、オプティマイザは、インデックスの左端のプリフィクスを使用して行をルックアップできます。たとえば、(col1, col2, col3) に 3 カラムのインデックスがある場合、(col1)(col1, col2)、および (col1, col2, col3) に対して、インデックス検索機能を使用できます。詳細については、セクション8.3.5「マルチカラムインデックス」を参照してください。

  • 結合の実行時に、ほかのテーブルから行を取得するため。カラムが同じ型とサイズで宣言されていると、MySQL はカラムのインデックスをより効率的に使用できます。このコンテキストでは、VARCHARCHAR は同じサイズで宣言されていれば同じとみなされます。たとえば、VARCHAR(10)CHAR(10) は同じサイズですが、VARCHAR(10)CHAR(15) は異なります。

    非バイナリ文字列カラム間での比較の場合、両方のカラムで同じ文字セットを使用しているべきです。たとえば、utf8 カラムと latin1 カラムの比較はインデックスの使用の可能性を否定します。

    異種のカラムの比較 (文字列カラムを時間または数値カラムと比較するなど) では、値を変換せずに直接比較できない場合、インデックスの使用が妨げられることがあります。数値カラム内の 1 などの特定の値の場合、'1'' 1''00001'、または '01.e1' などの文字列カラム内の任意の数の値と等しくなる可能性があります。これは、文字列カラムのインデックスの使用を除外します。

  • 特定のインデックス設定されたカラム key_col に対して、MIN() あるいは MAX() 値を見つけるため。これはインデックス内の key_col より前に発生するすべてのキーパートで、WHERE key_part_N = constant が使用されているかどうかをチェックするプリプロセッサによって最適化されます。この場合、MySQL は各 MIN() または MAX() 式に対して単一キールックアップを行い、それを定数で置き換えます。すべての式が定数で置き換えられた場合、クエリーは同時に返されます。例:

    SELECT MIN(key_part2),MAX(key_part2) FROM tbl_name WHERE key_part1=10;
  • 使用可能なインデックスの左端のプリフィクスに対してソートまたはグループ化が行われている場合 (たとえば、ORDER BY key_part1, key_part2) に、テーブルをソートまたはグループ化するため。すべてのキーパートのあとに DESC が付けられている場合、キーは逆の順序で読み取られます。セクション8.2.1.15「ORDER BY の最適化」およびセクション8.2.1.16「GROUP BY の最適化」を参照してください。

  • 場合によって、データ行を参照しないで値を取得するように、クエリーを最適化できます。(クエリーの必要なすべての結果を提供するインデックスは、カバーするインデックスと呼ばれます。)クエリーがテーブルから特定のインデックスに含まれるカラムのみを使用している場合、きわめて高速に、選択した値をインデックスツリーから取得できます。

    SELECT key_part3 FROM tbl_name WHERE key_part1=1

小さなテーブルまたは、レポートクエリーが行の大半またはすべてを処理する大きなテーブルに対するクエリーでは、インデックスはあまり重要ではありません。クエリーで行の大半にアクセスする必要がある場合は、順次読み取る方が、インデックスを処理するより高速です。クエリーですべての行が必要でない場合でも、順次読み取りは、ディスクシークを最小にします。詳細は、セクション8.2.1.20「フルテーブルスキャンを回避する方法」を参照してください。

8.3.2 主キーの使用

テーブルの主キーは、もっとも重要なクエリーで使用するカラムやカラムのセットを表します。それには、高速のクエリーパフォーマンスのため、インデックスが関連付けられます。それには NULL 値を含めることができないため、クエリーパフォーマンスは NOT NULL 最適化からメリットが得られます。InnoDB ストレージエンジンによって、テーブルデータが、主キーカラムに基づいて、超高速ルックアップおよびソートを実行するように物理的に編成されます。

テーブルが大きく、重要でも、主キーとして使用する明確なカラムやカラムのセットがない場合は、自動インクリメント値で個別のカラムを作成して、主キーとして使用できます。これらの一意の ID は、外部キーを使用してテーブルを結合する場合に、ほかのテーブル内の対応する行へのポインタとして使用できます。

8.3.3 外部キーの使用

テーブルに多くのカラムがあり、多くのさまざまなカラムの組み合わせをクエリーする場合、あまり頻繁に使用されないデータをそれぞれ少数のカラムを持つ個別のテーブルに分割し、それらを、メインテーブルの数値 ID カラムを複製してメインテーブルに関連付けると、効率的なことがあります。そのようにして、小さな各テーブルに、そのデータの高速ルックアップのための主キーを設定でき、結合操作を使用して必要とするカラムのセットだけをクエリーできます。データの分散状況に応じて、関連カラムがディスク上にまとめてパックされるため、クエリーで実行する I/O が少なくなり、使用するキャッシュメモリーが減る可能性があります。(パフォーマンスを最大にするため、クエリーはディスクから可能なかぎり少ないデータブロックを読み取ろうとします。数個のカラムしかないテーブルでは各データブロックにより多くのデータを収めることができます。)

8.3.4 カラムインデックス

もっとも一般的なインデックスの種類には、単一カラムがあり、データ構造にそのカラムの値のコピーを格納し、対応するカラム値のある行を高速にルックアップできます。B ツリーデータ構造により、インデックスは、WHERE 句内の =>BETWEENIN などの演算子に対応する特定の値、値のセット、または値の範囲をすばやく見つけることができます。

テーブルあたりの最大インデックス数とインデックスの最大長は、ストレージエンジンごとに定義されます。第15章「代替ストレージエンジンを参照してください。すべてのストレージエンジンは、1 テーブルあたり 16 個以上のインデックスと 256 バイト以上の合計インデックス長をサポートします。ほとんどのストレージエンジンでは、制限が高く設定されています。

プリフィクスインデックス

インデックス指定でcol_name(N) 構文を使用して、文字列カラムの先頭の N 文字のみを使用するインデックスを作成できます。このようにカラム値のプリフィクスのみのインデックスを作成すると、インデックスファイルをかなり小さくできます。BLOB または TEXT カラムにインデックス設定する場合、インデックスのプリフィクス長を指定する必要があります。例:

CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10)));

プリフィクスは最大 1000 バイト長 (innodb_large_prefix を設定していないかぎり、InnoDB テーブルの場合は 767 バイト) です。

注記

プリフィクスの制限はバイト単位で測定されますが、CREATE TABLE ステートメントでのプリフィクス長は文字数で解釈されます。複数バイト文字セットを使用するカラムのプリフィクス長を指定する場合はこれを考慮してください

FULLTEXT インデックス

FULLTEXT インデックスの作成も可能です。これらは全文検索に使用されます。InnoDB および MyISAM ストレージエンジンのみが、CHARVARCHAR、および TEXT カラムに対してのみ、FULLTEXT インデックスをサポートしています。インデックス設定は常にカラム全体に対して行われ、カラムプリフィクスインデックス設定はサポートされていません。詳細については、セクション12.9「全文検索関数」を参照してください。

最適化は、単一の InnoDB テーブルに対する特定の種類の FULLTEXT クエリーに適用されます。これらの特性を持つクエリーは特に効率的です。

  • ドキュメント ID またはドキュメント ID と検索ランクのみを返す FULLTEXT クエリー。

  • 一致する行をスコアの降順でソートし、LIMIT 句を適用して、上位 N 個の一致する行を取得する FULLTEXT クエリー。この最適化を適用するには、WHERE 句がなく、降順の単一の ORDER BY 句のみがある必要があります。

  • 検索語に一致する行の COUNT(*) 値のみを取得し、追加の WHERE 句がない FULLTEXT クエリー。WHERE 句を > 0 比較演算子を使用せずに、WHERE MATCH(text) AGAINST ('other_text') とコーディングします。

空間インデックス

空間データ型にインデックスを作成することもできます。現在、MyISAM のみが空間型への R ツリーインデックスをサポートしています。ほかのストレージエンジンは、空間型のインデックス設定に B ツリーを使用します (ARCHIVE を除きます。これは空間型のインデックス設定をサポートしていません)。

MEMORY ストレージエンジンでのインデックス

MEMORY ストレージエンジンはデフォルトで HASH インデックスを使用しますが、BTREE インデックスもサポートしています。

8.3.5 マルチカラムインデックス

MySQL は複合インデックス (つまり、複数のカラムに対するインデックス) を作成できます。インデックスは最大 16 カラムで構成できます。特定のデータ型では、カラムのプリフィクスにインデックスを設定できます (セクション8.3.4「カラムインデックス」を参照してください)。

MySQL では、インデックスで、すべてのカラムをテストするクエリーまたは最初のカラム、最初の 2 つのカラム、最初の 3 つのカラムというようにテストするクエリーにマルチカラムインデックスを使用できます。インデックス定義の正しい順序でカラムを指定する場合、単一の複合インデックスにより、同じテーブルへの複数の種類のクエリーを高速化できます。

マルチカラムインデックスは、インデックス設定されたカラムの値を連結して作成された値を格納する行である、ソート済みの配列とみなすことができます。

注記

複合インデックスの代わりに、ほかのカラムの情報に基づいてハッシュされたカラムを導入できます。このカラムが短く、十分に一意で、インデックスが設定されている場合は、多数のカラムへの広範なインデックスより速くなる可能性があります。MySQL では、この追加カラムをきわめて簡単に使用できます。

SELECT * FROM tbl_name WHERE hash_col=MD5(CONCAT(val1,val2)) AND col1=val1 AND col2=val2;

テーブルが次のような仕様であるとします。

CREATE TABLE test ( id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name,first_name)
);

name インデックスは、last_name カラムと first_name カラムに対するインデックスです。このインデックスは、last_name 値と first_name 値の組み合わせに既知の範囲の値を指定するクエリーで、ルックアップに使用できます。そのカラムはインデックスの左端のプリフィクスであるため、last_name 値だけを指定するクエリーにも使用できます (このセクションで後述するように)。そのため、name インデックスは、次のクエリーでのルックアップに使用されます。

SELECT * FROM test WHERE last_name='Widenius';
SELECT * FROM test WHERE last_name='Widenius' AND first_name='Michael';
SELECT * FROM test WHERE last_name='Widenius' AND (first_name='Michael' OR first_name='Monty');
SELECT * FROM test WHERE last_name='Widenius' AND first_name >='M' AND first_name < 'N';

ただし、name インデックスは次のクエリーでのルックアップには使用されません

SELECT * FROM test WHERE first_name='Michael';
SELECT * FROM test WHERE last_name='Widenius' OR first_name='Michael';

次の SELECT ステートメントを発行するとします。

SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;

col1col2 に対するマルチカラムインデックスが存在する場合、該当する行を直接フェッチできます。col1 および col2 に対して個別の単一カラムのインデックスが存在する場合、オプティマイザは、インデックスマージ最適化 (セクション8.2.1.4「インデックスマージの最適化」を参照してください) の使用を試みるか、またはより多くの行を除外するインデックスを判断して、そのインデックスを使用して行をフェッチすることで、もっとも制限の厳しいインデックスを見つけようとします。

テーブルにマルチカラムインデックスがある場合、オプティマイザは、インデックスの左端のプリフィクスを使用して行をルックアップできます。たとえば、(col1, col2, col3) に 3 カラムのインデックスがある場合、(col1)(col1, col2)、および (col1, col2, col3) に対して、インデックス検索機能を使用できます。

カラムがインデックスの左端のプリフィクスを形成していない場合、MySQL はこのインデックスを使用してルックアップを実行できません。ここに示す SELECT ステートメントがあるとします。

SELECT * FROM tbl_name WHERE col1=val1;
SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;
SELECT * FROM tbl_name WHERE col2=val2;
SELECT * FROM tbl_name WHERE col2=val2 AND col3=val3;

(col1, col2, col3) にインデックスが存在する場合、最初の 2 つのクエリーだけがインデックスを使用します。3 つめと 4 つめのクエリーには、インデックス設定されたカラムがかかわりますが、(col2)(col2, col3)(col1, col2, col3) の左端のプリフィクスではありません。

8.3.6 インデックスの使用の確認

すべてのクエリーがテーブル内に作成したインデックスを実際に使用していることを常に確認します。セクション8.8.1「EXPLAIN によるクエリーの最適化」に説明するように、EXPLAIN ステートメントを使用します。

8.3.7 InnoDB および MyISAM インデックス統計コレクション

ストレージエンジンはオプティマイザによって使用されるテーブルに関する統計を収集します。テーブル統計は値グループに基づきますが、ここで値グループは同じキープリフィクス値を持つ行のセットです。オプティマイザの目的で、重要な統計は平均値グループサイズです。

MySQL は平均値グループサイズを次のように使用します。

  • ref アクセスごとに読み取る必要がある行数を見積もるため

  • 部分結合で生成される行数、つまりこの形式の操作で生成される行数を見積もるため:

    (...) JOIN tbl_name ON tbl_name.key = expr

インデックスの平均値グループサイズが増えるほど、ルックアップあたりの平均行数が増えるため、それらの 2 つの目的でインデックスが役立たなくなります。インデックスが最適化の目的に役立つようにするには、各インデックス値でターゲットとするテーブル内の行を少なくすることがもっとも適切です。指定したインデックス値が多数の行を生成する場合、そのインデックスはあまり役に立たず、MySQL がそれを使用する可能性は少なくなります。

平均値グループサイズは、値グループの数であるテーブルカーディナリティーと関連しています。SHOW INDEX ステートメントは、N/S に基づいて、カーディナリティー値を表示します。ここで N はテーブル内の行数で、S は平均値グループサイズです。その比率から、テーブル内の値グループの概数がわかります。

<=> 比較演算子に基づいた結合では、NULL の扱いはほかの値と異なりません。ほかのどの N に対しても N <=> N とまったく同じように、NULL <=> NULL です。

ただし、= 演算子に基づく結合では、NULLNULL 以外の値と異なります。expr1 または expr2 (または両方) が NULL である場合、expr1 = expr2 は true になりません。これは、形式 tbl_name.key = expr の比較の ref アクセスに影響を与えます。expr の現在値が NULL の場合、比較は true にならないため、MySQL はテーブルにアクセスしません。

= 比較では、テーブルにある NULL 値の数は問題になりません。最適化の目的で、関連のある値は NULL 以外の値グループの平均サイズです。ただし、MySQL では現在その平均サイズを収集したり、使用したりできません。

InnoDB および MyISAM テーブルでは、innodb_stats_method および myisam_stats_method システム変数をそれぞれ使用して、テーブル統計のコレクションに対していくらかの制御ができます。これらの変数には、3 つの可能性のある値を使用でき、次のように異なります。

  • 変数が nulls_equal に設定されている場合、すべての NULL 値が同一として扱われます (つまり、それらすべてが単一の値グループを形成します)。

    NULL 値グループサイズが、NULL 以外の値グループサイズよりはるかに大きい場合、このメソッドは平均値グループサイズを上方に歪めます。これにより、オプティマイザには、NULL 以外の値を検索する結合に対して、インデックスが実際以上に役に立たないかのように見えます。結果として、nulls_equal メソッドにより、オプティマイザに ref アクセスに対してインデックスを使用すべきときでも使用させないようにすることがあります。

  • 変数が nulls_unequal に設定されている場合、NULL 値は同じとみなされません。代わりに、各 NULL 値はサイズ 1 の個別の値グループを形成します。

    多くの NULL 値がある場合、このメソッドは平均値グループサイズを下方に歪めます。NULL 以外の平均値グループサイズが大きい場合、NULL 値をサイズ 1 のグループとしてカウントすると、オプティマイザは NULL 以外の値を検索する結合に対して、インデックスの値を多く見積もりすぎます。結果として、nulls_unequal メソッドによって、ほかのメソッドの方が適している可能性がある場合に、オプティマイザに ref ルックアップに対してこのインデックスを使用させることがあります。

  • 変数が nulls_ignored に設定されている場合、NULL 値は無視されます。

= より <=> を使用する多くの結合を使用する傾向がある場合、比較で NULL 値は特別ではなく、NULL は互いに等しくなります。この場合、nulls_equal は適切な統計メソッドです。

innodb_stats_method システム変数にはグローバル値があります。myisam_stats_method システム変数にはグローバル値とセッション値の両方があります。グローバル値を設定すると、対応するストレージエンジンからのテーブルの統計収集に影響します。セッション値を設定すると、現在のクライアント接続のみに対する統計収集に影響します。これは、myisam_stats_method のセッション値を設定することで、ほかのクライアントに影響を与えずに、指定したメソッドで、テーブルの統計を強制的に再生成させることができることを意味します。

テーブル統計を再生成するには、次のいずれかのメソッドを使用できます。

  • myisamchk --stats_method=method_name --analyze を実行します

  • テーブルを変更して、統計を古くさせ (たとえば、行を挿入してから削除します)、次に myisam_stats_method を設定して、ANALYZE TABLE ステートメントを発行します。

innodb_stats_methodmyisam_stats_method の使用に関するいくつかの警告は次のとおりです。

  • 先述したように、テーブル統計を明示的に収集させることができます。ただし、MySQL は統計を自動的に収集することもあります。たとえば、テーブルへのステートメントの実行の途中で、そうしたステートメントの中にはテーブルを変更するものもあり、MySQL は統計を収集する場合があります。(たとえば、これは一括挿入や削除、または一部の ALTER TABLE ステートメントで行われることがあります。)これが行われた場合、その時点での innodb_stats_method または myisam_stats_method の値を使用して、統計が収集されます。そのため、あるメソッドを使用して統計を収集しても、あとでテーブルの統計が自動的に収集されたときに、システム変数にほかのメソッドが設定されていると、そのほかのメソッドが使われます。

  • 特定のテーブルの統計の生成に使用されたメソッドを伝える方法はありません。

  • これらの変数は InnoDB および MyISAM テーブルにのみ適用されます。ほかのストレージエンジンはテーブル統計を収集するメソッドが 1 つしかありません。通常、それは nulls_equal メソッドに近いものになります。

8.3.8 B ツリーインデックスとハッシュインデックスの比較

B ツリーおよびハッシュデータ構造を理解することは、インデックスにこれらのデータ構造を使用するさまざまなストレージエンジンで (特に B ツリーインデックスを使用するか、ハッシュインデックスを使用するかを選択できる MEMORY ストレージエンジンの場合に)、さまざまなクエリーがどのように実行されるかを予測するのに役立つ可能性があります。

B ツリーインデックスの特性

B ツリーインデックスは =>>=<<=、または BETWEEN 演算子を使用する式で、カラム比較に使用できます。このインデックスは、LIKE への引数がワイルドカード文字で始まらない定数文字列の場合の LIKE 比較にも使用できます。たとえば、次の SELECT ステートメントはインデックスを使用します。

SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%';

最初のステートメントでは、'Patrick' <= key_col < 'Patricl' の行のみが考慮されます。2 つめのステートメントでは、'Pat' <= key_col < 'Pau' の行のみが考慮されます。

次の SELECT ステートメントはインデックスを使用しません。

SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE other_col;

最初のステートメントでは、LIKE 値はワイルドカード文字で始まります。2 つめのステートメントでは、LIKE 値は定数ではありません。

... LIKE '%string%' を使用し、string が 3 文字より長い場合、MySQL は Turbo Boyer-Moore アルゴリズムを使用して、文字列のパターンを初期化してから、このパターンを使用して検索をより迅速に実行します。

col_name IS NULL を使用した検索では、col_name にインデックスが設定されている場合にインデックスが使用されます。

WHERE 句内のすべての AND レベルにまたがっていないインデックスは、クエリーの最適化に使用されません。言い換えると、インデックスの使用を可能にするには、インデックスのプリフィクスがすべての AND グループで使用されている必要があります。

次の WHERE 句ではインデックスが使用されます。

... WHERE index_part1=1 AND index_part2=2 AND other_column=3
... WHERE index=1 OR A=10 AND index=2
... WHERE index_part1='hello' AND index_part3=5
... WHERE index1=1 AND index2=2 OR index1=3 AND index3=3;

これらの WHERE 句ではインデックスが使用されません

... WHERE index_part2=1 AND index_part3=2
... WHERE index=1 OR A=10
... WHERE index_part1=1 OR index_part2=10

MySQL ではインデックスが使用できる場合でも使用しないことがあります。これが発生する 1 つの状況は、オプティマイザが、インデックスを使用することによって MySQL がテーブルの大部分の行にアクセスする必要があると推定した場合です。(この場合、必要なシークが少ないため、テーブルスキャンの方がはるかに高速になる可能性があります。) ただし、そのようなクエリーで、行の一部のみを取得する LIMIT を使用している場合、結果で返す少数の行をはるかにすばやく見つけることができるため、MySQL はとにかくインデックスを使用します。

ハッシュインデックスの特性

ハッシュインデックスは先述したものといくらか異なる特性を持ちます。

  • それらは、= または <=> 演算子を使用する等価比較にのみ使用されます (ただしきわめて高速です)。それらは、値の範囲を見つける < などの比較演算子には使用されません。この種類の単一値ルックアップに依存するシステムは、キー値ストアとして知られています。そのようなアプリケーションで MySQL を使用するには、可能なかぎりハッシュインデックスを使用します。

  • オプティマイザはハッシュインデックスを使用して、ORDER BY 操作を高速化することはできません。(この種類のインデックスは順番に次のエントリを検索するために使用できません。)

  • MySQL は 2 つの値の間におよそどのくらいの行数があるかを判断できません (これは範囲オプティマイザによって使用するインデックスを特定するために使用されます)。これは、MyISAM または InnoDB テーブルをハッシュインデックス設定された MEMORY テーブルに変更した場合に、一部のクエリーに影響することがあります。

  • 行の検索にはキー全体のみを使用できます。(B ツリーインデックスでは、キーの任意の左端のプリフィクスを使用して行を検索できます。)

8.4 データベース構造の最適化

データベース設計者としての役割では、スキーマ、テーブル、およびカラムを編成するもっとも効率的な方法を探します。アプリケーションコードをチューニングする場合、I/O を最小にし、関連項目をまとめて、データボリュームが増加してもパフォーマンスを高く維持するように、事前に計画します。効率的なデータベース設計から始めることで、チームメンバーは高性能のアプリケーションコードを簡単に書けるようになり、アプリケーションが発展して、書き換えられても、データベースを持ちこたえさせる可能性が高くなります。

8.4.1 データサイズの最適化

ディスク上の領域を最小にするようにテーブルを設計します。これにより、ディスクに対して読み取りおよび書き込みされるデータの量が減ることで、大幅な改善が見られます。内容がクエリー実行中にアクティブに処理される間、テーブルが小さいほど、通常必要なメインメモリーの量は少なくなります。テーブルデータの領域の削減により、インデックスも小さくなり、高速に処理できます。

MySQL は多数のさまざまなストレージエンジン (テーブル型) と行フォーマットをサポートしています。テーブルごとに、使用するストレージとインデックス設定方法を決定できます。アプリケーションに適切なテーブル形式を選択することで、大幅なパフォーマンスの向上が得られることがあります。第15章「代替ストレージエンジンを参照してください。

ここで挙げられた技法を使用して、テーブルのパフォーマンス改善とストレージ領域の最小化を図ることができます。

テーブルカラム

  • 可能なかぎりもっとも効率的 (最小) のデータ型を使用します。MySQL にはディスク領域とメモリーを節約する多くの専用の型があります。たとえば、可能な場合は、小さなテーブルを取得するために、小さな整数型を使用します。MEDIUMINT カラムが使用する領域は 25% 少ないため、MEDIUMINT は多くの場合に INT より適切な選択肢です。

  • 可能な場合は、カラムを NOT NULL として宣言します。それにより、インデックスを適切に使用し、各値が NULL であるかどうかをテストするためのオーバーヘッドがなくなることで、SQL の操作が速くなります。カラムあたり 1 ビットでいくらかのストレージ領域も節約します。テーブルで実際に NULL 値が必要な場合、それらを使用します。単にすべてのカラムで NULL 値を許可するデフォルトの設定を避けます。

行フォーマット

  • InnoDB テーブルはコンパクトストレージフォーマットを使用します。5.0.3 より前の MySQL のバージョンでは、InnoDB の行に、固定サイズのカラムでも、カラム数や各カラムの長さなど、いくつかの冗長な情報が含まれます。デフォルトで、テーブルはコンパクト形式 (ROW_FORMAT=COMPACT) で作成されます。MySQL の古いバージョンにダウングレードしたい場合、ROW_FORMAT=REDUNDANT で古い形式を要求できます。

    コンパクト行フォーマットの存在により、行のストレージ領域が約 20% 減少しますが、一部の操作で CPU の使用が増加する犠牲を伴います。ワークロードが、キャッシュヒット率とディスク速度によって制限される通常のワークロードであれば、速くなる可能性があります。CPU 速度によって制限されるまれな例では、遅くなることがあります。

    コンパクト InnoDB 形式では UTF-8 データを含む CHAR カラムが格納される方法も変わります。UTF-8 エンコード文字の最大長が 3 バイトであるとして、ROW_FORMAT=REDUNDANT では、UTF-8 CHAR(N) は 3 × N バイトを占有します。多くの言語は主にシングルバイト UTF-8 文字を使用して書くことができるため、固定のストレージ長は多くの場合に領域を無駄にします。ROW_FORMAT=COMPACT 形式では、InnoDBN から 3 × N バイトの範囲のストレージの可変容量を、必要に応じて末尾のスペースを取り除いて、これらのカラムに割り当てます。最小のストレージ長は、一般的な場合にインプレース更新を容易にする N バイトとして保持されます。

  • テーブルデータを圧縮形式で保存することで、さらに領域を最小にするには、InnoDB テーブルを作成する際に ROW_FORMAT=COMPRESSED を指定するか、既存の MyISAM テーブルに対して、myisampack コマンドを実行します。(InnoDB 圧縮テーブルは読み取り可能で書き込み可能ですが、MyISAM 圧縮テーブルは読み取り専用です。)

  • MyISAM テーブルで、可変長カラム (VARCHARTEXT、あるいは BLOB など) がない場合は、固定サイズ行フォーマットが使用されます。これは高速ですが、いくらか領域を無駄にすることがあります。セクション15.2.3「MyISAM テーブルのストレージフォーマット」を参照してください。CREATE TABLE オプション ROW_FORMAT=FIXED によって、VARCHAR カラムがある場合でも、固定長の行を必要としていることを伝えることができます。

インデックス

  • テーブルのプライマリインデックスは可能なかぎり短くしてください。これにより、各行の識別が容易になり効率的になります。InnoDB テーブルの場合、主キーカラムは、各セカンダリインデックスエントリに複製されるため、多数のセカンダリインデックスがある場合に、短い主キーによって、かなりの領域が節約されます。

  • クエリーパフォーマンスを向上するために必要なインデックスのみを作成します。インデックスは取得には有効ですが、挿入および更新操作を遅くします。ほとんどカラムの組み合わせに対して検索することによって、テーブルにアクセスする場合、カラムごとに個別のインデックスを作成するのではなく、それらに対して単一の複合インデックスを作成します。インデックスの最初の部分は、もっとも使用されるカラムにするべきです。テーブルから選択する場合に、常に多くのカラムを使用する場合、適切なインデックスの圧縮を取得するため、インデックスの最初のカラムは、もっとも重複の多いカラムにするべきです。

  • 長い文字列カラムで、最初の数文字に一意のプリフィクスがある可能性が高い場合、MySQL のカラムの左端の部分へのインデックスの作成のサポート (セクション13.1.13「CREATE INDEX 構文」を参照してください) を使用して、このプリフィクスのみにインデックスを設定することをお勧めします。短いインデックスほど速くなるのは、必要なディスク領域が少ないだけでなく、インデックスキャッシュでのヒットが多くなり、そのためにディスクシークが少なくなるためでもあります。セクション8.11.2「サーバーパラメータのチューニング」を参照してください。

結合

  • 状況によって、頻繁にスキャンされるテーブルを 2 つに分割することで、メリットがある場合があります。これは特に、それが動的形式テーブルで、テーブルのスキャン時に、関連行を見つけるために使用できる小さな静的形式テーブルを使用できる場合に当てはまります。

  • 対応するカラムに基づいた結合を高速化するには、異なるテーブル内の同一の情報を持つカラムを同一のデータ型で宣言します。

  • 異なるテーブルで同じ名前を使用し、結合クエリーを簡略化できるように、カラム名を簡単にします。たとえば、customer というテーブルでは customer_name ではなく name のカラム名を使用します。名前をほかの SQL サーバーに移植できるようにするため、18 文字より短くすることを考慮します。

正規化

  • 通常、すべてのデータを非冗長に維持しようとしてください (データベース理論で第 3 正規形と呼ばれるものを順守します)。名前や住所などの長い値を繰り返す代わりに、それらに一意の ID を割り当て、複数の小さなテーブルで必要なだけこれらの ID を繰り返し、結合句で ID を参照して、クエリーでテーブルを結合します。

  • たとえば、大きなテーブルからすべてのデータを解析するビジネスインテリジェンスシナリオなどで、ディスク領域やデータの複数のコピーを維持する保守コストより、速度の方が重要である場合、正規化ルールを緩和して、情報を複製したり、サマリーテーブルを作成したりして、速度を向上させることができます。

8.4.2 MySQL データ型の最適化

8.4.2.1 数値データの最適化

  • 文字列または数値として表すことができる一意の ID やその他の値の場合、文字列カラムより数値カラムをお勧めします。大きな数値は、対応する文字列より少ないバイト数で格納できるため、それらの転送と比較が高速になり、使用するメモリーが少なくなります。

  • 数値データを使用している場合、多くの場合にテキストファイルにアクセスするより、ライブ接続を使用して、データベースから情報にアクセスする方が高速です。データベース内の情報はテキストファイルよりコンパクトなフォーマットで格納される可能性が高いため、それへのアクセスにかかわるディスクアクセスが少なくなります。また、テキストファイルを解析して、行とカラムの境界を見つけることを回避できるため、アプリケーションのコードも節約できます。

8.4.2.2 文字および文字列型の最適化

文字および文字列カラムの場合、次のガイドラインに従います。

  • 言語固有の照合機能が必要でない場合は、比較およびソート操作を速くするため、バイナリ照合順序を使用します。特定のクエリー内でバイナリ照合順序を使用するには、BINARY 演算子を使用できます。

  • さまざまなカラムの値を比較する場合、可能なかぎり、それらのカラムを同じ文字セットと照合順序で宣言し、クエリー実行中の文字列変換を避けます。

  • サイズが 8K バイト未満のカラム値では、BLOB の代わりにバイナリ VARCHAR を使用します。GROUP BY および ORDER BY 句は一時テーブルを生成する可能性があり、これらの一時テーブルでは、元のテーブルに BLOB カラムが含まれない場合に、MEMORY ストレージエンジンを使用することがあります。

  • テーブルに名前や住所などの文字列カラムが含まれるが、多くのクエリーでそれらのカラムを取得しない場合、文字列カラムを個別のテーブルに分割し、必要に応じて、外部キーで結合クエリーを使用することを考慮します。MySQL で行から何らかの値を取得する場合、その行 (およびおそらくその他の隣接する行) のすべてのカラムを含むデータブロックを読み取ります。もっとも頻繁に使用するカラムのみで、各行を小さくすることで、より多くの行を各データブロックに収めることができます。そのようなコンパクトなテーブルは、一般的なクエリーのディスク I/O やメモリーの使用量を削減します。

  • InnoDB テーブルで主キーとして、ランダムに生成された値を使用する場合、可能であれば、現在の日時などの降順の値でプリフィクスを付けます。連続したプライマリ値が、相互に物理的に近くに保存されていれば、InnoDB はそれらを高速に挿入し、取得できます。

  • 数値カラムの方が通常同等の文字列カラムより推奨される理由については、セクション8.4.2.1「数値データの最適化」を参照してください。

8.4.2.3 BLOB 型の最適化

  • テキストデータを格納する大きな BLOB を保存する場合、まずそれを圧縮することを考慮します。テーブル全体が InnoDB または MyISAM によって圧縮されている場合は、この技法を使用しないでください。

  • 複数のカラムのあるテーブルで、BLOB カラムを使用しないクエリーのメモリー要件を削減するには、BLOB カラムを個別のテーブルに分割し、必要に応じて、結合クエリーでそれを参照することを考慮します。

  • BLOB 値を取得し、表示するためのパフォーマンス要件は、ほかのデータ型と大きく異なることがあるため、BLOB 固有テーブルを別のストレージデバイスまたは個別のデータベースインスタンスに置くことができます。たとえば、BLOB を取得するには、大量の順次ディスク読み取りが必要で、SSD デバイスより、従来のハードドライブの方が適しています。

  • バイナリ VARCHAR カラムの方が同等の BLOB カラムより推奨されることがある理由については、セクション8.4.2.2「文字および文字列型の最適化」を参照してください。

  • きわめて長いテキスト文字列に対して、同等性をテストする代わりに、個別のカラムにカラムのハッシュを格納し、そのカラムにインデックスを設定して、クエリー内のハッシュ値をテストします。(MD5() または CRC32() 関数を使用して、ハッシュ値を生成します。)ハッシュ関数は、異なる入力で重複した結果を生成することがあるため、引き続きクエリーに句 AND blob_column = long_string_value を含めて、誤った一致に対して保護します。パフォーマンスは、ハッシュ値の小さく、簡単にスキャンされるインデックスからメリットが得られます。

8.4.2.4 PROCEDURE ANALYSE の使用

ANALYSE([max_elements[,max_memory]])

ANALYSE() はクエリーからの結果を調査し、テーブルサイズの削減に役立つ可能性がある各カラムの最適なデータ型を提案する結果の分析を返します。この分析を取得するには、SELECT ステートメントの末尾に PROCEDURE ANALYSE を追加します。

SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]])

例:

SELECT col1, col2 FROM table1 PROCEDURE ANALYSE(10, 2000);

結果には、クエリーによって返された値のいくつかの統計が表示され、カラムの最適なデータ型が提案されます。これは、既存のテーブルのチェックや新しいデータのインポート後に役立つことがあります。ENUM データ型が適切でない場合に、PROCEDURE ANALYSE() がそれを提案しないように、引数の異なる設定を試してみる必要がある場合があります。

引数はオプションで次のように使用します。

  • max_elements (デフォルト 256) は、ANALYSE() がカラムあたりに認識する個々の値の最大数です。これは、ANALYSE() によって、最適なデータ型が型 ENUM であるかどうかをチェックするために使用されます。max_elements 個を超える個別の値がある場合、ENUM は提案される型ではありません。

  • max_memory (デフォルト 8192) は ANALYSE() がすべての個別の値を見つけようとする間に、カラムごとに割り当てるべき最大メモリー量です。

8.4.3 多数のテーブルの最適化

各クエリーを高速にするいくつかの技法には、多数のテーブルへのデータの分割が含まれます。テーブルの数が数千または数百万にもなる場合、これらすべてのテーブルの処理のオーバーヘッドは新たなパフォーマンスの考慮事項になります。

8.4.3.1 MySQL でのテーブルのオープンとクローズの方法

mysqladmin status コマンドを実行すると、次のように表示されるはずです。

Uptime: 426 Running threads: 1 Questions: 11082
Reloads: 1 Open tables: 12

テーブルが 6 つしかない場合に、12 の Open tables 値はいくぶん不可解に思うことがあります。

MySQL はマルチスレッド対応であるため、特定のテーブルに対して多くのクライアントが同時にクエリーを発行している場合があります。同じテーブルに対して、複数のクライアントセッションが異なる状態を持つ問題を最小にするため、テーブルは各同時セッションに独立して開かれます。これは追加メモリーを使用しますが、一般にパフォーマンスは向上します。MyISAM テーブルでは、テーブルを開いているクライアントごとに、データファイルに 1 つの追加のファイルディスクリプタが必要になります。(対照的に、インデックスファイルディスクリプタはすべてのセッションで共有されます。)

table_open_cache および max_connections システム変数は、サーバーが開いたままにするファイルの最大数に影響します。これらの値のいずれかまたは両方を増やすと、オープンファイルディスクリプタのプロセスあたりの数に関して、オペレーティングシステムによって適用されている制限に達する可能性があります。多くのオペレーティングシステムでは、オープンファイル制限を増やすことができますが、方法はシステムによって大きく異なります。制限値を増やすことができるかどうか、およびその実行方法については、使用するオペレーティングシステムのドキュメントを参照してください。

table_open_cachemax_connections に関連します。たとえば、200 の同時実行接続の場合、少なくとも 200 * N のテーブルキャッシュサイズを指定します。ここで N は実行するクエリーの結合あたりのテーブルの最大数です。また、一時テーブルとファイル用のいくつかの追加のファイルディスクリプタを予約する必要もあります。

オペレーティングシステムで、table_open_cache の設定に示されたオープンファイルディスクリプタの数を処理できることを確認してください。table_open_cache の設定が大きすぎると、MySQL がファイルディスクリプタを使い果たして接続を拒否し、クエリーの実行に失敗して、信頼性が大幅に低下します。また、MyISAM ストレージエンジンでは一意のオープンテーブルごとに 2 つのファイルディスクリプタが必要であることも考慮に入れる必要があります。mysqld--open-files-limit スタートアップオプションを使用すると、MySQL で使用可能なファイルディスクリプタの数を増やすことができます。セクションB.5.2.18「'File' が見つかりません、および同様のエラー」を参照してください。

オープンテーブルのキャッシュは、table_open_cache エントリのレベルで保持されます。サーバーはスタートアップ時にキャッシュサイズを自動サイズ設定します。サイズを明示的に設定するには、スタートアップ時に table_open_cache システム変数を設定します。MySQL は、クエリーを実行するために、一時的にこれより多くのテーブルを開くことがあります。

次の状況では、MySQL は未使用のテーブルを閉じ、それをテーブルキャッシュから削除します。

  • キャッシュがいっぱいで、スレッドがキャッシュにないテーブルを開こうとした場合。

  • キャッシュに table_open_cache を超えるエントリがあり、キャッシュ内のテーブルがどのスレッドによっても使用されなくなった場合。

  • テーブルフラッシュ操作が行われた場合。これは、だれかが FLUSH TABLES ステートメントを発行するか、または mysqladmin flush-tables または mysqladmin refresh コマンドを実行した場合に行われます。

テーブルキャッシュがいっぱいになると、サーバーは次の手順に従って使用するキャッシュエントリを見つけます。

  • 現在使用中でないテーブルは、もっとも長く使用されていないテーブルから、解放されます。

  • 新しいテーブルを開く必要があるが、キャッシュがいっぱいで、解放できるテーブルがない場合、必要に応じてキャッシュが一時的に拡張されます。キャッシュが一時的に拡張された状況で、テーブルが使用中から未使用状態になったときは、そのテーブルが閉じられ、キャッシュから解放されます。

MyISAM テーブルは同時アクセスごとに開かれます。つまり、2 つのスレッドで同じテーブルにアクセスする場合、または 1 つのスレッドが同一クエリーでテーブルに 2 回アクセスする場合 (テーブルをそれ自体に結合することによってなど) は、テーブルを 2 回開く必要があることを意味します。同時オープンは、それぞれテーブルキャッシュにエントリが必要になります。いずれかの MyISAM テーブルを最初に開くと、データファイルに 1 つとインデックスファイルに 1 つの 2 つのファイルディスクリプタが必要になります。テーブルの追加の使用では、それぞれデータファイルに 1 つだけのファイルディスクリプタが必要です。インデックスファイルディスクリプタはすべてのスレッドで共有されます。

HANDLER tbl_name OPEN ステートメントを使用してテーブルを開く場合、専用のテーブルオブジェクトがスレッドに割り当てられます。このテーブルオブジェクトはほかのスレッドと共有されず、スレッドが HANDLER tbl_name CLOSE を呼び出すか、スレッドが終了するまでクローズされません。これが発生すると、テーブルがテーブルキャッシュに戻されます (キャッシュがいっぱいでない場合)。セクション13.2.4「HANDLER 構文」を参照してください。

テーブルキャッシュが小さすぎるかどうかは、mysqld のステータス変数 Opened_tables をチェックして判断できます。これは、サーバーの起動以降のテーブルを開く操作の数を示します。

mysql> SHOW GLOBAL STATUS LIKE 'Opened_tables';+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Opened_tables | 2741 |
+---------------+-------+

多くの FLUSH TABLES ステートメントを発行していない場合でも、値がきわめて大きいか、急増した場合、テーブルキャッシュサイズを増やします。セクション5.1.4「サーバーシステム変数」およびセクション5.1.6「サーバーステータス変数」を参照してください。

8.4.3.2 同じデータベースに大量のテーブルを作成することの短所

同じデータベースディレクトリに多数の MyISAM テーブルがある場合、オープン、クローズ、および作成操作が遅くなります。多数のテーブルに対して SELECT ステートメントを実行した場合、開く必要があるテーブルごとに、別のテーブルを閉じる必要があるため、テーブルキャッシュがいっぱいの場合にオーバーヘッドが少し発生します。テーブルキャッシュで許可されるエントリ数を増やすことによって、このオーバーヘッドを減らすことができます。

8.4.4 MySQL が内部一時テーブルを使用する仕組み

場合により、サーバーはクエリーの処理中に内部一時テーブルを作成します。それらのテーブルは、メモリー内に保持して、MEMORY ストレージエンジンによって処理したり、ディスク上に格納して、MyISAM ストレージエンジンによって処理したりできます。サーバーは最初にインメモリーテーブルとして内部で一時テーブルを作成し、それが大きくなりすぎた場合に、それをディスク上テーブルに変換することがあります。サーバーが内部一時テーブルを作成するタイミングや、サーバーがそれを管理するためにどのストレージエンジンを使用するかに関して、ユーザーは直接制御できません。

一時テーブルは、次のような条件で作成される可能性があります。

  • UNION クエリーが一時テーブルを使用します。

  • TEMPTABLE アルゴリズムを使用して評価されるものや、UNION またはアグリゲーションを使用するものなど、一部のビューで一時テーブルを必要とします。

  • ORDER BY 句と別の GROUP BY 句がある場合、または、ORDER BY または GROUP BY に結合キュー内の最初のテーブルと異なるテーブルのカラムが含まれている場合は、一時テーブルが作成されます。

  • DISTINCTORDER BY の組み合わせで、一時テーブルが必要になることがあります。

  • SQL_SMALL_RESULT オプションを使用すると、MySQL では、クエリーにディスク上ストレージを必要とする要素 (後述) も含まれていないかぎり、インメモリー一時テーブルが使用されます。

  • 複数テーブル UPDATE ステートメント。

  • GROUP_CONCAT() または COUNT(DISTINCT) 評価。

  • 派生テーブル (FROM 句内のサブクエリー)。

  • サブクエリーまたは準結合実体化のために作成されるテーブル。

クエリーで一時テーブルを必要とするかどうかを判断するには、EXPLAIN を使用し、Extra カラムをチェックして、そこに Using temporary と示されているかどうかを確認します (セクション8.8.1「EXPLAIN によるクエリーの最適化」を参照してください)。EXPLAIN では、派生されるか、実体化された一時テーブルに対して、必ずしも Using temporary と表示しないことがあります。

内部一時テーブルが最初にインメモリーテーブルとして作成されたが、これが大きくなりすぎた場合、MySQL はこれを自動的にディスク上のテーブルに変換します。インメモリー一時テーブルの最大サイズは、tmp_table_sizemax_heap_table_size の最小値です。これは、CREATE TABLE によって明示的に作成された MEMORY テーブルと異なります。そのようなテーブルの場合、max_heap_table_size システム変数でのみ、テーブルがどのくらい拡大でき、ディスク上フォーマットへの変換がないことが判断されます。

サーバーは内部一時テーブル (メモリー内またはディスク上のいずれか) を作成すると、Created_tmp_tables ステータス変数を増分します。サーバーはディスク上にテーブルを作成する (内部で、またはインメモリーテーブルを変換して) 場合、Created_tmp_disk_tables ステータス変数を増分します。

状況によっては、インメモリー一時テーブルの使用が妨げられる場合があり、その場合サーバーは代わりにディスク上テーブルを使用します。

  • テーブル内の BLOB または TEXT カラムの存在

  • GROUP BY または DISTINCT 句内の、バイナリ文字列の場合に 512 バイトまたは非バイナリ文字列の場合に 512 文字より大きい文字列カラムの存在。(MySQL 5.6.15 より前のこの制限は、文字列の型に関係なく 512 バイトです。)

  • UNION または UNION ALL が使用された場合に、SELECT リスト内の 512 (バイナリ文字列の場合はバイト数、非バイナリ文字列の場合は文字数) より大きい最大長を持つ文字列カラムの存在。

8.5 InnoDB テーブルの最適化

InnoDB は、MySQL のお客様が一般に、信頼性と並列性が重要である本番環境のデータベースで使用するストレージエンジンです。InnoDB は MySQL 5.5 以上のデフォルトのストレージエンジンであるため、以前より頻繁に InnoDB テーブルを目にすることが予想されます。このセクションでは、InnoDB テーブルに対するデータベース操作を最適化する方法について説明します。

8.5.1 InnoDB テーブルのストレージレイアウトの最適化

  • データが安定したサイズに達するか、拡大しているテーブルが数十または数百メガバイト単位で増大した場合、OPTIMIZE TABLE ステートメントを使用して、テーブルを再編成し、無駄なスペースを圧縮することを考慮します。再編成されたテーブルでは、フルテーブルスキャンを実行するために必要なディスク I/O が減ります。これは、インデックスの使用の改善やアプリケーションコードのチューニングなどのほかの技法が現実的でない場合に、パフォーマンスを向上できる直接的な技法です。

    OPTIMIZE TABLE はテーブルのデータ部分をコピーし、インデックスを再構築します。インデックス内へのデータのパックの改善とテーブルスペース内およびディスク上の断片化の削減からメリットが得られます。このメリットは各テーブル内のデータによって異なります。利点が大きいものとそうでないものがあること、またはテーブルの次回の最適化まで、時間の経過とともに利点が減っていくことに気付く場合があります。この操作は、テーブルが大きい場合や再構築されるインデックスがバッファープールに収まらない場合に、遅くなることがあります。大量のデータをテーブルに追加したあとの最初の実行では、多くの場合にその後の実行よりかなり遅くなります。

  • InnoDB では、長い PRIMARY KEY (長い値を持つ単一カラムまたは長い複合値を形成する複数のカラムのいずれか) があると、大量のディスク領域を無駄にします。行の主キー値は、同じ行を指すすべてのセカンダリインデックスレコードに複製されます。(セクション14.2.13「InnoDB テーブルおよびインデックスの構造」を参照してください。)主キーが長い場合、AUTO_INCREMENT カラムを主キーとして作成するか、カラム全体ではなく、長い VARCHAR カラムのプリフィクスをインデックス設定します。

  • 可変長の文字列を格納するために、または多くの NULL 値を持つカラムに対して、CHAR の代わりに VARCHAR データ型を使用します。CHAR(N) カラムは、文字列が短いか、その値が NULL だとしても、データを格納するために常に N 文字を必要とします。テーブルが小さいほどバッファープールに収まりやすく、ディスク I/O が減ります。

    COMPACT 行フォーマット (MySQL 5.6 でのデフォルトの InnoDB フォーマット) と utf8sjis などの可変長文字セットを使用する場合、CHAR(N) カラムは可変量でも、やはり N バイト以上の領域を占有します。

  • 大きいか、繰り返しの多い大量のテキストや数値データを格納するテーブルでは、COMPRESSED 行フォーマットを使用することを考慮します。データをバッファープールに入れたり、フルテーブルスキャンを実行したりするために必要なディスク I/O が減ります。永続的な決断を下す前に、COMPRESSEDCOMPACT の行フォーマットを使用して達成できる圧縮の量を測定してください。

8.5.2 InnoDB トランザクション管理の最適化

InnoDB トランザクション処理を最適化するには、トランザクション機能のパフォーマンスオーバーヘッドとサーバーのワークロードの理想的なバランスを見つけます。たとえば、アプリケーションで、秒あたり数千回コミットする場合にパフォーマンスの問題が発生し、2、3 時間に 1 回だけコミットする場合に別のパフォーマンスの問題が発生することがあります。

  • デフォルトの MySQL 設定 AUTOCOMMIT=1 は、ビジーなデータベースサーバーにパフォーマンスの制限を課すことがあります。現実的であれば、SET AUTOCOMMIT=0 または START TRANSACTION ステートメントを発行し、すべての変更を行なったあとに、COMMIT ステートメントを発行することで、複数の関連 DML 操作を単一のトランザクションにまとめます。

    InnoDB は、トランザクションによってデータベースが変更された場合、そのトランザクションのコミットのたびにディスクにログをフラッシュする必要があります。変更のたびにあとでコミットされる場合 (デフォルトの自動コミット設定のように)、ストレージデバイスの I/O スループットによって、秒あたりに可能な操作数が制限されます。

  • または、単一の SELECT ステートメントのみから構成されるトランザクションの場合、AUTOCOMMIT をオンにすると、InnoDB が読み取り専用トランザクションを認識し、それらを最適化するのに役立ちます。要件については、セクション14.13.14「InnoDB の読み取り専用トランザクションの最適化」を参照してください。

  • 大量の行の挿入、更新、または削除後のロールバックの実行は避けます。大きなトランザクションによってサーバーのパフォーマンスが低下する場合、それをロールバックすると、問題が悪化し、元の DML 操作の数倍の実行時間がかかる可能性があります。ロールバックはサーバーの起動時に再度開始されるため、データベースプロセスを強制終了しても役立ちません。

    この問題の発生の可能性を最小にするには: すべての DML の変更をただちにディスクに書き込むのではなく、キャッシュできるように、バッファープールのサイズを増やします。挿入に加えて、更新および削除操作がバッファリングされるように、innodb_change_buffering=all を設定します。大きな DML 操作中に、COMMIT ステートメントを定期的に発行し、可能であれば単一の削除または更新を少数の行に対して操作する複数のステートメントに分割することを考慮します。

    ロールバックの暴走が発生した場合にそれを解消するには、ロールバックが CPU に依存して高速に実行するように、バッファープールを増加するか、セクション14.16.1「InnoDB のリカバリプロセス」に説明するように、サーバーを強制終了し、innodb_force_recovery=3 で再起動します。

    この問題は、MySQL 5.5 以上または InnoDB プラグイン付きの MySQL 5.1 では、あまり目立たなくなっていると予想されます。デフォルトの設定 innodb_change_buffering=all により、更新および削除操作がメモリー内にキャッシュされ、それらがそもそも高速に実行されるようになり、必要な場合にロールバックも高速になったためです。多くの挿入、更新、または削除を伴う長時間実行トランザクションを処理するサーバーでこのパラメータ設定を使うようにしてください。

  • クラッシュが発生した場合に、最新のコミットされたトランザクションの一部の損失を許容できる場合は、innodb_flush_log_at_trx_commit パラメータを 0 に設定できます。フラッシュが保証されていなくても、InnoDB はとにかく 1 秒に 1 回ログをフラッシュしようとします。さらに、innodb_support_xa の値を 0 に設定し、これにより、ディスク上データとバイナリログの同期によるディスクフラッシュの数を減らします。

  • 行が変更されるか削除される場合、行と関連付けられたUndo ログはただちに、またはトランザクションのコミットの直後でも、物理的に削除されません。以前または同時に開始したトランザクションが終了するまで古いデータは保持されるため、それらのトランザクションは変更または削除された行の以前の状態にアクセスできます。そのため、長時間実行トランザクションは、InnoDB が別のトランザクションによって変更されたデータをパージすることを妨げることがあります。

  • 長時間実行トランザクション内で行が変更されるか、削除された場合、READ COMMITTED および REPEATABLE READ 分離レベルを使用するほかのトランザクションは、古いデータを再構築するために、それらの同じ行を読み取る場合、多くの作業を実行する必要があります。

  • 長時間実行トランザクションでテーブルが変更された場合、ほかのトランザクションからのそのテーブルに対するクエリーは、カバリングインデックス技法を利用しません。通常、セカンダリインデックスからすべての結果カラムを取得できるクエリーは、代わりにテーブルデータから該当する値をルックアップします。

    セカンダリインデックスページに、新しすぎる PAGE_MAX_TRX_ID があることが検出された場合、またはセカンダリインデックス内のレコードに削除がマークされている場合、InnoDB はクラスタ化されたインデックスを使用してレコードをルックアップする必要がある可能性があります。

8.5.3 InnoDB ロギングの最適化

  • バッファープールと同じくらいの大きさまでログファイルを大きくします。InnoDB がログファイルにいっぱいまで書き込んだ場合、チェックポイントでバッファープールの変更された内容をディスクに書き込む必要があります。小さいログファイルは多くの不必要なディスク書き込みを発生させます。従来、大きなログファイルは長いリカバリ時間の原因になっていましたが、現在リカバリは大幅に速くなったため、確信を持って大きなログファイルを使うことができます。

  • ログバッファーのサイズも十分に大きく (約 8M バイト) してください。

8.5.4 InnoDB テーブルの一括データロード

これらのパフォーマンスのヒントは、セクション8.2.2.1「INSERT ステートメントの速度」の高速挿入の一般的なガイドラインを補足するものです。

  • InnoDB にデータをインポートする場合、自動コミットモードでは挿入のたびに、ディスクへのログのフラッシュを実行するため、それをオフにします。インポート操作時に自動コミットを無効にするには、それを、SET autocommit ステートメントと COMMIT ステートメントで囲みます。

    SET autocommit=0;... SQL import statements ...COMMIT;

    mysqldump オプション --opt は、それらを SET autocommit ステートメントと COMMIT ステートメントで囲まなくても、InnoDB テーブルに高速にインポートするダンプファイルを作成します。

  • 副キーに UNIQUE 制約がある場合、インポートセッション中に一意性チェックを一時的にオフにすることで、テーブルインポートを高速化できます。

    SET unique_checks=0;... SQL import statements ...SET unique_checks=1;

    大きいテーブルの場合、InnoDB はその挿入バッファーを使用して、セカンダリインデックスレコードを一括して書き込むことができるため、これにより、大量のディスク I/O が節約されます。データに重複キーが含まれていないことを確認してください。

  • テーブルに FOREIGN KEY 制約がある場合、インポートセッションの間の外部キーチェックをオフにすることで、テーブルインポートを高速化できます。

    SET foreign_key_checks=0;... SQL import statements ...SET foreign_key_checks=1;

    大きいテーブルの場合、これにより、大量のディスク I/O を節約できます。

  • 多くの行を挿入する必要がある場合、複数行 INSERT 構文を使用して、クライアントとサーバー間の通信オーバーヘッドを軽減します。

    INSERT INTO yourtable VALUES (1,2), (5,5), ...;

    このヒントは、InnoDB テーブルだけではなく、任意のテーブルへの挿入に有効です。

  • 自動インクリメントカラムのあるテーブルへの一括挿入を実行する場合、innodb_autoinc_lock_mode をデフォルト値の 1 の代わりに 2 に設定します。詳細は、セクション14.6.5.2「構成可能な InnoDB の自動インクリメントロック」を参照してください。

  • InnoDBFULLTEXT インデックスにデータをロードする場合の最高のパフォーマンスのため、次の一連のステップに従います。

    • テーブル作成時に、FTS_DOC_ID_INDEX という一意のインデックスで、型 BIGINT UNSIGNED NOT NULL のカラム FTS_DOC_ID を定義します。例:

      CREATE TABLE t1 (
      FTS_DOC_ID BIGINT unsigned NOT NULL AUTO_INCREMENT,
      title varchar(255) NOT NULL DEFAULT ”,
      text mediumtext NOT NULL,
      PRIMARY KEY (`FTS_DOC_ID`)
      ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
      CREATE UNIQUE INDEX FTS_DOC_ID_INDEX on t1(FTS_DOC_ID); 
    • テーブルにデータをロードします。

    • データがロードされたら、FULLTEXT インデックスを作成します。

    注記

    テーブル作成時に FTS_DOC_ID カラムを追加する場合、FTS_DOC_ID は各 INSERT または UPDATE によって単調に増分される必要があるため、FULLTEXT インデックス設定されたカラムが更新されたときに、FTS_DOC_ID カラムが更新されることを確認します。テーブル作成時に FTS_DOC_ID を追加せず、InnoDB に自動的に DOC ID を管理させるようにした場合、InnoDB は次の CREATE FULLTEXT INDEX 呼び出しで、FTS_DOC_ID を非表示カラムとして追加します。ただし、このアプローチでは、パフォーマンスに影響するテーブルの再構築が必要になります。

8.5.5 InnoDB クエリーの最適化

InnoDB テーブルのクエリーをチューニングするには、各テーブルに適切なインデックスのセットを作成します。詳細は、セクション8.3.1「MySQL のインデックスの使用の仕組み」を参照してください。InnoDB インデックスに関する次のガイドラインに従います。

  • InnoDB テーブルには主キー がある (それをリクエストするかしないかに関係なく) ため、もっとも重要で緊急を要するクエリーで使用されるカラムとして、テーブルごとに主キーカラムのセットを指定します。

  • 主キーのカラム値は各セカンダリインデックスに複製されるため、あまり多く、長すぎるカラムを指定しないでください。インデックスに不要なデータが含まれていると、このデータを読み取る I/O とそれをキャッシュするメモリーによって、サーバーのパフォーマンスとスケーラビリティーが低下します。

  • 各クエリーで使用できるインデックスは 1 つだけであるため、カラムごとに個別のセカンダリインデックスを作成しないでください。ほんの少数の異なる値を持ち、めったにテストされないカラムへのインデックスは、どのクエリーにも役立たない可能性があります。同じテーブルに対して多くのクエリーがあり、カラムのさまざまな組み合わせをテストする場合、多数の単一カラムインデックスよりも、少数の連結されたインデックスを作成してみてください。インデックスに結果セットに必要なすべてのカラムが含まれている (カバリングインデックスと呼ばれます) 場合、クエリーはテーブルデータをまったく読み取らなくても済む可能性があります。

  • インデックス設定されたカラムに NULL 値が含まれることがない場合は、テーブルの作成時に、それを NOT NULL として宣言します。オプティマイザは、各カラムに NULL 値が含まれているかどうかを知っていれば、クエリーに使用するためにもっとも効率的なインデックスをより適切に判断できます。

  • MySQL 5.6.4 以上では、セクション14.13.14「InnoDB の読み取り専用トランザクションの最適化」の技法を使用して、InnoDB テーブルの単一クエリートランザクションを最適化できます。

  • 頻繁に更新されないテーブルに対して、たびたび繰り返しのクエリーを行う場合、クエリーキャッシュを有効にします。

    [mysqld]
    query_cache_type = 1
    query_cache_size = 10M

8.5.6 InnoDB DDL 操作の最適化

  • テーブルとインデックスに対する DDL 操作 (CREATEALTER、および DROP ステートメント) で、InnoDB テーブルのもっとも重要な側面は、MySQL 5.5 以上でのセカンダリインデックスの作成と削除が MySQL 5.1 以前よりはるかに速くなっていることです。詳細は、セクション14.11「InnoDB とオンライン DDL」を参照してください。

  • 高速のインデックス作成により、特定の場合で、データをテーブルにロードする前にインデックスを削除し、次にデータのロード後にインデックスを再作成することが高速になります。

  • テーブルを空にするには DELETE FROM tbl_name ではなく、TRUNCATE TABLE を使用します。外部キー制約により、TRUNCATE ステートメントを通常の DELETE ステートメントのように動作させることができます。その場合、DROP TABLECREATE TABLE のようなコマンドのシーケンスがもっとも速くなる可能性があります。

  • 主キーは、各 InnoDB テーブルのストレージレイアウトに不可欠であり、主キーの定義の変更には、テーブル全体の再編成が必要であるため、常に主キーを CREATE TABLE ステートメントの一部としてセットアップし、あとで主キーを ALTER または DROP する必要がないように、事前に計画してください。

8.5.7 InnoDB ディスク I/O の最適化

データベース設計と SQL 操作のチューニング技法のベストプラクティスに従っても、データベースが大量のディスク I/O アクティビティーによってまだ遅い場合は、ディスク I/O に関するこれらの低レベル技法を調査してください。Unix top ツールまたは Windows タスクマネージャーに、ワークロードの CPU 使用率が 70% 未満であることが示されている場合、ワークロードはディスクに依存している可能性があります。

  • テーブルデータを InnoDB バッファープールにキャッシュすると、ディスク I/O を必要とせずに、クエリーでそれを繰り返し処理できます。バッファープールサイズは、innodb_buffer_pool_size オプションで指定します。このメモリー領域はきわめて重要であるため、ビジーなデータベースでは多くの場合、サイズを物理メモリーの量の約 80% に指定します。詳細については、セクション8.9.1「InnoDB バッファープール」を参照してください。

  • GNU/Linux および Unix の一部のバージョンでは、Unix fsync() 呼び出し (これは InnoDB がデフォルトで使用します) および類似のメソッドによるファイルのディスクへのフラッシュが驚くほど低速です。データベースの書き込みパフォーマンスが問題である場合、innodb_flush_method パラメータを O_DSYNC に設定してベンチマークを実行します。

  • x86_64 アーキテクチャー (AMD Opteron) の Solaris 10 で InnoDB ストレージエンジンを使用する場合、InnoDB 関連ファイルにダイレクト I/O を使用して、InnoDB のパフォーマンスの低下を回避します。InnoDB 関連ファイルを格納するために使用される UFS ファイルシステム全体にダイレクト I/O を使用するには、それを forcedirectio オプションでマウントします。mount_ufs(1M) を参照してください。(Solaris 10/x86_64 のデフォルトではこのオプションを使用しません)。ダイレクト I/O をファイルシステム全体ではなく、InnoDB ファイル操作にのみ適用するには、innodb_flush_method = O_DIRECT を設定します。この設定では、InnoDB はデータファイルへの I/O (ログファイルへの I/O ではなく) に fcntl() ではなく、directio() を呼び出します。

  • Solaris 2.6 以上の任意のリリースおよび任意のプラットフォーム (sparc/x86/x64/amd64) で、大きな innodb_buffer_pool_size 値を使用して、InnoDB ストレージエンジンを使用する場合、先述の forcedirectio マウントオプションを使用して、raw デバイスまたは個別のダイレクト I/O UFS ファイルシステムで、InnoDB データファイルおよびログファイルのベンチマークを実行します。(ログファイルのダイレクト I/O が必要な場合、innodb_flush_method を設定する代わりに、マウントオプションを使用する必要があります。)Veritas ファイルシステム VxFS のユーザーは、convosync=direct マウントオプションを使用してください。

    ダイレクト I/O ファイルシステムに、MyISAM テーブルのファイルなど、ほかの MySQL データファイルを配置しないでください。実行ファイルやライブラリは、ディレクト I/O ファイルシステムに配置しないでください

  • RAID 構成または別のディスクへのシンボリックリンクをセットアップするために追加のストレージデバイスを使用できるようにする場合、追加の低レベル I/O のヒントについては、セクション8.11.3「ディスク I/O の最適化」を参照してください。

  • InnoDBチェックポイント操作のため、スループットが周期的に低下する場合、innodb_io_capacity 構成オプションの値を増加することを考慮します。値を大きくすると、フラッシュが頻繁になり、スループットを低下させる可能性のある作業のバックログが避けられます。

  • InnoDBフラッシュ操作によって、システムが遅くならない場合は、innodb_io_capacity 構成オプションの値を小さくすることを考慮します。一般に、このオプション値はできるかぎり小さくしますが、前の箇条書きで示したように、スループットに周期的な低下が発生するほど小さくしないでください。オプション値を小さくすることができる一般的なシナリオでは、SHOW ENGINE INNODB STATUS からの出力に、次のような組み合わせが示されることがあります。

    • 履歴リストの長さが短く、数千未満です。

    • 挿入バッファーマージ数が挿入された行数に近いです。

    • バッファープール内の変更されたページが、一貫してバッファープールの innodb_max_dirty_pages_pct をはるかに下回っています。(サーバーが一括挿入を実行していないときに測定します。変更されたページの一括挿入時に、パーセンテージが大幅に高くなるのは正常です。)

    • Log sequence number - Last checkpoint が、InnoDBログファイルの合計サイズの 7/8 未満か、理想的には 6/8 未満です。

  • I/O に依存したワークロードのチューニング時に考慮するその他の InnoDB 構成オプションには次が含まれます: innodb_adaptive_flushinginnodb_change_buffer_max_sizeinnodb_change_bufferinginnodb_flush_neighborsinnodb_log_buffer_sizeinnodb_log_file_sizeinnodb_lru_scan_depthinnodb_max_dirty_pages_pctinnodb_max_purge_laginnodb_open_filesinnodb_page_sizeinnodb_random_read_aheadinnodb_read_ahead_thresholdinnodb_read_io_threadsinnodb_rollback_segmentsinnodb_write_io_threads、および sync_binlog

8.5.8 InnoDB 構成変数の最適化

軽量の予測可能な負荷のあるサーバーと、常時ほぼいっぱいの容量で実行していたり、高アクティビティーの急増が発生したりするサーバーとでは、もっとも適切に機能する設定が異なります。

InnoDB ストレージエンジンは、多くの最適化を自動的に実行するため、多くのパフォーマンスチューニングタスクには、データベースが適切に実行していることを確認するためのモニタリングと、パフォーマンスの低下時の構成オプションの変更が含まれます。詳細な InnoDB のパフォーマンスモニタリングについては、セクション14.13.11「InnoDB の MySQL パフォーマンススキーマとの統合」を参照してください。

もっとも重要で、最新の InnoDB パフォーマンス機能については、セクション14.13「InnoDB のパフォーマンス」を参照してください。以前のバージョンで InnoDB テーブルを使用していた場合でも、これらの機能は InnoDB プラグインからのものであるため、なじみがないと思われます。プラグインは MySQL 5.1 の組み込みの InnoDB と共存でき、MySQL 5.5 以上でのデフォルトのストレージエンジンになります。

実行できる主な構成ステップは次のようになります。

  • 高性能メモリーアロケータを装備するシステムで、InnoDB がそれらを使用できるようにします。セクション14.13.3「InnoDB のためのメモリーアロケータの構成」を参照してください。

  • 頻繁な小さなディスク書き込みを避けるため、InnoDB が変更されたデータをバッファーする DML 操作の種類を制御します。セクション14.13.4「InnoDB 変更バッファリングの構成」を参照してください。デフォルトはすべての種類の DML 操作をバッファーすることであるため、バッファリングの量を減らす必要がある場合にのみ、この設定を変更してください。

  • innodb_adaptive_hash_index オプションを使用して、アダプティブハッシュインデックス機能をオンまたはオフにします。詳しくはセクション14.2.13.6「適応型ハッシュインデックス」をご覧ください。異常なアクティビティーの間にこの設定を変更し、その後、その元の設定にリストアできます。

  • コンテキストスイッチングがボトルネックである場合に、InnoDB が処理する同時スレッドの数に制限を設定します。セクション14.13.5「InnoDB のスレッド並列性の構成」を参照してください。

  • InnoDB がその先読み操作で実行するプリフェッチの量を制御します。システムに未使用の I/O 容量がある場合、先読みによってクエリーのパフォーマンスが向上することがあります。先読みが多すぎると、負荷の大きいシステムで、パフォーマンスが周期的に低下する可能性があります。セクション14.13.1.1「InnoDB バッファープールのプリフェッチ (先読み) の構成」を参照してください。

  • デフォルト値で十分に活用されていないハイエンド I/O サブシステムがある場合、読み取りまたは書き込み操作のバックグラウンドスレッドの数を増やします。セクション14.13.6「InnoDB バックグラウンド I/O スレッドの数の構成」を参照してください。

  • バックグラウンドで InnoDB が実行する I/O の量を制御します。セクション14.13.8「InnoDB マスタースレッドの I/O レートの構成」を参照してください。バックグラウンド I/O の量は MySQL 5.1 より大きいため、パフォーマンスに周期的な低下が観察された場合、この設定を縮小した方がよいことがあります。

  • InnoDB が特定の種類のバックグラウンドの書き込みを実行するタイミングを判断するアルゴリズムを制御します。セクション14.13.1.2「InnoDB バッファープールのフラッシュの頻度の構成」を参照してください。アルゴリズムはワークロードの種類によって機能する場合と機能しない場合があるため、パフォーマンスに周期的な低下が観察された場合は、この設定をオフした方がよいことがあります。

  • コンテキストスイッチングの遅延を最小にするため、マルチコアプロセッサとそれらのキャッシュメモリー構成を利用します。セクション14.13.10「スピンロックのポーリングの構成」を参照してください。

  • テーブルスキャンなどの一度だけの操作が、InnoDB バッファーキャッシュに格納された頻繁にアクセスされるデータを妨げることを防ぎます。セクション14.13.1.3「バッファープールをスキャンに耐えられるようにする」を参照してください。

  • 信頼性とクラッシュリカバリに適切なサイズにログファイルを調整します。InnoDB ログファイルは、多くの場合にクラッシュ後の長い起動時間を避けるため、小さく維持されてきました。MySQL 5.5.4 で導入された最適化によって、クラッシュリカバリプロセスの特定のステップが高速化します。特に、Redo ログのスキャンと Redo ログの適用は、メモリー管理のアルゴリズムの改善のため、高速化します。長い起動時間を避けるため、ログファイルを人為的に小さく維持していた場合、ログファイルサイズを拡大し、Redo ログレコードのリサイクルのために発生する I/O を削減することを考慮できるようになりました。

  • InnoDB バッファープールのインスタンスのサイズと数を構成します。特に数ギガバイトのバッファープールのあるシステムに重要です。セクション14.13.1.4「複数のバッファープールインスタンスの使用」を参照してください。

  • 同時トランザクションの最大数を増やします。これはきわめてビジーなデータベースのスケーラビリティーを劇的に向上します。セクション14.13.12「複数のロールバックセグメントによるスケーラビリティーの向上」を参照してください。この機能は、日常の操作中のアクションを必要としませんが、データベースの MySQL 5.5 へのアップグレード中またはその後に、低速シャットダウンを実行して、制限を大きくできるようにする必要があります。

  • パージ操作 (ガベージコレクションの一種) をバックグラウンドスレッドに移動します。セクション14.13.13「InnoDB のパージスケジューリングの構成」を参照してください。この設定の結果を効率的に測定するには、ほかの I/O 関連およびスレッド関連の構成設定を先にチューニングします。

  • ビジーなサーバーで SQL 操作が列を成し、渋滞が発生しないように、InnoDB が同時スレッド間で実行するスイッチングの量を削減します。innodb_thread_concurrency オプションの値を設定します (強力な最新のシステムで最大約 32)。innodb_concurrency_tickets オプションの値を一般に 5000 程度に増やします。このオプションの組み合わせにより、InnoDB が一度に処理するスレッド数に制限を設定し、各スレッドがスワップアウトされるまでに大量の作業を実行できるようにするため、待機スレッドの数が少なくなり、過度なコンテキストスイッチングが発生せずに、操作を完了できます。

8.5.9 多くのテーブルのあるシステムに対する InnoDB の最適化

  • InnoDB は、起動後テーブルにはじめてアクセスされたときに、そのテーブルのインデックスカーディナリティー値を計算し、そのような値をテーブルに保存しません。データを多くのテーブルに分割しているシステムでは、このステップに大量の時間がかかることがあります。このオーバーヘッドは最初のテーブルオープン操作にのみ適用されるため、テーブルをあとで使用するためにウォームアップするには、SELECT 1 FROM tbl_name LIMIT 1 などのステートメントを発行して、起動後すぐにそれにアクセスします。

8.6 MyISAM テーブルの最適化

MyISAM ストレージエンジンは、テーブルロックによって同時更新を実行する機能を制限するため、読み取りが大半のデータや並列性の低い操作で最適に実行します。MySQL 5.6 では、MyISAM ではなく、InnoDB がデフォルトのストレージエンジンです。

8.6.1 MyISAM クエリーの最適化

MyISAM テーブルのクエリーを高速化するためのいくつかの一般的なヒント:

  • MySQL がクエリーをより適切に最適化できるようにするには、テーブルにデータがロードされたあとに、それに対して ANALYZE TABLE を使用するか、または myisamchk --analyze を実行します。これにより、同じ値がある平均行数を示す各インデックスパートの値を更新します。(一意のインデックスの場合、これは常に 1 です。)MySQL はこれを使用して、非定数式に基づいて、2 つのテーブルを結合する際に選択するインデックスを決定します。SHOW INDEX FROM tbl_name を使用し、Cardinality 値を調べることで、テーブル分析の結果を確認できます。myisamchk --description --verbose はインデックスの分布情報を示します。

  • インデックスに従ってインデックスとデータをソートするには、myisamchk --sort-index --sort-records=1 を使用します (インデックス 1 でソートすると仮定して)。インデックスに従って順番にすべての行を読み取りたいと考える一意のインデックスがある場合、これはクエリーを高速にする適切な方法です。この方法で大きなテーブルをはじめてソートするときは、長い時間がかかることがあります。

  • 頻繁に更新される MyISAM テーブルに対する複雑な SELECT クエリーを避け、リーダーとライターの競合のために発生するテーブルロックの問題を回避するようにしてください。

  • MyISAM は同時挿入をサポートしています。テーブルのデータファイルの途中に空きブロックがなければ、ほかのスレッドがテーブルから読み取るのと同時に新しい行をそれに INSERT できます。これを実行できることが重要な場合、行の削除を避けるようにテーブルを使用することを考慮してください。別の可能性は、テーブルの大量の行を削除したあとに OPTIMIZE TABLE を実行して、テーブルをデフラグすることです。この動作は concurrent_insert 変数の設定によって変更されます。行を削除したテーブルにも新しい行を強制的に追加 (したがって同時挿入を許可) できます。セクション8.10.3「同時挿入」を参照してください。

  • 頻繁に変更される MyISAM テーブルでは、すべての可変長カラム (VARCHARTEXT、および BLOB) を避けるようにします。テーブルに 1 つしか可変長カラムが含まれていない場合でも、テーブルは動的行フォーマットを使用します。第15章「代替ストレージエンジンを参照してください。

  • 一般に、行が大きくなるためだけに、1 つのテーブルを異なるテーブルに分割することは有益ではありません。行へのアクセスで、もっとも大きくパフォーマンスに打撃を与えるものは、行の先頭バイトを見つけるために必要なディスクシークです。データが見つかったあとは、ほとんどの最新のディスクで、大多数のアプリケーションに十分な速度で行全体を読み取ることができます。テーブルを分割することがかなりの違いをもたらす状況は、固定の行サイズに変更できる動的行フォーマットを使用している MyISAM テーブルの場合か、またはテーブルを著しく頻繁にスキャンする必要があるが、ほとんどのカラムには必要でない場合だけです。第15章「代替ストレージエンジンを参照してください。

  • 通常 expr1expr2、... の順で行を取得する場合は、ALTER TABLE ... ORDER BY expr1, expr2, ... を使用します。テーブルを大幅に変更したあとにこのオプションを使用することで、パフォーマンスを向上できることがあります。

  • 多数の行の情報に基づいたカウントなど、結果を頻繁に計算する必要がある場合、新しいテーブルを導入し、リアルタイムでカウンタを更新する方が望ましいことがあります。次のような形式の更新はきわめて高速です。

    UPDATE tbl_name SET count_col=count_col+1 WHERE key_col=constant;

    これは、テーブルレベルのロック (単一ライターと複数リーダー) しかない MyISAM のような MySQL ストレージエンジンを使用する場合に、きわめて重要です。また、この場合に行ロックマネージャーが実行する必要があることは少ないため、ほとんどのデータベースシステムでパフォーマンスが向上します。

  • データが書き込まれるタイミングを知る必要がない場合は、MyISAM (またはその他のサポートされる非トランザクションテーブル) に INSERT DELAYED を使用します。多くの行を 1 回のディスク書き込みで書き込むことができるため、これにより、挿入の全体の影響が少なくなります。

    注記

    MySQL 5.6.6 現在、INSERT DELAYED は非推奨であり、将来のリリースで削除されます。代わりに INSERT (DELAYED を付けない) を使用してください。

  • 定期的に OPTIMIZE TABLE を使用して、動的フォーマット MyISAM テーブルの断片化を防ぎます。セクション15.2.3「MyISAM テーブルのストレージフォーマット」を参照してください。

  • DELAY_KEY_WRITE=1 テーブルオプションを使用して MyISAM テーブルを宣言すると、テーブルが閉じられるまで、ディスクにフラッシュされないため、インデックスの更新が速くなります。短所は、そのようなテーブルが開いている間に、何かによってサーバーが強制終了させられた場合に、--myisam-recover-options オプションを使用してサーバーを実行するか、サーバーを再起動する前に myisamchk を実行して、テーブルが問題ないことを確認する必要があることです。(ただし、この場合でも、キー情報は常にデータ行から生成できるため、DELAY_KEY_WRITE を使用しても何も失われないはずです。)

  • MyISAM インデックスでは、文字列の前後のスペースが自動的に圧縮されます。セクション13.1.13「CREATE INDEX 構文」を参照してください。

  • アプリケーションでクエリーや応答をキャッシュしてから、多くの挿入や更新をまとめて実行することによって、パフォーマンスを向上できます。この操作中にテーブルをロックすることで、すべての更新後にインデックスキャッシュが 1 回だけフラッシュされます。同様の結果を得るために、MySQL のクエリーキャッシュを利用することもできます。セクション8.9.3「MySQL クエリーキャッシュ」を参照してください。

8.6.2 MyISAM テーブルの一括データロード

これらのパフォーマンスのヒントは、セクション8.2.2.1「INSERT ステートメントの速度」の高速挿入の一般的なガイドラインを補足するものです。

  • 複数のクライアントが大量の行を挿入する場合のパフォーマンスを向上するには、INSERT DELAYED ステートメントを使用します。セクション13.2.5.2「INSERT DELAYED 構文」を参照してください。この技法は、MyISAM およびその他の一部のストレージエンジンには有効ですが、InnoDB には機能しません。

    注記

    MySQL 5.6.6 現在、INSERT DELAYED は非推奨であり、将来のリリースで削除されます。代わりに INSERT (DELAYED を付けない) を使用してください。

  • MyISAM テーブルでは、データファイルの途中に削除された行がない場合、SELECT ステートメントの実行中に同時に、同時挿入を使用して行を追加できます。セクション8.10.3「同時挿入」を参照してください。

  • 少しの追加作業で、MyISAM テーブルに多数のインデックスがある場合に、テーブルの LOAD DATA INFILE の実行をさらに高速化できます。次の手順を使用します。

    1. FLUSH TABLES ステートメントまたは mysqladmin flush-tables コマンドを実行します。

    2. テーブルのインデックスのすべての使用を削除するには、myisamchk --keys-used=0 -rq /path/to/db/tbl_name を使用します。

    3. LOAD DATA INFILE を使用して、テーブルにデータを挿入します。これはインデックスを更新しないため、非常に高速です。

    4. 今後、テーブルから読み取りだけをする場合は、myisampack を使用してそれを圧縮します。セクション15.2.3.3「圧縮テーブルの特徴」を参照してください。

    5. myisamchk -rq /path/to/db/tbl_name を使用してインデックスを再作成します。これにより、ディスクに書き込む前にメモリー内にインデックスツリーを作成し、大量のディスクシークを回避するため、LOAD DATA INFILE 時のインデックスの更新よりかなり高速になります。結果のインデックスツリーは完全にバランスも取れています。

    6. FLUSH TABLES ステートメントまたは mysqladmin flush-tables コマンドを実行します。

    データを挿入する MyISAM テーブルが空の場合は、LOAD DATA INFILE は先述の最適化を自動的に実行します。自動の最適化と明示的に手順を使用することの主な違いは、サーバーに LOAD DATA INFILE ステートメントの実行時に、インデックスの再作成で割り当てさせることができる量より、myisamchk ではインデックスの作成のためにはるかに多くの一時メモリーを割り当てることができることです。

    myisamchk の代わりに次のステートメントを使用して、MyISAM テーブルの一意でないインデックスを無効または有効にすることもできます。これらのステートメントを使用すると、FLUSH TABLE 操作をスキップできます。

    ALTER TABLE tbl_name DISABLE KEYS;
    ALTER TABLE tbl_name ENABLE KEYS;
  • 非トランザクションテーブルに対して、複数ステートメントで実行される INSERT 操作を高速化するには、テーブルをロックします。

    LOCK TABLES a WRITE;
    INSERT INTO a VALUES (1,23),(2,34),(4,33);
    INSERT INTO a VALUES (8,26),(6,29);
    ...
    UNLOCK TABLES;

    これは、すべての INSERT ステートメントの完了後に、インデックスバッファーが 1 回だけディスクにフラッシュされるため、パフォーマンスにメリットがあります。通常は、INSERT ステートメントの数と同じだけ、インデックスバッファーのフラッシュが行われます。すべての行を 1 つの INSERT で挿入できる場合は、明示的なロックステートメントは必要ありません。

    ロックは複数接続テストの合計時間も短縮しますが、個々の接続がロックを待機するため、それらの最大待機時間は長くなることがあります。次のように 5 台のクライアントが同時に挿入の実行を試みるとします。

    • 接続 1 は 1000 回の挿入を実行します

    • 接続 2、3、および 4 は 1 回の挿入を実行します

    • 接続 5 は 1000 回の挿入を実行します

    ロックを使用しない場合、接続 2、3、および 4 は 1 と 5 の前に終了します。ロックを使用した場合、接続 2、3、および 4 は 1 または 5 の前に終了しない可能性がありますが、合計時間は約 40% 高速化するはずです。

    MySQL では、INSERTUPDATE、および DELETE 操作はきわめて高速ですが、約 5 回超の連続した挿入や更新を実行するすべての操作の周囲にロックを追加することによって、全体のパフォーマンスを向上できます。著しく多くの連続した挿入を実行する場合、LOCK TABLES のあとにときどき (1,000 行程度ごとに) UNLOCK TABLES を実行して、ほかのスレッドのテーブルへのアクセスを許可できます。これによってもパフォーマンスの向上が得られます。

    上記の戦略を使用した場合でも、データのロードには LOAD DATA INFILE より INSERT の方がはるかに遅くなります。

  • MyISAM テーブルの LOAD DATA INFILEINSERT の両方に対してパフォーマンスを向上するには、key_buffer_size システム変数を増やして、キーキャッシュを拡張します。セクション8.11.2「サーバーパラメータのチューニング」を参照してください。

8.6.3 REPAIR TABLE ステートメントの速度

MyISAM テーブルの REPAIR TABLE は、修復操作に myisamchk を使用することと似ており、同じパフォーマンス最適化の一部が適用されます。

  • myisamchk にはメモリー割り当てを制御する変数があります。セクション4.6.3.6「myisamchk メモリー使用量」に説明するように、これらの変数を設定してパフォーマンスを向上できることがあります。

  • REPAIR TABLE では、同じ原則が適用されますが、修復はサーバーによって実行されるため、myisamchk 変数の代わりに、サーバーシステム変数を設定します。さらに、メモリー割り当て変数の設定に加えて、myisam_max_sort_file_size システム変数を増やすと、修復で高速の filesort 方法が使用され、キーキャッシュ方法による遅い修復が避けられる可能性が高くなります。テーブルファイルのコピーを保持できるだけの十分な空き領域があることを確認したら、システムの最大ファイルサイズに変数を設定します。元のテーブルファイルを格納しているファイルシステムで、空き領域が使用できる必要があります。

次のオプションを使用して、そのメモリー割り当て変数を設定して、myisamchk テーブル修復操作が実行されたとします。

--key_buffer_size=128M --myisam_sort_buffer_size=256M
--read_buffer_size=64M --write_buffer_size=64M

それらの myisamchk 変数の一部はサーバーシステム変数に対応します。

myisamchk 変数システム変数
key_buffer_sizekey_buffer_size
myisam_sort_buffer_sizemyisam_sort_buffer_size
read_buffer_sizeread_buffer_size
write_buffer_sizenone

各サーバーシステム変数は実行時に設定でき、それらの一部 (myisam_sort_buffer_sizeread_buffer_size) にはグローバル値に加えてセッション値もあります。セッション値を設定することで、現在のセッションへの変更の影響を制限し、ほかのユーザーに影響しません。グローバルのみの変数 (key_buffer_sizemyisam_max_sort_file_size) を変更すると、ほかのユーザーにも影響します。key_buffer_size の場合、バッファーがそれらのユーザーと共有されることを考慮しておく必要があります。たとえば、myisamchkkey_buffer_size 変数を 128M バイトに設定した場合、対応する key_buffer_size システム変数をそれより大きく設定し (それがすでに大きく設定されていない場合)、ほかのセッションのアクティビティーによるキーバッファーの使用を許可できます。ただし、グローバルキーバッファーサイズを変更すると、バッファーが無効になり、ディスク I/O が増加して、ほかのセッションが遅くなります。この問題を回避する代替策は、個別のキーキャッシュを使用し、それを修復対象のテーブルのインデックスに割り当て、修復が完了したら、その割り当てを解除することです。セクション8.9.2.2「複合キーキャッシュ」を参照してください。

先述の説明に基づいて、REPAIR TABLE 操作は、次のように実行して、myisamchk コマンドに似た設定を使用できます。ここでは、個別の 128M バイトのキーバッファーが割り当てられ、ファイルシステムは 100G バイト以上のファイルサイズを許可するものとします。

SET SESSION myisam_sort_buffer_size = 256*1024*1024;
SET SESSION read_buffer_size = 64*1024*1024;
SET GLOBAL myisam_max_sort_file_size = 100*1024*1024*1024;
SET GLOBAL repair_cache.key_buffer_size = 128*1024*1024;
CACHE INDEX tbl_name IN repair_cache;
LOAD INDEX INTO CACHE tbl_name;
REPAIR TABLE tbl_name ;
SET GLOBAL repair_cache.key_buffer_size = 0;

グローバル変数を変更するが、ほかのユーザーへの影響を最小にするため、REPAIR TABLE 操作の間にのみ実行するようにしたい場合、その値をユーザー変数に保存して、あとでそれをリストアします。例:

SET @old_myisam_sort_buffer_size = @@GLOBAL.myisam_max_sort_file_size;
SET GLOBAL myisam_max_sort_file_size = 100*1024*1024*1024;
REPAIR TABLE tbl_name ;
SET GLOBAL myisam_max_sort_file_size = @old_myisam_max_sort_file_size;

REPAIR TABLE に影響するシステム変数は、変数をデフォルトで有効にしたい場合、サーバーの起動時にグローバルに設定できます。たとえば、次の行をサーバーの my.cnf ファイルに追加します。

[mysqld]
myisam_sort_buffer_size=256M
key_buffer_size=1G
myisam_max_sort_file_size=100G

これらの設定には read_buffer_size は含まれません。read_buffer_size をグローバルに大きな値に設定すると、すべてのセッションに対してそれが実行され、多くの同時セッションのあるサーバーに過剰なメモリーが割り当てられるため、パフォーマンスが低下する可能性があります。

8.7 MEMORY テーブルの最適化

頻繁にアクセスされ、読み取り専用かめったに更新されない非クリティカルデータに MEMORY テーブルを使用することを考慮します。現実的なワークロードで、同等の InnoDB または MyISAM テーブルに対してアプリケーションのベンチマークを実行し、追加のパフォーマンスが、データの損失のリスクやアプリケーションの起動時にディスクベースのテーブルからデータをコピーすることのオーバーヘッドに値するかを確認します。

MEMORY テーブルで最高のパフォーマンスを得るには、各テーブルに対するクエリーの種類を調査し、関連付けられた各インデックスに使用する B ツリーインデックスまたはハッシュインデックスのいずれかの種類を指定します。CREATE INDEX ステートメントで、句 USING BTREE または USING HASH を使用します。B ツリーインデックスは、>BETWEEN などの操作によって、greater-than または less-than の比較を実行するクエリーで高速です。ハッシュインデックスは、= 演算子によって単一の値、または IN 演算子によって制限された値のセットをルックアップするクエリーでのみ高速です。USING BTREE が多くの場合にデフォルトの USING HASH より適切な選択である理由については、セクション8.2.1.20「フルテーブルスキャンを回避する方法」を参照してください。さまざまな種類の MEMORY インデックスの実装の詳細については、セクション8.3.8「B ツリーインデックスとハッシュインデックスの比較」を参照してください。

8.8 クエリー実行プランの理解

WHERE 句内のテーブル、カラム、インデックス、および条件の詳細に応じて、MySQL オプティマイザは SQL クエリーに含まれるルックアップを効率的に実行するための多くの技法を考慮します。巨大なテーブルに対するクエリーは、すべての行を読み取らなくても実行でき、複数のテーブルを含む結合は、行のすべての組み合わせを比較しなくても実行できます。オプティマイザがもっとも効率的なクエリーを実行するために選択する操作のセットは、クエリー実行プランと呼ばれ、EXPLAIN プランとも呼ばれます。目的は、クエリーが適切に最適化されていることを示す EXPLAIN プランの側面を認識し、非効率的な操作が見られた場合に、プランを改善するための SQL 構文とインデックス設定技法を学ぶことです。

8.8.1 EXPLAIN によるクエリーの最適化

EXPLAIN ステートメントを使用して、MySQL がステートメントを実行する方法に関する情報を取得できます。

  • MySQL 5.6.3 現在、EXPLAIN に使用できる説明可能なステートメントは、SELECTDELETEINSERTREPLACE、および UPDATE です。MySQL 5.6.3 より前では、SELECT が唯一の説明可能なステートメントです。

  • 説明可能なステートメントで EXPLAIN を使用すると、MySQL は、オプティマイザからのステートメント実行プランに関する情報を表示します。つまり、MySQL はテーブルがどのように、どんな順番で結合されているかに関する情報を含む、ステートメントを処理する方法を説明します。EXPLAIN を使用して、実行プラン情報を取得することについては、セクション8.8.2「EXPLAIN 出力フォーマット」を参照してください。

  • EXPLAIN EXTENDED を使用して、追加の実行プラン情報を取得できます。セクション8.8.3「EXPLAIN EXTENDED 出力フォーマット」を参照してください。

  • EXPLAIN PARTITIONS は、パーティション化されたテーブルを含むクエリーの調査に役立ちます。セクション19.3.5「パーティションに関する情報を取得する」を参照してください。

  • MySQL 5.6.3 現在、FORMAT オプションを使用して、出力フォーマットを選択できます。TRADITIONAL は表形式で出力を表示します。FORMAT オプションが存在しない場合、これはデフォルトです。JSON フォーマットは JSON フォーマットで情報を表示します。FORMAT = JSON を使用すると、出力には拡張されたパーティション情報が含まれます。

EXPLAIN によって、インデックスを使用して行を見つけることで、ステートメントが高速に実行されるように、テーブルにインデックスを追加するべき場所がわかります。また、EXPLAIN を使用して、オプティマイザがテーブルを最適な順序で結合しているかどうかを確認することもできます。SELECT ステートメントでテーブルが指定されている順序に対応する結合順序を使用するように、オプティマイザにヒントを提供するには、ステートメントを SELECT だけでなく、SELECT STRAIGHT_JOIN で始めます。(セクション13.2.9「SELECT 構文」を参照してください。)

インデックスが使われるはずであると思うタイミングでそれらが使われていない問題がある場合、ANALYZE TABLE を実行して、オプティマイザが行う選択に影響する可能性があるキーのカーディナリティーなどのテーブル統計を更新します。セクション13.7.2.1「ANALYZE TABLE 構文」を参照してください。

注記

EXPLAIN はテーブル内のカラムに関する情報を取得するためにも使用できます。EXPLAIN tbl_nameDESCRIBE tbl_name および SHOW COLUMNS FROM tbl_name と同義です。詳細については、セクション13.8.1「DESCRIBE 構文」およびセクション13.7.5.6「SHOW COLUMNS 構文」を参照してください。

8.8.2 EXPLAIN 出力フォーマット

EXPLAIN ステートメントは SELECT ステートメントの実行プランに関する情報を提供します。

EXPLAINSELECT ステートメントで使用される各テーブルに関する情報の行を返します。これは、MySQL がステートメントの処理中にテーブルを読み取る順番で、出力にテーブルを一覧表示します。MySQL は Nested Loop 結合メソッドを使用して、すべての結合を解決します。これは、MySQL が最初のテーブルから行を読み取り、次に 2 つめのテーブル、3 つめのテーブルというように、一致する行を見つけることを意味します。すべてのテーブルが処理されると、MySQL は選択したカラムを出力し、さらに一致する行があるテーブルが見つかるまで、テーブルリストを逆戻りします。次の行がテーブルから読み取られ、プロセスは次のテーブルに進みます。

EXTENDED キーワードを使用すると、EXPLAIN は、EXPLAIN ステートメントに続けて SHOW WARNINGS ステートメントを発行することで表示できる追加の情報を生成します。EXPLAIN EXTENDEDフィルタ処理されたカラムも表示します。セクション8.8.3「EXPLAIN EXTENDED 出力フォーマット」を参照してください。

注記

EXTENDED キーワードと PARTITIONS キーワードを、同じ EXPLAIN ステートメントで一緒に使用することはできません。

EXPLAIN 出力カラム

このセクションでは、EXPLAIN によって生成される出力カラムについて説明します。あとのセクションで、typeExtra カラムに関する追加情報を提供します。

EXPLAIN からの各出力行は 1 つのテーブルに関する情報を提供します。各行には、表8.1「EXPLAIN 出力カラム」で要約し、次の表に詳しく説明している値が格納されます。

表 8.1 EXPLAIN 出力カラム

カラム意味
idSELECT 識別子。
select_typeSELECT
table出力行のテーブル
partitions一致するパーティション
type結合型
possible_keys選択可能なインデックス
key実際に選択されたインデックス
key_len選択されたキーの長さ
refインデックスと比較されるカラム
rows調査される行の見積もり
filteredテーブル条件によってフィルタ処理される行の割合
Extra追加情報

  • id

    SELECT 識別子。これはクエリー内の SELECT の連番です。行がほかの行の和集合結果を参照する場合に、値は NULL になることがあります。この場合、table カラムには、<unionM,N> などの値が表示され、行が M および Nid 値のある行の和集合を参照していることが示されます。

  • select_type

    SELECT の種類で、次の表に示すもののいずれかになります。

    select_type意味
    SIMPLE単純な SELECT (UNION やサブクエリーを使用しません)
    PRIMARYもっとも外側の SELECT
    UNIONUNION 内の 2 つめ以降の SELECT ステートメント
    DEPENDENT UNIONUNION 内の 2 つめ以降の SELECT ステートメントで、外側のクエリーに依存します
    UNION RESULTUNION の結果。
    SUBQUERYサブクエリー内の最初の SELECT
    DEPENDENT SUBQUERYサブクエリー内の最初の SELECT で、外側のクエリーに依存します
    DERIVED派生テーブル SELECT (FROM 句内のサブクエリー)
    MATERIALIZED実体化されたサブクエリー
    UNCACHEABLE SUBQUERY結果をキャッシュできず、外側のクエリーの行ごとに再評価される必要があるサブクエリー
    UNCACHEABLE UNIONキャッシュ不可能なサブクエリー (UNCACHEABLE SUBQUERY を参照してください) に属する UNION 内の 2 つめ以降の SELECT

    DEPENDENT は一般に、相関サブクエリーの使用を示します。セクション13.2.10.7「相関サブクエリー」を参照してください。

    DEPENDENT SUBQUERY の評価は UNCACHEABLE SUBQUERY の評価とは異なります。DEPENDENT SUBQUERY の場合、その外部コンテキストの変数の異なる値の各セットにつき、一回だけサブクエリーが再評価されます。UNCACHEABLE SUBQUERY の場合、外部コンテキストの行ごとにサブクエリーが再評価されます。

    サブクエリーのキャッシュ可能性は、クエリーキャッシュへのクエリー結果のキャッシュ (これについてはセクション8.9.3.1「クエリーキャッシュの動作」で説明しています) と異なります。サブクエリーのキャッシュは、クエリー実行中に行われ、クエリーキャッシュは、クエリーの実行が終了したあとにのみ、結果を格納するために使用されます。

  • table

    出力の行で参照しているテーブルの名前。これも次のいずれかの値になることがあります。

    • <unionM,N>: 行は M および Nid 値のある行の和集合を参照しています。

    • <derivedN>: 行は Nid 値のある行の派生テーブル結果を参照しています。派生テーブルは、たとえば FROM 句内のサブクエリーの結果などになります。

    • <subqueryN>: 行は Nid 値のある行の実体化されたサブクエリーの結果を参照しています。セクション8.2.1.18.2「サブクエリー実体化によるサブクエリーの最適化」を参照してください。

  • partitions

    クエリーでレコードが照合されるパーティション。このカラムは、PARTITIONS キーワードが使用されている場合にのみ表示されます。パーティション化されていないテーブルの場合、この値は NULL です。セクション19.3.5「パーティションに関する情報を取得する」を参照してください。

  • type

    結合型。さまざまな型の説明については、「EXPLAIN 結合型」を参照してください。

  • possible_keys

    possible_keys カラムは、MySQL がこのテーブル内の行の検索に使用するために選択できるインデックスを示します。このカラムは EXPLAIN の出力に表示されたテーブルの順序にまったく依存しません。つまり、possible_keys のキーの一部は、生成されたテーブルの順序で実際に使用できないことがあります。

    このカラムが NULL の場合は、関連するインデックスがありません。この場合、WHERE 句を調査して、それがインデックス設定に適したカラムを参照しているかどうかをチェックすることで、クエリーのパフォーマンスを向上できることがあります。その場合は、適切なインデックスを作成し、再度 EXPLAIN でクエリーをチェックします。セクション13.1.7「ALTER TABLE 構文」を参照してください。

    テーブルにあるインデックスを確認するには、SHOW INDEX FROM tbl_name を使用します。

  • key

    key カラムは、MySQL が実際に使用することを決定したキー (インデックス) を示します。MySQL が行をルックアップするために、いずれかの possible_keys インデックスを使用することを決定した場合、キー値としてそのインデックスが一覧表示されます。

    keypossible_keys 値に存在しないインデックスを指定している可能性があります。これは possible_keys インデックスのどれも行のルックアップに適していない場合に発生する可能性がありますが、クエリーによって選択されるすべてのカラムはほかのインデックスのカラムになります。つまり、指定されたインデックスは選択されたカラムをカバーするため、取得する行を決定するために使用されませんが、インデックススキャンはデータ行スキャンよりも効率的です。

    InnoDB は各セカンダリインデックスとともに主キー値を保存するため、InnoDB では、クエリーで主キーも選択している場合でも、セカンダリインデックスで選択されたカラムをカバーしている可能性があります。keyNULL の場合、MySQL はクエリーをより効率的に実行するために使用するインデックスを見つけられませんでした。

    MySQL で possible_keys カラムに示されたインデックスを強制的に使用させるか、無視させるには、クエリーで FORCE INDEXUSE INDEX、または IGNORE INDEX を使用します。セクション13.2.9.3「インデックスヒントの構文」を参照してください。

    MyISAM テーブルと NDB テーブルの場合、ANALYZE TABLE を実行することで、オプティマイザがより適切なインデックスを選択するために役立ちます。NDB テーブルの場合、これにより、分散されたプッシュダウン結合のパフォーマンスも向上します。MyISAM テーブルの場合、myisamchk --analyzeANALYZE TABLE と同じことを実行します。セクション7.6「MyISAM テーブルの保守とクラッシュリカバリ」を参照してください。

  • key_len

    key_len カラムは、MySQL が使用することを決定したキーの長さを示します。key カラムに NULL と示されている場合、この長さは NULL になります。key_len の値によって、MySQL が実際に使用するマルチパートキーのパート数を判断できます。

  • ref

    ref カラムは、テーブルから行を選択するために、key カラムに指定されたインデックスに対して比較されるカラムまたは定数を示します。

    値が func の場合、使用される値は、特定の関数の結果です。どの関数か確認するには、EXPLAIN EXTENDED のあとに SHOW WARNINGS を付けて使用します。関数は、実際には算術演算子などの演算子である場合があります。

  • rows

    rows カラムは、MySQL がクエリーを実行するために調査する必要があると考える行数を示します。

    InnoDB テーブルの場合、これは推定値であり、常に正確ではないことがあります。

  • filtered

    filtered カラムは、テーブル条件によってフィルタ処理されるテーブル行の推定の割合を示します。つまり、rows は調査される推定の行数を示し、rows × filtered / 100 が前のテーブルと結合される行数を示します。EXPLAIN EXTENDED を使用すると、このカラムが表示されます。

  • Extra

    このカラムには、MySQL がクエリーを解決する方法に関する追加情報が含まれます。さまざまな値の説明については、「EXPLAIN の追加情報」を参照してください。

EXPLAIN 結合型

EXPLAIN 出力の type カラムには、テーブルの結合方法が示されます。次のリストに、もっとも適切な型からもっとも不適切な型の順番で並べた結合型を示します。

  • system

    テーブルには行が 1 つしかありません (= system テーブル)。これは、const 結合型の特殊なケースです。

  • const

    テーブルには、一致するレコードが最大で 1 つあり、クエリーの開始時に読み取られます。行が 1 つしかないため、この行のカラムの値は、オプティマイザの残りによって定数とみなされることがあります。const テーブルは、1 回しか読み取られないため、非常に高速です。

    constPRIMARY KEY または UNIQUE インデックスのすべてのパートを定数値と比較する場合に使用されます。次のクエリーでは、tbl_nameconst テーブルとして使用できます。

    SELECT * FROM tbl_name WHERE primary_key=1;
    SELECT * FROM tbl_name WHERE primary_key_part1=1 AND primary_key_part2=2;
  • eq_ref

    前のテーブルの行の組み合わせごとに、このテーブルから 1 行ずつ読み取られます。systemconst 型以外で、これは最適な結合型です。これは、結合でインデックスのすべてのパートが使用されており、インデックスが PRIMARY KEY または UNIQUE NOT NULL インデックスである場合に使用されます。

    eq_ref は、= 演算子を使用して比較されるインデックス設定されたカラムに使用できます。比較値は、定数またはこのテーブルより前に読み取られたテーブルのカラムを使用する式を指定できます。次の例では、MySQL は eq_ref 結合を使用して、ref_table を処理できます。

    SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;
    SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
  • ref

    前のテーブルの行の組み合わせごとに、一致するインデックス値を持つすべての行がこのテーブルから読み取られます。ref は、結合でキーの左端のプリフィクスのみが使用される場合、またはキーが PRIMARY KEYUNIQUE インデックスではない場合 (つまり、結合で、キー値に基づいて単一の行を選択できない場合) に使用されます。使用されているキーがほんの数行にしか一致しない場合、これは適切な結合型です。

    ref は、= または <=> 演算子を使用して比較されるインデックス設定されたカラムに使用できます。次の例では、MySQL は ref 結合を使用して、ref_table を処理できます。

    SELECT * FROM ref_table WHERE key_column=expr;
    SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;
    SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
  • fulltext

    結合は FULLTEXT インデックスを使用して実行されます。

  • ref_or_null

    この結合型は、ref と似ていますが、MySQL が NULL 値を含む行の追加検索を実行することが追加されます。この結合型の最適化は、ほとんどの場合に、サブクエリーの解決で使用されます。次の例では、MySQL は ref_or_null 結合を使用して、ref_table を処理できます。

    SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;

    セクション8.2.1.8「IS NULL の最適化」を参照してください。

  • index_merge

    この結合型はインデックスマージ最適化が使用されたことを示します。この場合、出力行の key カラムには使用されたインデックスのリストが含まれ、key_len には使用されたインデックスの最長キーパートのリストが含まれます。詳細については、セクション8.2.1.4「インデックスマージの最適化」を参照してください。

  • unique_subquery

    この型は、次の形式の IN サブクエリーの ref を置き換えます。

    value IN (SELECT primary_key FROM single_table WHERE some_expr)

    unique_subquery は、効率化のため、サブクエリーを完全に置き換える単なるインデックスルックアップ関数です。

  • index_subquery

    この結合型は unique_subquery に似ています。IN サブクエリーを置き換えますが、次の形式のサブクエリー内の一意でないインデックスに対して機能します。

    value IN (SELECT key_column FROM single_table WHERE some_expr)
  • range

    行を選択するためのインデックスを使用して、特定の範囲にある行のみが取得されます。出力行の key カラムは、使用されるインデックスを示します。key_len には使用された最長のインデックスパートが格納されます。この型の ref カラムは NULL です。

    range は、=<>>>=<<=IS NULL<=>BETWEEN、または IN() 演算子のいずれかを使用して、キーカラムを定数と比較する場合に使用できます。

    SELECT * FROM tbl_name WHERE key_column = 10;
    SELECT * FROM tbl_name WHERE key_column BETWEEN 10 and 20;
    SELECT * FROM tbl_name WHERE key_column IN (10,20,30);
    SELECT * FROM tbl_name WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
  • index

    index 結合型は、インデックスツリーがスキャンされることを除いて、ALL と同じです。これは 2 つの方法で行われます。

    • インデックスがクエリーのカバリングインデックスで、使用すると、テーブルから必要なすべてのデータを満たすことができる場合、インデックスツリーのみがスキャンされます。この場合、Extra カラムには Using index と示されます。インデックスのサイズは通常テーブルデータより小さいため、インデックスのみのスキャンは通常、ALL より高速です。

    • フルテーブルスキャンは、インデックスからの読み取りを使用して、インデックス順でデータ行をルックアップして実行されます。Extra カラムに Uses index が表示されません。

    MySQL は、クエリーで単一のインデックスの一部であるカラムのみが使用されている場合に、この結合型を使用できます。

  • ALL

    フルテーブルスキャンは、前のテーブルの行の組み合わせごとに実行されます。これは、通常テーブルが const とマークされていない最初のテーブルである場合には適しておらず、通常ほかのすべてのケースで著しく不適切です。通常、定数値または以前のテーブルからのカラム値に基づいて、テーブルからの行の取得を可能にするインデックスを追加することで、ALL を回避できます。

EXPLAIN の追加情報

EXPLAIN 出力の Extra カラムには、MySQL がクエリーを解決する方法に関する追加情報が含まれます。次のリストに、このカラムに表示される可能性のある値について説明します。クエリーを可能なかぎり高速にしたい場合は、Using filesort および Using temporaryExtra 値に注意します。

  • Child of 'table' pushed join@1

    このテーブルは、NDB カーネルにプッシュダウンできる結合内の table の子として参照されます。MySQL Cluster で、プッシュダウンされた結合が有効な場合にのみ適用されます。詳細と例については、ndb_join_pushdown サーバーシステム変数の説明を参照してください。

  • const row not found

    SELECT ... FROM tbl_name などのクエリーの場合、テーブルは空でした。

  • Deleting all rows

    DELETE に対し、一部のストレージエンジン (MyISAM など) は簡単で高速にすべての行テーブルを削除するハンドラメソッドをサポートしています。この Extra 値は、エンジンでこの最適化が使用された場合に表示されます。

  • Distinct

    MySQL は個別の値を検索するため、最初に一致する行が見つかったら、現在の行の組み合わせについてのそれ以上の行の検索を停止します。

  • FirstMatch(tbl_name)

    tbl_name には、準結合 FirstMatch 結合ショートカット戦略が使用されます。

  • Full scan on NULL key

    これは、オプティマイザがインデックスルックアップアクセスメソッドを使用できない場合の代替の戦略として、サブクエリーの最適化で行われます。

  • Impossible HAVING

    HAVING 句は常に false で、どの行も選択できません。

  • Impossible WHERE

    WHERE 句は常に false で、どの行も選択できません。

  • Impossible WHERE noticed after reading const tables

    MySQL はすべての const (および system) テーブルを読み取り、WHERE 句が常に false であることを通知します。

  • LooseScan(m..n)

    準結合 LooseScan 戦略が使用されます。mn はキーパート番号です。

  • MaterializeScan

    MySQL 5.6.7 より前では、これは単一の実体化された一時テーブルの使用を示します。Scan が存在する場合、テーブルの読み取りに一時テーブルインデックスは使用されません。そうでない場合は、インデックスルックアップが使用されます。さらに、Start materialize エントリも参照してください。

    MySQL 5.6.7 現在、実体化は、MATERIALIZEDselect_type 値のある行と、<subqueryN>table 値のある行によって示されます。

  • No matching min/max row

    SELECT MIN(...) FROM ... WHERE condition などのクエリーの条件を満たす行がありません。

  • no matching row in const table

    結合のあるクエリーで、空のテーブルまたは一意のインデックス条件を満足する行がないテーブルがありました。

  • No matching rows after partition pruning

    DELETE または UPDATE に対し、オプティマイザはパーティションのプルーニング後に削除または更新するものが何も見つかりませんでした。それは、SELECT ステートメントの Impossible WHERE に意味が似ています。

  • No tables used

    クエリーに FROM 句がないか、FROM DUAL 句があります。

    INSERT または REPLACE ステートメントで、SELECT パートがない場合に、EXPLAIN にこの値が表示されます。たとえば、EXPLAIN INSERT INTO t VALUES(10) に対して、それは EXPLAIN INSERT INTO t SELECT 10 FROM DUAL と同等であるために表示されます。

  • Not exists

    MySQL はクエリーに対する LEFT JOIN 最適化を実行でき、LEFT JOIN 条件に一致する 1 つの行が見つかったら、前の行の組み合わせについて、このテーブルでそれ以上の行を調査しません。これは、このように最適化できるクエリーの種類の例です。

    SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;

    t2.idNOT NULL で定義されているとします。この場合、MySQL は t1 をスキャンし、t1.id の値を使用して t2 内の行をルックアップします。MySQL が t2 内に一致する行を見つけた場合、t2.idNULL にならないことがわかっているため、同じ id 値を持つ t2 内の残りの行をスキャンしません。つまり、t1 の各行について、MySQL は、t2 内の実際に一致する行数にかかわらず、t2 内の単一のルックアップのみを実行する必要があります。

  • Range checked for each record (index map: N)

    MySQL は使用に適したインデックスを見つけられませんでしたが、前のテーブルからのカラム値がわかったあとに、いくつかのインデックスが使用できることがわかりました。以前のテーブルの行の組み合わせごとに、MySQL は range または index_merge アクセスメソッドを使用して、行を取得できるかどうかをチェックします。これは、非常に高速ではありませんが、インデックスがまったくない結合の実行より高速です。前のテーブルのすべてのカラム値がわかっており、定数とみなされることを除き、適用基準は、セクション8.2.1.3「range の最適化」セクション8.2.1.4「インデックスマージの最適化」で説明されているとおりです。

    インデックスは、テーブルの SHOW INDEX に示される同じ順序で 1 から番号付けされます。インデックスマップ値 N は、候補となるインデックスを示すビットマスク値です。たとえば、0x19 (2 進数の 11001) の値は、インデックス 1、4、および 5 が考慮されることを示します。

  • Scanned N databases

    これは、セクション8.2.4「INFORMATION_SCHEMA クエリーの最適化」に説明するように、サーバーが INFORMATION_SCHEMA テーブルのクエリーを処理する際に実行するディレクトリスキャンの数を示します。N の値は 0、1、または all です。

  • Select tables optimized away

    クエリーにはすべてインデックスを使用して解決された集約関数 (MIN()MAX())、または COUNT(*) のみが含まれていますが、GROUP BY 句は含まれていませんでした。オプティマイザは 1 行のみを返すべきであると判断しました。

  • Skip_open_tableOpen_frm_onlyOpen_trigger_onlyOpen_full_table

    これらの値は、セクション8.2.4「INFORMATION_SCHEMA クエリーの最適化」に説明するように、INFORMATION_SCHEMA テーブルに対するクエリーに適用するファイルオープン最適化を示します。

    • Skip_open_table: テーブルファイルを開く必要はありません。データベースディレクトリをスキャンすることによって、クエリー内ですでに情報を使用できるようになっています。

    • Open_frm_only: テーブルの .frm ファイルのみを開く必要があります。

    • Open_trigger_only: テーブルの .TRG ファイルのみを開く必要があります。

    • Open_full_table: 最適化されていない情報のルックアップ。.frm.MYD、および .MYI ファイルを開く必要があります。

  • Start materializeEnd materializeScan

    MySQL 5.6.7 より前では、これは複数の実体化された一時テーブルの使用を示します。Scan が存在する場合、テーブルの読み取りに一時テーブルインデックスは使用されません。そうでない場合は、インデックスルックアップが使用されます。さらに、Materialize エントリも参照してください。

    MySQL 5.6.7 現在、実体化は、MATERIALIZEDselect_type 値のある行と、<subqueryN>table 値のある行によって示されます。

  • Start temporaryEnd temporary

    これは、準結合重複除去戦略の一時テーブルの使用を示します。

  • unique row not found

    SELECT ... FROM tbl_name などのクエリーの場合に、テーブルに UNIQUE インデックスまたは PRIMARY KEY の条件を満たす行がありません。

  • Using filesort

    MySQL はソート順で行を取得する方法を見つけるために、追加のパスを実行する必要があります。ソートは、結合型に従ってすべての行を進み、ソートキーと WHERE 句に一致するすべての行について行へのポインタを格納して実行されます。次にキーがソートされ、ソート順で行が取得されます。セクション8.2.1.15「ORDER BY の最適化」を参照してください。

  • Using index

    実際の行を読み取るための追加のシークを実行する必要がなく、インデックスツリーの情報のみを使用して、テーブルからカラム情報が取得されます。この戦略は、クエリーで単一のインデックスの一部であるカラムのみを使用している場合に使用できます。

    Extra カラムに Using where とも示されている場合、キー値のルックアップを実行するためにインデックスが使用されていることを意味します。Using where がない場合、オプティマイザはインデックスを読み取って、データ行の読み取りを回避できますが、それをルックアップに使用していません。たとえば、インデックスがクエリーのカバリングインデックスである場合、オプティマイザはそれをルックアップに使用せずにそれをスキャンできます。

    ユーザー定義のクラスタ化されたインデックスを持つ InnoDB テーブルの場合、そのインデックスは Extra カラムに Using index がない場合でも使用できます。これは、typeindexkeyPRIMARY の場合です。

  • Using index condition

    インデックスタプルにアクセスし、まずそれらをテストして、すべてのテーブル行を読み取るかどうかを判断することによって、テーブルが読み取られます。このように、必要でないかぎり、すべてのテーブル行の読み取りを遅延 (プッシュダウン) するためにインデックス情報が使用されます。セクション8.2.1.6「インデックスコンディションプッシュダウンの最適化」を参照してください。

  • Using index for group-by

    Using index テーブルアクセスメソッドと同様に、Using index for group-by は MySQL が、実際のテーブルへの追加のディスクアクセスをせずに、GROUP BY または DISTINCT クエリーのすべてのカラムを取得するために使用できるインデックスを見つけたことを示します。さらに、各グループに対して、少数のインデックスエントリだけが読み取られるように、インデックスがもっとも効率的に使われます。詳細については、セクション8.2.1.16「GROUP BY の最適化」を参照してください。

  • Using join buffer (Block Nested Loop)Using join buffer (Batched Key Access)

    初期の結合からのテーブルは、部分ごとに結合バッファーに読み込まれ、それらの行がバッファーから使用されて、現在のテーブルとの結合が実行されます。(Block Nested Loop) は Block Nested Loop アルゴリズムの使用を示し、(Batched Key Access) は Batched Key Access アルゴリズムの使用を示します。つまり、EXPLAIN 出力の前の行のテーブルからのキーがバッファリングされ、Using join buffer が表示された行によって表されるテーブルから、一致する行が一括してフェッチされます。

  • Using MRR

    テーブルは Multi-Range Read 最適化戦略を使用して読み取られます。セクション8.2.1.13「Multi-Range Read の最適化」を参照してください。

  • Using sort_union(...)Using union(...)Using intersect(...)

    これらは index_merge 結合型でインデックススキャンがどのようにマージされるかを示しています。セクション8.2.1.4「インデックスマージの最適化」を参照してください。

  • Using temporary

    クエリーを解決するために、MySQL は結果を保持する一時テーブルを作成する必要があります。これは一般に、クエリーに、カラムを異なって一覧表示する GROUP BY 句と ORDER BY 句が含まれる場合に発生します。

  • Using where

    WHERE 句は、次のテーブルに対して照合されるか、またはクライアントに送信される行を制限するために使用されます。具体的にテーブルからすべての行をフェッチするか、調査する意図がないかぎり、Extra 値が Using where でなく、テーブル結合型が ALL または index である場合、クエリーに何らかの誤りがある可能性があります。

  • Using where with pushed condition

    この項目は NDB テーブルのみに適用されます。つまり、MySQL Cluster がコンディションプッシュダウン最適化を使用して、インデックス設定されていないカラムと定数の直接比較の効率を向上します。そのような場合、条件がクラスタのデータノードにプッシュダウンされ、すべてのデータノードで同時に評価されます。これにより、一致しない行をネットワーク経由で送る必要がなくなり、コンディションプッシュダウンを使用できるが使用しない場合より、そのようなクエリーを 5 - 10 倍高速化できます。詳細については、セクション8.2.1.5「エンジンコンディションプッシュダウンの最適化」を参照してください。

EXPLAIN 出力の解釈

EXPLAIN 出力の rows カラムの値の積を取得することで、結合がどの程度適しているかを示す適切な目安を得ることができます。これは、クエリーを実行するために MySQL が調査する必要がある行数を大ざっぱに示すはずです。max_join_size システム変数によってクエリーを制限する場合、この行の積は、どの複数テーブル SELECT ステートメントを実行し、どれを中止するかを判断するためにも使用されます。セクション8.11.2「サーバーパラメータのチューニング」を参照してください。

次の例は、EXPLAIN によって得られた情報に基づいて、複数テーブル結合を段階的に最適化する方法を示しています。

ここに示す SELECT ステートメントがあり、EXPLAIN を使用して調査するつもりであるとします。

EXPLAIN SELECT tt.TicketNumber, tt.TimeIn, tt.ProjectReference, tt.EstimatedShipDate, tt.ActualShipDate, tt.ClientID, tt.ServiceCodes, tt.RepetitiveID, tt.CurrentProcess, tt.CurrentDPPerson, tt.RecordVolume, tt.DPPrinted, et.COUNTRY, et_1.COUNTRY, do.CUSTNAME FROM tt, et, et AS et_1, do WHERE tt.SubmitTime IS NULL AND tt.ActualPC = et.EMPLOYID AND tt.AssignedPC = et_1.EMPLOYID AND tt.ClientID = do.CUSTNMBR;

この例では次のように想定しています。

  • 比較対象のカラムは次のように宣言されています。

    テーブルカラムデータ型
    ttActualPCCHAR(10)
    ttAssignedPCCHAR(10)
    ttClientIDCHAR(10)
    etEMPLOYIDCHAR(15)
    doCUSTNMBRCHAR(15)
  • テーブルには次のインデックスがあります。

    テーブルインデックス
    ttActualPC
    ttAssignedPC
    ttClientID
    etEMPLOYID (主キー)
    doCUSTNMBR (主キー)
  • tt.ActualPC 値は均一に分布されていません。

最初、最適化が実行される前は、EXPLAIN ステートメントで次の情報が生成されました。

table type possible_keys key key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
do ALL PRIMARY NULL NULL NULL 2135
et_1 ALL PRIMARY NULL NULL NULL 74
tt ALL AssignedPC, NULL NULL NULL 3872 ClientID, ActualPC Range checked for each record (index map: 0x23)

各テーブルの typeALL であるため、この出力は MySQL がすべてのテーブル、つまりすべての行の組み合わせのデカルト積を生成することを示しています。これは、各テーブルの行数の積を調査する必要があるため、著しく時間がかかります。このケースの場合は、この積が 74 × 2135 × 74 × 3872 = 45,268,558,720 行になります。テーブルがもっと大きければ、どのくらい時間がかかっていたか簡単に想像がつきます。

ここでの問題の 1 つは、カラムが同じ型とサイズで宣言されている場合に、MySQL はカラムに対してインデックスをより効率的に使用できることです。このコンテキストでは、VARCHARCHAR は同じサイズとして宣言されている場合、それらは同じとみなされます。tt.ActualPCCHAR(10) として宣言されており、et.EMPLOYIDCHAR(15) であるため、長さの不一致があります。

このカラム長の不一致を修正するには、ALTER TABLE を使用して ActualPC を 10 文字から 15 文字に長くします。

mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

これで tt.ActualPCet.EMPLOYID はいずれも VARCHAR(15) になります。EXPLAIN ステートメントを再度実行すると、次の結果が生成されます。

table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC, NULL NULL NULL 3872 Using ClientID, where ActualPC
do ALL PRIMARY NULL NULL NULL 2135 Range checked for each record (index map: 0x1)
et_1 ALL PRIMARY NULL NULL NULL 74 Range checked for each record (index map: 0x1)
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1

これは完全ではありませんが、はるかに改善されています。rows 値の積は 74 の係数分だけ少なくなります。このバージョンは、数秒で実行します。

2 つめの変更を実行して、tt.AssignedPC = et_1.EMPLOYIDtt.ClientID = do.CUSTNMBR の比較でのカラム長の不一致を解消できます。

mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15), -> MODIFY ClientID VARCHAR(15);

その変更後、EXPLAIN は次に示す出力を生成します。

table type possible_keys key key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using ClientID, where ActualPC
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

この時点で、クエリーはほぼ可能なかぎり十分に最適化されています。残りの問題は、MySQL はデフォルトで tt.ActualPC カラムの値が均一に分布しているものと想定しますが、tt テーブルにはそれが当てはまらないことです。さいわい、MySQL にキー分布を分析するように伝えることは簡単です。

mysql> ANALYZE TABLE tt;

追加のインデックス情報によって、結合が完全になり、EXPLAIN が次の結果を生成します。

table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC NULL NULL NULL 3872 Using ClientID, where ActualPC
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

EXPLAIN の出力の rows カラムは、MySQL 結合オプティマイザの学習による推測です。rows の積とクエリーが返す実際の行数を比較して、数値が実際と近いかどうかをチェックしてください。数値がかなり異なる場合は、SELECT ステートメントで STRAIGHT_JOIN を使用し、FROM 句で異なる順序でテーブルを一覧表示してみるとパフォーマンスを改善できる可能性があります。

場合によっては、サブクエリーで EXPLAIN SELECT を使用するときに、データを変更するステートメントを実行できることもあります。詳細については、セクション13.2.10.8「FROM 句内のサブクエリー」を参照してください。

8.8.3 EXPLAIN EXTENDED 出力フォーマット

EXPLAINEXTENDED キーワードを付けて使用すると、出力に、ほかの場合に表示されない filtered カラムが含まれます。このカラムは、テーブル条件によってフィルタ処理されるテーブル行の推定の割合を示します。さらに、ステートメントは、EXPLAIN ステートメントに続けて SHOW WARNINGS ステートメントを発行することで表示できる追加の情報を生成します。SHOW WARNINGS 出力の Message 値には、オプティマイザが SELECT ステートメント内のテーブルおよびカラム名をどのように修飾するか、書き換えおよび最適化ルールの適用後に SELECT がどのように見えるか、および場合によって最適化プロセスに関するその他のメモが表示されます。

これは拡張された出力の例です。

mysql> EXPLAIN EXTENDED -> SELECT t1.a, t1.a IN (SELECT t2.a FROM t2) FROM t1\G*************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1 type: index
possible_keys: NULL key: PRIMARY key_len: 4 ref: NULL rows: 4 filtered: 100.00 Extra: Using index
*************************** 2. row *************************** id: 2 select_type: SUBQUERY table: t2 type: index
possible_keys: a key: a key_len: 5 ref: NULL rows: 3 filtered: 100.00 Extra: Using index
2 rows in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS\G*************************** 1. row *************************** Level: Note Code: 1003
Message: select `test`.`t1`.`a` AS `a`, <in_optimizer>(`test`.`t1`.`a`,`test`.`t1`.`a` in ( <materialize> ( select `test`.`t2`.`a` from `test`.`t2` where 1 having 1 ), <primary_index_lookup>(`test`.`t1`.`a` in <temporary table> on <auto_key> where ((`test`.`t1`.`a` = `materialized-subquery`.`a`))))) AS `t1.a IN (SELECT t2.a FROM t2)` from `test`.`t1`
1 row in set (0.00 sec)

MySQL 5.6.3 現在、EXPLAIN EXTENDEDSELECTDELETEINSERTREPLACE、および UPDATE ステートメントで使用できます。ただし、次の SHOW WARNINGS ステートメントは、SELECT ステートメントに対してのみ、空でない結果を表示します。MySQL 5.6.3 より前では、EXPLAIN EXTENDEDSELECT ステートメントでのみ使用できます。

SHOW WARNINGS によって表示されるステートメントには、クエリーの書き換えやオプティマイザのアクションに関する情報を提供する特別なマーカーが含まれることがあるため、ステートメントは必ずしも有効な SQL ではなく、実行されることを目的としていません。出力には、オプティマイザによってとられたアクションに関する追加の SQL でない説明のメモを提供する Message 値のある行が含まれることもあります。

次のリストに、SHOW WARNINGS によって表示され、EXTENDED 出力に示される可能性がある特別なマーカーを説明します。

  • <auto_key>

    一時テーブルの自動的に生成されるキー。

  • <cache>(expr)

    式 (スカラーサブクエリーなど) が 1 回実行され、あとで使用するために、結果の値がメモリーに保存されます。複数の値から構成される結果の場合は、一時テーブルが作成されることがあり、代わりに <temporary table> が表示されます。

  • <exists>(query fragment)

    サブクエリー述語は EXISTS 述語に変換され、サブクエリーは EXISTS 述語と一緒に使用できるように変換されます。

  • <in_optimizer>(query fragment)

    これは、ユーザーにとっては意味がない内部オプティマイザオブジェクトです。

  • <index_lookup>(query fragment)

    対象の行を見つけるためにインデックスルックアップを使用して、クエリーフラグメントが処理されます。

  • <if>(condition, expr1, expr2)

    条件が true の場合は expr1、そうでない場合は expr2 に評価されます。

  • <is_not_null_test>(expr)

    式が NULL に評価されないことを確認するためのテスト。

  • <materialize>(query fragment)

    サブクエリーの実体化が使用されます。

  • `materialized-subquery`.col_name, `materialized subselect`.col_name

    サブクエリーの評価の結果を保持するために実体化された内部一時テーブル内のカラム col_name への参照。

  • <primary_index_lookup>(query fragment)

    対象の行を見つけるために主キールックアップを使用して、クエリーフラグメントが処理されます。

  • <ref_null_helper>(expr)

    これは、ユーザーにとっては意味がない内部オプティマイザオブジェクトです。

  • select_stmt

    SELECT は、EXTENDEDEXPLAIN 以外の出力で、Nid 値を持つ行に関連付けられます。

  • outer_tables semi join (inner_tables)

    準結合操作。inner_tables は、取り出されなかったテーブルを示します。セクション8.2.1.18.1「準結合変換によるサブクエリーの最適化」を参照してください。

  • <temporary table>

    これは、中間結果をキャッシュするために作成される内部一時テーブルを表します。

一部のテーブルが const または system 型である場合、これらのテーブルからのカラムを含む式は、オプティマイザによって早期に評価され、表示されるステートメントに含まれません。ただし、FORMAT=JSON では、一部の const テーブルアクセスが定数値を使用する ref アクセスとして表示されます。

8.8.4 クエリーパフォーマンスの推定

ほとんどの場合、ディスクシークをカウントしてクエリーパフォーマンスを推定できます。小さいテーブルの場合は一般に 1 回のディスクシークでレコードが見つかります (インデックスがキャッシュされている可能性が高いため)。大きなテーブルの場合、B ツリーインデックスを使用して、それを推定できますが、行を見つけるためにこのように多くのシークが必要です。log(row_count) / log(index_block_length / 3 * 2 / (index_length + data_pointer_length)) + 1

MySQL では、インデックスブロックが通常 1,024 バイトで、データポインタは通常 4 バイトです。3 バイトのキー値長 (MEDIUMINT のサイズ) の 500,000 行のテーブルの場合、この公式は log(500,000)/log(1024/3*2/(3+4)) + 1 = 4 シークを示します。

このインデックスには、約 500,000 * 7 * 3/2 = 5.2M バイト (2/3 の一般的なインデックスバッファー充てん率と想定して) のストレージが必要であるため、インデックスの多くをメモリーに置く可能性が高く、データを読み取り、行を見つけるために 1 つか 2 つの呼び出しだけで済みます。

ただし、書き込みについては、新しいインデックス値の配置場所を見つけるために 4 つのシークリクエスト、およびインデックスの更新と行の書き込みに通常 2 回のシークが必要になります。

前の説明は、アプリケーションのパフォーマンスが log N ずつ徐々に低下することを意味しているわけではありません。OS または MySQL サーバーによってすべてがキャッシュされているかぎり、テーブルが大きくなってもほんの少し遅くなるだけです。データがキャッシュできないほど大きくなると、アプリケーションがディスクシーク (これは log NN ずつ増加する) によってのみ制限されるまで著しく遅くなり始めます。これを回避するには、データの増加に合わせてキーキャッシュを増やします。MyISAM テーブルでは、キーキャッシュサイズは key_buffer_size システム変数によって制御されます。セクション8.11.2「サーバーパラメータのチューニング」を参照してください。

8.8.5 クエリーオプティマイザの制御

MySQL では、クエリー計画の評価方法や有効にされている切り替え可能な最適化に影響するシステム変数によって、オプティマイザを制御します。

8.8.5.1 クエリー計画評価の制御

クエリーオプティマイザのタスクは SQL クエリーを実行するために最適なプランを見つけることです。良いプランと悪いプランのパフォーマンスの差は、桁違い (つまり、数秒に対して数時間や数日にまで) になる可能性があるため、MySQL のオプティマイザを含むほとんどのクエリーオプティマイザは、多かれ少なかれ、すべての可能なクエリー評価プランの中から最適なプランを徹底的に探します。結合クエリーに対して、MySQL オプティマイザによって調査される可能なプランの数は、クエリーで参照されるテーブル数とともに指数関数的に増大します。少数のテーブル (一般に 7 から 10 未満) の場合、これは問題になりません。ただし、大きなクエリーが送信された場合、クエリーの最適化に費やされる時間が、サーバーのパフォーマンスの大きなボトルネックになりやすいことがあります。

クエリー最適化のより柔軟な方法により、ユーザーはオプティマイザが最適なクエリー評価プランをどの程度徹底的に探すかを制御できます。一般的な考えは、オプティマイザによって調査されるプランが少ないほど、クエリーのコンパイルに費やす時間も少なくなるということです。一方、オプティマイザは一部のプランをスキップするため、最適なプランを見逃す可能性もあります。

評価するプランの数に関して、オプティマイザの動作を 2 つのシステム変数を使用して制御できます。

  • optimizer_prune_level 変数は、オプティマイザに、テーブルごとにアクセスされる行数の見積もりに基づいて、特定のプランをスキップするように伝えます。経験上、この種類の学習による推測は最適なプランをめったに見逃すことはなく、クエリーのコンパイル時間を劇的に短縮できます。デフォルトでこのオプションがオン optimizer_prune_level=1 であるのはこのためです。ただし、オプティマイザがより適したクエリー計画を見逃したと思う場合は、クエリーのコンパイルにかなりの時間がかかるリスクを伴いますが、このオプションをオフにする (optimizer_prune_level=0) ことができます。この経験則を使用しても、オプティマイザはまだ指数関数的な数のプランを探索します。

  • optimizer_search_depth 変数は、オプティマイザがそれ以上拡張すべきかどうかを評価するために、不完全な各プランの将来をどの程度見通すかを伝えます。optimizer_search_depth の値を小さくするほど、クエリーのコンパイル時間が桁違いに少なくなる可能性があります。たとえば、12、13、またはそれ以上のテーブルのクエリーは、optimizer_search_depth がクエリー内のテーブル数に近い場合、コンパイルに数時間または数日間も容易に必要になることがあります。同時に、3 か 4 に等しい optimizer_search_depth でコンパイルされた場合、オプティマイザは同じクエリーで 1 分以内にコンパイルできることがあります。optimizer_search_depth の適切な値が不明な場合、この変数を 0 に設定することで、オプティマイザに自動的に値を決定させることができます。

8.8.5.2 切り替え可能な最適化の制御

optimizer_switch システム変数を使用するとオプティマイザの動作を制御できます。その値はフラグのセットで、それぞれ対応するオプティマイザの動作を有効にするかまたは無効にするかを示す on または off の値を持ちます。この変数はグローバル値およびセッション値を持ち、実行時に変更できます。グローバル値のデフォルトはサーバーの起動時に設定できます。

オプティマイザの現在のフラグセットを表示するには、変数値を選択します。

mysql> SELECT @@optimizer_switch\G*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=on, index_merge_sort_union=on, index_merge_intersection=on, engine_condition_pushdown=on, index_condition_pushdown=on, mrr=on,mrr_cost_based=on, block_nested_loop=on,batched_key_access=off, materialization=on,semijoin=on,loosescan=on, firstmatch=on, subquery_materialization_cost_based=on, use_index_extensions=on

optimizer_switch の値を変更するには、1 つ以上のコマンドのカンマ区切りのリストから構成される値を割り当てます。

SET [GLOBAL|SESSION] optimizer_switch='command[,command]...';

command 値は、次の表に示すいずれかの形式になるようにしてください。

コマンドの構文意味
defaultすべての最適化をそのデフォルト値にリセットします
opt_name=default指定した最適化をそのデフォルト値に設定します
opt_name=off指定した最適化を無効にします
opt_name=on指定した最適化を有効にします

default コマンドが存在する場合最初に実行されますが、値の中のコマンドの順序は問題ではありません。opt_name フラグを default に設定すると、そのデフォルト値が on または off のどちらであってもそれに設定されます。値に特定の opt_name を複数回指定することは許可されず、エラーが発生します。値のエラーによって、割り当てがエラーを伴って失敗し、optimizer_switch の値が変更されないままになります。

次の表に、最適化戦略別にグループ化した、許可される opt_name フラグ名を一覧表示します。

最適化フラグ名意味
Batched Key Accessbatched_key_accessBKA 結合アルゴリズムの使用を制御します
Block Nested Loopblock_nested_loopBNL 結合アルゴリズムの使用を制御します
エンジンコンディションプッシュダウンengine_condition_pushdownエンジンコンディションプッシュダウンを制御します
インデックスコンディションプッシュダウンindex_condition_pushdownインデックスコンディションプッシュダウンを制御します
インデックス拡張use_index_extensionsインデックス拡張の使用を制御します
インデックスマージindex_mergeすべてのインデックスマージ最適化を制御します
 index_merge_intersectionインデックスマージ共通集合アクセス最適化を制御します
 index_merge_sort_unionインデックスマージソート和集合アクセス最適化を制御します
 index_merge_unionインデックスマージ和集合アクセス最適化を制御します
Multi-Range ReadmrrMulti-Range Read 戦略を制御します
 mrr_cost_basedmrr=on の場合にコストベースの MRR の使用を制御します
準結合semijoinすべての準結合戦略を制御します
 firstmatch準結合 FirstMatch 戦略を制御します
 loosescan準結合 LooseScan 戦略を制御します (GROUP BY の LooseScan と混同しないでください)
サブクエリー実体化materialization実体化を制御します (準結合実体化を含む)
 subquery_materialization_cost_based使用されたコストベースの実体化の選択

block_nested_loop および batched_key_access フラグは MySQL 5.6.3 で追加されました。batched_key_accesson に設定されている場合に何らかの効果を持つためには、mrr フラグも on である必要があります。現在、MRR のコスト見積もりはきわめて悲観的です。したがって、BKA を使用するには、mrr_cost_basedoff にする必要もあります。

semijoinfirstmatchloosescan、および materialization フラグは MySQL 5.6.5 で、準結合およびサブクエリー実体化戦略を制御できるようにするために追加されました。semijoin フラグは準結合を使用するかどうかを制御します。これが on に設定されている場合、firstmatch および loosescan フラグによって、使用可能な準結合戦略を詳細に制御できます。materialization フラグはサブクエリー実体化を使用するかどうかを制御します。semijoinmaterialization が両方とも on の場合、該当すれば準結合でも実体化が使用されます。これらのフラグはデフォルトで on です。

subquery_materialization_cost_based は、MySQL 5.6.7 で、サブクエリー実体化と IN -> EXISTS サブクエリー変換の選択を制御できるようにするために追加されました。フラグが on (デフォルト) の場合、オプティマイザは、サブクエリー実体化と IN -> EXISTS サブクエリー変換のどちらの方法も使用できる場合に、コストベースの選択を実行します。フラグが off の場合、オプティマイザは、MySQL 5.6.7 より前の動作だった IN -> EXISTS サブクエリー変換より、サブクエリー実体化を選択します。

個々の最適化戦略の詳細については、次のセクションを参照してください。

optimizer_switch に値を割り当てると、指定されていないフラグはそれらの現在の値を維持します。これにより、ほかの動作に影響を与えることなく、単一のステートメントで特定のオプティマイザの動作を有効または無効にできます。ステートメントは、ほかの存在するオプティマイザフラグやそれらの値に依存しません。すべてのインデックスマージ最適化が有効になっているとします。

mysql> SELECT @@optimizer_switch\G*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=on, index_merge_sort_union=on, index_merge_intersection=on, engine_condition_pushdown=on, index_condition_pushdown=on, mrr=on,mrr_cost_based=on, block_nested_loop=on,batched_key_access=off, materialization=on,semijoin=on,loosescan=on, firstmatch=on, subquery_materialization_cost_based=on, use_index_extensions=on

サーバーが特定のクエリーに対して、インデックスマージ和集合アクセスメソッドとインデックスマージソート和集合アクセスメソッドを使用しており、それらがなければオプティマイザの実行が改善されるかどうかをチェックする場合は、変数値を次のように設定します。

mysql> SET optimizer_switch='index_merge_union=off,index_merge_sort_union=off';mysql> SELECT @@optimizer_switch\G*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=off, index_merge_sort_union=off, index_merge_intersection=on, engine_condition_pushdown=on, index_condition_pushdown=on, mrr=on,mrr_cost_based=on, block_nested_loop=on,batched_key_access=off, materialization=on,semijoin=on,loosescan=on, firstmatch=on, subquery_materialization_cost_based=on, use_index_extensions=on

8.9 バッファリングとキャッシュ

MySQL は、パフォーマンスを向上するため、メモリーバッファーに情報をキャッシュするいくつかの戦略を使用します。

8.9.1 InnoDB バッファープール

InnoDB は、データとインデックスをメモリーにキャッシュするためのバッファープールと呼ばれるストレージ領域を維持しています。InnoDB バッファープールの仕組みを知り、頻繁にアクセスされるデータをメモリーに維持するためにそれを利用することは、MySQL チューニングの重要な側面です。

ガイドライン

理想的には、バッファープールのサイズをできるだけ大きな値に設定して、サーバー上のほかのプロセスが過剰なページングなく実行するように、十分なメモリーを残します。バッファープールが大きいほど、InnoDB はさらにインメモリーデータベースのように動作し、ディスクから 1 回データを読み取り、後続の読み取り時に、メモリーからデータにアクセスします。パフォーマンス向上のため、ディスク書き込みをグループ化できるように、バッファープールは挿入および更新操作によって変更されたデータもキャッシュします。

システムの一般的なワークロードに応じて、バッファープール内の各パートの割合を調整した方がよい場合があります。バッファープールがいっぱいになったときにキャッシュするブロックを選択する方法をチューニングし、バックアップやレポートなどの操作のアクティビティーが急増しても頻繁にアクセスされるデータをメモリーに保持できます。

大きなメモリーサイズを備える 64 ビットシステムでは、バッファープールを複数のパートに分割することで、同時操作中のメモリー構造の競合を最小にできます。詳細については、セクション14.13.1.4「複数のバッファープールインスタンスの使用」を参照してください。

内部の詳細

InnoDB は、LRU (Least Recently Used) アルゴリズムのバリエーションを使用して、プールをリストとして管理します。プールに新しいブロックを追加するために、空きが必要な場合、InnoDB は最近もっとも使用されていないブロックを削除し、新しいブロックをリストの途中に追加します。このミッドポイント挿入戦略はリストを 2 つのサブリストとして扱います。

  • 先頭は、最近アクセスされた新しい (または若い) ブロックのサブリストです。

  • 末尾は、最近あまりアクセスされていない古いブロックのサブリストです。

このアルゴリズムでは、クエリーによって頻繁に使用されるブロックを新しいサブリストに維持します。古いサブリストはあまり使用されないブロックを格納し、これらのブロックはエビクションの候補になります。

LRU アルゴリズムはデフォルトで次のように動作します。

  • バッファープールの 3/8 が古いサブリストに割り振られます。

  • リストのミッドポイントは、新しいサブリストの末尾と古いサブリストの先頭が接する境界です。

  • InnoDB がブロックをバッファープールに読み込むと、最初にそれをミッドポイント (古いサブリストの先頭) に挿入します。ブロックを読み取ることができるのは、SQL クエリーなどのユーザー指定の操作や、InnoDB によって自動的に実行される先読み操作の一部として、必要であるためです。

  • 古いサブリスト内のブロックにアクセスすると、それが若くなり、バッファープールの先頭 (新しいサブリストの先頭) に移動されます。ブロックが必要であるために読み取られた場合、最初のアクセスはただちに行われ、ブロックが若くなります。先読みのためにブロックが読み取られた場合、最初のアクセスはただちに行われません (ブロックが削除されるまでまったく行われないこともあります)。

  • データベースが動作すると、アクセスされていないバッファープール内のブロックが、リストの末尾に移動されることによって、古くなります。新しいサブリストと古いサブリストの両方のブロックは、ほかのブロックが新しくなると、古くなります。古いサブリストのブロックは、ブロックがミッドポイントに挿入されたときも古くなります。最終的に、長い間使われないままのブロックは、古いサブリストの末尾に到達し、削除されます。

デフォルトで、クエリーによって読み取られたブロックは、ただちに新しいサブリストに移動され、それらが長時間バッファープールにとどまることを意味します。テーブルスキャン (mysqldump 操作または WHERE 句のない SELECT ステートメントで実行されるような) によって、大量のデータがバッファープールに取り込まれ、新しいデータが再度使われることがない場合でも、同等の量の古いデータが削除されることがあります。同様に、先読みバックグラウンドスレッドによってロードされ、その後 1 回だけアクセスされたブロックは新しいリストの先頭に移動されます。こうした状況では、頻繁に使用されるブロックが古いサブリストに押し出され、そこでそれらがエビクション対象になることがあります。

構成オプション

いくつかの InnoDB システム変数で、バッファープールのサイズを制御し、LRU アルゴリズムをチューニングできます。

  • innodb_buffer_pool_size

    バッファープールのサイズを指定します。バッファープールが小さく、十分なメモリーがある場合、プールを大きくすると、クエリーが InnoDB テーブルにアクセスするときに必要なディスク I/O の量が減ることによってパフォーマンスが向上することがあります。

  • innodb_buffer_pool_instances

    バッファープールを、それぞれ独自の LRU リストと関連データ構造を持つユーザー指定の数の個別の領域に分割して、同時メモリー読み取りおよび書き込み操作中の競合を削減します。このオプションは、innodb_buffer_pool_size を 1G バイト以上のサイズに設定した場合にのみ有効になります。指定した合計サイズは、すべてのバッファープール間で分割されます。最高の効率を得るには、innodb_buffer_pool_instancesinnodb_buffer_pool_size の組み合わせを、各バッファープールインスタンスが少なくとも 1G バイトになるように指定します。

  • innodb_old_blocks_pct

    InnoDB が古いブロックサブリストに使用するバッファープールのおおよその割合を指定します。値の範囲は 5 から 95 です。デフォルト値は 37 (つまり、プールの 3/8 ) です。

  • innodb_old_blocks_time

    古いサブリストに挿入されたブロックが、その最初のアクセス後、新しいサブリストに移動するまでに、そこにとどまる必要のある時間をミリ秒 (ms) 単位で指定します。デフォルト値は 0 です。挿入後にどのくらいの期間でアクセスが発生するかに関係なく、古いサブリストに挿入されたブロックは、Innodb がバッファープールから、挿入されたブロックのページの 1/4 を削除したときに、新しいサブリストに移動されます。この値が 0 より大きい場合、ブロックは最初のアクセス後、少なくともそのミリ秒でアクセスが発生するまで、古いサブリストに残ります。たとえば、1000 の値では、ブロックは最初のアクセス後、それらが新しいサブリストに移動される資格を得るまで、1 秒間古いサブリストにとどまります。

innodb_old_blocks_time を 0 より大きく設定すると、1 回のテーブルスキャンで、スキャンだけに使用されたブロックによって新しいサブリストがいっぱいになることを防ぎます。スキャンで読み取られるブロック内の行は、すばやく連続して何回もアクセスされますが、その後ブロックは使用されません。innodb_old_blocks_time がブロックを処理するより長い時間の値に設定されていれば、ブロックは古いサブリストに残り、リストの末尾まで古くなり、すぐに削除されます。このようにすると、1 回のスキャンだけに使用されるブロックが、新しいサブリスト内の頻繁に使用されるブロックの損失を促進しません。

innodb_old_blocks_time は実行時に設定できるため、テーブルスキャンやダンプなどの操作の実行中にそれを一時的に変更できます。

SET GLOBAL innodb_old_blocks_time = 1000;... perform queries that scan tables ...SET GLOBAL innodb_old_blocks_time = 0;

目的が、テーブルの内容をバッファープールに入れることによって、バッファープールをウォームアップすることである場合、この戦略は適用されません。たとえば、通常の使用期間後、データは通常バッファープール内にあるため、ベンチマークテストでは、多くの場合サーバーの起動時にテーブルまたはインデックススキャンを実行します。この場合、少なくともウォームアップフェーズが完了するまで、innodb_old_blocks_time を 0 に設定されたままにします。

バッファープールのモニタリング

InnoDB 標準モニターからの出力には、BUFFER POOL AND MEMORY セクションに、バッファープール LRU アルゴリズムの操作に属するいくつかのフィールドが含まれます。

  • Old database pages: バッファープールの古いサブリスト内のページ数。

  • Pages made young, not young: バッファープールの先頭 (新しいサブリスト) に移動された古いページ数と、新しくなることなく、古いサブリストに残されているページ数。

  • youngs/s non-youngs/s: 若くされたか、またはされなかった古いページへのアクセスの数。このメトリックは、2 つの点で、前の項目のそれとは異なります。まず、これは古いページにのみ関連します。2 つめに、それはページへのアクセス数に基づき、ページ数に基づきません。(特定のページに複数のアクセスがあることがあり、そのすべてがカウントされます。)

  • young-making rate: ブロックがバッファープールの先頭に移動されるヒット。

  • not: ブロックがバッファープールの先頭に移動されないヒット (満たされていない遅延のため)。

young-making 率と not 率は通常バッファープール全体のヒット率まで達することはありません。古いサブリスト内のブロックのヒットによって、それらが新しいサブリストに移動されますが、新しいサブリスト内のブロックへのヒットでは、それらが先頭から特定の距離にある場合にのみ、リストの先頭に移動されます。

モニターからの先述の情報が、LRU のチューニングの決定に役立つことがあります。

  • 大きなスキャンが実行中でないときに、youngs/s 値がきわめて低いことが確認された場合、遅延時間を減らすか、古いサブリストに使用されるバッファープールの割合を増やす必要がある可能性があることを示しています。割合を増やすと、古いサブリストが大きくなるため、そのサブリスト内のブロックが末尾に移動され、削除されるまで長くかかるようになります。これにより、再度アクセスされ、若くされる可能性が高くなります。

  • 大きなテーブルスキャンの実行中 (および大量の youngs/s) に大量の non-youngs/s が確認されない場合、遅延値を大きくするようにチューニングします。

注記

InnoDB モニターの出力で示される 1 秒あたりの平均は、現在の時間と InnoDB モニターの出力が最後に出力された時間の間の経過時間に基づいています。

InnoDB モニターの詳細については、セクション14.15「InnoDB モニター」を参照してください。

INNODB_BUFFER_POOL_STATS テーブルと InnoDB バッファープールのサーバーステータス変数は、SHOW ENGINE INNODB STATUS 出力によって提供される、多くの同じバッファープール情報を提供します。

8.9.2 MyISAM キーキャッシュ

ディスク I/O を最小にするために、MyISAM ストレージエンジンは多くのデータベース管理システムで使用されている戦略を利用します。それは、もっとも頻繁にアクセスされるテーブルブロックをメモリー内で保持するキャッシュメカニズムを採用しています。

  • インデックスブロックの場合、キーキャッシュ (またはキーバッファー) と呼ばれる特別な構造が維持されます。その構造には、もっとも多く使用されるインデックスブロックが置かれる多数のブロックバッファーが含まれます。

  • データブロックに対しては、MySQL は特別なキャッシュを使用しません。代わりに、ネイティブオペレーティングシステムのファイルシステムキャッシュに依存します。

このセクションではまず MyISAM キーキャッシュの基本動作について説明します。次に、キーキャッシュパフォーマンスを向上させる機能と、キャッシュ操作をより適切に制御できるようにする機能について説明します。

  • 複数のセッションが同時にキャッシュにアクセスできます。

  • 複数のキーキャッシュをセットアップし、特定のキャッシュにテーブルインデックスを割り当てることができます。

キーキャッシュのサイズを制御するには、key_buffer_size システム変数を使用します。この変数がゼロに設定されている場合、キーキャッシュは使われません。キーキャッシュは、key_buffer_size 値が小さすぎて、最小数のブロックバッファー (8) を割り当てられない場合も使用されません。

キーキャッシュが動作していない場合、インデックスファイルはオペレーティングシステムによって提供されるネイティブファイルシステムバッファリングのみを使用してアクセスされます。(つまり、テーブルインデックスブロックは、テーブルデータブロックに採用されている同じ戦略を使用してアクセスされます。)

インデックスブロックは MyISAM インデックスファイルへの連続したアクセスの単位です。通常、インデックスブロックのサイズは、インデックス B ツリーのノードのサイズと等しくなります。(インデックスはディスク上で B ツリーデータ構造を使用して表されます。ツリーの下部にあるノードはリーフノードです。リーフノードの上にあるノードは非リーフノードです。)

キーキャッシュ構造内のすべてのブロックバッファーは同じサイズです。このサイズは、テーブルインデックスブロックのサイズと等しいか、大きいか、小さくできます。通常これら 2 つの値のうちの一方は、他方の倍数になります。

いずれかのテーブルインデックスブロックのデータにアクセスする必要がある場合、サーバーはまず、キーキャッシュの何らかのブロックバッファーでそれを使用できるかどうかを確認します。そうである場合、サーバーはディスク上ではなく、キーキャッシュ内のデータにアクセスします。つまり、ディスクから読み取ったり、それに書き込んだりするのではなく、キャッシュから読み取ったり、それに書き込んだりします。そうでない場合、サーバーは別のテーブルインデックスブロックを含むキャッシュブロックバッファーを選択し、そのデータを必要なテーブルインデックスブロックのコピーで置き換えます。新しいインデックスブロックがキャッシュに入れられるとただちに、インデックスデータにアクセスできます。

置き換えのために選択されているブロックが変更されていた場合、ブロックはダーティーとみなされます。この場合、置き換えられる前に、その内容が取得元のテーブルインデックスにフラッシュされます。

通常サーバーは LRU (Least Recently Used) 戦略に従います。置き換えるブロックを選択する場合、直近で使用されていないインデックスブロックを選択します。この選択を簡単にするため、キーキャッシュモジュールは、使用されたすべてのブロックを特別なリスト (LRU チェーン) に使用時間で順序付けして保持しています。ブロックがアクセスされると、それは直近で使用されたものになり、リストの末尾に置かれます。ブロックを置き換える必要がある場合、リストの先頭にあるブロックが、直近で使用されていないことになり、エビクションの最初の候補になります。

InnoDB ストレージエンジンは、そのバッファープールを管理するためにも LRU アルゴリズムを使用します。セクション8.9.1「InnoDB バッファープール」を参照してください。

8.9.2.1 共有キーキャッシュアクセス

スレッドはキーキャッシュバッファーに同時にアクセスでき、次の条件に従います。

  • 更新中でないバッファーは複数のセッションによってアクセスできます。

  • 更新中のバッファーは、更新が完了するまで、それを使用する必要があるセッションを待機させます。

  • 複数のセッションは、互いに干渉しないかぎり (つまり、それらは異なるインデックスブロックを必要とし、そのため、異なるキャッシュブロックが置き換えられるかぎり)、キャッシュブロックの置換を引き起こすリクエストを開始できます。

キーキャッシュへの共有アクセスによって、サーバーのスループットを大幅に向上できます。

8.9.2.2 複合キーキャッシュ

キーキャッシュへの共有アクセスはパフォーマンスを向上させますが、セッション間の競合を完全には排除しません。それらはまだキーキャッシュバッファーへのアクセスを管理する制御構造を得るために争います。キーキャッシュアクセスの競合をもっと軽減するために、MySQL は複合キーキャッシュも提供しています。この機能により、異なるキーキャッシュにさまざまなテーブルインデックスを割り当てることができます。

複合キーキャッシュがある場合、サーバーは特定の MyISAM テーブルに対してクエリーを処理する際に、使用すべきキャッシュを知っている必要があります。デフォルトでは、すべての MyISAM テーブルインデックスはデフォルトのキーキャッシュにキャッシュされます。テーブルインデックスを特定のキーキャッシュに割り当てるには、CACHE INDEX ステートメントを使用します (セクション13.7.6.2「CACHE INDEX 構文」を参照してください)。たとえば、次のステートメントは t1t2、および t3 テーブルから、hot_cache という名前のキーキャッシュにインデックスを割り当てます。

mysql> CACHE INDEX t1, t2, t3 IN hot_cache;+---------+--------------------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+--------------------+----------+----------+
| test.t1 | assign_to_keycache | status | OK |
| test.t2 | assign_to_keycache | status | OK |
| test.t3 | assign_to_keycache | status | OK |
+---------+--------------------+----------+----------+

CACHE INDEX ステートメントで参照されているキーキャッシュは、SET GLOBAL パラメータ設定ステートメントでそのサイズを設定するか、またはサーバー起動オプションを使用して作成できます。例:

mysql> SET GLOBAL keycache1.key_buffer_size=128*1024;

キーキャッシュを破棄するには、そのサイズをゼロに設定します。

mysql> SET GLOBAL keycache1.key_buffer_size=0;

デフォルトのキーキャッシュは破棄できません。これを実行するすべての試みは無視されます。

mysql> SET GLOBAL key_buffer_size = 0;mysql> SHOW VARIABLES LIKE 'key_buffer_size';+-----------------+---------+
| Variable_name | Value |
+-----------------+---------+
| key_buffer_size | 8384512 |
+-----------------+---------+

キーキャッシュ変数は名前とコンポーネントのある構造化システム変数です。keycache1.key_buffer_size の場合、keycache1 はキャッシュ変数名であり、key_buffer_size はキャッシュコンポーネントです。構造化キーキャッシュシステム変数を参照するために使用する構文の詳細については、セクション5.1.5.1「構造化システム変数」を参照してください。

デフォルトで、テーブルインデックスは、サーバー起動時に作成されるメイン (デフォルト) キーキャッシュに割り当てられます。キーキャッシュが破棄されると、それに割り当てられたすべてのインデックスはデフォルトのキーキャッシュに再割り当てされます。

ビジーなサーバーの場合、3 つのキーキャッシュを含む戦略を使用できます。

  • すべてのキーキャッシュに割り当てられたスペースの 20% を占めるホットキーキャッシュ。これは、検索に頻繁に使用されるが、更新されないテーブルに使用します。

  • すべてのキーキャッシュに割り当てられたスペースの 20% を占めるコールドキーキャッシュ。このキャッシュは、一時テーブルなどの中規模の集中的に変更されるテーブルに使用します。

  • キーキャッシュスペースの 60% を占めるウォームキーキャッシュ。これは、デフォルトでほかのすべてのテーブルに使用されるように、デフォルトのキーキャッシュとして使用します。

3 つのキーキャッシュを使用することに利点がある理由の 1 つは、1 つのキーキャッシュ構造へのアクセスが、ほかへのアクセスをブロックしないことです。あるキャッシュに割り当てられたテーブルにアクセスするステートメントは、ほかのキャッシュに割り当てられたテーブルにアクセスするステートメントと競合しません。パフォーマンスの向上はほかの理由でも発生します。

  • ホットキャッシュはクエリーの取得にのみ使用されるため、その内容が変更されることはありません。その結果、インデックスブロックをディスクから取り出す必要がある場合常に、置き換えのために選択されたキャッシュブロックの内容を最初にフラッシュする必要はありません。

  • ホットキャッシュに割り当てられたインデックスの場合、インデックススキャンを必要とするクエリーがなければ、インデックス B ツリーの非リーフノードに対応するインデックスブロックがキャッシュに残っている可能性が高くなります。

  • 一時テーブルに対するもっとも頻繁に実行される更新操作は、更新されるノードがキャッシュ内にあり、最初にディスクから読み取られる必要がない場合、はるかに高速に実行されます。一時テーブルのインデックスのサイズがコールドキーキャッシュのサイズと同程度である場合、更新されるノードがキャッシュ内にある可能性が高くなります。

CACHE INDEX ステートメントは、テーブルとキーキャッシュ間のアソシエーションをセットアップしますが、そのアソシエーションはサーバーが再起動されるたびに失われます。サーバーが起動するたびにアソシエーションを有効にしたい場合、これを実現する 1 つの方法はオプションファイルを使用することです。キーキャッシュを構成する変数設定と、実行される CACHE INDEX ステートメントを含むファイルを指定する init-file オプションを含めます。例:

key_buffer_size = 4G
hot_cache.key_buffer_size = 2G
cold_cache.key_buffer_size = 2G
init_file=/path/to/data-directory/mysqld_init.sql

サーバーが起動するたびに mysqld_init.sql 内のステートメントが実行されます。ファイルには 1 行に 1 つずつ SQL ステートメントを含めてください。次の例は hot_cachecold_cache に複数のテーブルをそれぞれ割り当てます。

CACHE INDEX db1.t1, db1.t2, db2.t3 IN hot_cache
CACHE INDEX db1.t4, db2.t5, db2.t6 IN cold_cache

8.9.2.3 ミッドポイント挿入戦略

デフォルトで、キーキャッシュ管理システムは、削除されるキーキャッシュブロックの選択に、単純な LRU 戦略を使用しますが、ミッドポイント挿入戦略というさらに高度な方法もサポートしています。

ミッドポイント挿入戦略を使用すると、LRU チェーンがホットサブリストとウォームサブリストの 2 つのパートに分割されます。2 つのパート間の分割点は固定ではありませんが、キーキャッシュ管理システムでは、ウォームパートが短くなりすぎず、常にキーキャッシュブロックの少なくとも key_cache_division_limit パーセントを含むように配慮されます。key_cache_division_limit は構造化キーキャッシュ変数のコンポーネントであるため、その値はキャッシュごとに設定可能なパラメータです。

インデックスブロックがテーブルからキーキャッシュに読み込まれると、それはウォームサブリストの末尾に置かれます。特定の数のヒット (ブロックのアクセス) 後、それはホットサブリストに昇格されます。現在のところ、ブロックを昇格させるために必要なヒット数 (3) はすべてのインデックスブロックで同じです。

ホットサブリストに昇格されるブロックはリストの末尾に置かれます。ブロックはこのサブリスト内で循環されます。ブロックが十分な時間サブリストの先頭にとどまっている場合、それはウォームサブリストに降格されます。この時間はキーキャッシュの key_cache_age_threshold コンポーネントの値によって決定されます。

しきい値は、N ブロックを含むキーキャッシュの場合、最後の N * key_cache_age_threshold / 100 ヒット内にアクセスされないホットサブリストの先頭のブロックが、ウォームサブリストの先頭に移動されることを規定します。置き換えられるブロックは常にウォームサブリストの先頭から取得されるため、その後、それは削除の最初の候補になります。

ミッドポイント挿入戦略により、価値の高いブロックを常にキャッシュ内に保持できます。単純な LRU 戦略を使用したい場合は、key_cache_division_limit 値をそのデフォルトの 100 に設定したままにします。

ミッドポイント挿入戦略は、インデックススキャンを必要とするクエリーの実行で、価値の高い高レベル B ツリーノードに対応するすべてのインデックスブロックを、キャッシュから効率的に押し出す際のパフォーマンスの向上に役立ちます。これを回避するには、key_cache_division_limit を 100 よりかなり小さい値に設定して、ミッドポイント挿入戦略を使用する必要があります。これにより、インデックススキャン操作中でも、価値の高い頻繁にヒットされるノードがホットサブリストに保持されます。

8.9.2.4 インデックスプリロード

キーキャッシュ内に、インデックス全体のブロックを保持するために十分なブロックがあるか、または少なくともその非リーフノードに対応するブロックがある場合、使用を開始する前に、キーキャッシュにインデックスブロックをプリロードすることは役立ちます。プリロードにより、インデックスブロックをディスクから順番に読み取ることで、もっとも効率的にテーブルインデックスをキーキャッシュバッファーに挿入できます。

プリロードしない場合、ブロックは、引き続きクエリーによって必要とされるときに、キーキャッシュに置かれます。ブロックはキャッシュ内にとどまりますが、それらのすべてに対して十分なバッファーがあるため、ディスクからランダムな順序で、順番ではなくフェッチされます。

インデックスをキャッシュにプリロードするには LOAD INDEX INTO CACHE ステートメントを使用します。たとえば、次のステートメントはテーブル t1 および t2 のインデックスのノード (インデックスブロック) をプリロードします。

mysql> LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES;+---------+--------------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+--------------+----------+----------+
| test.t1 | preload_keys | status | OK |
| test.t2 | preload_keys | status | OK |
+---------+--------------+----------+----------+

IGNORE LEAVES 修飾子によって、インデックスの非リーフノードのブロックのみがプリロードされます。したがって、上記のステートメントは t1 からすべてのインデックスブロックをプリロードしますが、t2 からは非リーフノードのブロックのみをプリロードします。

インデックスが CACHE INDEX ステートメントを使用してキーキャッシュに割り当てられている場合、プリロードによって、インデックスブロックがそのキャッシュに置かれます。そうでない場合は、インデックスはデフォルトのキーキャッシュにロードされます。

8.9.2.5 キーキャッシュブロックサイズ

key_cache_block_size 変数を使用して、個々のキーキャッシュのブロックバッファーのサイズを指定できます。これによって、インデックスファイルの I/O 操作のパフォーマンスをチューニングできます。

I/O 操作の最適なパフォーマンスは、読み取りバッファーのサイズがネイティブオペレーティングシステム I/O バッファーのサイズに等しい場合に達成されます。ただし、キーノードのサイズを I/O バッファーのサイズと等しく設定しても、常に全体の最適なパフォーマンスが確保されるわけではありません。大きなリーフノードを読み取る場合、サーバーは大量の不要なデータを取り出し、事実上ほかのリーフノードの読み取りを妨げます。

MyISAM テーブルの .MYI インデックスファイルのブロックのサイズを制御するには、サーバーの起動時に --myisam-block-size オプションを使用します。

8.9.2.6 キーキャッシュの再構築

キーキャッシュはそのパラメータ値を更新することで、いつでも再構築できます。例:

mysql> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024;

key_buffer_size または key_cache_block_size キーキャッシュコンポーネントに、コンポーネントの現在値と異なる値を割り当てた場合、サーバーはキャッシュの古い構造を破棄し、新しい値に基づいた新しい構造を作成します。キャッシュにダーティーブロックが含まれる場合、サーバーはキャッシュを破棄し、再作成する前にディスクにそれらを保存します。ほかのキーキャッシュパラメータを変更した場合は、再構築が行われません。

キーキャッシュを再構築する場合、サーバーはまずダーティーバッファーの内容をディスクにフラッシュします。その後、キャッシュの内容は使用できなくなります。しかし、再構築は、キャッシュに割り当てられたインデックスを使用する必要があるクエリーをブロックしません。代わりに、サーバーはネイティブファイルシステムキャッシュを使用して、テーブルインデックスに直接アクセスします。ファイルシステムキャッシュはキーキャッシュを使用するときほど効率的ではないため、クエリーが実行されても速度の低下が予想されます。キャッシュが再構築されると、それに割り当てられたインデックスをキャッシュするためにふたたび使用できるようになり、インデックスのファイルシステムキャッシュの使用は停止されます。

8.9.3 MySQL クエリーキャッシュ

クエリーキャッシュには、クライアントに送信された対応する結果とともに、SELECT ステートメントのテキストが格納されます。あとで同じステートメントを受け取った場合、サーバーはそのステートメントを再度解析して実行する代わりに、クエリーキャッシュから結果を取得します。クエリーキャッシュはセッション間で共有されるため、1 つのクライアントで生成された結果セットを、別のクライアントによって発行された同じクエリーへの応答で送信できます。

クエリーキャッシュは、あまり頻繁に変更されないテーブルがあり、それに対してサーバーが多くの同一のクエリーを受け取る環境で役立つことがあります。これは、データベースの内容に基づいて、多くの動的ページを生成する多くの Web サーバーに一般的な状況です。

クエリーキャッシュは古くなったデータを返しません。テーブルが変更されると、クエリーキャッシュ内の関連エントリがフラッシュされます。

注記

同じ MyISAM テーブルを更新する複数の mysqld サーバーがある環境では、クエリーキャッシュは機能しません。

クエリーキャッシュは、セクション8.9.3.1「クエリーキャッシュの動作」に説明された条件の下で、準備されたステートメントに使用されます。

注記

MySQL 5.6.5 現在、クエリーキャッシュは、パーティション化されたテーブルに対してはサポートされておらず、パーティション化されたテーブルを含むクエリーには自動的に無効にされます。そのようなクエリーに対しては、クエリーキャッシュを有効にできません。(Bug #53775)

クエリーキャッシュの一部のパフォーマンスデータを次に示します。これらの結果は、2G バイトの RAM と 64M バイトのクエリーキャッシュを搭載する Linux Alpha 2 × 500MHz システムで、MySQL ベンチマークスイートを実行して生成されました。

  • 実行しているすべてのクエリーは単純です (1 行のテーブルから行を選択するなど) が、それでも異なっているため、クエリーをキャッシュできない場合、クエリーキャッシュをアクティブにしておくためのオーバーヘッドは 13% です。これは最悪のケースのシナリオとみなすことができます。実際には、クエリーははるかに複雑になる傾向があるため、オーバーヘッドは通常かなり低くなります。

  • 単一行テーブル内の単一行の検索は、クエリーキャッシュがあると、それがない場合より、238% 高速化します。これは、キャッシュされているクエリーに予想される最小の高速化に近いとみなすことができます。

サーバーの起動時にクエリーキャッシュを無効にするには、query_cache_size システム変数を 0 に設定します。クエリーキャッシュコードを無効にすることによって、目立ったオーバーヘッドはなくなります。

クエリーキャッシュは、かなりのパフォーマンスの改善の可能性を提供しますが、すべての環境でそうなるものと想定しないでください。クエリーキャッシュの構成やサーバーのワークロードによっては、実際にパフォーマンスの低下が見られることもあります。

  • クエリーキャッシュのサイズを過度に大きくすると、キャッシュの保守に必要なオーバーヘッドが増え、それを有効にすることのメリットに勝る可能性があります。数十メガバイトのサイズが通常は有益です。数百メガバイトのサイズはそうでない可能性があります。

  • サーバーのワークロードは、クエリーキャッシュの効率にかなりの影響を与えます。ほぼ全体が固定の SELECT ステートメントのセットで構成される複合クエリーでは、頻繁な INSERT ステートメントによってキャッシュ内の結果が絶えず無効にされるような複合クエリーよりも、キャッシュを有効にすることでメリットが得られる可能性がはるかに高くなります。場合によっては、回避方法として、SQL_NO_CACHE オプションを使用して、頻繁に変更されるテーブルを使用する SELECT ステートメントに対して、結果をキャッシュに入れないようにします。(セクション8.9.3.2「クエリーキャッシュ SELECT オプション」を参照してください。)

クエリーキャッシュを使用することでメリットがあるかどうかを確認するには、キャッシュを有効および無効にして MySQL サーバーの動作をテストします。サーバーのワークロードが変わるとクエリーキャッシュの効率も変わることがあるため、その後、定期的に再テストします。

8.9.3.1 クエリーキャッシュの動作

このセクションでは、クエリーキャッシュが動作可能な場合のその仕組みについて説明します。セクション8.9.3.3「クエリーキャッシュの構成」では、それを動作可能にするかどうかを制御する方法について説明しています。

受信したクエリーは、解析前にクエリーキャッシュにあるそれらと比較されるため、次の 2 つのクエリーは、クエリーキャッシュによって異なるものとみなされます。

SELECT * FROM tbl_nameSelect * from tbl_name

クエリーは、同一とみなされるためには、正確に同じ (バイトごと) である必要があります。さらに、ほかの理由で、同一のクエリー文字列が異なるものとして扱われることもあります。異なるデータベース、異なるプロトコルバージョン、または異なるデフォルトの文字セットを使用するクエリーは、異なるクエリーとみなされ、別々にキャッシュされます。

次の種類のクエリーにはキャッシュが使用されません。

  • 外部クエリーのサブクエリーであるクエリー

  • ストアドファンクション、トリガー、またはイベントの本体内で実行されるクエリー

クエリー結果をクエリーキャッシュからフェッチする前に、MySQL は、関連するすべてのデータベースとテーブルに対して、ユーザーが SELECT 権限を持っているかどうかをチェックします。これが当てはまらない場合、キャッシュ結果は使用されません。

クエリー結果がクエリーキャッシュから返される場合、サーバーは Com_select ではなく Qcache_hits ステータス変数を増やします。セクション8.9.3.4「クエリーキャッシュのステータスと保守」を参照してください。

テーブルが変更された場合、そのテーブルを使用するキャッシュされたすべてのクエリーが無効になり、キャッシュから削除されます。これには、変更されたテーブルにマップされた MERGE テーブルを使用するクエリーも含まれます。テーブルは、INSERTUPDATEDELETETRUNCATE TABLEALTER TABLEDROP TABLE、または DROP DATABASE などの多くの種類のステートメントによって変更できます。

InnoDB テーブルを使用する際のトランザクション内でもクエリーキャッシュは機能します。

MySQL 5.6 では、ビュー上の SELECT クエリーからの結果がキャッシュされます。

クエリーキャッシュは、SELECT SQL_CALC_FOUND_ROWS ... クエリーに対して機能し、後続の SELECT FOUND_ROWS() クエリーで返される値を格納します。FOUND_ROWS() は前のクエリーがキャッシュからフェッチされている場合でも、見つかった行の数もキャッシュに格納されているため、正確な値を返します。SELECT FOUND_ROWS() クエリー自体はキャッシュできません。

mysql_stmt_prepare() および mysql_stmt_execute() を使用し、バイナリプロトコルを使用して発行されたプリペアドステートメント (セクション23.7.8「C API プリペアドステートメント」を参照してください) はキャッシュが制限されます。クエリーキャッシュ内のステートメントとの比較は、? パラメータマーカーの拡張後のステートメントのテキストに基づきます。ステートメントは、バイナリプロトコルを使用して実行されたほかのキャッシュされたステートメントとのみ比較されます。つまり、クエリーキャッシュの目的で、バイナリプロトコルを使用して発行されたプリペアドステートメントは、テキストプロトコルを使用して発行されたプリペアドステートメント (セクション13.5「準備済みステートメントのための SQL 構文」を参照してください) と区別されます。

クエリーに次の表に示すいずれかの関数が含まれる場合、クエリーはキャッシュできません。

AES_DECRYPT() (5.7.4 現在)AES_ENCRYPT() (5.7.4 現在)BENCHMARK()
CONNECTION_ID()CONVERT_TZ()CURDATE()
CURRENT_DATE()CURRENT_TIME()CURRENT_TIMESTAMP()
CURTIME()DATABASE()1 つのパラメータを持つ ENCRYPT()
FOUND_ROWS()GET_LOCK()LAST_INSERT_ID()
LOAD_FILE()MASTER_POS_WAIT()NOW()
PASSWORD()RAND()RANDOM_BYTES()
RELEASE_LOCK()SLEEP()SYSDATE()
パラメータを持たない UNIX_TIMESTAMP()USER()UUID()
UUID_SHORT()  

次の条件下のクエリーもキャッシュされません。

  • それがユーザー定義関数 (UDF) またはストアドファンクションを参照している。

  • それがユーザー変数またはローカルに保存されたプログラム変数を参照している。

  • それが mysqlINFORMATION_SCHEMA、または performance_schema データベース内のテーブルを参照している。

  • (MySQL 5.6.5 以降:) それがパーティション化されたテーブルを参照している。

  • それが次のいずれかの形式である。

    SELECT ... LOCK IN SHARE MODE
    SELECT ... FOR UPDATE
    SELECT ... INTO OUTFILE ...
    SELECT ... INTO DUMPFILE ...
    SELECT * FROM ... WHERE autoincrement_col IS NULL

    最後の形式は、最後の挿入 ID 値を取得するための ODBC の回避方法として使用されるため、キャッシュされません。第23章「Connector および APIの Connector/ODBC のセクションを参照してください。

    SERIALIZABLE 分離レベルを使用するトランザクション内のステートメントは LOCK IN SHARE MODE ロックを使用するため、それらもキャッシュできません。

  • それが TEMPORARY テーブルを使用している。

  • それがどのテーブルも使用していない。

  • それが警告を生成する。

  • ユーザーが関連するすべてのテーブルのカラムレベルの権限を持っている。

8.9.3.2 クエリーキャッシュ SELECT オプション

クエリーキャッシュ関連の 2 つのオプションを SELECT ステートメントに指定できます。

  • SQL_CACHE

    クエリー結果がキャッシュ可能で、query_cache_type システム変数の値が ON または DEMAND である場合、クエリー結果はキャッシュされます。

  • SQL_NO_CACHE

    サーバーはクエリーキャッシュを使用しません。それは、結果がすでにキャッシュされているかどうかを確認するためにクエリーキャッシュをチェックせず、クエリー結果もキャッシュしません。(パーサーの制限のため、スペース文字の前後に SQL_NO_CACHE キーワードを付ける必要があります。改行などのスペース以外では、結果がすでにキャッシュされているかどうかを確認するために、サーバーにクエリーキャッシュをチェックさせます。)

例:

SELECT SQL_CACHE id, name FROM customer;
SELECT SQL_NO_CACHE id, name FROM customer;

8.9.3.3 クエリーキャッシュの構成

have_query_cache サーバーシステム変数は、クエリーキャッシュが使用できるかどうかを示します。

mysql> SHOW VARIABLES LIKE 'have_query_cache';+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| have_query_cache | YES |
+------------------+-------+

標準 MySQL バイナリを使用している場合、クエリーキャッシュが無効にされている場合でも、この値は常に YES です。

ほかのいくつかのシステム変数は、クエリーキャッシュ操作を制御します。これらは、mysqld の起動時に、オプションファイルやコマンド行で設定できます。クエリーキャッシュシステム変数はすべて、query_cache_ で始まる名前を持ちます。それらについては、ここで提供している追加の構成情報とともに、セクション5.1.4「サーバーシステム変数」で簡単に説明しています。

クエリーキャッシュのサイズを設定するには、query_cache_size システム変数を設定します。それを 0 に設定すると、query_cache_type=0 を設定するのと同様に、クエリーキャッシュが無効になります。デフォルトでは、クエリーキャッシュは無効化されます。これは 1M のデフォルトのサイズと、0 の query_cache_type のデフォルトを使用して実現されます。(MySQL 5.6.8 より前では、1 のデフォルトの query_cache_type で、デフォルトのサイズは 0 です。)

オーバーヘッドを大幅に削減するには、クエリーキャッシュを使用しない場合に query_cache_type=0 でサーバーも起動します。

注記

Windows Configuration Wizard を使用して、MySQL をインストールまたは構成する場合、query_cache_size のデフォルト値が、使用可能なさまざまな構成の種類に基づいて、自動的に構成されます。Windows Configuration Wizard を使用する場合、選択した構成のため、クエリーキャッシュが有効になる (つまり、ゼロではない値に設定される) ことがあります。クエリーキャッシュは、query_cache_type 変数の設定によっても制御されます。構成が行われたあとに、my.ini ファイルに設定されたこれらの変数の値をチェックしてください。

query_cache_size をゼロ以外の値に設定する場合は、その構造を割り当てるために、クエリーキャッシュに約 40KB の最小サイズが必要であることを覚えておいてください。(正確なサイズはシステムアーキテクチャーによります。)小さすぎる値を設定すると、この例のように警告を受け取ります。

mysql> SET GLOBAL query_cache_size = 40000;Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> SHOW WARNINGS\G*************************** 1. row *************************** Level: Warning Code: 1282
Message: Query cache failed to set size 39936; new query cache size is 0
mysql> SET GLOBAL query_cache_size = 41984;Query OK, 0 rows affected (0.00 sec)
mysql> SHOW VARIABLES LIKE 'query_cache_size';+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| query_cache_size | 41984 |
+------------------+-------+

クエリーキャッシュで実際にクエリー結果を保持できるようにするには、そのサイズを大きく設定する必要があります。

mysql> SET GLOBAL query_cache_size = 1000000;Query OK, 0 rows affected (0.04 sec)
mysql> SHOW VARIABLES LIKE 'query_cache_size';+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| query_cache_size | 999424 |
+------------------+--------+
1 row in set (0.00 sec)

query_cache_size 値は、もっとも近い 1024 バイトブロックに調整されます。そのため、レポートされる値は、割り当てた値と異なることがあります。

クエリーキャッシュのサイズが 0 より大きい場合、query_cache_type 変数はその動作に影響します。この変数は次の値に設定できます。

  • 0 または OFF の値は、キャッシュまたはキャッシュされた結果の取得を妨げます。

  • 1 または ON の値は、SELECT SQL_NO_CACHE から始まるステートメントを除いて、キャッシュを有効にします。

  • 2 または DEMAND の値は、SELECT SQL_CACHE で始まるステートメントのみをキャッシュさせます。

query_cache_size が 0 の場合、query_cache_type 変数も 0 に設定してください。この場合、サーバーはクエリーキャッシュ相互排他ロックをまったく獲得しません。これは、実行時にクエリーキャッシュを有効にできず、クエリー実行のオーバーヘッドが削減されることを意味します。

GLOBALquery_cache_type 値を設定すると、変更が行われたあとに接続するすべてのクライアントのクエリーキャッシュの動作が決定されます。SESSIONquery_cache_type 値を設定して、個々のクライアントでそれぞれ独自の接続のキャッシュ動作を制御できます。たとえば、クライアントは次のように独自のクエリーへのクエリーキャッシュの使用を無効にできます。

mysql> SET SESSION query_cache_type = OFF;

サーバーの起動時 (SET ステートメントによる実行時ではなく) に query_cache_type を設定する場合、数値のみが許可されます。

キャッシュ可能な個々のクエリー結果の最大サイズを制御するには、query_cache_limit システム変数を設定します。デフォルト値は 1M バイトです。

キャッシュを大きすぎるサイズに設定しないでください。更新時にキャッシュをロックするスレッドの必要性のため、きわめて大きいキャッシュではロックの競合問題が見られることがあります。

注記

コマンド行または構成ファイルに --maximum-query_cache_size=32M オプションを使用して、SET ステートメントで実行時にクエリーキャッシュに指定できる最大サイズを設定できます。

クエリーがキャッシュされるようにすると、その結果 (クライアントに送信されたデータ) が結果の取得時に、クエリーキャッシュに格納されます。そのため、データは通常 1 つの大きなまとまりで処理されません。クエリーキャッシュはオンデマンドでこのデータを格納するためのブロックを割り当てるため、1 つのブロックがいっぱいになると、新しいブロックが割り当てられます。メモリーの割り当て操作はコスト (時間的) がかかるため、クエリーキャッシュは query_cache_min_res_unit システム変数によって指定された最小サイズでブロックを割り当てます。クエリーが実行されると、未使用のメモリーが解放されるように、最後の結果ブロックが実際のデータサイズにトリミングされます。サーバーで実行するクエリーの種類によっては、query_cache_min_res_unit の値をチューニングすることが有効であるとわかる場合があります。

  • query_cache_min_res_unit のデフォルト値は 4K バイトです。ほとんどの場合、これで十分であるはずです。

  • 小さい結果の大量のクエリーがある場合、多数の空きブロックに示されるように、デフォルトのブロックサイズはメモリーの断片化につながることがあります。断片化は、メモリー不足のために、クエリーキャッシュにキャッシュからクエリーを強制的にプルーニング (削除) させる可能性があります。この場合、query_cache_min_res_unit の値を減らします。空きブロックと、プルーニングによって削除されたクエリーの数は Qcache_free_blocks および Qcache_lowmem_prunes ステータス変数の値によって得られます。

  • ほとんどのクエリーの結果が大きい (Qcache_total_blocks および Qcache_queries_in_cache ステータス変数をチェックします) 場合、query_cache_min_res_unit を増やして、パフォーマンスを向上できます。ただし、大きくしすぎないようにしてください (前の項目を参照してください)。

8.9.3.4 クエリーキャッシュのステータスと保守

MySQL サーバーにクエリーキャッシュが存在するかどうかをチェックするには、次のステートメントを使用します。

mysql> SHOW VARIABLES LIKE 'have_query_cache';+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| have_query_cache | YES |
+------------------+-------+

FLUSH QUERY CACHE ステートメントによって、クエリーキャッシュをデフラグして、そのメモリーの利用を改善できます。このステートメントは、キャッシュからクエリーを削除しません。

RESET QUERY CACHE ステートメントは、クエリーキャッシュからすべてのクエリー結果を削除します。FLUSH TABLES ステートメントもこれを実行します。

クエリーキャッシュのパフォーマンスをモニターするには、SHOW STATUS を使用して、キャッシュのステータス変数を表示します。

mysql> SHOW STATUS LIKE 'Qcache%';+-------------------------+--------+
| Variable_name | Value |
+-------------------------+--------+
| Qcache_free_blocks | 36 |
| Qcache_free_memory | 138488 |
| Qcache_hits | 79570 |
| Qcache_inserts | 27087 |
| Qcache_lowmem_prunes | 3114 |
| Qcache_not_cached | 22989 |
| Qcache_queries_in_cache | 415 |
| Qcache_total_blocks | 912 |
+-------------------------+--------+

これらの各変数の説明は、セクション5.1.6「サーバーステータス変数」にあります。ここでは、それらのいくつかの使用方法について説明します。

SELECT クエリーの合計数は、次の式で得られます。

 Com_select
+ Qcache_hits
+ queries with errors found by parser

Com_select 値は次の式で得られます。

 Qcache_inserts
+ Qcache_not_cached
+ queries with errors found during the column-privileges check

クエリーキャッシュでは、可変長ブロックを使用するため、Qcache_total_blocks および Qcache_free_blocks に、クエリーキャッシュのメモリー断片化が示されることがあります。FLUSH QUERY CACHE 後には、空きブロックが 1 つだけ残ります。

キャッシュされるすべてのクエリーには、少なくとも 2 つのブロック (1 つはクエリーテキスト用で、1 つ以上はクエリー結果用) が必要です。さらに、クエリーで使用される各テーブルにも 1 つのブロックが必要です。ただし、複数のクエリーで同じテーブルを使用している場合、1 つのテーブルブロックだけを割り当てる必要があります。

Qcache_lowmem_prunes ステータス変数によって提供される情報は、クエリーキャッシュサイズをチューニングするのに役立つことがあります。それは、新しいクエリーのキャッシュのためにメモリーを解放するために、キャッシュから削除されたクエリーの数をカウントします。クエリーキャッシュは、LRU (Least Recently Used) 戦略を使用して、キャッシュから削除するクエリーを決定します。チューニング情報は、セクション8.9.3.3「クエリーキャッシュの構成」にあります。

8.9.4 プリペアドステートメントおよびストアドプログラムのキャッシュ

セッション中にクライアントが複数回実行する可能性がある特定のステートメントに対し、サーバーはステートメントを内部構造に変換し、実行時にその構造が使用されるようにキャッシュします。キャッシュによって、セッション中にそれが再度必要になった場合に、ステートメントを再変換するオーバーヘッドが避けられるため、サーバーはより効率的に実行できます。変換とキャッシュは、次のステートメントに対して行われます。

  • SQL レベルで処理されるもの (PREPARE ステートメントを使用して) とバイナリクライアント/サーバープロトコルを使用して処理されるもの (mysql_stmt_prepare() C API 関数を使用して) の両方のプリペアドステートメント。max_prepared_stmt_count システム変数は、サーバーがキャッシュするステートメントの合計数を制御します。(すべてのセッションでのプリペアドステートメントの合計数。)

  • ストアドプログラム (ストアドプロシージャーおよび関数、トリガー、およびイベント)。この場合、サーバーはプログラム本体全体を変換し、キャッシュします。stored_program_cache システム変数は、サーバーがセッションあたりにキャッシュするストアドプログラムのおおよその数を示します。

サーバーは、セッション単位でプリペアドステートメントおよびストアドプログラム用のキャッシュを保守します。1 つのセッションでキャッシュされたステートメントは、ほかのセッションからアクセスできません。セッションが終了すると、サーバーはそのためにキャッシュされたすべてのステートメントを破棄します。

サーバーがキャッシュされた内部ステートメント構造を使用する場合、構造が古くなっていないことに注意する必要があります。ステートメントによって使用されているオブジェクトにメタデータの変更があり、現在のオブジェクト定義と内部ステートメント構造で表されている定義に不一致が発生することがあります。メタデータの変更は、テーブルの作成、削除、変更、名前変更、切り捨てを行う DDL ステートメントや、テーブルの解析、最適化、修復を行う DDL ステートメントなどに対して発生します。テーブルの内容の変更 (INSERTUPDATE などによる) ではメタデータが変更されず、SELECT ステートメントも変更されません。

次にこの問題を説明します。クライアントがこのステートメントを準備するとします。

PREPARE s1 FROM 'SELECT * FROM t1';

SELECT * は内部構造からテーブル内のカラムのリストに展開します。テーブル内のカラムのセットが ALTER TABLE によって変更されている場合、プリペアドステートメントが古くなります。次回にクライアントが s1 を実行したときに、サーバーがこの変更を検出しない場合、プリペアドステートメントは正しくない結果を返します。

プリペアドステートメントによって参照されているテーブルやビューのメタデータの変更に原因がある問題を避けるため、サーバーはこれらの変更を検出し、次回の実行時にステートメントを自動的に再準備します。つまり、サーバーはステートメントを再解析し、内部構造を再構築します。再解析は、キャッシュ内に新しいエントリのための空きを作るために暗黙的に、または FLUSH TABLES によって明示的に、参照されているテーブルやビューがテーブル定義キャッシュからフラッシュされたあとにも行われます。

同様に、ストアドプログラムによって使用されているオブジェクトに変更が発生した場合、サーバーはプログラム内の影響のあるステートメントを再解析します。(MySQL 5.6.6 より前では、サーバーはストアドプログラムに影響するメタデータの変更を検出しないため、そのような変更によって、誤った結果やエラーが発生する可能性があります。)

サーバーは式内のオブジェクトのメタデータの変更も検出します。これらは、DECLARE CURSOR などのストアドプログラムに固有のステートメントや IFCASE、および RETURN などのフロー制御ステートメントで使用できます。

ストアドプログラム全体の再解析を避けるため、サーバーは必要に応じて、プログラム内の影響のあるステートメントや式のみを再解析します。例:

  • テーブルまたはビューのメタデータが変更されているとします。再解析は、テーブルやビューにアクセスするプログラム内の SELECT * に対して行われますが、テーブルやビューにアクセスしない SELECT * に対しては行われません。

  • ステートメントが影響を受ける場合、サーバーは可能なかぎり部分的にのみそれを再解析します。この CASE ステートメントを考慮します。

    CASE case_expr WHEN when_expr1 ... WHEN when_expr2 ... WHEN when_expr3 ... ...
    END CASE

    メタデータの変更が WHEN when_expr3 にのみ影響する場合、その式が再解析されます。case_expr およびその他の WHEN 式は再解析されません。

再解析では、元の内部形式への変換に有効であったデフォルトのデータベースと SQL モードが使われます。

サーバーは最大 3 回再解析を試みます。すべての試みが失敗した場合、エラーが発生します。

再解析は自動ですが、それが行われた場合、プリペアドステートメントとストアドプログラムのパフォーマンスが低下します。

プリペアドステートメントの場合、Com_stmt_reprepare ステータス変数が再準備の数を追跡します。

8.10 ロック操作の最適化

MySQL はロックを使用して、テーブルの内容の競合を管理します。

  • 内部ロックは、複数スレッドによるテーブルの内容の競合を管理するために、MySQL サーバー自体の内部で実行されます。この種類のロックは、完全にサーバーによって実行され、ほかのプログラムは関与しないため、内部です。セクション8.10.1「内部ロック方法」を参照してください。

  • 外部ロックは、サーバーとほかのプログラム間で、どのプログラムがいつテーブルにアクセスできるかを調整するために、MyISAM テーブルファイルをロックする場合に発生します。セクション8.10.5「外部ロック」を参照してください。

8.10.1 内部ロック方法

このセクションでは、内部ロック、つまり複数のセッションによるテーブル内容の競合を管理するために、MySQL サーバー自体の内部で実行されるロックについて説明します。この種類のロックは、完全にサーバーによって実行され、ほかのプログラムは関与しないため、内部です。ほかのプログラムによって MySQL ファイルに対して実行されるロックについては、セクション8.10.5「外部ロック」を参照してください。

行レベルロック

MySQL は InnoDB テーブルに行レベルロックを使用して、複数のセッションによる同時書き込みアクセスをサポートし、それらを複数ユーザー、高度な並列性、および OLTP アプリケーションに適したものにします。

単一の InnoDB テーブルに対する複数の同時書き込み操作の実行時のデッドロックを避けるには、トランザクションのあとの方に DML ステートメントがある場合でも、変更が予想される行のグループごとに、SELECT ... FOR UPDATE ステートメントを発行して、トランザクションの開始時に必要なロックを獲得します。トランザクションで複数のテーブルを変更またはロックする場合、各トランザクション内で、該当するステートメントを同じ順序で発行します。InnoDB は自動的にデッドロック状況を検出し、影響のあるいずれかのトランザクションをロールバックするため、デッドロックは重大エラーを表すより、パフォーマンスに影響します。

行レベルロックの利点:

  • 異なるセッションが異なる行にアクセスする場合、ロックの競合は少なくなります。

  • ロールバックする変更が少なくなります。

  • 1 つの行を長時間ロックできます。

テーブルレベルロック

MySQL は、MyISAMMEMORY、および MERGE テーブルにテーブルレベルロックを使用して、一度に 1 つだけのセッションがそれらのテーブルを更新できるようにし、それらを読み取り専用、読み取りが大部分、または単一ユーザーのアプリケーションに適したものにします。

これらのストレージエンジンは、常にクエリーの最初に 1 回だけ必要なすべてのロックをリクエストし、常に同じ順序でテーブルをロックすることによって、デッドロックを回避します。このトレードオフは、この戦略では並列性が低くなることです。テーブルを変更したいほかのセッションは、現在の DML ステートメントが終了するまで待機する必要があります。

MySQL はテーブル書き込みロックを次のように許可します。

  1. テーブルにロックがない場合、それを書き込みロックします。

  2. そうでない場合、書き込みロックキューにロックリクエストを入れます。

MySQL はテーブル読み取りロックを次のように許可します。

  1. テーブルに書き込みロックがない場合、それを読み取りロックします。

  2. そうでない場合、読み取りロックキューにロックリクエストを入れます。

テーブルの更新は、テーブルの取得よりも高い優先度が与えられます。そのため、ロックが解放されると、ロックは書き込みロックキュー内のリクエストに使用できるようになり、次に読み取りロックキュー内のリクエストに使用できるようになります。これにより、テーブルに対して重い SELECT アクティビティーがある場合でも、テーブルに対する更新が不足することはありません。ただし、テーブルに対して多くの更新がある場合、SELECT ステートメントは更新がなくなるまで待機します。

読み取りと書き込みの優先度を変更する方法については、セクション8.10.2「テーブルロックの問題」を参照してください。

Table_locks_immediate および Table_locks_waited ステータス変数をチェックすることでシステム上のテーブルロック競合を分析できます。これらは、テーブルロックのリクエストがすぐに許可された回数と待機する必要があった回数を示します。

mysql> SHOW STATUS LIKE 'Table%';+-----------------------+---------+
| Variable_name | Value |
+-----------------------+---------+
| Table_locks_immediate | 1151552 |
| Table_locks_waited | 15324 |
+-----------------------+---------+

MyISAM ストレージエンジンでは、特定のテーブルのリーダーとライター間の競合を軽減するために、同時挿入をサポートしています。MyISAM テーブルでデータファイルの途中に空きブロックがない場合、行は常にデータファイルの末尾に挿入されます。この場合、ロックなしで MyISAM テーブルに対して同時 INSERT および SELECT ステートメントを自由に組み合わせることができます。つまり、ほかのクライアントが MyISAM テーブルから読み取ると同時に、それに行を挿入できます。テーブルの途中で行が削除されるか更新されると、隙間が発生します。隙間がある場合、同時挿入は無効にされますが、すべての隙間が新しいデータで埋められた場合は、自動的にふたたび有効にされます。この動作は concurrent_insert システム変数によって変更します。セクション8.10.3「同時挿入」を参照してください。

LOCK TABLES で明示的にテーブルロックを獲得する場合、READ ロックではなく READ LOCAL ロックをリクエストして、テーブルをロックしている間に、ほかのセッションが同時挿入を実行できるようにできます。

同時挿入が可能でない場合に、テーブル real_table に対して多くの INSERT および SELECT 操作を実行するには、一時テーブル temp_table に行を挿入し、定期的に一時テーブルからの行で実際のテーブルを更新します。これは次のコードで実行できます。

mysql> LOCK TABLES real_table WRITE, temp_table WRITE;mysql> INSERT INTO real_table SELECT * FROM temp_table;mysql> DELETE FROM temp_table;mysql> UNLOCK TABLES;

テーブルレベルロックの利点:

  • 必要なメモリーが比較的少なくなります。

  • 単一のロックだけが必要であるため、テーブルの大部分に対して使用する場合に高速です。

  • データの大部分に対して GROUP BY 操作を頻繁に実行する場合や、テーブル全体を頻繁にスキャンする必要がある場合に高速です。

一般にテーブルロックは次の場合に適しています。

  • テーブルに対するほとんどのステートメントが読み取りです。

  • テーブルに対するステートメントが読み取りと書き込みの組み合わせであり、そのうち書き込みは 1 つのキーの読み取りでフェッチできる単一の行に対する更新または削除です。

    UPDATE tbl_name SET column=value WHERE unique_key_col=key_value;
    DELETE FROM tbl_name WHERE unique_key_col=key_value;
  • 同時 INSERT ステートメントとごく少数の UPDATE または DELETE ステートメントと組み合わされた SELECT

  • ライターを使用しない、テーブル全体への多くのスキャンまたは GROUP BY 操作。

8.10.2 テーブルロックの問題

InnoDB テーブルでは、複数のセッションとアプリケーションが互いに待機したり、不整合の結果を生成したりすることなく、同じテーブルに対して同時に読み取りや書き込みを実行できるように、行レベルロックを使用します。このストレージエンジンでは、LOCK TABLES ステートメントは特別な保護を提供せず、代わりに並列性が低くなるため、この使用を避けてください。自動の行レベルロックにより、これらのテーブルがもっとも重要なデータを格納するもっともビジーなデータベースに適合し、同時にテーブルのロックやロック解除が必要ないためアプリケーションロジックが簡単になります。その結果、InnoDB ストレージエンジンは MySQL 5.6 のデフォルトです。

MySQL は InnoDB を除く、すべてのストレージエンジンに対して、テーブルロック (ページ、行、またはカラムロックの代わりに) を使用します。ロック操作自体には、あまりオーバーヘッドがありません。ただし、一度に 1 つのセッションしかテーブルに書き込むことができないため、これらのほかのストレージエンジンでの最高のパフォーマンスのため、頻繁にクエリーされ、めったに挿入または更新されないテーブルに対して主にそれらを使用します。

InnoDB を優先するパフォーマンスの考慮事項

テーブルを作成するために、InnoDB を使用するか、別のストレージエンジンを使用するかを選択する場合、テーブルロックの次の短所を考慮してください。

  • テーブルロックにより、多くのセッションを同時にテーブルから読み取ることができますが、セッションでテーブルに書き込む必要がある場合、まず排他的アクセスを取得する必要がありますが、これはまずほかのセッションがテーブルを処理し終わるのを待つ必要がある可能性があることを意味します。更新中、この特定のテーブルにアクセスしようとするほかのすべてのセッションは、更新が完了するまで待機する必要があります。

  • ディスクがいっぱいで、セッションを続行するには空き領域が使用できるようになる必要があるため、セッションが待機している場合にテーブルロックによって問題が発生します。この場合、問題のテーブルにアクセスしようとするすべてのセッションが、より多くのディスク領域が使用できるようになるまで待機状態になります。

  • 実行に長時間かかる SELECT ステートメントにより、その間ほかのセッションのテーブルの更新が妨げられ、ほかのセッションが遅くなり、応答していないように見えます。セッションが更新のためにテーブルへの排他アクセスを取得するのを待機している間、SELECT ステートメントを発行するほかのセッションはそのあとに列をなし、読み取りセッションでも並列性が低くなります。

ロックパフォーマンスの問題の回避

次の項目では、テーブルロックによって発生する競合を回避または軽減するいくつかの方法について説明します。

  • セットアップ時に CREATE TABLE ... ENGINE=INNODB を使用するか、既存のテーブルに対して ALTER TABLE ... ENGINE=INNODB を使用して、テーブルを InnoDB ストレージエンジンに切り替えることを考慮します。このストレージエンジンの詳細については、第14章「InnoDB ストレージエンジンを参照してください。

  • テーブルをロックする時間が短くなるように、SELECT ステートメントを最適化して、実行を高速化します。これを実行するには、いくつかのサマリーテーブルを作成する必要がある場合があります。

  • --low-priority-updatesmysqld を起動します。テーブルレベルロックのみを使用するストレージエンジン (MyISAMMEMORY、および MERGE など) の場合、これにより、テーブルを更新 (変更) するすべてのステートメントに SELECT ステートメントより低い優先度を与えます。この場合、先述のシナリオの 2 つめの SELECT ステートメントは UPDATE ステートメントの前に実行され、最初の SELECT の終了を待機しません。

  • 特定の接続で発行されたすべての更新を低い優先度で実行させるように指定するには、low_priority_updates サーバーシステム変数を 1 に等しく設定します。

  • 特定の INSERTUPDATE、または DELETE ステートメントに低い優先度を与えるには、LOW_PRIORITY 属性を使用します。

  • 特定の SELECT ステートメントに高い優先度を与えるには、HIGH_PRIORITY 属性を使用します。セクション13.2.9「SELECT 構文」を参照してください。

  • max_write_lock_count システム変数の値を低くして mysqld を開始し、テーブルに対する特定の数の挿入が行われたあとにテーブルを待機しているすべての SELECT ステートメントの優先度を一時的に強制的に高めます。これにより、特定の数の WRITE ロックのあとの READ ロックが許可されます。

  • SELECT と組み合わせた INSERT に問題がある場合は、同時 SELECT ステートメントと INSERT ステートメントをサポートする MyISAM テーブルに切り替えることを考慮します。(セクション8.10.3「同時挿入」を参照してください。)

  • 同じ非トランザクションテーブルに対して挿入と削除を組み合わせる場合、INSERT DELAYED が役立つことがあります。セクション13.2.5.2「INSERT DELAYED 構文」を参照してください。

    注記

    MySQL 5.6.6 現在、INSERT DELAYED は非推奨であり、将来のリリースで削除されます。代わりに INSERT (DELAYED を付けない) を使用してください。

  • 組み合わされた SELECTDELETE ステートメントに問題がある場合、DELETE への LIMIT オプションが役立つことがあります。セクション13.2.2「DELETE 構文」を参照してください。

  • SELECT ステートメントで SQL_BUFFER_RESULT を使用すると、テーブルロックの時間の短縮に役立つことがあります。セクション13.2.9「SELECT 構文」を参照してください。

  • テーブルの内容を個別のテーブルに分割すると (クエリーを 1 つのテーブルのカラムに対して実行し、更新を別のテーブルのカラムに制限することによって)、役立つことがあります。

  • 単一のキューを使用するように、mysys/thr_lock.c のロックコードを変更できます。この場合、書き込みロックと読み取りロックは同じ優先度を持ち、一部のアプリケーションに役立つことがあります。

8.10.3 同時挿入

MyISAM ストレージエンジンでは、特定のテーブルに対する読み取りと書き込みの競合を軽減するために、同時挿入をサポートしています。MyISAM テーブルのデータファイルに隙間 (途中の削除された行) がない場合、SELECT ステートメントがテーブルの行を読み取るのと同時に、INSERT ステートメントを実行してテーブルの末尾に行を追加できます。複数の INSERT ステートメントがある場合、それらはキューに入れられ、SELECT ステートメントと同時に順番に実行されます。同時 INSERT の結果はすぐに見られないことがあります。

concurrent_insert システム変数を設定して、同時挿入の処理を変更できます。デフォルトで、変数は AUTO (または 1) に設定され、同時挿入が先述のように処理されます。concurrent_insertNEVER (または 0) に設定されている場合、同時挿入は無効にされます。変数が ALWAYS (または 2) に設定されている場合、行が削除されたテーブルに対してもテーブルの末尾での同時挿入が許可されます。concurrent_insert システム変数の説明も参照してください。

同時挿入を使用できる状況下では、INSERT ステートメントの DELAYED 修飾子を使用する必要はほとんどありません。セクション13.2.5.2「INSERT DELAYED 構文」を参照してください。

バイナリログを使用している場合、同時挿入は CREATE ... SELECT または INSERT ... SELECT ステートメントの通常の挿入に変換されます。これは、バックアップ操作中にログを適用することでテーブルの正確なコピーを確実に再作成できるようにするために行われます。セクション5.2.4「バイナリログ」を参照してください。また、これらのステートメントに対しては、選択元のテーブルへの挿入がブロックされるように、そのテーブルに読み取りロックが設定されます。その結果、そのテーブルに対する同時挿入も待機する必要があります。

LOAD DATA INFILE で同時挿入の条件 (つまり、途中に空きブロックを含まない) を満たす MyISAM テーブルを使用して CONCURRENT を指定する場合、ほかのセッションは LOAD DATA の実行中にテーブルからデータを取得できます。CONCURRENT オプションの使用は、同時にテーブルを使用しているほかのセッションがない場合でも、LOAD DATA のパフォーマンスに多少の影響があります。

HIGH_PRIORITY を指定すると、サーバーが --low-priority-updates オプションで起動されている場合に、その効果がオーバーライドされます。また、同時挿入も使用されなくなります。

LOCK TABLE の場合、READ LOCALREAD の違いは READ LOCAL が、ロックが保持されている間に、競合していない INSERT ステートメント (同時挿入) の実行を許可することです。ただし、ロックを保持している間にサーバーの外部のプロセスを使用してデータベースを操作する場合、これを使用することはできません。

8.10.4 メタデータのロック

MySQL はメタデータのロックを使用して、オブジェクト (テーブル、トリガーなど) へのアクセスを管理します。メタデータのロックは、データの一貫性を確保するために使用されますが、いくらかのオーバーヘッドがあり、クエリーボリュームが増えるとそれも大きくなります。複数のクエリーが同じオブジェクトにアクセスを試みることが多くなるほど、メタデータの競合が増加します。

メタデータのロックは、テーブル定義キャッシュの代替ではなく、その相互排他ロックとロックは、LOCK_open 相互排他ロックと異なります。次の説明では、メタデータのロックの仕組みに関する情報を提供します。

トランザクションのシリアライザビリティーを確保するため、サーバーは、別のセッションで、未完了の明示的または暗黙的に開始されたトランザクションで使用されているテーブルに対して、セッションがデータ定義言語 (DDL) ステートメントを実行することを許可してはいけません。サーバーは、トランザクション内で使用されているテーブルに対してメタデータロックを獲得し、トランザクションが終了するまでそれらのロックの解放を延期させることによって、これを実現します。テーブルへのメタデータロックは、テーブルの構造への変更を妨げます。このロックアプローチには、あるセッション内のトランザクションによって使用されているテーブルは、トランザクションが終了するまで、ほかのセッションによって DDL ステートメントで使用できないという問題があります。

この原則は、トランザクションテーブルだけでなく、非トランザクションテーブルにも適用されます。あるセッションがトランザクションテーブル t と非トランザクションテーブル nt を次のように使用するトランザクションを開始するとします。

START TRANSACTION;
SELECT * FROM t;
SELECT * FROM nt;

サーバーはトランザクションが終了するまで、tnt の両方に対するメタデータロックを保持します。別のセッションがいずれかのテーブルに対して、DDL または書き込みロック操作を試みると、それはトランザクションの終了時にメタデータロックが解放されるまでブロックされます。たとえば、2 つめのセッションはこれらのいずれかの操作を試みるとブロックされます。

DROP TABLE t;
ALTER TABLE t ...;
DROP TABLE nt;
ALTER TABLE nt ...;
LOCK TABLE t ... WRITE;

サーバーが構文上有効であるが、実行中に失敗するステートメントのメタデータロックを獲得した場合、そのロックを早期に解放しません。失敗したステートメントがバイナリログに書き込まれ、ロックによってログの一貫性が保護されるため、ロックの解放はまだトランザクションの終了まで延期されます。

自動コミットモードでは、各ステートメントが事実上完全なトランザクションであるため、そのステートメントに対して獲得されたメタデータロックは、ステートメントの終了までしか保持されません。

PREPARE ステートメント中に獲得されたメタデータロックは、準備が複数ステートメントトランザクション内で行われる場合でも、ステートメントが準備されると解放されます。

MySQL 5.5 より前では、トランザクションがステートメント内で使用されているテーブルのメタデータロックと同等のロックを獲得した場合、ステートメントの終了時にロックを解放していました。このアプローチには、アクティブなトランザクションで別のセッションによって使用されているテーブルに対して、DDL ステートメントが実行された場合、ステートメントは誤った順序でバイナリログに書き込まれる可能性があるという欠点がありました。

8.10.5 外部ロック

外部ロックは、複数のプロセスによる MyISAM データベーステーブルの競合を管理するためのファイルシステムロックの使用です。外部ロックは、MySQL サーバーなどの単一のプロセスが、テーブルへのアクセスを必要とする唯一のプロセスであると想定できない状況で使用されます。次にいくつかの例を示します。

  • 同じデータベースディレクトリを使用する複数のサーバーを実行する場合 (推奨されません)、各サーバーで外部ロックが有効になっている必要があります。

  • myisamchk を使用して MyISAM テーブルに対して保守操作を実行する場合、サーバーが実行中でないことを確認するか、サーバーが必要に応じてテーブルファイルをロックし、テーブルへのアクセスを myisamchk によって調整するように、サーバーで外部ロックが有効になっていることを確認する必要があります。同じことが、MyISAM テーブルをパックするために myisampack を使用する場合にも当てはまります。

    外部ロックを有効にしてサーバーを実行する場合、テーブルのチェックなどの読み取り操作のために、いつでも myisamchk を使用できます。この場合に、サーバーが myisamchk で使用しているテーブルを更新しようとする場合、サーバーは myisamchk が終了するまで待ってから、続行します。

    テーブルの修復や最適化などの書き込み操作のために myisamchk を使用する場合、または myisampack を使用してテーブルをパックする場合は、mysqld サーバーがそのテーブルを使用していないことを常に確認する必要がありますmysqld を停止しない場合、myisamchk を実行する前に、少なくとも mysqladmin flush-tables を実行してください。サーバーと myisamchk が同時にテーブルにアクセスすると、テーブルが破損する可能性があります

外部ロックが有効になっていると、テーブルへのアクセスを必要とする各プロセスは、テーブルへのアクセスに進む前にテーブルファイルに対するファイルシステムロックを獲得します。必要なすべてのロックを獲得できない場合、(現在ロックを保持しているプロセスがそれらを解放したあとに) ロックを取得できるまで、プロセスはテーブルへのアクセスをブロックされます。

サーバーは場合によってテーブルにアクセスできるまでほかのプロセスを待機する必要があるため、外部ロックはサーバーのパフォーマンスに影響します。

単一のサーバーを実行して特定のデータディレクトリにアクセスする場合 (これは通常のケースです) およびサーバーの実行中に myisamchk などのほかのプログラムでテーブルを変更する必要がない場合、外部ロックは不要です。ほかのプログラムでテーブルを読み取るだけである場合、外部ロックは不要ですが、myisamchk がテーブルを読み取っている間にサーバーがテーブルを変更すると、myisamchk が警告をレポートすることがあります。

外部ロックが無効になっていて、myisamchk を使用するには、myisamchk の実行中にサーバーを停止するか、myisamchk を実行する前にテーブルをロックし、フラッシュする必要があります。(セクション8.11.1「システム要素およびスタートアップパラメータのチューニング」を参照してください。)この要件を回避するには、CHECK TABLE および REPAIR TABLE ステートメントを使用して、MyISAM テーブルをチェックし、修復します。

mysqld の場合、外部ロックは skip_external_locking システム変数の値で制御されます。この変数が有効にされている場合、外部ロックは無効になり、その逆も同じです。MySQL 4.0 以降、外部ロックはデフォルトで無効にされます。

外部ロックの使用は、サーバーの起動時に --external-locking または --skip-external-locking オプションを使用して制御できます。

多数の MySQL プロセスから MyISAM テーブルを更新できるようにするために外部ロックオプションを使用する場合、次の条件を満たしていることを確認する必要があります。

  • 別のプロセスによって更新されるテーブルを使用するクエリーには、クエリーキャッシュを使用しないでください。

  • サーバーを --delay-key-write=ALL オプションで起動したり、共有テーブルに対して DELAY_KEY_WRITE=1 テーブルオプションを使用したりしないでください。そうでないと、インデックスが破損する可能性があります。

これらの条件をもっとも簡単に満たす方法は、常に --external-locking--delay-key-write=OFF および --query-cache-size=0 と一緒に使用することです。(これは、多くのセットアップで、前述のオプションを組み合わせることが有用であるため、デフォルトで実行されません。)

8.11 MySQL サーバーの最適化

このセクションでは、主に SQL ステートメントのチューニングではなくシステム構成を扱うデータベースサーバーの最適化技法について説明します。このセクションの情報は、管理しているサーバー全体のパフォーマンスとスケーラビリティーを確保したい DBA、データベースのセットアップを含むインストールスクリプトを構築する開発者、および生産性を最大にしたいと考え、開発、テストなどのために自分自身で MySQL を実行しているユーザーに適しています。

8.11.1 システム要素およびスタートアップパラメータのチューニング

大幅なパフォーマンスの向上を実現するためには、システムレベルの要素の一部を早急に決定する必要があるため、その要素から始めます。ほかの場合は、このセクションをざっと目を通すだけで十分かもしれません。ただし、このレベルで適用する要素を変更することで、どの程度改善できるかの感覚をつかんでおくことは常に望ましいと考えられます。

本番環境で MySQL を使用する前に、目的のプラットフォームでそれをテストすることをお勧めします。

ほかのヒント:

  • RAM が十分にある場合は、すべてのスワップデバイスを取り外すことができます。オペレーティングシステムによっては、空きメモリーがある場合でも、特定のコンテキストで、スワップデバイスが使用されることがあります。

  • MyISAM テーブルの外部ロックを避けます。MySQL 4.0 以降、すべてのシステムで外部ロックはデフォルトで無効にされています。--external-locking--skip-external-locking オプションは外部ロックを明示的に有効および無効にします。

    外部ロックを無効にしても、1 台のサーバーしか実行していないかぎり、MySQL の機能に影響しません。myisamchk を実行する前にサーバーを停止する (または関連するテーブルをロックしてフラッシュする) ことを忘れないでください。一部のシステムでは外部ロックが機能しないため、無効にする必要があります。

    外部ロックを無効にできない唯一のケースは、同じデータに対して複数の MySQL サーバー (クライアントではない) を実行している場合、またはサーバーにまずテーブルをフラッシュしてロックするように伝えずに、myisamchk を実行してテーブルをチェックする (修復しない) 場合です。MySQL Cluster を使用している場合を除き、複数の MySQL サーバーを使用して、同じデータに同時にアクセスすることは一般に推奨されません

    注記

    MySQL Cluster は現在 MySQL 5.6 でサポートされていません。MySQL Cluster のアップグレードを希望するユーザーは、代わりに MySQL Cluster NDB 7.3 に移行してください。これは MySQL 5.6 に基づいていますが、最新の NDB の改善と修正が含まれています。

    LOCK TABLES および UNLOCK TABLES ステートメントは内部ロックを使用するため、外部ロックが無効にされている場合でもそれらを使用できます。

8.11.2 サーバーパラメータのチューニング

次のコマンドを使用して、mysqld サーバーで使用されるデフォルトのバッファーサイズを判断できます。

shell> mysqld --verbose --help

このコマンドによって、すべての mysqld オプションと構成可能なシステム変数のリストが生成されます。この出力には、デフォルトの変数値も含まれ、次のように見えます。

abort-slave-event-count 0
allow-suspicious-udfs FALSE
archive ON
auto-increment-increment 1
auto-increment-offset 1
autocommit TRUE
automatic-sp-privileges TRUE
back-log 80
basedir /home/jon/bin/mysql-5.6/
big-tables FALSE
bind-address *
binlog-cache-size 32768
binlog-checksum CRC32
binlog-direct-non-transactional-updates FALSE
binlog-format STATEMENT
binlog-max-flush-queue-time 0
binlog-order-commits TRUE
binlog-row-event-max-size 8192
binlog-row-image FULL
binlog-rows-query-log-events FALSE
binlog-stmt-cache-size 32768
blackhole ON
bulk-insert-buffer-size 8388608
character-set-client-handshake TRUE
character-set-filesystem binary
character-set-server latin1
character-sets-dir /home/jon/bin/mysql-5.6/share/charsets/
chroot (No default value)
collation-server latin1_swedish_ci
completion-type NO_CHAIN
concurrent-insert AUTO
connect-timeout 10
console FALSE
datadir (No default value)
date-format %Y-%m-%d
datetime-format %Y-%m-%d %H:%i:%s
default-storage-engine InnoDB
default-time-zone (No default value)
default-tmp-storage-engine InnoDB
default-week-format 0
delay-key-write ON
delayed-insert-limit 100
delayed-insert-timeout 300
delayed-queue-size 1000
des-key-file (No default value)
disconnect-on-expired-password TRUE
disconnect-slave-event-count 0
div-precision-increment 4
end-markers-in-json FALSE
enforce-gtid-consistency FALSE
eq-range-index-dive-limit 10
event-scheduler OFF
expire-logs-days 0
explicit-defaults-for-timestamp FALSE
external-locking FALSE
flush FALSE
flush-time 0
ft-boolean-syntax + -><()~*:""&|
ft-max-word-len 84
ft-min-word-len 4
ft-query-expansion-limit 20
ft-stopword-file (No default value)
gdb FALSE
general-log FALSE
general-log-file /home/jon/bin/mysql-5.6/data/havskatt.log
group-concat-max-len 1024
gtid-mode OFF
help TRUE
host-cache-size 279
ignore-builtin-innodb FALSE
init-connect
init-file (No default value)
init-slave
innodb ON
innodb-adaptive-flushing TRUE
innodb-adaptive-flushing-lwm 10
innodb-adaptive-hash-index TRUE
innodb-adaptive-max-sleep-delay 150000
innodb-additional-mem-pool-size 8388608
innodb-api-bk-commit-interval 5
innodb-api-disable-rowlock FALSE
innodb-api-enable-binlog FALSE
innodb-api-enable-mdl FALSE
innodb-api-trx-level 0
innodb-autoextend-increment 64
innodb-autoinc-lock-mode 1
innodb-buffer-page ON
innodb-buffer-page-lru ON
innodb-buffer-pool-dump-at-shutdown FALSE
innodb-buffer-pool-dump-now FALSE
innodb-buffer-pool-filename ib_buffer_pool
innodb-buffer-pool-instances 0
innodb-buffer-pool-load-abort FALSE
innodb-buffer-pool-load-at-startup FALSE
innodb-buffer-pool-load-now FALSE
innodb-buffer-pool-size 134217728
innodb-buffer-pool-stats ON
innodb-change-buffer-max-size 25
innodb-change-buffering all
innodb-checksum-algorithm innodb
innodb-checksums TRUE
innodb-cmp ON
innodb-cmp-per-index ON
innodb-cmp-per-index-enabled FALSE
innodb-cmp-per-index-reset ON
innodb-cmp-reset ON
innodb-cmpmem ON
innodb-cmpmem-reset ON
innodb-commit-concurrency 0
innodb-compression-failure-threshold-pct 5
innodb-compression-level 6
innodb-compression-pad-pct-max 50
innodb-concurrency-tickets 5000
innodb-data-file-path (No default value)
innodb-data-home-dir (No default value)
innodb-disable-sort-file-cache FALSE
innodb-doublewrite TRUE
innodb-fast-shutdown 1
innodb-file-format Antelope
innodb-file-format-check TRUE
innodb-file-format-max Antelope
innodb-file-io-threads 4
innodb-file-per-table TRUE
innodb-flush-log-at-timeout 1
innodb-flush-log-at-trx-commit 1
innodb-flush-method (No default value)
innodb-flush-neighbors 1
innodb-flushing-avg-loops 30
innodb-force-load-corrupted FALSE
innodb-force-recovery 0
innodb-ft-aux-table (No default value)
innodb-ft-being-deleted ON
innodb-ft-cache-size 8000000
innodb-ft-config ON
innodb-ft-default-stopword ON
innodb-ft-deleted ON
innodb-ft-enable-diag-print FALSE
innodb-ft-enable-stopword TRUE
innodb-ft-index-cache ON
innodb-ft-index-table ON
innodb-ft-inserted ON
innodb-ft-max-token-size 84
innodb-ft-min-token-size 3
innodb-ft-num-word-optimize 2000
innodb-ft-server-stopword-table (No default value)
innodb-ft-sort-pll-degree 2
innodb-ft-user-stopword-table (No default value)
innodb-io-capacity 200
innodb-io-capacity-max 18446744073709551615
innodb-large-prefix FALSE
innodb-lock-wait-timeout 50
innodb-lock-waits ON
innodb-locks ON
innodb-locks-unsafe-for-binlog FALSE
innodb-log-buffer-size 8388608
innodb-log-compressed-pages TRUE
innodb-log-file-size 50331648
innodb-log-files-in-group 2
innodb-log-group-home-dir (No default value)
innodb-lru-scan-depth 1024
innodb-max-dirty-pages-pct 75
innodb-max-dirty-pages-pct-lwm 0
innodb-max-purge-lag 0
innodb-max-purge-lag-delay 0
innodb-metrics ON
innodb-mirrored-log-groups 1
innodb-monitor-disable (No default value)
innodb-monitor-enable (No default value)
innodb-monitor-reset (No default value)
innodb-monitor-reset-all (No default value)
innodb-old-blocks-pct 37
innodb-old-blocks-time 1000
innodb-online-alter-log-max-size 134217728
innodb-open-files 0
innodb-optimize-fulltext-only FALSE
innodb-page-size 16384
innodb-print-all-deadlocks FALSE
innodb-purge-batch-size 300
innodb-purge-threads 1
innodb-random-read-ahead FALSE
innodb-read-ahead-threshold 56
innodb-read-io-threads 4
innodb-read-only FALSE
innodb-replication-delay 0
innodb-rollback-on-timeout FALSE
innodb-rollback-segments 128
innodb-sort-buffer-size 1048576
innodb-spin-wait-delay 6
innodb-stats-auto-recalc TRUE
innodb-stats-method nulls_equal
innodb-stats-on-metadata FALSE
innodb-stats-persistent TRUE
innodb-stats-persistent-sample-pages 20
innodb-stats-sample-pages 8
innodb-stats-transient-sample-pages 8
innodb-status-file FALSE
innodb-strict-mode FALSE
innodb-support-xa TRUE
innodb-sync-array-size 1
innodb-sync-spin-loops 30
innodb-sys-columns ON
innodb-sys-datafiles ON
innodb-sys-fields ON
innodb-sys-foreign ON
innodb-sys-foreign-cols ON
innodb-sys-indexes ON
innodb-sys-tables ON
innodb-sys-tablespaces ON
innodb-sys-tablestats ON
innodb-table-locks TRUE
innodb-thread-concurrency 0
innodb-thread-sleep-delay 10000
innodb-trx ON
innodb-undo-directory .
innodb-undo-logs 128
innodb-undo-tablespaces 0
innodb-use-native-aio TRUE
innodb-use-sys-malloc TRUE
innodb-write-io-threads 4
interactive-timeout 28800
join-buffer-size 262144
keep-files-on-create FALSE
key-buffer-size 8388608
key-cache-age-threshold 300
key-cache-block-size 1024
key-cache-division-limit 100
language /home/jon/bin/mysql-5.6/share/
large-pages FALSE
lc-messages en_US
lc-messages-dir /home/jon/bin/mysql-5.6/share/
lc-time-names en_US
local-infile TRUE
lock-wait-timeout 31536000
log-bin (No default value)
log-bin-index (No default value)
log-bin-trust-function-creators FALSE
log-bin-use-v1-row-events FALSE
log-error
log-isam myisam.log
log-output FILE
log-queries-not-using-indexes FALSE
log-raw FALSE
log-short-format FALSE
log-slave-updates FALSE
log-slow-admin-statements FALSE
log-slow-slave-statements FALSE
log-tc tc.log
log-tc-size 24576
log-throttle-queries-not-using-indexes 0
log-warnings 1
long-query-time 10
low-priority-updates FALSE
lower-case-table-names 0
master-info-file master.info
master-info-repository FILE
master-retry-count 86400
master-verify-checksum FALSE
max-allowed-packet 4194304
max-binlog-cache-size 18446744073709547520
max-binlog-dump-events 0
max-binlog-size 1073741824
max-binlog-stmt-cache-size 18446744073709547520
max-connect-errors 100
max-connections 151
max-delayed-threads 20
max-error-count 64
max-heap-table-size 16777216
max-join-size 18446744073709551615
max-length-for-sort-data 1024
max-prepared-stmt-count 16382
max-relay-log-size 0
max-seeks-for-key 18446744073709551615
max-sort-length 1024
max-sp-recursion-depth 0
max-tmp-tables 32
max-user-connections 0
max-write-lock-count 18446744073709551615
memlock FALSE
metadata-locks-cache-size 1024
metadata-locks-hash-instances 8
min-examined-row-limit 0
multi-range-count 256
myisam-block-size 1024
myisam-data-pointer-size 6
myisam-max-sort-file-size 9223372036853727232
myisam-mmap-size 18446744073709551615
myisam-recover-options OFF
myisam-repair-threads 1
myisam-sort-buffer-size 8388608
myisam-stats-method nulls_unequal
myisam-use-mmap FALSE
net-buffer-length 16384
net-read-timeout 30
net-retry-count 10
net-write-timeout 60
new FALSE
old FALSE
old-alter-table FALSE
old-passwords 0
old-style-user-limits FALSE
open-files-limit 1024
optimizer-prune-level 1
optimizer-search-depth 62
optimizer-switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,subquery_materialization_cost_based=on,use_index_extensions=on
optimizer-trace
optimizer-trace-features greedy_search=on,range_optimizer=on,dynamic_range=on,repeated_subselect=on
optimizer-trace-limit 1
optimizer-trace-max-mem-size 16384
optimizer-trace-offset -1
partition ON
performance-schema TRUE
performance-schema-accounts-size -1
performance-schema-consumer-events-stages-current FALSE
performance-schema-consumer-events-stages-history FALSE
performance-schema-consumer-events-stages-history-long FALSE
performance-schema-consumer-events-statements-current TRUE
performance-schema-consumer-events-statements-history FALSE
performance-schema-consumer-events-statements-history-long FALSE
performance-schema-consumer-events-waits-current FALSE
performance-schema-consumer-events-waits-history FALSE
performance-schema-consumer-events-waits-history-long FALSE
performance-schema-consumer-global-instrumentation TRUE
performance-schema-consumer-statements-digest TRUE
performance-schema-consumer-thread-instrumentation TRUE
performance-schema-digests-size -1
performance-schema-events-stages-history-long-size -1
performance-schema-events-stages-history-size -1
performance-schema-events-statements-history-long-size -1
performance-schema-events-statements-history-size -1
performance-schema-events-waits-history-long-size -1
performance-schema-events-waits-history-size -1
performance-schema-hosts-size -1
performance-schema-instrument
performance-schema-max-cond-classes 80
performance-schema-max-cond-instances -1
performance-schema-max-file-classes 50
performance-schema-max-file-handles 32768
performance-schema-max-file-instances -1
performance-schema-max-mutex-classes 200
performance-schema-max-mutex-instances -1
performance-schema-max-rwlock-classes 30
performance-schema-max-rwlock-instances -1
performance-schema-max-socket-classes 10
performance-schema-max-socket-instances -1
performance-schema-max-stage-classes 150
performance-schema-max-statement-classes 167
performance-schema-max-table-handles -1
performance-schema-max-table-instances -1
performance-schema-max-thread-classes 50
performance-schema-max-thread-instances -1
performance-schema-session-connect-attrs-size -1
performance-schema-setup-actors-size 100
performance-schema-setup-objects-size 100
performance-schema-users-size -1
pid-file /home/jon/bin/mysql-5.6/data/havskatt.pid
plugin-dir /home/jon/bin/mysql-5.6/lib/plugin/
port 3306
port-open-timeout 0
preload-buffer-size 32768
profiling-history-size 15
query-alloc-block-size 8192
query-cache-limit 1048576
query-cache-min-res-unit 4096
query-cache-size 1048576
query-cache-type OFF
query-cache-wlock-invalidate FALSE
query-prealloc-size 8192
range-alloc-block-size 4096
read-buffer-size 131072
read-only FALSE
read-rnd-buffer-size 262144
relay-log (No default value)
relay-log-index (No default value)
relay-log-info-file relay-log.info
relay-log-info-repository FILE
relay-log-purge TRUE
relay-log-recovery FALSE
relay-log-space-limit 0
replicate-same-server-id FALSE
report-host (No default value)
report-password (No default value)
report-port 0
report-user (No default value)
safe-user-create FALSE
secure-auth TRUE
secure-file-priv (No default value)
server-id 0
server-id-bits 32
sha256-password-private-key-path private_key.pem
sha256-password-public-key-path public_key.pem
show-slave-auth-info FALSE
skip-grant-tables FALSE
skip-name-resolve FALSE
skip-networking FALSE
skip-show-database FALSE
skip-slave-start FALSE
slave-allow-batching FALSE
slave-checkpoint-group 512
slave-checkpoint-period 300
slave-compressed-protocol FALSE
slave-exec-mode STRICT
slave-load-tmpdir /tmp
slave-max-allowed-packet 1073741824
slave-net-timeout 3600
slave-parallel-workers 0
slave-pending-jobs-size-max 16777216
slave-rows-search-algorithms TABLE_SCAN,INDEX_SCAN
slave-skip-errors (No default value)
slave-sql-verify-checksum TRUE
slave-transaction-retries 10
slave-type-conversions
slow-launch-time 2
slow-query-log FALSE
slow-query-log-file /home/jon/bin/mysql-5.6/data/havskatt-slow.log
socket /tmp/mysql.sock
sort-buffer-size 262144
sporadic-binlog-dump-fail FALSE
sql-mode NO_ENGINE_SUBSTITUTION
ssl FALSE
ssl-ca (No default value)
ssl-capath (No default value)
ssl-cert (No default value)
ssl-cipher (No default value)
ssl-crl (No default value)
ssl-crlpath (No default value)
ssl-key (No default value)
stored-program-cache 256
super-large-pages FALSE
symbolic-links TRUE
sync-binlog 0
sync-frm TRUE
sync-master-info 10000
sync-relay-log 10000
sync-relay-log-info 10000
sysdate-is-now FALSE
table-definition-cache 615
table-open-cache 431
table-open-cache-instances 1
tc-heuristic-recover COMMIT
temp-pool TRUE
thread-cache-size 9
thread-concurrency 10
thread-handling one-thread-per-connection
thread-stack 262144
time-format %H:%i:%s
timed-mutexes FALSE
tmp-table-size 16777216
tmpdir /tmp
transaction-alloc-block-size 8192
transaction-isolation REPEATABLE-READ
transaction-prealloc-size 4096
transaction-read-only FALSE
updatable-views-with-limit YES
verbose TRUE
wait-timeout

現在実行中の mysqld サーバーの場合、それに接続し、次のステートメントを発行することで、そのシステム変数の現在の値を確認できます。

mysql> SHOW VARIABLES;

また、次のステートメントを発行して、実行中のサーバーの統計やステータスインジケータの一部を表示することもできます。

mysql> SHOW STATUS;

システム変数とステータス情報は、mysqladmin を使用して取得することもできます。

shell> mysqladmin variablesshell> mysqladmin extended-status

すべてのシステムおよびステータス変数の完全な説明については、セクション5.1.4「サーバーシステム変数」およびセクション5.1.6「サーバーステータス変数」を参照してください。

MySQL はきわめてスケーラブルなアルゴリズムを使用しているため、通常ごくわずかなメモリーで実行できます。ただし、通常 MySQL に多くのメモリーを割り当てることによって、パフォーマンスが向上します。

MySQL サーバーをチューニングする場合、構成するもっとも重要な 2 つの変数は key_buffer_sizetable_open_cache です。ほかの変数の変更を試みる前に、まずこれらの変数が適切に設定されていることを確信しておくべきです。

次の例に、さまざまな実行時構成の一般的な変数値を示します。

  • 少なくとも 256M バイトのメモリーと多くのテーブルがあり、中程度のクライアント数で最大のパフォーマンスを必要とする場合、次のようなものを使用します。

    shell> mysqld_safe --key_buffer_size=64M --table_open_cache=256 \--sort_buffer_size=4M --read_buffer_size=1M &
  • メモリーが 128M バイトで、少数のテーブルしかないが、大量のソートを実行する場合、次のようなものを使用できます。

    shell> mysqld_safe --key_buffer_size=16M --sort_buffer_size=1M

    著しく多くの同時接続がある場合、mysqld が接続ごとにごく少量のメモリーを使用するように構成されていないかぎり、スワップの問題が発生する可能性があります。すべての接続に十分なメモリーがある場合に、mysqld は効率的に実行します。

  • メモリーがほとんどなく大量の接続がある場合は、次のようなものを使用します。

    shell> mysqld_safe --key_buffer_size=512K --sort_buffer_size=100K \--read_buffer_size=100K &

    これでもかまいません。

    shell> mysqld_safe --key_buffer_size=512K --sort_buffer_size=16K \--table_open_cache=32 --read_buffer_size=8K \--net_buffer_length=1K &

使用可能なメモリーよりはるかに大きいテーブルに対して、GROUP BY または ORDER BY 操作を実行する場合、read_rnd_buffer_size の値を増やして、行の読み取りとそれに続くソート操作を高速化します。

MySQL 配布に付属するサンプルオプションファイルを使用できます。セクション5.1.2「サーバー構成のデフォルト値」を参照してください。

コマンド行で mysqld または mysqld_safe のオプションを指定する場合、そのサーバーの呼び出しに対してのみ有効です。サーバーの実行のたびにオプションを使用するには、それをオプションファイルに入れます。

パラメータの変更の効果を確認するには、次のようなものを実行します。

shell> mysqld --key_buffer_size=32M --verbose --help

変数値は出力の最後近くに一覧表示されます。--verbose および --help オプションが最後になるようにしてください。そうでないと、コマンド行でそれらのあとに挙げられているすべてのオプションの効果が出力に反映されません。

InnoDB ストレージエンジンのチューニングについては、セクション8.5「InnoDB テーブルの最適化」を参照してください。

8.11.3 ディスク I/O の最適化

  • ディスクシークはパフォーマンスの大きなボトルネックです。この問題は、データの量が、効果的なキャッシュが実行不能になるほど大きくなり始めると、明確になります。多かれ少なかれランダムにデータにアクセスする大きなデータベースの場合、読み取りには最低 1 回、書き込みには 2 回のディスクシークが確実に必要になります。この問題を最小にするには、少ないシーク回数でディスクを使用します。

  • さまざまなディスクにファイルをシンボリックリンクするか、ディスクストライピングを行なって、使用可能なディスクスピンドル数を増やします (およびそれによってシークのオーバーヘッドを軽減します)。

    • シンボリックリンクの使用

      これは、MyISAM テーブルの場合、データディレクトリ内の通常の場所から別のディスクへのインデックスファイルやデータファイルのシンボリックリンクを作成する (ストライピングされることもある) ことを意味します。ディスクがほかの目的にも使用されていないものとして、これによって、シークと読み取り時間がともに改善されます。セクション8.11.3.1「シンボリックリンクの使用」を参照してください。

    • ストライピング

      ストライピングは、多数のディスクがあり、最初のブロックを最初のディスクに、2 番目のブロックを 2 番目のディスクに、N 番目のブロックを (N MOD number_of_disks) 番目のディスクにというように配置することを意味します。つまり、通常のデータサイズがストライプサイズより小さい (または完全に一致している) 場合に、パフォーマンスが大幅に向上します。ストライピングはオペレーティングシステムとストライプサイズに大きく依存するため、さまざまなストライプサイズでアプリケーションのベンチマークを行なってください。セクション8.12.3「独自のベンチマークの使用」を参照してください。

      ストライピングの速度の違いは、パラメータに大きく依存します。ストライピングパラメータの設定方法とディスク数によって、桁違いの差が測定されることがあります。ランダムアクセスに対する最適化か順次アクセスに対する最適化かを選択する必要があります。

  • 信頼性のため、RAID 0+1 (ストライピングとミラーリング) を使用したいと考える場合がありますが、この場合、N 個のドライブのデータを保持するために 2 × N 個のドライブが必要です。これは、そのための資金がある場合に最適なオプションである可能性があります。ただし、それを効率的に処理するために、何らかのボリューム管理ソフトウェアに投資する必要がある場合もあります。

  • 適切なオプションは、ある種類のデータがどのくらい重要であるかに応じて RAID レベルを変えることです。たとえば、再生成が可能なやや重要なデータは RAID 0 ディスクに格納しますが、ホスト情報やログなどの本当に重要なデータは、RAID 0+1 または RAID N ディスクに格納します。RAID N は、パリティビットの更新に必要な時間のため、多くの書き込みがある場合に問題になる可能性があります。

  • Linux では、hdparm を使用して、ディスクのインタフェースを構成することによって、パフォーマンスを大幅に向上できます。(負荷時に最大 100% も珍しくありません。)次の hdparm オプションは、MySQL、およびおそらくその他の多くのアプリケーションに非常に適しているはずです。

    hdparm -m 16 -d 1

    このコマンドを使用したときのパフォーマンスと信頼性はハードウェアに依存するため、hdparm の使用後はシステムを徹底的にテストすることを強くお勧めします。詳細については、hdparm のマニュアルページを参照してください。hdparm を適切に使用しないと、ファイルシステムの破損が発生することがあるため、実験する前に、すべてをバックアップしてください。

  • データベースが使用するファイルシステムのパラメータを設定することもできます。

    ファイルに最後にアクセスされたタイミングを知る必要がない (実際にデータベースサーバーで役立たない) 場合、-o noatime オプションを使用してファイルシステムをマウントできます。それは、ファイルシステム上の i ノードの最終アクセス時間への更新をスキップするため、一部のディスクシークが避けられます。

    多くのオペレーティングシステムで、-o async オプションを使用してファイルシステムをマウントすることによって、ファイルシステムが非同期に更新されるように設定できます。コンピュータが適度に安定している場合、これにより、それほど信頼性を犠牲にすることなく、パフォーマンスが向上するはずです。(Linux ではこのフラグがデフォルトでオンにされています。)

8.11.3.1 シンボリックリンクの使用

データベースやテーブルをデータベースディレクトリからほかの場所に移動して、それらを新しい場所へのシンボリックリンクに置き換えることができます。これを実行したいと考える可能性があるのは、たとえば、データベースを空き領域の多いファイルシステムに移動する場合や、テーブルを別のディスクに分散させてシステムの速度を高める場合です。

InnoDB テーブルの場合、セクション14.5.4「テーブルスペースの位置の指定」に説明するように、シンボリックリンクの代わりに、CREATE TABLE ステートメントで DATA DIRECTORY 句を使用します。この新機能は、サポートされるクロスプラットフォーム技法です。

これを実行する推奨される方法は、データベースディレクトリ全体の別のディスクへのシンボリックリンクを作成することです。MyISAM テーブルのシンボルリンク作成は最後の手段として行います。

データディレクトリの場所を特定するには、次のステートメントを使用します。

SHOW VARIABLES LIKE 'datadir';
8.11.3.1.1 Unix 上のデータベースへのシンボリックリンクの使用

Unix で、データベースのシンボリックリンクを作成する方法は、まず空き領域のあるディスクにディレクトリを作成してから、MySQL データディレクトリからそれへのソフトリンクを作成することです。

shell> mkdir /dr1/databases/testshell> ln -s /dr1/databases/test /path/to/datadir

MySQL は、1 つのディレクトリから複数のデータベースへのリンクをサポートしていません。データベースディレクトリとシンボリックリンクの置換は、データベース間のシンボリックリンクを作成しないかぎり、機能します。MySQL データディレクトリにデータベース db1 があり、db1 を指すシンボリックリンク db2 を作成するとします。

shell> cd /path/to/datadirshell> ln -s db1 db2

その結果、db1 のすべてのテーブル tbl_a は、db2 にもテーブル tbl_a として表示されます。あるクライアントが db1.tbl_a を更新し、ほかのクライアントが db2.tbl_a を更新すると、問題が発生する可能性があります。

8.11.3.1.2 Unix 上の MyISAM へのシンボリックリンクの使用

シンボリックリンクは、MyISAM テーブルに対してのみ完全にサポートされています。ほかのストレージエンジンのテーブルで使用されているファイルの場合、シンボリックリンクを使用しようとすると、未知の問題が発生することがあります。InnoDB テーブルの場合は、代わりにセクション14.5.4「テーブルスペースの位置の指定」に説明する代替の技法を使用します。

完全に動作する realpath() 呼び出しがないシステムでは、テーブルのシンボリックリンクを作成しないでください。(Linux と Solaris では realpath() をサポートしています)。システムでシンボリックリンクをサポートしているかどうかを判断するには、次のステートメントを使用して、have_symlink システム変数の値をチェックします。

SHOW VARIABLES LIKE 'have_symlink';

MyISAM テーブルのシンボリックリンクの処理は次のように機能します。

  • データディレクトリには、常にテーブルフォーマット (.frm) ファイル、データ (.MYD) ファイル、およびインデックス (.MYI) ファイルがあります。データファイルとインデックスファイルは、ほかの場所に移動し、データディレクトリ内でシンボリックリンクによって置き換えることができます。フォーマットファイルはできません。

  • データファイルとインデックスファイルは、独立して別々のディレクトリへのシンボリックリンクを作成できます。

  • 実行中の MySQL サーバーにシンボリックリンクの作成を実行するように指示するには、CREATE TABLEDATA DIRECTORY および INDEX DIRECTORY オプションを使用します。セクション13.1.17「CREATE TABLE 構文」を参照してください。または、mysqld が実行中でない場合は、コマンド行から ln -s を使用して、シンボリックリンクの作成を手動で実行できます。

    注記

    DATA DIRECTORY および INDEX DIRECTORY オプションのいずれか、または両方で使用されるパスには、MySQL data ディレクトリを含めることができません。(Bug #32167)

  • myisamchk が、シンボリックリンクをデータファイルやインデックスファイルに置き換えません。それは、シンボリックリンクが指しているファイルに対して直接作用します。一時ファイルはすべてデータファイルやインデックスファイルが配置されているディレクトリに作成されます。同じことが ALTER TABLEOPTIMIZE TABLE、および REPAIR TABLE ステートメントにも当てはまります。

  • 注記

    シンボリックリンクを使用しているテーブルを削除すると、シンボリックリンクとシンボリックリンクが指しているファイルの両方が削除されます。これは、システム root として mysqld を実行したり、システムユーザーに MySQL データベースディレクトリへの書き込みアクセス権を許可したりしないきわめて正当な理由です。

  • ALTER TABLE ... RENAME または RENAME TABLE を使用してテーブルの名前を変更し、テーブルを別のデータベースに移動しない場合、データベースディレクトリのシンボリックリンクの名前が新しい名前に変更され、データファイルとインデックスファイルもそれに従って名前が変更されます。

  • ALTER TABLE ... RENAME または RENAME TABLE を使用してテーブルを別のデータベースに移動すると、テーブルが別のデータベースディレクトリに移動されます。テーブル名が変更された場合、新しいデータベースディレクトリ内のシンボリックリンクの名前が新しい名前に変更され、データファイルとインデックスファイルもそれに従って名前が変更されます。

  • シンボリックリンクを使用していない場合、--skip-symbolic-links オプションを付けて mysqld を起動し、だれも mysqld を使用して、データディレクトリ外のファイルを削除したり名前を変更したりできないようにします。

これらのテーブルシンボリックリンクの操作はサポートされていません。

  • ALTER TABLEDATA DIRECTORY および INDEX DIRECTORY テーブルオプションを無視します。

  • 前に示したように、データファイルとインデックスファイルにのみシンボリックリンクにできます。.frm ファイルはシンボリックリンクにできません。これを実行しようとすると (たとえば、1 つのテーブル名を別のテーブルのシノニムにするなど) 正しくない結果が生成されます。MySQL データディレクトリにデータベース db1、このデータベースにテーブル tbl1 があり、db1 ディレクトリに tbl1 を指すシンボリックリンク tbl2 を作成するとします。

    shell> cd /path/to/datadir/db1shell> ln -s tbl1.frm tbl2.frmshell> ln -s tbl1.MYD tbl2.MYDshell> ln -s tbl1.MYI tbl2.MYI

    あるスレッドが db1.tbl1 を読み取り、別のスレッドで db1.tbl2 を更新すると、問題が発生します。

    • クエリーキャッシュがだまされます (tbl1 が更新されていないことを知る方法がないため、古くなっている結果を返します)。

    • tbl2 に対する ALTER ステートメントが失敗します。

8.11.3.1.3 Windows 上のデータベースへのシンボリックリンクの使用

Windows では、データベースディレクトリにシンボリックリンクを使用できます。これにより、データベースディレクトリへのシンボリックリンクを設定して、それを別の場所 (別のディスク上など) に置くことができます。Windows でのデータベースシンボリックリンクの使用は、Unix でのそれらの使用に似ていますが、リンクのセットアップの手順は異なります。

mydb というデータベースのデータベースディレクトリを D:\data\mydb に配置したいとします。これを実行するには、MySQL データディレクトリ内に D:\data\mydb を指すシンボリックリンクを作成します。ただし、シンボリックリンクを作成する前に、必要に応じて D:\data\mydb ディレクトリを作成して、それが存在することを確認します。データディレクトリ内に mydb というデータベースディレクトリがすでにある場合は、それを D:\data に移動します。そうしないと、シンボリックリンクは無効になります。問題を避けるために、データベースディレクトリの移動時にサーバーが実行していないことを確認してください。

データベースシンボリックリンクを作成するための手順は Windows のバージョンによって異なります。

Windows Vista、Windows Server 2008 以降には、ネイティブのシンボリックリンクのサポートがあるため、mklink コマンドを使用して、シンボリックリンクを作成できます。このコマンドには管理者権限が必要です。

  1. 場所をデータディレクトリ内に変更します。

    C:\> cd \path\to\datadir
  2. データディレクトリで、データベースディレクトリの場所を指す mydb というシンボリックリンクを作成します。

    C:\> mklink /d mydb D:\data\mydb

このあと、データベース mydb に作成されるすべてのテーブルが D:\data\mydb に作成されます。

または、MySQL でサポートされる任意のバージョンの Windows で、データディレクトリに宛先ディレクトリのパスを格納する .sym ファイルを作成して、MySQL データベースへのシンボリックリンクを作成できます。ファイルは、db_name.sym という名前にしてください。ここで db_name はデータベース名です。

Windows で、.sym ファイルを使用したデータベースシンボリックリンクのサポートは、デフォルトで有効にされています。.sym ファイルシンボリックリンクが必要でない場合は、--skip-symbolic-links オプションで mysqld を起動して、それらのサポートを無効にできます。システムで .sym ファイルシンボリックリンクをサポートしているかどうかを判断するには、次のステートメントを使用して、have_symlink システム変数の値をチェックします。

SHOW VARIABLES LIKE 'have_symlink';

.sym ファイルシンボリックリンクを作成するには、次の手順を使用します。

  1. 場所をデータディレクトリ内に変更します。

    C:\> cd \path\to\datadir
  2. データディレクトリ内に、パス名 D:\data\mydb\ を含む mydb.sym というテキストファイルを作成します。

    注記

    新しいデータベースとテーブルのパス名は絶対パスにしてください。相対パスを指定する場合、場所は mydb.sym ファイルに相対的になります。

このあと、データベース mydb に作成されるすべてのテーブルが D:\data\mydb に作成されます。

注記

.sym ファイルのサポートは、mklink を使用して使用可能なネイティブシンボリックリンクのサポートと重複しているため、.sym ファイルの使用は、MySQL 5.6.9 現在非推奨にされ、それらのサポートは将来の MySQL リリースで削除されます。

Windows でのデータベースシンボリックリンクへの .sym ファイルの使用には、次の制限が適用されます。これらの制限は mklink を使用して作成されるシンボリックリンクには適用されません。

  • MySQL データディレクトリにデータベースと同じ名前のディレクトリが存在する場合、シンボリックリンクは使用されません。

  • --innodb_file_per_table オプションは使用できません。

  • mysqld をサービスとして実行する場合、リモートサーバーにマップされたドライブをシンボリックリンクのリンク先として使用することはできません。回避方法として、フルパス (\\servername\path\) を使用できます。

8.11.4 メモリーの使用の最適化

8.11.4.1 MySQL のメモリーの使用方法

次のリストに、mysqld サーバーがメモリーを使用する方法のいくつかを示します。該当する場合、メモリー使用に関連するサーバー変数の名前も示しています。

  • すべてのスレッドは MyISAM キーバッファーを共有し、そのサイズは key_buffer_size 変数によって決定されます。サーバーによって使用されるほかのバッファーは、必要に応じて割り当てられます。セクション8.11.2「サーバーパラメータのチューニング」を参照してください。

  • クライアント接続の管理に使用される各スレッドは、いくらかのスレッド固有の領域を使用します。次のリストに、これらとそれらのサイズを制御する変数を示します。

    • スタック (変数 thread_stack)

    • 接続バッファー (変数 net_buffer_length)

    • 結果バッファー (変数 net_buffer_length)

    接続バッファーと結果バッファーはそれぞれ net_buffer_length バイトに等しいサイズから開始されますが、必要に応じて max_allowed_packet バイトまで動的に拡大されます。結果バッファーは各 SQL ステートメントのあとに net_buffer_length バイトに縮小されます。ステートメントの実行中は現在のステートメント文字列のコピーも割り当てられます。

  • すべてのスレッドで同じベースメモリーを共有します。

  • スレッドが必要ない場合、それに割り当てられたメモリーが解放され、スレッドがスレッドキャッシュに戻らないかぎり、システムに返されます。その場合、メモリーは割り当てられた状態のままになります。

  • myisam_use_mmap システム変数を 1 に設定して、すべての MyISAM テーブルのメモリーマッピングを有効にできます。

  • テーブルの順次スキャンを実行する各リクエストは、read buffer (変数 read_buffer_size) を割り当てます。

  • 行を任意の順序で読み取る場合 (たとえば、ソートに続いて)、random-read buffer (変数 read_rnd_buffer_size) を割り当てて、ディスクシークを避けることができます。

  • すべての結合は単一のパスで実行され、ほとんどの結合は一時テーブルも使用せずに実行できます。ほとんどの一時テーブルはメモリーベースのハッシュテーブルです。大きな行長 (すべてのカラム長の合計として算出される) を持つか BLOB カラムを含む一時テーブルはディスク上に格納されます。

    内部インメモリー一時テーブルが大きくなりすぎると、MySQL は、テーブルをインメモリーから、MyISAM ストレージエンジンによって処理されるディスク上フォーマットに変更して、これを自動的に処理します。セクション8.4.4「MySQL が内部一時テーブルを使用する仕組み」に説明するように、許可される一時テーブルのサイズを増やすことができます。

  • ソートを実行するほとんどのリクエストは、ソートバッファーおよび結果セットサイズに応じた 0 から 2 つの一時ファイルを割り当てます。セクションB.5.4.4「MySQL が一時ファイルを格納する場所」を参照してください。

  • ほとんどすべての解析と計算は、スレッドローカルの再利用可能なメモリープールで実行されます。小さい項目にはメモリーオーバーヘッドが不要であるため、通常の低速メモリーの割り当てと解放が回避されます。メモリーは、予測外に大きな文字列にのみ割り当てられます。

  • 開かれる MyISAM テーブルごとにインデックスファイルが 1 回開かれ、データファイルは同時実行中のスレッドごとに 1 回開かれます。同時スレッドごとに、テーブル構造、各カラムのカラム構造、およびサイズ 3 * N のバッファーが割り当てられます (ここで N は最大行長で、BLOB カラムをカウントしていません)。BLOB カラムには、5 から 8 バイト+ BLOB データの長さが必要です。MyISAM ストレージエンジンは、内部使用のため 1 つ余分な行バッファーを保持します。

  • BLOB カラムがあるテーブルごとに、大きな BLOB 値を読み取るためにバッファーが動的に拡大されます。テーブルをスキャンする場合は、最大の BLOB 値と同じ大きさのバッファーが割り当てられます。

  • 使用中のすべてのテーブルのハンドラ構造がキャッシュに保存され、FIFO として管理されます。初期キャッシュサイズは、table_open_cache システム変数の値から取得されます。テーブルが同時に 2 つの実行中のスレッドによって使用されている場合、キャッシュにはそのテーブルの 2 つのエントリが含まれます。セクション8.4.3.1「MySQL でのテーブルのオープンとクローズの方法」を参照してください。

  • FLUSH TABLES ステートメントまたは mysqladmin flush-tables コマンドは、使用中でないすべてのテーブルを一度に閉じ、現在実行中のスレッドの終了時に閉じられるように使用中のすべてのテーブルをマークします。これにより、事実上ほとんどの使用中のメモリーが解放されます。FLUSH TABLES はすべてのテーブルが閉じられるまで戻りません。

  • GRANTCREATE USERCREATE SERVER、および INSTALL PLUGIN ステートメントの結果として、サーバーは情報をメモリーにキャッシュします。このメモリーは、対応する REVOKEDROP USERDROP SERVER、および UNINSTALL PLUGIN ステートメントによって解放されないため、キャッシュを発生させるステートメントの多数のインスタンスを実行するサーバーでは、メモリー使用量が増加します。このキャッシュされたメモリーは FLUSH PRIVILEGES で解放できます。

ps およびその他のステータスプログラムが、mysqld が大量のメモリーを使用していることをレポートすることがあります。これは、さまざまなメモリーアドレス上のスレッドスタックによって発生する可能性があります。たとえば、Solaris バージョンの ps はスタック間の未使用のメモリーが使用されているメモリーとしてカウントされます。これを確認するには、swap -s で使用可能なスワップをチェックします。いくつかのメモリーリーク検出ツール (市販とオープンソースの両方の) で mysqld をテストしているため、メモリーリークはないはずです。

8.11.4.2 ラージページのサポートの有効化

ハードウェアまたはオペレーティングシステムのアーキテクチャーによっては、デフォルト (通常は 4K バイト) よりも大きいメモリーページをサポートしています。このサポートの実際の実装は、ベースとなるハードウェアやオペレーティングシステムに依存します。大量のメモリーアクセスがあるアプリケーションの場合、大きいページを使用して、トランスレーションルックアサイドバッファー (TLB; Translation Lookaside Buffer) のミスが減ることによってパフォーマンスが改善される可能性があります。

MySQL では、InnoDB でラージページを使用して、バッファープールと追加のメモリープールにメモリーを割り当てることができます。

MySQL での標準的な大規模ページの使用では、サポートされる最大サイズである 4M バイトまでの使用が試行されます。Solaris では超大規模ページ機能により 256M バイトまでのページの使用が可能です。この機能は最新の SPARC プラットフォームで使用できます。これは --super-large-pages または --skip-super-large-pages オプションを使用して有効または無効にできます。

MySQL は、ラージページのサポートの Linux 実装 (Linux では HugeTLB と呼ばれる) もサポートします。

Linux でラージページを使用する前に、カーネルで、それらをサポートできるようにする必要があり、HugeTLB メモリープールを構成する必要があります。参考のため、HugeTBL API は、Linux ソースの Documentation/vm/hugetlbpage.txt ファイルで説明されています。

Red Hat Enterprise Linux などの一部の最近のシステムのカーネルでは、ラージページ機能がデフォルトで有効にされているようです。使用しているカーネルにこれが当てはまるかどうかを確認するには、次のコマンドを使用し、huge を含む出力行を探します。

shell> cat /proc/meminfo | grep -i hugeHugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 4096 kB

空でないコマンド出力は、ラージページのサポートが存在することを示しますが、ゼロの値は、使用するように構成されたページがないことを示します。

ラージページをサポートするようにカーネルを再構成する必要がある場合、手順については hugetlbpage.txt ファイルを参照してください。

Linux カーネルでラージページのサポートが有効にされていると仮定し、それを次のコマンドを使用して、MySQL で使用するように構成します。通常、システムが起動するたびにコマンドが実行されるように、システムのブートシーケンス中に実行される rc ファイルまたは同等の起動ファイルにこれらを入れます。コマンドは、ブートシーケンスの早期の、MySQL サーバーが起動する前に実行されるべきです。システムに適切なように、割り当ての数値とグループ番号を変更してください。

# Set the number of pages to be used.
# Each page is normally 2MB, so a value of 20 = 40MB.
# This command actually allocates memory, so this much
# memory must be available.
echo 20 > /proc/sys/vm/nr_hugepages
# Set the group number that is permitted to access this
# memory (102 in this case). The mysql user must be a
# member of this group.
echo 102 > /proc/sys/vm/hugetlb_shm_group
# Increase the amount of shmem permitted per segment
# (12G in this case).
echo 1560281088 > /proc/sys/kernel/shmmax
# Increase total amount of shared memory. The value
# is the number of pages. At 4KB/page, 4194304 = 16GB.
echo 4194304 > /proc/sys/kernel/shmall

MySQL で使用する場合、通常 shmmax の値を shmall の値に近くなるようにしたいと考えます。

ラージページの構成を確認するには、前述のとおりに再度 /proc/meminfo をチェックします。これで、0 以外の値が表示されるはずです。

shell> cat /proc/meminfo | grep -i hugeHugePages_Total: 20
HugePages_Free: 20
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 4096 kB

hugetlb_shm_group を使用するための最後の手順は、mysql ユーザーに、memlock 制限として unlimited 値を指定することです。これを実行するには、/etc/security/limits.conf を編集するか、mysqld_safe スクリプトに次のコマンドを追加します。

ulimit -l unlimited

ulimit コマンドを mysqld_safe に追加すると、mysql ユーザーに切り替える前に root ユーザーの memlock 制限が unlimited に設定されます。(これは、mysqld_saferoot によって起動されたものと仮定します。)

MySQL のラージページのサポートはデフォルトで無効にされています。それを有効にするには、サーバーを --large-pages オプションで起動します。たとえば、サーバーの my.cnf ファイルで次の行を使用できます。

[mysqld]
large-pages

このオプションを使用すると、InnoDB はそのバッファープールと追加のメモリープールに自動的にラージページを使用します。InnoDB がこれを実行できない場合、従来のメモリーの使用に戻り、エラーログに警告を書き込みます: Warning: Using conventional memory pool

ラージページが使用されていることを確認するには、再度 /proc/meminfo をチェックします。

shell> cat /proc/meminfo | grep -i hugeHugePages_Total: 20
HugePages_Free: 20
HugePages_Rsvd: 2
HugePages_Surp: 0
Hugepagesize: 4096 kB

8.11.5 ネットワークの使用の最適化

8.11.5.1 MySQL のクライアント接続のためのスレッドの使用方法

接続マネージャースレッドは、サーバーが待機しているネットワークインタフェース上でクライアントの接続要求を処理します。どのプラットフォームでも、1 つのマネージャースレッドが TCP/IP 接続要求を処理します。Unix では、このマネージャースレッドは Unix ソケットファイルの接続要求も処理します。Windows では、1 つのマネージャースレッドが共有メモリーの接続要求を処理し、別のマネージャースレッドが名前付きパイプの接続要求を処理します。サーバーは、待機していないインタフェースを処理するためのスレッドを作成しません。たとえば、Windows サーバーで名前付きパイプ接続のサポートが有効になっていない場合、これらの接続を処理するスレッドは作成されません。

接続マネージャースレッドは、各クライアント接続を、その接続の認証および要求を処理する専用スレッドに関連付けます。マネージャースレッドは、必要に応じて新しいスレッドを作成しますが、まずスレッドキャッシュを調べて接続に使用できるスレッドが含まれているかどうかを確認することによって、それを回避することを試みます。接続が終了すると、スレッドキャッシュが満杯でない場合は、そのスレッドがスレッドキャッシュに返されます。

この接続スレッドモデルでは、現在接続しているクライアントと同数のスレッドが存在し、多数の接続を処理するためにサーバーのワークロードを拡大する必要がある場合にはいくつか欠点があります。たとえば、スレッドの作成と破棄の負荷が大きくなります。また、各スレッドにスタック領域などのサーバーリソースとカーネルリソースが必要になります。多数の同時接続に対応するには、スレッドあたりのスタックサイズは小さく保つ必要があり、それが小さくなりすぎるか、またはサーバーで大量のメモリーを消費することになる状況につながります。ほかのリソースを使い果たす可能性もあり、スケジューリングのオーバーヘッドがかなり大きくなる可能性があります。

MySQL 5.6.10 現在、MySQL 5.6 の商用配布には、オーバーヘッドを軽減し、パフォーマンスを向上するように設計されている代替のスレッド処理モデルを提供するスレットプールプラグインが付属しています。これは、多数のクライアント接続のステートメント実行スレッドを効率的に管理して、サーバーのパフォーマンスを向上させるスレッドプールを実装します。セクション8.11.6「スレッドプールプラグイン」を参照してください。

クライアント接続を処理するスレッドをサーバーがどのように管理するかを制御し、モニターするには、いくつかのシステム変数とステータス変数が関連します。(セクション5.1.4「サーバーシステム変数」およびセクション5.1.6「サーバーステータス変数」を参照してください。)

スレッドキャッシュは、thread_cache_size システム変数によって決定されるサイズを持ちます。デフォルト値は 0 (キャッシュなし) で、この場合、スレッドは新しい接続ごとにセットアップされ、接続の終了時に破棄されます。thread_cache_sizeN に設定し、N 個の非アクティブ接続スレッドをキャッシュできるようにします。thread_cache_size はサーバーの起動時に設定するか、サーバーの実行中に変更できます。関連付けられていたクライアント接続が終了すると、接続スレッドは非アクティブになります。

キャッシュ内のスレッド数、およびスレッドをキャッシュから取得できなかったため作成されたスレッドの数をモニターするには、Threads_cached および Threads_created ステータス変数をモニターします。

サーバーの起動時または実行時に max_connections を設定して、同時に接続できるクライアントの最大数を制御できます。

スレッドスタックが小さすぎると、これによって、サーバーが処理できる SQL ステートメントの複雑さ、ストアドプロシージャーの再帰の深さ、およびその他のメモリーを大量に消費するアクションが制限されます。各スレッドに N バイトのスタックサイズを設定するには、サーバーを --thread_stack=N で起動します。

8.11.5.2 DNS ルックアップの最適化とホストキャッシュ

MySQL サーバーはクライアントに関する情報 (IP アドレス、ホスト名、エラー情報) を格納するホストキャッシュをメモリーに保持します。サーバーはこのキャッシュを非ローカル TCP 接続に使用します。それは、ループバックインタフェースアドレス (127.0.0.1 または ::1) を使用して確立された TCP 接続、または Unix ソケットファイル、名前付きパイプ、または共有メモリーを使用して確立された接続には、キャッシュを使用しません。

新しいクライアント接続ごとに、サーバーはクライアント IP アドレスを使用して、クライアントホスト名がホストキャッシュ内にあるかどうかをチェックします。ない場合は、サーバーはホスト名の解決を試みます。まず、それは IP アドレスをホスト名に解決し、そのホスト名を再度 IP アドレスに解決します。次に、その結果と元の IP アドレスを比較して、それらが同じであることを確認します。サーバーはこの操作の結果に関する情報をホストキャッシュに格納します。キャッシュがいっぱいである場合、直近で使用されていないエントリが破棄されます。

host_cache パフォーマンススキーマテーブルは、SELECT ステートメントを使用して調査できるようにホストキャッシュの内容を公開します。これは、接続の問題の原因の診断に役立つことがあります。セクション22.9.10.1「host_cache テーブル」を参照してください。

サーバーは次のようにホストキャッシュ内のエントリを処理します。

  1. 最初の TCP クライアント接続が指定された IP アドレスからサーバーに到達すると、クライアント IP、ホスト名、およびクライアントルックアップ検証フラグを記録する新しいエントリが作成されます。最初に、ホスト名が NULL に設定され、フラグは false になります。このエントリは同じ発信元 IP からの後続のクライアント接続にも使用されます。

  2. クライアント IP エントリの検証フラグが false の場合、サーバーは IP からホスト名への DNS の解決を試みます。それが成功した場合、ホスト名が解決されたホスト名で更新され、検証フラグが true に設定されます。解決が成功しない場合、とられるアクションは、エラーが永続的か一時的かによって異なります。永続的なエラーの場合、ホスト名は NULL のままになり、検証フラグは true に設定されます。一時的なエラーの場合、ホスト名と検証フラグは変更されないままになります。(次回にクライアントがこの IP から接続したときは、別の DNS 解決の試みが行われます。)

  3. 特定の IP アドレスからの着信クライアント接続の処理中にエラーが発生した場合、サーバーはその IP のエントリ内の対応するエラーカウンタを更新します。記録されたエラーの説明については、セクション22.9.10.1「host_cache テーブル」を参照してください。

オペレーティングシステムでスレッドセーフな gethostbyaddr_r() および gethostbyname_r() 呼び出しをサポートしている場合、サーバーはそれらを使用してホスト名解決を実行します。そうでない場合、ルックアップを実行するスレッドは相互排他ロックを実行し、代わりに、gethostbyaddr() および gethostbyname() を呼び出します。この場合、相互排他ロックを保持するスレッドがそれを解放するまで、ほかのスレッドはホストキャッシュ内にないホスト名を解決できません。

サーバーはいくつかの目的でホストキャッシュを使用します。

  • IP からホスト名へのルックアップの結果をキャッシュすることによって、サーバーはクライアント接続ごとの DNS ルックアップの実行を回避します。代わりに、特定のホストに対して、そのホストからの最初の接続でのみルックアップを実行する必要があります。

  • キャッシュには、接続プロセス中に発生したエラーに関する情報が格納されます。一部のエラーはブロッキングとみなされます。成功した接続がない特定のホストから、これらの多くが連続して発生している場合、サーバーはそのホストからのその後の接続をブロックします。max_connect_errors システム変数は、ブロックが行われるまで許可されるエラーの数を指定します。セクションB.5.2.6「ホスト 'host_name' は拒否されました」を参照してください。

ブロックされたホストのブロックを解除するには、FLUSH HOSTS ステートメントを発行するか、mysqladmin flush-hosts コマンドを実行して、ホストキャッシュをフラッシュします。

ブロックされたホストからの最後の接続の試み以降に、ほかのホストからのアクティビティーが発生した場合、FLUSH HOSTS を使用しなくても、ブロックされたホストのブロックが解除される可能性があります。これは、キャッシュ内にないクライアント IP から接続が到着したときに、キャッシュがいっぱいである場合、サーバーが直近で使用されていないキャッシュエントリを破棄して、新しいエントリのための空きを作るために発生する可能性があります。破棄されたエントリがブロックされたホストのものである場合、そのホストのブロックが解除されます。

ホストキャッシュはデフォルトで有効になっています。それを無効にするには、サーバーの起動時や実行時に、host_cache_size システム変数を 0 に設定します。

DNSホスト名ルックアップを無効にするには、--skip-name-resolve オプションでサーバーを起動します。この場合、サーバーは IP アドレスのみを使用し、ホスト名を使用しないで、接続しているホストを MySQL 付与テーブル内の行と照合します。IP アドレスを使用してそれらのテーブルに指定されたアカウントのみを使用できます。

著しく遅い DNS と多くのホストがある場合、--skip-name-resolve で DNS ルックアップを無効にするか、または host_cache_size の値を増やしてホストキャッシュを大きくすることによって、パフォーマンスを向上できる可能性があります。

TCP/IP 接続を完全に禁止するには、--skip-networking オプションでサーバーを起動します。

一部の接続エラーは TCP 接続に関連付けられないか、接続プロセスのきわめて早期に (IP アドレスも判明する前に) 発生するか、または特定の IP アドレスに固有でありません (メモリー不足の状況など)。これらのエラーについては、Connection_errors_xxx ステータス変数をチェックしてください (セクション5.1.6「サーバーステータス変数」を参照してください)。

8.11.6 スレッドプールプラグイン

注記

MySQL スレッドプールは商用拡張機能です。商用製品 (MySQL Enterprise Edition) の詳細については、https://www.mysql.com/products/ を参照してください。

MySQL 5.6.10 現在、MySQL 5.6 の商用配布には、サーバープラグインを使用して実装される MySQL スレッドプールが付属しています。MySQL サーバーのデフォルトのスレッド処理モデルでは、クライアント接続ごとに 1 つのスレッドを使用してステートメントが実行されます。より多くのクライアントがサーバーに接続してステートメントを実行すると、全体的なパフォーマンスが低下します。スレットプールプラグインは、オーバーヘッドを軽減し、パフォーマンスを向上するように設計されている代替のスレッド処理モデルを提供します。このプラグインは、多数のクライアント接続に対してステートメント実行スレッドを効率的に管理することによってサーバーのパフォーマンスを向上させるスレッドプールを実装します。

スレッドプールは、接続モデルあたり 1 つのスレッドのいくつかの問題に対処します。

  • スレッドが多すぎると、高度な並列実行ワークロードで CPU キャッシュがほとんど役に立たなくなります。スレッドプールはスレッドスタックの再利用を促進し、CPU キャッシュのフットプリントを最小にします。

  • 並列で実行しているスレッド数が多すぎると、コンテキストスイッチングのオーバーヘッドが高くなります。これは、オペレーティングシステムスケジューラにも困難なタスクを与えます。スレッドプールは、アクティブスレッドの数を制御して、それが処理可能で、MySQL を実行しているサーバーホストに適切なレベルで MySQL サーバー内の並列性を維持します。

  • 並列で実行するトランザクションが多すぎると、リソースの競合が増加します。InnoDB では、これにより中央の相互排他ロックの保持に費やされる時間が多くなります。スレッドプールは、あまり多く並列で実行しないように、トランザクションが開始するタイミングを制御します。

スレッドプールプラグインは商用機能です。MySQL コミュニティー配布には含まれていません。

Windows では、スレッドプールプラグインに Windows Vista 以降が必要です。Linux では、プラグインにカーネル 2.6.9 以降が必要です。

追加のリソース

セクションA.14「MySQL 5.6 FAQ: MySQL エンタープライズスケーラビリティースレッドプール」

8.11.6.1 スレッドプールコンポーネントとインストール

スレッドプール機能は次のコンポーネントで構成されます。

  • プラグインライブラリオブジェクトファイルには、スレッドプールコード用のプラグインと、いくつかの INFORMATION_SCHEMA テーブル用のプラグインが含まれています。

    スレッドプールの仕組みの詳細については、セクション8.11.6.2「スレッドプール操作」を参照してください。

    INFORMATION_SCHEMA テーブルには、TP_THREAD_STATETP_THREAD_GROUP_STATE、および TP_THREAD_GROUP_STATS という名前が付けられています。これらのテーブルは、スレッドプール操作に関する情報を提供します。詳細については、セクション21.31「スレッドプールの INFORMATION_SCHEMA テーブル」を参照してください。

  • いくつかのシステム変数がスレッドプールに関連しています。thread_handling システム変数は、サーバーがスレッドプールプラグインを正常にロードしたときに、loaded-dynamically の値になります。

    ほかの関連の変数はスレッドプールプラグインによって実装されます。それが有効にされていない場合、それらは使用できません。

    • thread_pool_algorithm: スケジューリングに使用する並列性アルゴリズム。

    • thread_pool_high_priority_connection: セッションのステートメント実行のスケジュール方法。

    • thread_pool_prio_kickup_timer: スレッドプールが、実行を待機しているステートメントを低優先度キューから高優先度キューに移動するまでの時間。

    • thread_pool_max_unused_threads: 許可するスリープ中のスレッド数。

    • thread_pool_size: スレッドプール内のスレッドグループの数。これはスレッドプールのパフォーマンスを制御するもっとも重要なパラメータです。

    • thread_pool_stall_limit: 実行中のステートメントが停滞しているとみなされるまでの時間。

    起動時に、プラグインによって実装されているいずれかの変数が不正な値に設定された場合、プラグインの初期化が失敗し、プラグインはロードされません。

    スレッドプールパラメータの設定については、セクション8.11.6.3「スレッドプールのチューニング」を参照してください。

  • パフォーマンススキーマは、スレッドプールに関する情報を公開し、操作のパフォーマンスの調査に使用できます。詳細については、第22章「MySQL パフォーマンススキーマを参照してください。

サーバーが使用できるように、スレッドプールライブラリオブジェクトファイルは MySQL プラグインディレクトリ (plugin_dir システム変数によって指定されたディレクトリ) に存在する必要があります。スレッドプール機能を有効にするには、--plugin-load オプションでサーバーを起動することによって、使用されるプラグインをロードします。たとえば、プラグインオブジェクトファイルだけを指定した場合、サーバーはそれに含まれるすべてのプラグイン (つまり、スレッドプールプラグインとすべての INFORMATION_SCHEMA テーブル) をロードします。これを実行するには、これらの行を my.cnf ファイルに挿入します。

[mysqld]
plugin-load=thread_pool.so

それは、個別にスレッドプールプラグインを指定して、それらをすべてロードするのと同等です。

[mysqld]
plugin-load=thread_pool.so
plugin-load=thread_pool=thread_pool.so;tp_thread_state=thread_pool.so;tp_thread_group_state=thread_pool.so;tp_thread_group_stats=thread_pool.so

システム上のオブジェクトファイルのサフィクスが .so とは異なる場合、正しいサフィクスに置き換えてください (たとえば Windows の場合は .dll)。

必要に応じて、サーバーにプラグインディレクトリの場所を伝えるために、plugin_dir システム変数の値を設定します。

必要な場合、ライブラリファイルから個々のプラグインをロードできます。スレッドプールプラグインをロードするが、INFORMATION_SCHEMA テーブルはロードしない場合、次のようなオプションを使用します。

[mysqld]
plugin-load=thread_pool=thread_pool.so

スレッドプールプラグインと TP_THREAD_STATEINFORMATION_SCHEMA テーブルのみをロードするには、次のようなオプションを使用します。

[mysqld]
plugin-load=thread_pool=thread_pool.so;TP_THREAD_STATE=thread_pool.so

ただし、すべての INFORMATION_SCHEMA テーブルをロードしない場合、一部またはすべての MySQL Enterprise Monitor スレッドプールグラフが空になります。

プラグインのインストールを検証するには、INFORMATION_SCHEMA.PLUGINS テーブルを調査するか、SHOW PLUGINS ステートメントを使用します。セクション5.1.8.2「サーバープラグイン情報の取得」を参照してください。

サーバーはスレッドプラグインを正常にロードしたら、thread_handling システム変数を dynamically-loaded に設定します。プラグインのロードに失敗した場合、サーバーはエラーログにメッセージを書き込みます。

8.11.6.2 スレッドプール操作

スレッドプールは、それぞれクライアント接続のセットを管理するいくつかのスレッドグループから構成されます。接続が確立されると、スレッドプールはラウンドロビン方式でそれらをスレッドグループに割り当てます。

スレッドグループの数は、thread_pool_size システム変数を使用して構成できます。グループのデフォルトの数は 16 です。この変数の設定のガイドラインについては、セクション8.11.6.3「スレッドプールのチューニング」を参照してください。

グループあたりのスレッドの最大数は 4096 (または 1 つのスレッドが内部で使用される一部のシステムでは 4095) です。

スレッドプールは接続とスレッドを区別するため、接続と、それらの接続から受信したステートメントを実行するスレッド間に固定の関係はありません。これは、1 つのスレッドを 1 つの接続に関連付けて、そのスレッドがその接続からのすべてのステートメントを実行するようにするデフォルトのスレッド処理モデルとは異なります。

スレッドプールは、いつでも各グループで最大 1 つのスレッドが実行するように努めますが、ときによって、最高のパフォーマンスのため、一時的に多くのスレッドの実行を許可することがあります。このアルゴリズムは次のように機能します。

  • 各スレッドグループには、グループに割り当てられた接続からの着信ステートメントを待機するリスナースレッドがあります。ステートメントが到着すると、スレッドグループはその実行をただちに開始するか、あとで実行するためにキューに入れます。

    • 即時の実行は、ステートメントが受信した唯一のもので、キューに入れられていたり、現在実行していたりするステートメントがない場合に行われます。

    • キューイングは、ステートメントの実行をすぐに開始できない場合に行われます。

  • 即時の実行が行われる場合、実行はリスナースレッドによって行われます。(つまり、グループ内に一時的に待機しているスレッドがなくなります。)ステートメントがすぐに終了すると、実行中のスレッドがステートメントの待機に戻ります。そうでない場合、スレッドプールはステートメントを停滞中とみなし、別のスレッド (必要に応じて作成して) をリスナースレッドとして開始します。スレッドグループが停滞中のステートメントによってブロックされないように、スレッドプールには、スレッドグループ状態を定期的にモニターするバックグラウンドスレッドがあります。

    待機中のスレッドを使用して、ただちに開始できるステートメントを実行することによって、ステートメントがすぐに終了した場合、追加のスレッドを作成する必要はありません。これにより、同時スレッド数が少ない場合に、もっとも効率的な実行が可能になります。

    スレッドプールプラグインが開始すると、それはグループあたり 1 つのスレッド (リスナースレッド) に加えてバックグラウンドスレッドを作成します。ステートメントを実行するための必要に応じて、追加のスレッドが作成されます。

  • thread_pool_stall_limit システム変数の値は、先述の項目のすぐに終了するの意味を決定します。スレッドが停滞中とみなされるまでのデフォルトの時間は 60 ミリ秒ですが、6 秒まで設定できます。このパラメータは、サーバーのワークロードに適切なバランスがとれるように構成できます。待機の値が短いと、スレッドはよりすみやかに開始できます。短い値はデッドロック状況を回避により適しています。長い待機の値は、長時間実行するステートメントを含むワークロードで有用で、現在のステートメントの実行時に多数の新しいステートメントが開始しないようにします。

  • スレッドプールは、同時の短時間実行ステートメントの数を制限することに焦点を合わせています。実行中のステートメントが停滞時間に達する前に、ほかのステートメントの実行の開始を妨げます。ステートメントが停滞時間を過ぎて実行している場合、それは続行が許可されますが、ほかのステートメントの開始は妨げられなくなります。このように、スレッドプールは、各スレッドグループに、複数の長時間実行ステートメントがあっても、複数の短時間実行ステートメントがないように努めます。必要な待機時間に対する制限がないため、長時間実行ステートメントによって、ほかのステートメントの実行が妨げられることは望ましくありません。たとえば、レプリケーションマスターで、バイナリログイベントをスレーブに送信するスレッドは、事実上永久に実行します。

  • ステートメントはディスク I/O 操作またはユーザーレベルロック (行ロックまたはテーブルロック) を検出するとブロックされます。ブロックによって、スレッドグループは使用されなくなることがあるため、スレッドプールがこのグループで新しいスレッドをただちに開始して、別のステートメントを実行できるようにするため、スレッドプールへのコールバックがあります。ブロックされたスレッドが返されると、スレッドプールはそれをすぐに再開することを許可します。

  • 優先度が高いキューと優先度が低いキューの 2 つのキューがあります。トランザクションの最初のステートメントは優先度が低いキューに入ります。トランザクションの後続のステートメントは、トランザクションが進行中 (そのステートメントが実行を開始している) 場合、優先度が高いキューに入れられ、そうでない場合は優先度が低いキューに入れられます。キューの割り当ては、thread_pool_high_priority_connection システム変数を有効にすることによって影響を受けることがあります。これにより、セッションのすべてのキューに入れられているステートメントが優先度の高いキューに入れられます。

    非トランザクションストレージエンジンまたは autocommit が有効にされている場合のトランザクションエンジンのステートメントは、この場合に各ステートメントがトランザクションであるため、優先度の低いステートメントとして扱われます。そのため、InnoDB テーブルと MyISAM テーブルに対するステートメントを組み合わせると、autocommit が有効にされていないかぎり、スレッドプールは MyISAM に対するステートメントより、InnoDB に対するステートメントを優先します。autocommit が有効にされていると、すべてのステートメントの優先度が低くなります。

  • スレッドグループが実行のためにキューに入れられているステートメントを選択する場合、まず優先度が高いキューを調べて、次に優先度が低いキューを調べます。ステートメントが見つかった場合、そのキューからそれが削除され、実行が開始されます。

  • ステートメントが優先度の低いキューに長くとどまりすぎた場合、スレッドプールは優先度の高いキューに移動します。thread_pool_prio_kickup_timer システム変数の値は、移動までの時間を制御します。スレッドグループごとに、最大 10 ms あたり 1 つのステートメントまたは 1 秒あたり 100 個のステートメントが優先度の低いキューから優先度の高いキューに移動されます。

  • スレッドプールは、CPU キャッシュの使用を大幅に効率化するために、もっともアクティブなスレッドを再利用します。これは、パフォーマンスに大きな影響を与える小さな調整です。

  • スレッドがユーザー接続からステートメントを実行している間、パフォーマンススキーマインストゥルメンテーションは、ユーザー接続にスレッドアクティビティーを報告します。それ以外の場合、パフォーマンススキーマはアクティビティーをスレッドプールに報告します。

これは、スレッドグループがステートメントを実行するために複数のスレッドを開始している状況の例です。

  • 1 つのスレッドがステートメントの実行を開始しますが、長時間実行しているため、停滞中とみなされます。スレッドグループは、最初のスレッドがまだ実行中であっても、別のスレッドに別のステートメントの実行の開始を許可します。

  • 1 つのスレッドがステートメントの実行を開始し、その後ブロックされ、このことをスレッドプールにレポートします。スレッドグループは、別のスレッドに別のステートメントの実行の開始を許可します。

  • 1 つのスレッドがステートメントの実行を開始し、ブロックされましたが、スレッドプールのコールバックによってインストゥルメントされたコードでブロックが発生していないため、ブロックされたことをレポートしません。この場合、スレッドはスレッドグループにまだ実行中であるように見えます。ステートメントが停滞中とみなされるほどブロックが長く継続した場合、グループは、別のスレッドに別のステートメントの実行の開始を許可します。

スレッドプールは、増加する接続全体に拡張できるように設計されています。さらに、アクティブに実行しているステートメントの数を制限することから発生する可能性のあるデッドロックを回避するようにも設計されています。スレッドプールにレポートしないスレッドは、ほかのステートメントの実行を妨げないため、スレッドプールのデッドロックを引き起こすことは重要です。そのようなステートメントの例を次に示します。

  • 長時間実行ステートメント。これらによって、すべてのリソースがほんの少数のステートメントで使用されることになり、ほかのすべてのステートメントのサーバーへのアクセスを妨げる可能性があります。

  • バイナリログを読み取り、それをスレーブに送信するバイナリログダンプスレッド。これは、きわめて長い時間実行する長時間実行ステートメントの一種であり、ほかのステートメントの実行を妨げないはずです。

  • MySQL Server またはストレージエンジンによって、スレッドプールにレポートされていない、行ロック、テーブルロック、またはほかの何らかのブロックアクティビティーでブロックされたステートメント。

どの場合も、デッドロックを避けるため、スレッドグループが別のステートメントの実行の開始を許可できるように、ステートメントがすぐに完了しない場合、停滞中カテゴリに移動されます。この設計により、スレッドが長時間実行するか、ブロックされた場合に、スレッドプールはスレッドを停滞中カテゴリに移動し、ステートメントの実行の残りの間、ほかのステートメントの実行を妨げません。

発生する可能性のあるスレッドの最大数は、max_connectionsthread_pool_size の合計です。これは、すべての接続が実行モードにあり、グループあたりに追加のステートメントを待機する 1 つの追加スレッドが作成される状況で発生する可能性があります。これは必ずしも頻繁に発生する状態ではありませんが、理論的には可能性があります。

8.11.6.3 スレッドプールのチューニング

このセクションでは、秒あたりのトランザクション数などのメトリックを使用して測定された、最高のパフォーマンスを得るためのスレッドプールシステム変数の設定に関するガイドラインを提供します。

thread_pool_size はスレッドプールのパフォーマンスを制御するもっとも重要なパラメータです。それはサーバーの起動時にのみ設定できます。スレッドプールのテストにおける経験では、次のように示されます。

  • プライマリストレージエンジンが InnoDB である場合、最適な thread_pool_size 設定は、16 から 36 の間になる可能性があり、もっとも一般的な最適な値は 24 から 36 になる傾向があります。36 を超える設定が最適であった状況はありませんでした。16 未満の値が最適である特殊なケースがある場合もあります。

    DBT2 や Sysbench などのワークロードの場合、InnoDB の最適な値は通常 36 くらいであるようです。著しく書き込みの多いワークロードでは、最適な設定はもっと少ない可能性があります。

  • プライマリストレージエンジンが MyISAM である場合、thread_pool_size 設定はかなり小さくするべきです。4 から 8 の値で最適なパフォーマンスが得られる傾向があります。値を大きくすると、パフォーマンスにややマイナスでも劇的な影響を与える傾向はありません。

もう 1 つのシステム変数 thread_pool_stall_limit はブロックされたステートメントと長時間実行ステートメントの処理に重要です。MySQL Server をブロックするすべての呼び出しがスレッドプールにレポートされる場合、実行スレッドがブロックされるといつでもわかります。ただし、これは常には当てはまらないことがあります。たとえば、ブロックはスレッドプールコールバックによってインストゥルメントされていないコードで発生する可能性があります。そのような場合、スレッドプールはブロックされているように見えるスレッドを識別できる必要があります。これは thread_pool_stall_limit システム変数を使用してチューニングできる長さであるタイムアウトを使用して実行されます。このパラメータにより、サーバーは完全にブロックされることはありません。thread_pool_stall_limit の値は、デッドロックされたサーバーのリスクを回避するため、6 秒の上限があります。

thread_pool_stall_limit により、スレッドプールは長時間実行ステートメントを処理することもできます。長期間実行するステートメントがスレッドグループをブロックすることを許可された場合、グループに割り当てられるその他のすべての接続はブロックされ、長期間実行するステートメントが完了するまで実行を開始できません。最悪の場合、これには数時間または数日かかることもあります。

thread_pool_stall_limit の値は、その値より長く実行するステートメントが停滞中とみなされるように選択するべきです。停滞中のステートメントは、追加のコンテキストスイッチと場合によっては追加のスレッド作成が必要であるため、大量の追加のオーバーヘッドを生成します。一方、thread_pool_stall_limit パラメータを高く設定しすぎることは、長時間実行ステートメントが必要以上に長い間、多数の短時間実行ステートメントをブロックすることを意味します。待機の値が短いと、スレッドはよりすみやかに開始できます。短い値はデッドロック状況を回避により適しています。長い待機の値は、長時間実行するステートメントを含むワークロードで有用で、現在のステートメントの実行時に多数の新しいステートメントが開始しないようにします。

サーバーに負荷がかかっている場合でも、サーバーはステートメントの 99.9% が 100 ミリ秒以内に完了するワークロードを実行しており、残りのステートメントが 100 ミリ秒から 2 時間の間でまったく均等に分散してかかるものとします。この場合、thread_pool_stall_limit を 10 (100 ミリ秒を示す) に設定すると有益であると考えられます。60 ミリ秒のデフォルト値は、主にきわめて簡単なステートメントを実行するサーバーには十分です。

thread_pool_stall_limit パラメータは、サーバーのワークロードに対して適切なバランスをとることができるように、実行時に変更できます。TP_THREAD_GROUP_STATS テーブルが有効にされているとすると、次のクエリーを使用して、実行されたステートメントの停滞した部分を特定できます。

SELECT SUM(STALLED_QUERIES_EXECUTED) / SUM(QUERIES_EXECUTED)
FROM information_schema.TP_THREAD_GROUP_STATS;

この数値は可能なかぎり小さくするべきです。ステートメントの停滞の可能性を削減するには、thread_pool_stall_limit の値を増やします。

ステートメントが到着したときに、それが実際に実行を開始するまで、遅延できる最大の時間はどれくらいですか。次の条件が当てはまるとします。

  • 優先度が低いキューに 200 ステートメントが入れられています。

  • 優先度が高いキューに 10 ステートメントが入れられています。

  • thread_pool_prio_kickup_timer は 10000 (10 秒) に設定されています。

  • thread_pool_stall_limit は 100 (1 秒) に設定されています。

最悪の場合、10 個の優先度の高いステートメントは長時間実行し続ける 10 個のトランザクションを表します。そのため、最悪の場合に、優先度の高いキューには常に実行を待機しているステートメントがすでに含まれるため、このキューにステートメントが移動されません。10 秒後、新しいステートメントは優先度の高いキューに移動される資格を得ます。ただし、それが移動される前に、その前のすべてのステートメントも移動される必要があります。優先度の高いキューに移動されるのは、1 秒あたり最大 100 ステートメントであるため、これはさらに 2 秒かかる可能性があります。ステートメントが優先度の高いキューに到達したときに、多くの長時間実行ステートメントがその前にある可能性があります。最悪の場合、それらのすべてが停滞中になり、次のステートメントが優先度の高いキューから取得されるまで、ステートメントごとに 1 秒かかります。そのため、このシナリオでは、新しいステートメントが実行を開始するまで 222 秒かかります。

この例では、アプリケーションの最悪のケースを示しています。その処理方法はアプリケーションに依存します。アプリケーションの応答時間に対する要件が高い場合、おそらくそれ自体で高いレベルでユーザーを制限するはずです。そうでない場合は、スレッドプール構成パラメータを使用して、何らかの最大待機時間を設定できます。

8.12 パフォーマンスの測定 (ベンチマーク)

パフォーマンスを測定するには、次の要因を考慮します。

  • ビジーでないシステムで単一の操作の速度を測定するかどうか、一連の操作 (ワークロード) が一定の期間でどの程度機能するか。単純なテストでは、通常 1 つの側面 (構成設定、テーブルのインデックスのセット、クエリー内の SQL 句) の変化がパフォーマンスにどのように影響するかをテストします。ベンチマークは一般に長時間実行の複雑なパフォーマンステストであり、結果によって、ハードウェアやストレージ構成などの高レベルの選択や新しい MySQL バージョンにあとどのくらいでアップグレードするかが決まります。

  • ベンチマークでは、正確な実態を得るために、重いデータベースワークロードをシミュレートする必要がある場合があります。

  • パフォーマンスはきわめて多くのさまざまな要因によって異なる可能性があり、数パーセントの違いが決定的勝利にならないことがあります。結果は、別の環境でテストした場合に、逆の方向に転換することもあります。

  • 特定の MySQL 機能は、ワークロードに応じて、パフォーマンスに役立つ場合と役立たない場合があります。完全性のため、常にそれらの機能をオンにした状態とオフにした状態でパフォーマンスをテストします。各ワークロードで試すべきもっとも重要な 2 つの機能は、MySQL クエリーキャッシュInnoDB テーブルのアダプティブハッシュインデックスです。

このセクションでは、1 人の開発者が実行できる単純で直接的な測定技法から、実行と結果の解釈に追加の専門技術を必要とするもっと複雑な技法に進めていきます。

8.12.1 式と関数の速度の測定

特定の MySQL 式または関数の速度を測定するには、mysql クライアントプログラムを使用して、BENCHMARK() 関数を呼び出します。その構文は BENCHMARK(loop_count,expression) です。戻り値は常に 0 ですが、mysql はステートメントの実行にどのくらいの時間を要したかを表示する行を出力します。例:

mysql> SELECT BENCHMARK(1000000,1+1);+------------------------+
| BENCHMARK(1000000,1+1) |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.32 sec)

この結果は Pentium II 400MHz システムで取得されました。これは、MySQL がそのシステムで 1,000,000 件の単純な加算式を 0.32 秒間で実行できることを示しています。

組み込みの MySQL 関数は一般に高度に最適化されますが、例外がある場合もあります。BENCHMARK() はクエリーで特定の関数が問題になっているかどうかを調べる場合に優れたツールです。

8.12.2 MySQL ベンチマークスイート

このベンチマークスイートは、特定の SQL 実装のパフォーマンスが向上または低下する操作をユーザーに示すことを目的としています。MySQL ソース配布の sql-bench ディレクトリにあるコードと結果を確認することで、ベンチマークの動作について十分に理解できます。

このベンチマークはシングルスレッドであるため、実行される操作の最短時間を測定します。将来はこのベンチマークスイートにマルチスレッドのテストも追加する予定です。

ベンチマークスイートを使用するには、次の要件を満たす必要があります。

  • ベンチマークスイートは、MySQL ソース配布によって提供されます。https://dev.mysql.com/downloads/ からリリース済みの配布をダウンロードするか、現在の開発ソースツリーを使用します。(セクション2.9.3「開発ソースツリーを使用して MySQL をインストールする」を参照してください。)

  • ベンチマークスクリプトは Perl で書かれ、データベースサーバーにアクセスするために Perl DBI モジュールを使用しているため、DBI をインストールする必要があります。さらに、テスト対象のサーバーのそれぞれにサーバー固有の DBD ドライバも必要です。たとえば、MySQL、PostgreSQL、および DB2 をテストするには、DBD::mysqlDBD::PgDBD::DB2 のモジュールがインストールされている必要があります。セクション2.13「Perl のインストールに関する注釈」 を参照してください。

MySQL ソース配布の入手後、その sql-bench ディレクトリにあるベンチマークスイートを見つけることができます。ベンチマークテストを実行するには、MySQL を構築し、場所を sql-bench ディレクトリに変更し、run-all-tests スクリプトを実行します。

shell> cd sql-benchshell> perl run-all-tests --server=server_name

server_name はサポートされるいずれかのサーバーの名前にするべきです。すべてのオプションとサポート対象サーバーの一覧を取得するには、このコマンドを呼び出します。

shell> perl run-all-tests --help

crash-me スクリプトも sql-bench ディレクトリにあります。crash-me では、実際のクエリーを実行することによって、データベースシステムがサポートする機能と、その性能と制限を判断しようとします。たとえば、次を判断します。

  • サポートされるデータ型

  • サポートされるインデックス数

  • サポートされる関数

  • 使用可能なクエリーの大きさ

  • 使用可能な VARCHAR カラムの大きさ

ベンチマーク結果の詳細については、http://www.mysql.com/why-mysql/benchmarks/ を参照してください。

8.12.3 独自のベンチマークの使用

アプリケーションとデータベースのベンチマークを行い、ボトルネックのある場所を見つけます。1 つのボトルネックを修正 (または、それをダミーモジュールで置換) することによって、次のボトルネックの識別に進むことができます。現在のアプリケーションの全体的なパフォーマンスが許容できるものであっても、いつか実際にパフォーマンスの強化が必要になった場合に、少なくとも各ボトルネックの計画を立て、解決方法を決定しておくべきです。

移植可能なベンチマークプログラムの例については、MySQL ベンチマークスイートのそれらを参照してください。セクション8.12.2「MySQL ベンチマークスイート」を参照してください。このスイートから任意のプログラムを選び、独自のニーズに合わせて変更できます。これを実行することによって、それぞれの問題に対してさまざまな解決方法を試してみて、実際にもっとも高速であるのはどれかをテストできます。

もう 1 つの無料のベンチマークスイートは Open Source Database Benchmark であり、http://osdb.sourceforge.net/ で入手できます。

システムの負荷が非常に高い場合にのみ問題が発生することはよくあることです。(テスト済みの) システムを本稼働させて、負荷の問題が発生したときに、問い合わせてくる顧客が多数いました。ほとんどの場合、パフォーマンスの問題は、高負荷時のテーブルスキャンの不良などデータベースの基本的な設計の問題か、オペレーティングシステムやライブラリの問題によると判明しています。ほとんどの場合、システムがまだ本稼働に入っていない場合の方がこれらの問題の修正がはるかに容易です。

このような問題を回避するには、可能性のある最悪の負荷でアプリケーション全体のベンチマークを行います。

これらのプログラムやパッケージはシステムを破損させる可能性があるため、それらは開発システムでのみ使用するようにしてください。

8.12.4 performance_schema によるパフォーマンスの測定

performance_schema データベースのテーブルをクエリーし、それを実行しているサーバーとアプリケーションのパフォーマンス特性に関するリアルタイムの情報を確認できます。詳細は、第22章「MySQL パフォーマンススキーマを参照してください。

8.12.5 スレッド情報の検査

MySQL サーバーで何が実行されているかを確認しようとする場合、プロセスリストを調査すると役立つ場合があります。これは、サーバー内で現在実行されているスレッドのセットです。プロセスリストの情報はこれらのソースから入手できます。

threads へのアクセスには相互排他ロックは必要なく、サーバーパフォーマンスへの影響は最小です。INFORMATION_SCHEMA.PROCESSLIST および SHOW PROCESSLIST は、相互排他ロックを必要とするので、負のパフォーマンスの結果になります。threads はまた、バックグラウンドスレッドに関する情報も表示しますが、INFORMATION_SCHEMA.PROCESSLIST および SHOW PROCESSLIST は表示しません。これは、threads は、ほかのスレッド情報源では行えないアクティビティーのモニターに使用できることを意味します。

自分のスレッドに関する情報はいつでも表示できます。ほかのアカウントで実行されているスレッドに関する情報を表示するには、PROCESS 権限が必要です。

プロセスリストの各エントリには、いくつかの情報が含まれています。

  • Id は、スレッドに関連付けられているクライアントの接続識別子です。

  • UserHost は、スレッドに関連付けられているアカウントを示します。

  • db は、スレッドのデフォルトのデータベースで、または何も選択されていない場合は NULL です。

  • CommandState は、スレッドが何を実行しているかを示します。

    ほとんどの状態がきわめてすばやい操作に対応します。スレッドの状態が何秒間も特定の状態にとどまっている場合は、調査が必要な問題が発生している可能性があります。

  • Time は、スレッドの現在の状態がどれだけ続いているかを示します。特定の場合に、スレッドの現在の時間の概念が変わることがあります。スレッドは、SET TIMESTAMP = value によって時間を変更することがあります。マスターからのイベントを処理しているスレーブで実行しているスレッドの場合、スレッドの時間はイベント内に見つかった時間に設定されるため、スレーブではなくマスターの現在の時間を反映します。

  • Info には、スレッドで実行されているステートメントのテキストが含まれるか、または何も実行されていない場合は NULL です。デフォルトでは、この値にはステートメントの先頭の 100 文字だけが含まれます。完全なステートメントを表示するには、SHOW FULL PROCESSLIST を使用します。

以下のセクションでは、Command の可能な値と、カテゴリ別にグループ化した State の値を説明します。これらの一部の値の意味は自明です。その他については追加の説明を提供しています。

8.12.5.1 スレッドのコマンド値

スレッドの Command 値は次のいずれかになります。

  • Binlog Dump

    これは、バイナリログの内容をスレーブサーバーに送信するためのマスターサーバー上のスレッドです。

  • Change user

    スレッドはユーザー変更操作を実行しています。

  • Close stmt

    スレッドはプリペアドステートメントをクローズしています。

  • Connect

    レプリケーションスレーブはそのマスターに接続されています。

  • Connect Out

    レプリケーションスレーブはそのマスターに接続しています。

  • Create DB

    スレッドはデータベース作成操作を実行しています。

  • Daemon

    このスレッドはサーバーの内部で使用され、クライアント接続をホストするスレッドではありません。

  • Debug

    スレッドはデバッグ情報を生成しています。

  • Delayed insert

    スレッドは遅延挿入ハンドラです。

  • Drop DB

    スレッドはデータベース削除操作を実行しています。

  • Error

  • Execute

    スレッドはプリペアドステートメントを実行しています。

  • Fetch

    スレッドはプリペアドステートメントの実行から結果をフェッチしています。

  • Field List

    スレッドはテーブルカラムの情報を取得しています。

  • Init DB

    スレッドはデフォルトのデータベースを選択しています。

  • Kill

    スレッドは別のスレッドを強制終了しています。

  • Long Data

    スレッドはプリペアドステートメントの実行の結果から長いデータを取得しています。

  • Ping

    スレッドはサーバー ping 要求を処理しています。

  • Prepare

    スレッドはプリペアドステートメントを準備しています。

  • Processlist

    スレッドはサーバースレッドに関する情報を生成しています。

  • Query

    スレッドはステートメントを実行しています。

  • Quit

    スレッドは終了しています。

  • Refresh

    スレッドは、テーブル、ログ、またはキャッシュをフラッシュしているか、ステータス変数またはレプリケーションサーバーの情報をリセットしています。

  • Register Slave

    スレッドはスレーブサーバーを登録しています。

  • Reset stmt

    スレッドはプリペアドステートメントをリセットしています。

  • Set option

    スレッドはクライアントのステートメント実行オプションを設定またはリセットしています。

  • Shutdown

    スレッドはサーバーをシャットダウンしています。

  • Sleep

    スレッドはクライアントが新しいステートメントをそれに送信するのを待機しています。

  • Statistics

    スレッドはサーバーステータス情報を生成しています。

  • Table Dump

    スレッドはテーブルの内容をスレーブサーバーに送信しています。

  • Time

    使用されません。

8.12.5.2 一般的なスレッドの状態

次のリストは、レプリケーションなどの特殊なアクティビティーではなく、一般的なクエリーの処理に関連付けられた、スレッドの State 値を説明しています。これらの多くは、サーバーのバグを見つけるためにのみ役立ちます。

  • After create

    これは、スレッドがテーブル (内部一時テーブルも含む) を作成する際の、テーブルを作成する関数の最後に発生します。何らかのエラーのためテーブルを作成できなかった場合でも、この状態が使われます。

  • altering table

    サーバーはインプレース ALTER TABLE の実行中です。

  • Analyzing

    スレッドは MyISAM テーブルのキー分布を計算しています (ANALYZE TABLE などで)。

  • checking permissions

    スレッドは、サーバーがステートメントを実行するために必要な権限を持っているかどうかを確認しています。

  • Checking table

    スレッドはテーブルチェック操作を実行しています。

  • cleaning up

    スレッドは 1 つのコマンドを処理し、メモリーの解放と特定の状態変数のリセットを準備しています。

  • closing tables

    スレッドは、変更されたテーブルデータをディスクにフラッシュし、使用されたテーブルをクローズしています。これは高速の操作であるはずです。そうでない場合は、ディスクがいっぱいでないか、ディスクが著しく頻繁に使用されていないかを確認してください。

  • committing alter table to storage engine

    サーバーはインプレース ALTER TABLE を終了し、結果をコミットしています。

  • converting HEAP to MyISAM

    スレッドは内部一時テーブルを MEMORY テーブルからディスク上の MyISAM テーブルに変換しています。

  • copy to tmp table

    スレッドは ALTER TABLE ステートメントを処理しています。この状態は、新しい構造でテーブルが作成されたあと、ただし、それに行がコピーされる前に発生します。

  • Copying to group table

    ステートメントの ORDER BYGROUP BY の基準が異なる場合、行はグループによってソートされ、一時テーブルにコピーされます。

  • Copying to tmp table

    サーバーはメモリー内の一時テーブルにコピーしています。

  • Copying to tmp table on disk

    サーバーはディスク上の一時テーブルにコピーしています。一時結果セットが大きくなりすぎました (セクション8.4.4「MySQL が内部一時テーブルを使用する仕組み」を参照してください)。その結果、スレッドは一時テーブルをインメモリーからディスクベースのフォーマットに変更して、メモリーを節約します。

  • Creating index

    スレッドは MyISAM テーブルに対する ALTER TABLE ... ENABLE KEYS を処理しています。

  • Creating sort index

    スレッドは内部一時テーブルを使用して解決される SELECT を処理しています。

  • creating table

    スレッドはテーブルを作成しています。これには一時テーブルの作成が含まれます。

  • Creating tmp table

    スレッドはメモリー内またはディスク上に一時テーブルを作成しています。メモリー内に作成されたテーブルがあとでディスク上のテーブルに変換される場合、その操作中の状態は Copying to tmp table on disk になります。

  • deleting from main table

    サーバーは複数テーブル削除の最初の部分を実行しています。最初のテーブルからのみ削除し、別の (参照) テーブルからの削除に使用されるカラムとオフセットを保存しています。

  • deleting from reference tables

    サーバーは複数テーブル削除の 2 番目の部分を実行しており、別のテーブルから一致した行を削除しています。

  • discard_or_import_tablespace

    スレッドは ALTER TABLE ... DISCARD TABLESPACE または ALTER TABLE ... IMPORT TABLESPACE ステートメントを処理しています。

  • end

    これは、ALTER TABLECREATE VIEWDELETEINSERTSELECT、または UPDATE ステートメントの最後、ただしクリーンアップの前に発生します。

  • executing

    スレッドはステートメントの実行を開始しました。

  • Execution of init_command

    スレッドは init_command システム変数の値のステートメントを実行しています。

  • freeing items

    スレッドはコマンドを実行しました。この状態中に実行される項目の解放の一部には、クエリーキャッシュが含まれます。通常、この状態のあとは cleaning up になります。

  • Flushing tables

    スレッドは FLUSH TABLES を実行しており、すべてのスレッドがそれぞれのテーブルをクローズするのを待っています。

  • FULLTEXT initialization

    サーバーは自然言語全文検索を実行する準備をしています。

  • init

    これは、ALTER TABLEDELETEINSERTSELECT、または UPDATE ステートメントの初期化の前に発生します。この状態のサーバーによってとられるアクションには、バイナリログ、InnoDB ログ、および一部のクエリーキャッシュクリーンアップ操作のフラッシュが含まれます。

    end 状態では、次の操作が行われることがあります。

    • テーブルのデータが変更されたあとのクエリーキャッシュエントリの削除

    • バイナリログへのイベントの書き込み

    • BLOB 用を含むメモリーバッファーの解放

  • Killed

    だれかがスレッドに KILL ステートメントを送っており、スレッドは次に強制終了フラグをチェックしたときに中止するはずです。フラグは MySQL の各主要ループ内でチェックされますが、場合によってはスレッドが停止するまでに少し時間がかかる場合があります。スレッドがほかのスレッドにロックされている場合、強制終了はほかのスレッドがそのロックを解除するとすぐに有効になります。

  • logging slow query

    スレッドはステートメントを低速クエリーログに書き込んでいます。

  • NULL

    この状態は SHOW PROCESSLIST 状態に使用されます。

  • login

    クライアントが正常に認証されるまでの接続スレッドの初期状態です。

  • manage keys

    サーバーはテーブルインデックスを有効または無効にしています。

  • Opening tablesOpening table

    スレッドはテーブルをオープンしようと試みています。これは、何かにオープンを妨げられないかぎり、きわめて高速な手順であるはずです。たとえば、ALTER TABLE または LOCK TABLE ステートメントは、そのステートメントが終了するまでテーブルのオープンを妨げることがあります。table_open_cache 値が十分に大きいことをチェックすることも価値があります。

  • optimizing

    サーバーはクエリーの初期最適化を実行しています。

  • preparing

    この状態はクエリーの最適化中に発生します。

  • preparing for alter table

    サーバーはインプレース ALTER TABLE の実行を準備しています。

  • Purging old relay logs

    スレッドは不要なリレーログファイルを削除しています。

  • query end

    この状態は、クエリーを処理したあと、ただし freeing items 状態の前に発生します。

  • Reading from net

    サーバーはネットワークからパケットを読み取っています。

  • Removing duplicates

    クエリーは、MySQL が早い段階で個別の操作を最適化できなくなるような方法で SELECT DISTINCT を使用していました。このため、MySQL は結果をクライアントに送る前にすべての重複した行を削除するための追加の段階を必要とします。

  • removing tmp table

    スレッドは SELECT ステートメントを処理したあとに内部一時テーブルを削除しています。一時テーブルが作成されなかった場合、この状態は使用されません。

  • rename

    スレッドはテーブルの名前を変更しています。

  • rename result table

    スレッドは ALTER TABLE ステートメントを処理しており、新しいテーブルを作成し、元のテーブルを置き換えるためにその名前を変更しています。

  • Reopen tables

    スレッドはテーブルのロックを取得しましたが、ロックの取得後、基盤となるテーブル構造が変更されたことを認識しました。それはロックを解除し、テーブルをクローズして、再度オープンしようとしています。

  • Repair by sorting

    修復コードはインデックスを作成するためにソートを使用しています。

  • Repair done

    スレッドは MyISAM テーブルに対するマルチスレッドの修復を完了しました。

  • Repair with keycache

    修復コードはキーキャッシュ経由で、1 つずつキーの作成を使用しています。これは Repair by sorting よりはるかに遅くなります。

  • Rolling back

    スレッドはトランザクションをロールバックしています。

  • Saving state

    MyISAM テーブルの修復や分析などの操作で、スレッドは新しいテーブルの状態を .MYI ファイルヘッダーに保存しています。状態には、行の数、AUTO_INCREMENT カウンタ、キー分布などの情報が含まれています。

  • Searching rows for update

    スレッドは、すべての一致する行を更新する前に、それらを見つけるための第 1 フェーズを実行しています。これは、UPDATE が、関連する行を見つけるために使用されるインデックスを変更している場合に、実行される必要があります。

  • Sending data

    スレッドは SELECT ステートメントの行を読み取り、処理して、データをクライアントに送信しています。この状態で行われる操作は、大量のディスクアクセス (読み取り) を実行する傾向があるため、特定のクエリーの存続期間にわたる最長時間実行状態になることがあります。

  • setup

    スレッドは ALTER TABLE 操作を開始しています。

  • Sorting for group

    スレッドは GROUP BY を満たすためにソートを実行しています。

  • Sorting for order

    スレッドは ORDER BY を満たすためにソートを実行しています。

  • Sorting index

    スレッドは MyISAM テーブルの最適化操作中に、より効率的なアクセスのためにインデックスページをソートしています。

  • Sorting result

    SELECT ステートメントの場合、これは Creating sort index と似ていますが、非一時テーブルに対するものです。

  • statistics

    サーバーはクエリー実行プランを開発するための統計を計算しています。スレッドが長期間この状態にある場合、サーバーはディスクに依存してほかの作業を実行している可能性があります。

  • System lock

    スレッドは、テーブルの内部または外部システムロックをリクエストしようとしているか待機しています。この状態が外部ロックへのリクエストによって発生しており、同じ MyISAM テーブルにアクセスしている複数の mysqld サーバーを使用していない場合、--skip-external-locking オプションによって外部システムロックを無効にできます。ただし、外部ロックはデフォルトで無効になるため、このオプションには効果がない可能性があります。SHOW PROFILE の場合、この状態はスレッドがロックをリクエストしている (待機しているのではなく) ことを意味します。

  • update

    スレッドはテーブルの更新を開始する準備ができています。

  • Updating

    スレッドは更新する行を探していて、それらを更新しています。

  • updating main table

    サーバーは複数テーブル更新の最初の部分を実行しています。最初のテーブルのみを更新しており、別の (参照) テーブルの更新に利用されるカラムとオフセットを保存しています。

  • updating reference tables

    サーバーは複数テーブル更新の 2 番目の部分を実行しており、ほかのテーブルから一致した行を更新しています。

  • User lock

    スレッドは GET_LOCK() 呼び出しによってリクエストされたアドバイザリロックを、リクエストしようとしているか待機しています。SHOW PROFILE の場合、この状態はスレッドがロックをリクエストしている (待機しているのではなく) ことを意味します。

  • User sleep

    スレッドは SLEEP() 呼び出しを呼び出しました。

  • Waiting for commit lock

    FLUSH TABLES WITH READ LOCK はコミットロックを待機しています。

  • Waiting for global read lock

    FLUSH TABLES WITH READ LOCK はグローバル読み取りロックまたはグローバル read_only システム変数が設定されるのを待機しています。

  • Waiting for tables, Waiting for table flush

    スレッドは、テーブルの基盤となる構造が変更され、その新しい構造を得るためにテーブルを再度オープンする必要があるという通知を受け取りました。ただし、テーブルを再度オープンするには、ほかのすべてのスレッドが問題のテーブルをクローズするまで待機する必要があります。

    この通知は、別のスレッドが FLUSH TABLES か、問題のテーブルに次のステートメントのいずれかを使用した場合に、この通知が行われます: FLUSH TABLES tbl_nameALTER TABLERENAME TABLEREPAIR TABLEANALYZE TABLE、または OPTIMIZE TABLE

  • Waiting for lock_type lock

    サーバーはロックの獲得を待機しています。ここで lock_type はロックの種類を示しています。

    • Waiting for event metadata lock

    • Waiting for global read lock

    • Waiting for schema metadata lock

    • Waiting for stored function metadata lock

    • Waiting for stored procedure metadata lock

    • Waiting for table level lock

    • Waiting for table metadata lock

    • Waiting for trigger metadata lock

  • Waiting on cond

    スレッドが条件が true になるのを待機している一般的な状態です。特定の状態情報は使用できません。

  • Writing to net

    サーバーはネットワークにパケットを書き込んでいます。

8.12.5.3 遅延挿入スレッドの状態

これらのスレッドの状態は、DELAYED 挿入の処理に関連付けられています (セクション13.2.5.2「INSERT DELAYED 構文」を参照してください)。一部の状態は、クライアントから INSERT DELAYED ステートメントを処理する接続スレッドに関連付けられています。ほかの状態は、行を挿入する遅延挿入ハンドラスレッドに関連付けられています。INSERT DELAYED ステートメントが発行された各テーブルに、遅延挿入ハンドラスレッドが存在します。

クライアントから INSERT DELAYED ステートメントを処理する接続スレッドに関連付けられているスレッド:

  • allocating local table

    スレッドは遅延挿入ハンドラスレッドに行を提供する準備をしています。

  • Creating delayed handler

    スレッドは DELAYED 挿入のハンドラを作成しています。

  • got handler lock

    これは、allocating local table 状態の前、かつ waiting for handler lock 状態のあとの、接続スレッドが遅延挿入ハンドラスレッドにアクセスするときに発生します。

  • got old table

    これは waiting for handler open 状態のあとに発生します。遅延挿入ハンドラスレッドは、初期化フェーズを終了したことを通知しました。これには、遅延挿入のためのテーブルのオープンが含まれます。

  • storing row into queue

    スレッドは、遅延挿入ハンドラスレッドで挿入する必要のある行のリストに、新しい行を追加しています。

  • waiting for delay_list

    これは、初期化フェーズ中、スレッドがテーブルの遅延挿入ハンドラスレッドを見つけようとしているときで、遅延挿入スレッドのリストにアクセスを試みる前に発生します。

  • waiting for handler insert

    INSERT DELAYED ハンドラはすべての未解決の挿入を処理し、新しい挿入を待機しています。

  • waiting for handler lock

    これは、接続スレッドが遅延挿入ハンドラスレッドへのアクセスを待機しているときの allocating local table 状態の前に発生します。

  • waiting for handler open

    これは Creating delayed handler 状態のあとで got old table 状態の前に発生します。遅延挿入ハンドラスレッドが開始したばかりで、接続スレッドはそれが初期化されるのを待機しています。

行を挿入する遅延挿入ハンドラスレッドに関連付けられている状態:

  • insert

    テーブルに行を挿入する直前に発生する状態。

  • reschedule

    いくつかの行の挿入後、遅延挿入スレッドはスリープし、ほかのスレッドが作業を実行できるようにします。

  • upgrading lock

    遅延挿入ハンドラは行を挿入するために、テーブルのロックを取得しようとしています。

  • Waiting for INSERT

    遅延挿入ハンドラは、接続スレッドがキューに行を追加するのを待機しています (storing row into queue を参照してください)。

8.12.5.4 クエリーキャッシュスレッドの状態

これらのスレッドの状態はクエリーキャッシュに関連付けられています (セクション8.9.3「MySQL クエリーキャッシュ」を参照してください)。

  • checking privileges on cached query

    サーバーはユーザーがキャッシュされたクエリー結果にアクセスする権限を持っているかどうかをチェックしています。

  • checking query cache for query

    サーバーは、現在のクエリーがクエリーキャッシュに存在するかどうかをチェックしています。

  • invalidating query cache entries

    基盤となるテーブルが変更されているため、クエリーキャッシュエントリは無効とマークされています。

  • sending cached result to client

    サーバーはクエリーキャッシュからクエリーの結果を取得し、それをクライアントに送信しています。

  • storing result in query cache

    サーバーはクエリーの結果をクエリーキャッシュに保存しています。

  • Waiting for query cache lock

    この状態は、セッションがクエリーキャッシュのロックを取得するのを待機している間に発生します。これは、クエリーキャッシュを無効にする INSERTDELETE、キャッシュされたエントリを検索する SELECTRESET QUERY CACHE などの一部のクエリーキャッシュ操作を実行するために必要なステートメントで発生する可能性があります。

8.12.5.5 レプリケーションマスタースレッドの状態

次のリストに、マスターの Binlog Dump スレッドの State カラムに示される可能性があるもっとも一般的な状態を示します。マスターサーバーで、Binlog Dump スレッドが見られない場合、これは、レプリケーションが実行中でない、つまりスレーブが現在接続されていないことを意味します。

  • Sending binlog event to slave

    バイナリログはイベントで構成され、そこではイベントが通常更新と何らかのその他の情報が追加されたものになります。スレッドはバイナリログからイベントを読み取り、現在それをスレーブに送信しています。

  • Finished reading one binlog; switching to next binlog

    スレッドはバイナリログファイルの読み取りを完了し、スレーブに送信するために次のファイルをオープンしています。

  • Master has sent all binlog to slave; waiting for binlog to be updated

    スレッドはバイナリログからすべての未処理の更新を読み取り、それらをスレーブに送信しました。スレッドは現在アイドルで、マスターで行われている新しい更新の結果として、新しいイベントがバイナリログに表示されるのを待機しています。

  • Waiting to finalize termination

    スレッド停止中に発生するきわめて短い状態。

8.12.5.6 レプリケーションスレーブの I/O スレッド状態

次のリストに、スレーブサーバーの I/O スレッドの State カラムに表示されるもっとも一般的な状態を示します。この状態は、SHOW SLAVE STATUS によって表示される Slave_IO_State カラムにも表示されるため、そのステートメントを使用して、何が起こっているかを十分に把握できます。

  • Waiting for master update

    Connecting to master の前の初期状態。

  • Connecting to master

    スレッドはマスターへの接続を試みています。

  • Checking master version

    マスターへの接続が確立されたあとに一時的に発生する状態。

  • Registering slave on master

    マスターへの接続が確立されたあとに一時的に発生する状態。

  • Requesting binlog dump

    マスターへの接続が確立されたあとに一時的に発生する状態。スレッドは、マスターにそのバイナリログの内容のリクエストを送信し、リクエストしたバイナリログファイル名と位置から開始します。

  • Waiting to reconnect after a failed binlog dump request

    切断のため、バイナリログダンプリクエストに失敗した場合、スレッドはスリープ中にこの状態になり、定期的に再接続を試みます。再試行の間隔は、CHANGE MASTER TO ステートメントを使用して指定できます。

  • Reconnecting after a failed binlog dump request

    スレッドはマスターへの再接続を試みています。

  • Waiting for master to send event

    スレッドはマスターに接続し、バイナリログイベントの到着を待機しています。マスターがアイドル状態の場合、これは長時間続く可能性があります。待機が slave_net_timeout 秒継続した場合、タイムアウトになります。その時点で、スレッドは接続が切断されているとみなし、再接続を試みます。

  • Queueing master event to the relay log

    スレッドはイベントを読み取っており、SQL スレッドがそれを処理できるように、それをリレーログにコピーしています。

  • Waiting to reconnect after a failed master event read

    切断のため、読み取り中にエラーが発生しました。スレッドは CHANGE MASTER TO ステートメントに設定された秒数 (デフォルト 60) の間スリープしてから、再接続を試みます。

  • Reconnecting after a failed master event read

    スレッドはマスターへの再接続を試みています。ふたたび接続が確立されると、状態は Waiting for master to send event になります。

  • Waiting for the slave SQL thread to free enough relay log space

    0 以外の relay_log_space_limit 値を使用しており、リレーログの組み合わせたサイズがこの値を超えるまで拡大しています。I/O スレッドは、一部のリレーログファイルを削除できるように、リレーログ内容を処理することによって SQL スレッドが十分な領域を解放するまで、待機しています。

  • Waiting for slave mutex on exit

    スレッドの停止中に一時的に発生する状態。

8.12.5.7 レプリケーションスレーブ SQL スレッドの状態

次のリストに、スレーブサーバー SQL スレッドの State カラムに表示される可能性のあるもっとも一般的な状態を示します。

  • Waiting for the next event in relay log

    Reading event from the relay log の前の初期状態です。

  • Reading event from the relay log

    スレッドはイベントを処理できるように、イベントをリレーログから読み取りました。

  • Making temporary file (append) before replaying LOAD DATA INFILE

    スレッドは LOAD DATA INFILE ステートメントを実行しており、スレーブが行を読み取る、データを格納している一時ファイルにデータを追加しています。

  • Making temporary file (create) before replaying LOAD DATA INFILE

    スレッドは LOAD DATA INFILE ステートメントを実行しており、スレーブが行を読み取る、データを格納している一時ファイルを作成しています。この状態は、元の LOAD DATA INFILE ステートメントが、バージョン 5.0.3 より前の MySQL のバージョンを実行しているマスターによって記録された場合にのみ検出される可能性があります。

  • Slave has read all relay log; waiting for more updates

    スレッドはリレーログファイル内のすべてのイベントを処理しており、現在 I/O スレッドが新しいイベントをリレーログに書き込むのを待機しています。

  • Waiting for slave mutex on exit

    スレッド停止中に発生するきわめて短い状態。

  • Waiting until MASTER_DELAY seconds after master executed event

    SQL スレッドはイベントを読み取りましたが、スレーブの遅延の満了を待機しています。この遅延は、CHANGE MASTER TOMASTER_DELAY オプションによって設定されます。

I/O スレッドの Info カラムには、ステートメントのテキストも表示されることがあります。これは、スレッドがリレーログからイベントを読み取り、それからステートメントを抽出して、それを実行している可能性があることを示しています。

8.12.5.8 レプリケーションスレーブ接続スレッドの状態

これらのスレッドの状態はレプリケーションスレーブで発生しますが、I/O スレッドや SQL スレッドではなく、接続スレッドに関連付けられています。

  • Changing master

    スレッドは CHANGE MASTER TO ステートメントを処理しています。

  • Killing slave

    スレッドは STOP SLAVE ステートメントを処理しています。

  • Opening master dump table

    この状態は Creating table from master dump のあとに発生します。

  • Reading master dump table data

    この状態は Opening master dump table のあとに発生します。

  • Rebuilding the index on master dump table

    この状態は Reading master dump table data のあとに発生します。

8.12.5.9 MySQL Cluster スレッドの状態

  • Committing events to binlog

  • Opening mysql.ndb_apply_status

  • Processing events

    スレッドはバイナリロギングのイベントを処理しています。

  • Processing events from schema table

    スレッドはスキーマレプリケーションの作業を実行しています。

  • Shutting down

  • Syncing ndb table schema operation and binlog

    これは、NDB のスキーマ操作の正しいバイナリログを維持するために使用されます。

  • Waiting for event from ndbcluster

    サーバーは MySQL クラスタ内の SQL ノードとして機能しており、クラスタ管理ノードに接続されています。

  • Waiting for first event from ndbcluster

  • Waiting for ndbcluster binlog update to reach current position

  • Waiting for ndbcluster to start

  • Waiting for schema epoch

    スレッドはスキーマエポック (つまり、グローバルチェックポイント) を待機しています。

  • Waiting for allowed to take ndbcluster global schema lock

    スレッドはグローバルスキーマロックを取得する権限を待機しています。

  • Waiting for ndbcluster global schema lock

    スレッドは、別のスレッドによって保持されているグローバルスキーマロックが解放されるのを待機しています。

8.12.5.10 イベントスケジューラスレッドの状態

これらの状態は、イベントスケジューラスレッド、スケジュールされたイベントを実行するために作成されるスレッド、またはスケジューラを終了するスレッドで発生します。

  • Clearing

    スケジューラスレッドまたはイベントを実行していたスレッドは終了中で、まもなく終了します。

  • Initialized

    スケジューラスレッドまたはイベントを実行するスレッドが初期化されました。

  • Waiting for next activation

    スケジューラには空でないイベントキューがありますが、次のアクティブ化はあとで行われます。

  • Waiting for scheduler to stop

    スレッドは SET GLOBAL event_scheduler=OFF を発行し、スケジューラが停止するのを待機しています。

  • Waiting on empty queue

    スケジューラのイベントキューは空で、スリープ中です。

関連キーワード:  テーブル,します,8,クエリー,インデックス,ステートメント,MySQL,カラム,されます,SELECT