目次
SELECT
ステートメントおよびその他のクエリの最適化EXPLAIN
を使用して、クエリを最適化するSELECT
クエリの速度WHERE
節最適化IS NULL
最適化DISTINCT
最適化LEFT JOIN
とRIGHT JOIN
最適化ORDER BY
最適化GROUP BY
最適化LIMIT
の最適化INSERT
ステートメントの速度UPDATE
ステートメントの速度DELETE
ステートメントの速度最終的に全てのシステムの理解が必要なため、最適化は複雑な作業です。ユーザのシステムやアプリケーションの知識でローカル最適化は可能ですが、システムをさらに最適化するには、システムに関する深い知識が必要になります。
この章ではMySQLを最適化するためのさまざまな手法を、例を交えて説明します。取得には更なる努力が必要ですが、必ずシステムの速度を上げる方法は数多く存在することを覚えていてください。
システムの速度を上げる際に最も重要な要素は基本設計です。また、使用するシステムの用途およびそのボトルネックを認識しておく必要もあります。最も一般的なボトルネックは下記のとおりです。
ディスクシーク。ディスクが 1 つのデータを検索するには時間がかかります。最新のディスクでは、通常これにかかる平均時間が 10 ms 未満であるため、理論的には 1 秒間に 100 のシークを実行できることになります。新しいディスクではこの時間の改善が緩やかで、1 つのテーブルの最適化が非常に困難です。これを最適化する方法として、複数のディスクにデータを分散することが挙げられます。
ディスクの読み取りと書き込み。ディスクが適切な位置にある場合、データの読み取りが必要になります。最新のディスクでは、1 つのディスクで約 10?20MB/s の読み取りが可能になります。これは、複数のディスクから並行した読み取りが可能であるため、シークに比較して最適化が容易に行えます。
CPU サイクル。メインメモリにデータがある場合(またはすでにそこに存在している場合)、結果を得るためには処理が必要になります。メモリと比較してテーブルが小さい場合は、最も一般的な制限要因になります。しかし、テーブルが小さくても、一般に速度上の問題は発生しない。
メモリ帯域幅。 CPU キャッシュの適正量より多く CPU がデータを必要とする場合、メインメモリの帯域幅がボトルネックになる。これは、ほとんどのシステムに一般的な問題ではないが、認識しておく必要がある。
MyISAM
ストレージエンジンの使用時に、MySQL
では非常に高速のテーブルロック(複数リーダ/単一ライタ)が使用されます。
このテーブル型の最大の問題は、同じテーブルに対して複数の
UPDATE と遅い SELECT
が混在する場合に発生します。テーブルでこのような問題が発生した場合は、別のテーブル型を使用してもかまいません。
章?13. ストレージエンジンとテーブルタイプを参照してください。
MySQL
はトランザクションテーブル、非トランザクションテーブルの両方で機能します。非トランザクションテーブル(何らかのエラー発生した場合にロールバックができない)での動作をスムーズにするため、MySQL
には次のルールがあります。
このルールが適用されるのは正確にSQLモードで作動しているとき、あるいはIGNORE
スペシファイヤをINSERT
もしくはUPDATE
に使用しているときのみです。
すべてのカラムにデフォルト値がある。
カラムに対して'正しくない' 値を挿入した場合や、数値列の数値が大きすぎる場合、MySQL ではエラーを発生するのではなく、「とりうる可能な値のうちの最適値」に値を設定する。数値の場合は 0 で、可能な最小値か最大値になる。文字列の場合は、空白文字かカラムの最大長さにあわせた文字列になる。
計算式はすべて、エラー状態を表示するのではなく、使用可能な値を返す。たとえば、1/0
の場合は、NULL
を返す。
SQLのサーバモードの設定をすることで、さらに制御されたデータの扱いを有効にします。このことにより、以前の動作反応を変更することができます。この詳細については、項1.8.6. 「MySQL における制約の処理」、項4.2.6. 「SQL モード」、そして項12.2.4. 「INSERT
構文」を参照してください。
SQL サーバは SQL のさまざまな部分を実装しているので、移植可能な SQL アプリケーションの作成が可能となります。非常に単純な SELECT や INSERT は容易ですが、必要なことが増えれば増えるほど、作成が難しくなります。多数のデータベースを使用しながら素早い速度が要求されるアプリケーションの場合は、さらに難度が上がります。
どのデータベースシステムにも欠点はあります。それはつまり、データベースのすべてに何らかの弱点があります。言い換えると、動作の相違を招くさまざまな設計上の障害があります。
複雑なアプリケーションを移植可能にするには、ともに稼動する必要のある SQL サーバ数を選択する必要があります。MySQL crash-meプログラムを使用すると、データベースサーバの選択に使用できる関数、データ型、制約を調べることができます。現在の crash-meは可能なことすべてのテストを実行できるとは決して言えませんが、約 450 項目のテストが幅広く行われています。 たとえば、crash-meが提供する情報の例として、Infomix や DB2 の使用を可能にするには、18 文字を超えるカラム名は使用できません、といったものがあります。
MySQL ベンチマークと
crash-meプログラムはいずれもデータベースへの依存度が非常に低くなっています。これらのプログラムがどのように処理されているかを調べることによって、データベースに依存しないアプリケーションを作成する際に必要なことに関する感覚を得ることができます。ベンチマーク自体は、MySQL
ソースディストリビューションの
sql-bench
ディレクトリにあります。
これらはPerlで書かれ、DBIデータベースインターフェースを使用する。DBI
の使用それ自体で移植性の問題の1部分を解決できます。これはデータベースから独立したアクセス方法をとるからです。項6.1.4. 「MySQL ベンチマークスィート」を参照してください。
データベースの独立性の獲得を目指す場合は、SQL
サーバそれぞれのボトルネックを正しく理解する必要があります。MySQL
では、非常に高速にMyISAM
レコード(テーブルや行)の取り出しと更新が行われますが、1
つのテーブル上に低速のリーダとライタが混在することに問題があります。異なり、Oracle
では、更新直後のレコードがディスクに保存される前にそのレコードにアクセスしようとする際に大きな問題があります。一般にトランザクションデータベースの場合、ログテーブルからのサマリテーブルの生成時は行ロックがほとんど役に立たず、問題が生じやすくなっています。
アプリケーションを実際にデータベース非依存にするには、データ操作に使用する簡単な拡張可能インタフェースを定義する必要があります。 例えば、C++ほとんどのシステムでは C++ が使用できるため、データベースに C++ クラスインタフェースを使用することは道理にかなっています。
あるデータベースに固有の機能を使用する場合(MySQL
の REPLACE
コマンドなど)は、他の
SQL
サーバでその機能を実装できるようにするメソッドをコード化する必要があります(ただし低速化します)。低速化しますが、他のサーバで同じ作業を実行できるようにします。
MySQL を使用すると、/*!
*/
構文を使用して MySQL
固有のキーワードをクエリに追加できます。/*
*/
内のコードは、その他の SQL
サーバのほとんどでコメントとして処理(無視)されます。コメントの挿入に関する情報は、項8.5. 「コメント構文」を参照してください。
一部の Web アプリケーションのように正確性よりパフォーマンスが重視される場合は、すべての結果をキャッシュするアプリケーションレイヤを作成すると、さらにパフォーマンスを改善できます。一定の期間後に古い結果を '期限切れ' することで、キャッシュを適度に最新の状態に保持できます。これにより、キャッシュを動的に拡大し、通常の状況に戻るまでタイムアウト期限を高速に設定して、高負荷のスパイクを処理するメソッドが提供されます。
この場合、テーブル作成情報にキャッシュの初期サイズと通常時にテーブルがリフレッシュされる頻度に関する情報が組み込まれます。
アプリケーションを実行する代わりにMySQLクエリのキャッシュを使用するのは、効果的です。クエリキャッシュを有効にすることで、クエリ結果が再利用できるかどうかの判断の詳細をサーバに一任します。これによりユーザのアプリケーションは単純化します。項4.13. 「MySQL クエリ キャッシュ」を参照してください。
このセクションではMySQL開発当初のアプリケーションを説明します。
MySQL の初期開発当時は、最大顧客に合わせて MySQL の機能が開発されてきました。この機能は、スウェーデンの最大小売商数社向けにデータウェアハウスを処理するものです。
すべての店舗からボーナスカード取引すべてのサマリを毎週取得し、店舗の所有者が顧客に対する広告キャンペーンの効果を調べる際に役立つ情報を提供するように求められています。
このデータは非常に大量(1 か月に約 700 万のサマリ取引)で、ユーザへの提示に必要な 4?10 年間のデータを保有しています。 このデータから新しいレポートに '即時' アクセスできるようにしたいという顧客からの要求が毎週ありました。
1 か月ごとにすべての情報を圧縮 「トランザクション' テーブル」に格納することでこの要求を解決しました。トランザクションテーブルからさまざまな基準(製品グループ、顧客 ID、店舗など)によって分類されたサマリテーブルを生成する単純なマクロ(スクリプト)セットを開発しています。レポートは Web ページ形式で、Web ページを解析し、SQL ステートメントを実行して、結果を挿入する、短い Perl スクリプトから動的に生成されます。mod_perlの使用のほうが適しているとも言えますが、その当時は利用できませんでした。
グラフィカルデータについては、SQL クエリの結果(この結果に処理を加えて)から GIF を生成する簡単なツールを C で作成しました。これも HTML ファイルを解析する Perl スクリプトから動的に実行されます。
ほとんどの場合、既存のスクリプトをコピーし、その SQL クエリを修正することで新規のレポートを簡単に実行することができます。状況によっては、既存のサマリテーブルにフィールドを追加したり、新規のテーブルを生成することが必要な場合もありますが、これもディスク上にすべてのトランザクションテーブルを保存しているため非常に容易なことです。(現在、少なくとも 50 G のトランザクションテーブルとその他の 200 G の顧客データを保持しています)。
顧客は、ODBC によってサマリテーブルに直接アクセスすることができ、上級ユーザであれば各自でデータを処理することができます。
非常に適度な規模の Sun Ultra SPARCstation(2×200 Mhz)を使用した処理では何も問題が発生していません。徐々にシステムはLinuxに移植されていきました。
このベンチマークスィートは、SQL
実装のパフォーマンスを向上または低下させる操作をユーザに示すことを目的としています。MySQL
ソースディストリビューションの
sql-bench
ディレクトリにあるコードと結果を確認することでベンチマークに関するヒントが得られます。
このベンチマークはシングルスレッドであるため、実行される操作の最短時間が測定されていることに注意してください。将来はこのベンチマークスィートにマルチスレッドのテストも多数追加する予定です。
ベンチマークスィートを使用するには、以下の要件を満たす必要があります。
ベンチマークスィートは、MySQL ソースディストリビューションによって提供されます。http://dev.mysql.com/downloads/のサイトからリリースされているディストリビューションをダウンロードするか、現在の開発ツリーを使用できる。(詳しくは項2.9.3. 「開発ソース ツリーからのインストール」をご確認ください。)
ベンチマークスクリプトは Perl
で作成され、データベースサーバへのアクセスには
Perl DBI モジュールを使用しているため、DBI
のインストールが必要である。テスト対象のサーバのそれぞれにサーバ専用の
DBD ドライバも必要である。
たとえば、MySQL、PostgreSQL、DB2
をテストするには、DBD::mysql
、DBD::Pg
、DBD::DB2
のモジュールをインストールする必要がある。
項2.15. 「Perl のインストールに関する注釈」を参照してください。
MySQLソースディストリビューション入手後、ベンチマークスィートは、MySQL
ソースディストリビューションの
sql-bench
ディレクトリにあります。
ベンチマークテストを実行するには、ロケーションをそのディレクトリsql-bench
に変更し、run-all-tests
スクリプトを実行します。
shell>cd sql-bench
shell>perl run-all-tests --server=
server_name
server_name
はサポートされるサーバの 1
つを表します。すべてのオプションとサポート対象サーバの一覧を取得するには、このコマンドを呼び出してください。
shell> perl run-all-tests --help
sql-bench
ディレクトリ内にcrash-meがあります。
crash-meでは、データベースがサポートする機能と、実際のクエリを実行した場合の機能と制約の判定が試行されます。
たとえば、以下についての判定が行われます。
サポートされるデータ型
サポートされるインデックス数
サポートされる関数
使用可能なクエリのサイズ
使用可能な
VARCHAR
カラムのサイズ
確実にアプリケーションとデータベースのベンチマークを行い、ボトルネックを検出しておく必要があります。これを修正(または、ボトルネックを 「dummy」に置換)することによって、次のボトルネック(など)の確認が容易になります。現在のアプリケーションの総合的なパフォーマンスが許容できるものであっても、実際にパフォーマンスの強化が迫られる場合に備えて、少なくともボトルネックそれぞれに対して計画を立て解決方法を判定しておく必要があります。
移植可能なベンチマークプログラムの例として、MySQL ベンチマークスィートを取り上げます。詳しくは項6.1.4. 「MySQL ベンチマークスィート」を参照してください。このスィートから任意のプログラムを選び、必要に合わせて修正することができます。これによって、それぞれの問題に対して複数の解決方法を試行して、実際に最も高速が得られるのはどれであるかについてテストすることができます。
これ以外の無料のベンチマークスィートに Open Source Database Benchmark があり、これは http://osdb.sourceforge.net/で入手できます。
一般的には、システムの負荷が非常に高い状況にのみ問題が発生します。負荷の問題が(テスト済の)本稼動のシステムで発生したと問い合わせてくる顧客が多数いました。ほとんどの場合、パフォーマンスに関わる問題は基本的な設計上の問題(高負荷時のテーブルスキャンの不良)かオペレーティングシステムやライブラリの問題が原因だと判明しています。たいていは、システムがまだ本稼動に入っていない場合のほうが問題の修正がはるかに容易です。
このような問題を回避するには、想定可能な最悪の負荷でアプリケーション全体のベンチマークにある程度力を注ぐ必要があります。
複数のクライアントが同時にクエリを発行したときに生じる高負荷状態をシミュレートするには、mysqlslapプログラムが効果的です。 項7.16. 「mysqlslap ? クライアント負荷エミュレーション」 を参照してください。
Super Smack も試してください。これは、http://jeremy.zawodny.com/mysql/super-smack/で入手できます。
その名(Smack = 打ちこわし)のとおり、システムに限界まで負荷をかけることができるため、必ず開発システムでのみ使用するようにしてください。
EXPLAIN
を使用して、クエリを最適化するSELECT
クエリの速度WHERE
節最適化IS NULL
最適化DISTINCT
最適化LEFT JOIN
とRIGHT JOIN
最適化ORDER BY
最適化GROUP BY
最適化LIMIT
の最適化INSERT
ステートメントの速度UPDATE
ステートメントの速度DELETE
ステートメントの速度
第 1 にすべてのクエリに影響を及ぼすことが 1
つあります。アクセス権システムのセットアップの複雑性が増すほど、オーバヘッドも増加します。
GRANT
ステートメントを発行する際に単純なアクセス権を使用することで、クライアントがステートメントを事項する際のMySQLにアクセス権確認オーバーヘッドを減らすことができます。例えば、テーブルレベルやカラムレベルの特権を許可したくない場合、サーバはtables_priv
とcolumns_priv
テーブルの内容を確認する必要はまったくありません。同じように、どのアカウントにもリソース制限を設けない場合、サーバは性能リソースカカウンティングを行う必要がありません。大量の処理が必要なときは、GRANT
を使用しないことで時間を節約できる場合もあります。
明示的な MySQL
関数に関わる問題がある場合は、常に
mysqlクライアントでBENCHMARK()
関数の計時を行うことができます。
その構文はBENCHMARK(
。返される値は常に0ですが、mysqlはステートメントの実行にどの程度の時間を要したかを表示するラインをプリントします。例
:
loop_count
,表現
)
mysql> SELECT BENCHMARK(1000000,1+1);
+------------------------+
| BENCHMARK(1000000,1+1) |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.32 sec)
これは、PentiumII 400MHz 上で MySQL によって 1,000,000 の + 式を 0.32 秒間に実行できることを示しています。
MySQL
関数はすべて最適化されていますが、例外も若干あります。BENCHMARK()
はクエリに関数上の問題があるかどうかを調べる際に最適のツールです。
EXPLAIN tbl_name
または
EXPLAIN [EXTENDED | PARTITIONS] SELECT select_options
EXPLAIN
ステートメントはDESCRIBE
のシノニムとして使用するか、MySQLがどのようにSELECT
ステートメントを実行するかの情報が得られます。
EXPLAIN
はtbl_name
DESCRIBE
またはtbl_name
SHOW COLUMNS FROM
のシノニムです。
tbl_name
キーワード EXPLAIN
を
SELECT
ステートメントの前に置いた場合、MySQL
によってテーブルの結合状況と順序に関する情報が提供され、テーブルの
SELECT
の処理方法が説明されます。
EXPLAIN PARTITIONS
はMySQL
5.1.5から提供されています。区割りされたテーブルのクエリを調べるときに便利です。詳細については、
項15.3.4. 「パーティション情報の取得」
をご参照ください。
このセクションでは、クエリ実行情報を得るためのEXPLAIN
の2つめの使用方法を記述します。DESCRIBE
とSHOW
COLUMNS
ステートメントの詳細については、項12.3.1. 「DESCRIBE
構文」と項12.5.4.4. 「SHOW COLUMNS
構文」を参照してください。
EXPLAIN
を利用すると、より速くレコードを検索する
SELECT
を得るために、どの時テーブルにインデックスを追加しなければならないかを確認できます。
また、EXPLAIN
を使用して、オプティマイザがテーブルを最適な順序で結合しているかどうかも確認することができます。オプティマイザが特定の順番で結合を行うように強制するにはただSELECT
でステートメントをはじめるのではく、SELECT
ステートメントに
SELECT STRAIGHT_JOIN
節を追加します。
最適化方法の選択に影響を及ぼすキーの、カーディナリティなどのテーブル統計を更新するために、ANALYZE
TABLE
を定期的に実行する必要があります。
項12.5.2.1. 「ANALYZE TABLE
構文」を参照してください。
EXPLAIN
はSELECT
ステートメントで使用される各テーブルに関する情報を返します。テーブルは、読み取られた順序に従って一覧表示されます。MySQL
は、単一スイープ多結合メソッドを使用してすべての結合を解決します。これは、MySQL
が最初のテーブルからレコードを読み取ってから、第
2 のテーブル、第 3
のテーブルといった順序で、一致するレコードの検索を行うことを意味します。すべてのテーブルの処理が終わると、選択したカラムと、さらに一致レコードがあるテーブルが検索されるまでのテーブル一覧のバックトラックが出力されます。次のレコードはこのテーブルから読み取られ、処理が次のテーブルから続行されます。
EXTENDED
キーワードが使用された時、EXPLAIN
はSHOW
WARNINGS
ステートメントをEXPLAIN
ステートメントの後で発行することで閲覧できる余分な情報を表示する。この情報は、SELECT
ステートメント内でオプティマイザがどのようにテーブル名とカラム名を認証するか、SELECT
が再書き込みと最適化ルールの適用後どのように表示されるか、そして最適化プロセスの他の注意点なども表示します。EXPLAIN
EXTENDED
はMySQL
5.1.12以降、filtered
カラムも表示します。
注:EXTENDED
とPARTITIONS
キーワードを、同じEXPLAIN
ステートメントで使用することはできません。
EXPLAIN
の各出力行は1つのテーブルの情報を提供し、各行は以下のカラムを含んでいます。
id
SELECT
識別子。クエリ内におけるこの
SELECT
の順序番号。
select_type
SELECT
節の種類、次のいずれかが示される。
SIMPLE | 単純なSELECT
(UNION やサブクエリを使用しない)。 |
PRIMARY | 最外部のSELECT 。 |
UNION | 内の第2およびそれ以降のSELECT ステートメント。 |
DEPENDENT UNION | UNION 内の第2およびそれ以降のSELECT ステートメント内のUNION 、外側のサブクエリに依存する。 |
UNION RESULT | UNION の結果。 |
SUBQUERY | サブクエリ内の第一SELECT 。 |
DEPENDENT SUBQUERY | 第1SELECT 、外側のサブクエリに依存する。 |
DERIVED | 派生テーブルSELECT
(FROM 節内のサブクエリ) |
UNCACHEABLE SUBQUERY | 結果がキャッシュされず、外側のクエリの各行ごとに再評価されるサブクエリ。 |
DEPENDENT
は主に、相互に関係するサブクエリの使用を表します。項12.2.8.7. 「相関サブクエリ」を参照してください。
「依存型サブクエリ」の評価はUNCACHEABLE
SUBQUERY
評価とは異なります。「DEPENDENT
SUBQUERY」に関しては、外側コンテキストの変数の値が異なるたびに、一回のみサブクエリの再評価が行われます。UNCACHEABLE
SUBQUERY
に関しては、サブクエリは外側コンテキストの各行ごとに再評価されます。サブクエリのキャッシュアビリティは項4.13.1. 「クエリ キャッシュの動作」で記述される制限によります。例えば、ユーザ変数に参照することでサブクエリがキャッシュできなくなります。
テーブル
結果を得るために参照するテーブル。
type
結合型。各結合型を最適なものから順に紹介する。
1 レコードのみで構成されるテーブル(=
システムテーブル)。これは、const
結合型の特殊なケースである。
テーブルに、一致するレコードが最大で 1
つあり、クエリの開始時に読み取られる。レコードが
1
つしかないため、このレコードのカラムの値はオプティマイザによって定数と見なされる。const
テーブルは、1
回しか読み取られないため、非常に高速である。
const
はPRIMARY
KEY
/UNIQUE
キーを定数と比較する場合に使用される。
.下記のクエリでは、tbl_name
はconst
テーブルとして使用できる。
SELECT * FROMtbl_name
WHEREprimary_key
=1; SELECT * FROMtbl_name
WHEREprimary_key_part1
=1 ANDprimary_key_part2
=2;
eq_ref
前のテーブルのレコードの組み合わせのそれぞれに対して、このテーブルから
1
レコードずつ読み取られる。これは、system
とconst
型以外で最適な結合型である。
結合でインデックスのすべての部分が使用され、このインデックスが
UNIQUE
または PRIMARY
KEY
である場合に使用される。
=
演算子と比較されるインデックスの張られたカラムには、eq_ref
を使用できる。
較対象のアイテムは定数でも、このテーブル以前に読み取られたテーブルのカラムを使用する式でもかまわない。
下記の例では、ref_table
で
eq_ref
が使用される。
SELECT * FROMref_table
,other_table
WHEREref_table
.key_column
=other_table
.column
; SELECT * FROMref_table
,other_table
WHEREref_table
.key_column_part1
=other_table
.column
ANDref_table
.key_column_part2
=1;
ref
前のテーブルのレコードの組み合わせのそれぞれに対して、インデックス値にマッチするすべてのレコードがこのテーブルから読み取られる。ref
は、インデックスの左端の先頭部分のみが結合で使用される場合、またはインデックスが
UNIQUE
や PRIMARY
KEY
ではない場合(すなわち、この結合において、インデックス値から1つのレコードをSELECTできない場合)に使用される。この結合型は、使用されるインデックスと一致するレコードが数レコードしかない場合に適している。
=
あるいは<=>
演算子と比較されるインデックスの張られたカラムには、ref
を使用できる。
下記の例では、MySQLはref_table
で
ref
が使用される。
SELECT * FROMref_table
WHEREkey_column
=expr
; SELECT * FROMref_table
,other_table
WHEREref_table
.key_column
=other_table
.column
; SELECT * FROMref_table
,other_table
WHEREref_table
.key_column_part1
=other_table
.column
ANDref_table
.key_column_part2
=1;
ref_or_null
ref
と同様だが、NULL
を使用したレコードの補足検索も追加で実行される。
この結合型の最適化は主としてサブクエリを解決する場合に使用される。
下記の例では、MySQLはref_table
で
ref_or_null
が使用される。
SELECT * FROMref_table
WHEREkey_column
=expr
ORkey_column
IS NULL;
index_merge
この結合型はインデックス併合最適化が使用されたことを示しています。この場合、出力行のkey
カラムは使用されたインデックスのリストが含まれ、key_len
には使用されたインデックスの最長キー部分が含まれます。詳細は
項6.2.6. 「インデックス結合最適化」
をご覧ください。
unique_subquery
この型は、下記のフォームでIN
サブクエリの代わりに、ref
を使用します。
value
IN (SELECTprimary_key
FROMsingle_table
WHEREsome_expr
)
unique_subquery
は、効率化のためサブクエリの代わりをつとめるインデックスルックアップ関数です。
index_subquery
この結合型はunique_subquery
に似ています。IN
サブクエリの代わりに使用されますが、下記のサブクエリのフォームでユニークではないインデックスで使用できます。
value
IN (SELECTkey_column
FROMsingle_table
WHEREsome_expr
)
range
インデックスを使用して、一定の範囲にあるレコードのみが取り出される。key
カラムに使用されるインデックスが示される。key_len
_には使用される最長のインデックス部分が記載される。
この型ではref
カラムがNULL
になる。
range
は、インデックスを張っているカラムが
=
、<>
、>
、>=
、<
、<=
、IS
NULL
、<=>
、BETWEEN
、およびIN
を使用して定数と比較される場合に使用される。
SELECT * FROMtbl_name
WHEREkey_column
= 10; SELECT * FROMtbl_name
WHEREkey_column
BETWEEN 10 and 20; SELECT * FROMtbl_name
WHEREkey_column
IN (10,20,30); SELECT * FROMtbl_name
WHEREkey_part1
= 10 ANDkey_part2
IN (10,20,30);
index
これは、インデックスツリーのみがスキャンされる点を除いて
ALL
と同じである。一般にインデックスファイルはデータファイルより小さいため、通常は
ALL
より高速である。
MySQL は、クエリで 1 インデックスの構成部分であるカラムのみが使用される場合にのみ使用できます。
ALL
前のテーブルのレコードの組み合わせのそれぞれに対して、フルテーブルスキャンが実行される。一般に、テーブルが
const
の指定がない第 1
テーブルの場合には適さず、その他の場合はすべて非常に不適である。
通常は、さらにインデックスを追加することで
ALL
を回避し、定数値または以前のテーブルのカラム値を基準にレコードを取り出すようにすることができる。
possible_keys
possible_keys
カラムは、このテーブル内のレコードの検索に
MySQL
で使用可能なインデックスを示す。このカラムはEXPLAIN
からの出力により表示されたテーブルの順序にはまったく依存しないことに注意する。すなわち、possible_keys
のキーの一部は、生成されたテーブルの順序では事実上使用できないことになる。
このカラムが
NULL
の場合は、対応するインデックスがない。tこの場合は、WHERE
節でインデックス作成に適するカラムを
1
つ以上参照しているかどうかを調べることでクエリのパフォーマンスを改善できる。
参照している場合は適切なインデックスを作成し、再度
EXPLAIN
を使用してクエリをチェックする。
項12.1.2. 「ALTER TABLE
構文」を参照してください。
テーブルにあるインデックスを調べるには
SHOW INDEX FROM
を使用する。
tbl_name
key
key
カラムは、MySQL
が実際に使用を決定したキー(インデックス)を示す。
MySQL
が行をルックアップするためpossible_keys
インデックスを使用した場合、キー値としてそのインデックスがリストされる。
key
はpossible_keys
値に存在しないインデックスを指名する可能性もあります。これはpossible_keys
インデックスのうちどれも行をルックアップするのに適していない場合におこりますが、クエリに選択された全てのカラムは他のインデックスのカラムになります。つまり、指名されたインデックスが選択されたカラムをカバーします。どの行を取得するか判別するのに使用されていなくとも、データ行スキャンよりもインデックススキャンの方が効率的です。
InnoDB
では、クエリがプライマリキーを選択していてもセカンダリインデックスが選択されたカラムをカバーするかもしれません。これはクエリがプライマリキーを選択した場合もありえるのは、InnoDB
が各セカンダリインデックスと共にプライマリキー値も保存するからです。MySQLがクエリを効率的に実行するインデックスを見つけられなかった場合、このkey
は
NULL
になる。
MySQLで
possible_keys
カラムに記載されたキーが使用されるように強制するには、クエリでFORCE
INDEX
、USE
INDEX
、またはIGNORE
INDEX
を使用する。
項12.2.7. 「SELECT
構文」を参照してください。
MyISAM
テーブルには、ANALYZE
TABLE
を実行することでオプティマイザでより適したインデックスを選択する際役立つ。MyISAM
テーブルに関しても、myisamchk
--analyzeは同じことをします。項12.5.2.1. 「ANALYZE TABLE
構文」、項4.9.4. 「テーブル保守とクラッシュ リカバリ」
を参照して下さい。
key_len
key_len
カラムは、MySQL
が実際に使用を決定したキーの長さを示す。
key
が
NULL
の場合、この長さは
NULL
になる。
key_len
の値によって、複合キーで
MySQL
が実際に使用するパート数が示されることに注意する。
ref
ref
カラムは、テーブルからレコードを選択する際に
key
とともに使用されるカラムまたは定数を示す。
rows
rows
カラムは、クエリの実行に際して調べる必要があると
MySQL
によって判定されたレコードの数を示す。
filtered
filtered
カラムはテーブルの状態によってフィルターされるテーブル行のパーセンテージ(予想)を表示します。つまり、rows
は検査された行の予想数を表示し、rows
× filtered
/
100
は前のテーブルと結合する行の数を表示します。EXPLAIN
EXTENDED
を使用すると、このカラムが表示されます。(MySQL
5.1.12の新しい機能です。)
Extra:
このカラムには、MySQL
でどのようにクエリが解決されるかに関する追加情報が記載される。下記のリストはこのカラムで表示される可能性のある値を説明する。クエリの速度をできる限り上げたい場合は、Using
filesort
とUsing
temporary
のExtra
値に注目してください。
Distinct
マッチした最初のレコードが検索されると、MySQL は現在のレコードの組み合わせによるその後のレコード検索を続行しないことを示す。
const tablesを読んだ後 Impossible
WHERE発見
MySQL は全てのconst
(あと、system
)
テーブルを読んだ後、WHERE
節が常に偽となります。
No tables
クエリにはFROM
節がないか、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.id
が NOT
NULL
で定義されているとする。この場合、MySQL
で
t1
がスキャンされ、t1.id
で
t2
内のレコードのルックアップが行われる。MySQL
によって
t2
内のマッチするレコードが検索されると、t2.id
はNULL
ではないと認識され、t2
内の同じ
id
を持つ残りのレコードのスキャンは行われない。言い換えると、t2
にあるマッチするレコードの数に関わらず、MySQL
で実行が必要なことは
t1
のレコードのそれぞれに対して、t2
のルックアップを
1 回実行することだけである。
range checked for each record (index map:
N
)
MySQL
で使用に適した実際のインデックスを検索できなかったことを示す。代替として、先行テーブルのレコードの組み合わせのそれぞれに対して、使用するインデックス(存在する場合)range
またはindex_merge
のチェックが実行され、このインデックスがテーブルからのレコードの取り出しに使用される。非常に高速ではないが、インデックスなしの結合と比較すると高速である。
適用基準は項6.2.5. 「Range 最適化」と項6.2.6. 「インデックス結合最適化」で説明されています。ただし、これは前テーブルの全てのカラム値が知られており、定数であるという前提においてです。
Select tables optimized away
クエリはMyISAM
用に、インデックスで解決された集約ファンクション(MIN()
、MAX()
)そしてCOUNT(*)
があり、GROUP
BY
節は含みませんでした。オプティマイザは1つの行のみが返されるべきと判断しました。
Using filesort
レコードをソートして取り出す方法を決定するには、MySQL
はパスを余分に実行しなくてはならないことを示す。
join type
に従ってすべてのレコードをスキャンし、WHERE
条件に一致する全てのレコードに、ソートキー
+
行ポインタを格納して、ソートは実行される。その後キーがソートされる。
最後に、ソートされた順にレコードが取り出される。項6.2.12. 「ORDER BY
最適化」を参照してください。
Using index
インデックスツリーの情報のみを使用してカラム情報がテーブルから取り出され、実際の行を読み取るその後の検索を実行する必要がないことを示す。MySQL は、クエリで 1 インデックスの構成部分であるカラムのみが使用される場合にのみ使用できます。
Using temporary
クエリの解決に MySQL
で結果を保持するテンポラリテーブルの作成が必要であることを示す。これは一般に、GROUP
BY
を実行したカラムセットと異なるカラムセットに対して
ORDER
BY
を実行した場合に発生する。
Using where
次のテーブルとの一致が調べられるレコードまたはクライアントに送信されるレコードの限定に
WHERE
節が使用されることを示す。
この情報がなく、Extra
の値がUsing
where
ではなく、テーブルの型が
ALL
または
index
である場合はクエリが正常に実行されないことがある(テーブルのすべてのレコードの取得や検査を意図していない場合)。
Using
sort_union(...)
、Using
union(...)
、Using
intersect(...)
これらはindex_merge
結合型でインデックススキャンがどのように併合されるかを示しています。詳細は
項6.2.6. 「インデックス結合最適化」 を参照。
Using index for group-by
Using
index
を使用してテーブルをアクセスする方法に似て、Using
index for
group-by
はMySQLが余分なディスクアクセスを実際のテーブルに行うことなく、GROUP
BY
またはDISTINCT
クエリのカラムを全て取得することができるインデックスを見つけたことを意味します。加えて、インデックスは各グループにとって最も効率的に使われるので、数種類のインデックスしか読まれません。詳細については、項6.2.13. 「GROUP BY
最適化」をご参照ください。
Using where with pushed condition
このアイテムはNDB
Cluster
テーブルにのみ適用されます。それはMySQL
クラスタがcondition
pushdownを使用して行う、インデックスのないカラムと定数を直接比較(=
)の効率化を図ることを意味します。その場合、状態はクラスターのデータノードに「押し戻され」ており、全てのパーティションで同時に評価されます。これはマッチしない行をネットワーク上で送る必要を無くし、コンディションプッシュダウンが使える状態にあり、使用しないケースでそのようなクエリの速度を5乗から10乗に増やす。
以下のように定義されたクラスタテーブルがあるとします。
CREATE TABLE t1 ( a INT, b INT, KEY(a) ) ENGINE=NDBCLUSTER;
この場合、コンディションプッシュダウンは下記のようなクエリで使用できます。
SELECT a,b FROM t1 WHERE b = 10;
これはEXPLAIN
SELECT
の出力で見られます。例えば
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
カラムに存在するため、コンディションプッシュダウンは適用できません。2番目のクエリの場合、インデックスのないカラムb
に関する比較は直接的でないため、コンディションプッシュダウンが適用できません。(ただし、b
+ 1 = 10
をWHERE
節内でb =
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
コンディションプッシュダウンに関して、以下のことに留意してください。
コンディションプッシュダウンはMySQLクラスタにのみ関連しており、他の保存エンジンを使用するテーブルに対してクエリを実行するときは起こりえません。
コンディションプッシュダウン機能はデフォルトでは使用されません。起動するには、mysqld
を--engine-condition-pushdown
オプションで使用するか、以下のステートメントを実行してください。
SET engine_condition_pushdown=On;
注:コンディションプッシュダウンはBLOB
やTEXT
タイプのどのカラムに対してもサポートされていません。
EXPLAIN
出力の
rows
カラムのすべての値を掛け算することで、結合がどの程度適しているかを示す指針を取得できます。Thisこれは、クエリの実行時に
MySQL
で調べる必要があるレコード数の概要を示します。この数値は、max_join_size
変数でクエリを制限する際にも使用される他、どのマルチテーブルSELECT
ステートメントを実行するか、あるいはアボートするかを判別します。項6.5.2. 「サーバパラメータのチューニング」を参照してください。
下記の例は、EXPLAIN
によって得られた情報を使用して、マルチテーブルjoinを累進的に最適化する方法を示しています。
ここでは、EXPLAIN
を使用して、SELECT
ステートメントを調べるとします。
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;
この例では以下のように想定しています。
比較対象のカラムは以下のように宣言されます。
テーブル | カラム | データ型 |
tt | ActualPC | CHAR(10) |
tt | AssignedPC | CHAR(10) |
tt | ClientID | CHAR(10) |
et | EMPLOYID | CHAR(15) |
do | CUSTNMBR | CHAR(15) |
テーブルには以下のインデックスがあります。
テーブル | インデックス |
tt | ActualPC |
tt | AssignedPC |
tt | ClientID |
et | EMPLOYID (プライマリキー) |
do | CUSTNMBR (プライマリキー) |
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 (key map: 35)
各テーブルで type
が
ALL
であるため、この出力は MySQL
がすべてのテーブルのデカルト積を生成すると示しています。各テーブルのレコードの数の積の分量を調べる必要があるため、これは非常に時間がかかります。この例の場合は、レコードの数が
74 ×2135 ×74 ×3872 = 45,268,558,720
になります。テーブルがこれより大きい場合は、さらに時間がかかると考えられます。
ここでの問題の 1 つは、宣言の方法が異なると
MySQL
でカラムのインデックスを効率的に使用できないことにあります。この例では、VARCHAR
と
CHAR
が異なる長さで宣言されていなければ同じになります。tt.ActualPC
が
CHAR(10)
として、et.EMPLOYID
が
CHAR(15)
として宣言されているため、長さの不一致が発生します。
カラムの長さの不一致を修正するため、ALTER
TABLE
を使用して ActualPC
を 10
文字から 15 文字にします。
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
これで tt.ActualPC
と
et.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 (key map: 1) et_1 ALL PRIMARY NULL NULL NULL 74 range checked for each record (key map: 1) et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
これも完全ではありませんが、かなり改善されています(rows
値の積が
74
の係数分だけ減少)。このバージョンの場合実行に数秒かかります。
第 2 の変更を加えると、tt.AssignedPC =
et_1.EMPLOYID
と tt.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
節でテーブルの順序を変えて一覧表示してみるとパフォーマンスを改善できます。
MySQL Enterprise MySQL ネットワーク監視とアドバイスサービス加入者は定期的にプロから最適化のアドバイスを提供されます。追加情報については http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
ほとんどの場合、ディスクシークをカウントしてパフォーマンスを推定できます。
小さいテーブルの場合は一般に 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 が使用されていると想定)、メモリにインデックスの多くがあり、OS からデータを読み取り、レコードを検索するには、1 回か 2 回の呼び出しで済むと推定されます。
ただし、書き込みについては、上記の例で新規インデックスの配置場所を探し出すのに 4 シークの要求が、また、インデックスの更新とレコードの書き込みに通常 2 シークが必要になります。
Note that このことは、アプリケーションが対数
N
の分だけ低速になるという意味ではないことに注意してください。OS
または SQL
サーバですべてがキャッシュされている限り、テーブルが拡大しても速度の低下はわずかです。データがキャッシュできないほど増加すると、ディスクシーク(対数
N
N
の分だけ増加する)によって最終的にアプリケーションがバインドされるまで大幅に速度の低下が始まります。)これを回避するには、データの増加に合わせてインデックスキャッシュも拡大します。
MyISAM
テーブルに関しては、キーキャッシュサイズはkey_buffer_size
システム変数に制御されます。項6.5.2. 「サーバパラメータのチューニング」を参照してください。
一般に、低速の SELECT ...
WHERE
の速度を上げる必要がある場合は、まず、インデックスを追加できるかどうかをチェックします。一般に複数のテーブル間の参照はすべてインデックスを使用して実行する必要があります。EXPLAIN
コマンドを使用して、SELECT
に使用されるインデックスを判定できます。
項6.2.1. 「EXPLAIN
を使用して、クエリを最適化する」
と 項6.4.5. 「MySQLにおけるインデックスの使用」を参照してください。
MyISAM
テーブルのクエリ速度を上げる一般的なヒント。
To MySQL
によるクエリの最適化を容易にするには、関連データをロードした後にテーブルに対して
ANALYZE
TABLE
あるいはmyisamchk
--analyzeを実行する。これはインデックスのために、同じ値があるレコードの平均値を更新する(ユニークインデックスの場合、これは常に
1 になる)。MySQL はこれを使用して、2
つのテーブルを '非定数式'
で接続する際に選択するインデックスを判定する。
SHOW INDEX FROM
を実行しtbl_name
Cardinality
値を調べるとmyisamchk
--description
--verboseはインデックスの分布情報を表示する。
インデックスに従ってインデックスとデータをソートするには myisamchk --sort-index --sort-records=1(インデックス 1 でソートする場合)を使用する。速度を上げるには、すべてのレコードの読み取りにユニークインデックスを使用し、そのインデックスに従った順序で読み取りを行うように推奨される。ただし、このソートでは書き込みの最適化はできず、テーブルが大きい場合は時間がかかる。
このセクションではWHERE
節をプロセスする際の最適化方法を記述します。WHERE
の最適化は、ほとんどの場合
SELECT
とともに使用されるため、SELECT
部分に適用されますがDELETE
や
UPDATE
のステートメントの
WHERE
にも同じ最適化が適用されます。
また、このセクションは完全なものではないため、注意が必要です。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 回に限られる。
COUNT(*)
WHERE
がない単一テーブルの
COUNT(*) は、MyISAM
と HEAP
テーブルのテーブル情報から直接取り出される。
これは、テーブル 1
つのみで使用する場合はすべての NOT
NULL
式でも実行される。
無効定数式の早期検出。MySQL は実行不可能な
SELECT
ステートメントがある場合、それを迅速に検出し、結果としてレコードを返さない。
GROUP
BY
またはグループ関数(COUNT()
、MIN()
)を使用しない場合、HAVING
は
WHERE
とマージされる。
サブ結合のそれぞれに、単純な
WHERE
が構造化され、サブ結合ごとに迅速に
WHERE
評価を取得し、可能な限り迅速にレコードをスキップする。
クエリ内の他のすべてのテーブルの前に、まず、すべての定数テーブルが読み込まれる。定数テーブルとは以下のものを指す。
空白テーブルまたは 1 行のみのテーブル。
UNIQUE
インデックスまたは
PRIMARY KEY
を使う
WHERE
節とともに使用されるテーブルで、インデックス部分のすべてが定数式とともに使用され、そのインデックス部分が
NOT
NULL
として定義されている場合。
以下のテーブルはすべて定数テーブルとして使用される。
SELECT * FROM t WHEREprimary_key
=1; SELECT * FROM t1,t2 WHERE t1.primary_key
=1 AND t2.primary_key
=t1.id;
テーブルを結合する最適な結合の組み合わせは、すべての可能性を試行してみることで発見される。ORDER
BY
および GROUP
BY
内の全てのカラムが 1
つのテーブルに存在する場合、結合を行う時は第一にこのテーブルが選ばれる。
ORDER BY
節とそれと異なる
GROUP
BY
節がある場合、あるいは、ORDER
BY
または GROUP
BY
に結合キューの第 1
テーブルとは異なるテーブルのカラムが含まれている場合は、テンポラリテーブルが作成される。
SQL_SMALL_RESULT
を使用する場合、MySQL
ではメモリ内のテンポラリテーブルが使用される。
オプティマイザがテーブルスキャンをしたほうが効率的と判断しない限り、テーブルインデックスごとにクエリが行われ、スパンがレコードの 30% 以上である最適インデックスが使用される。しかし、現在では一定の率でインデックスかスキャンを使用するか判断されます。今バージョンのオプティマイザはより複雑で、テーブルサイズ、行数、そしてI/Oブロックサイズを基準に推定します。
状況によっては、MySQL でデータファイルの参照もせずにインデックスからレコードを読み取れる場合もある。インデックスから使用されるカラムのすべてが数値型の場合、クエリの解決にはインデックスツリーのみが使用される。
レコードのそれぞれが出力される前に、HAVING
節と一致しないレコードはスキップされる。
非常に高速なクエリのサンプルをいくつか紹介します。
SELECT COUNT(*) FROMtbl_name
; SELECT MIN(key_part1
),MAX(key_part1
) FROMtbl_name
; SELECT MAX(key_part2
) FROMtbl_name
WHEREkey_part1
=constant
; SELECT ... FROMtbl_name
ORDER BYkey_part1
,key_part2
,... LIMIT 10; SELECT ... FROMtbl_name
ORDER BYkey_part1
DESC,key_part2
DESC, ... LIMIT 10;
MySQLは次のクエリで、インデックスツリーのみを使用して解決します(インデックスのあるカラムが数値型であると想定)。
SELECTkey_part1
,key_part2
FROMtbl_name
WHEREkey_part1
=val
; SELECT COUNT(*) FROMtbl_name
WHEREkey_part1
=val1
ANDkey_part2
=val2
; SELECTkey_part2
FROMtbl_name
GROUP BYkey_part1
;
次のクエリは、ソートのパスを分けることなく、ソートしたレコードを取り出すためにインデックスを使用します。
SELECT ... FROMtbl_name
ORDER BYkey_part1
,key_part2
,... ; SELECT ... FROMtbl_name
ORDER BYkey_part1
DESC,key_part2
DESC, ... ;
range
アクセスメソッドはシングルインデックスを使用して、1つもしくは複数のインデックス値インターバルに含まれるテーブル行のサブセットを取得します。シングルパートかマルチパートインデックスに使用できます。以下のセクションではWHERE
節からどのようにしてインターバルが抽出されるかの詳細説明を記述します。
シングルパートインデックスでは、インデックス値インターバルはWHERE
節内の対応する状態で表現されます。よって「インターバル」よりもrange
状態が使用されます。
シングルパートインデックスのためのrange状態の定義は以下のとおりです。
BTREE
とHASH
の両インデックスにとって、=
,
<=>
、IN
、IS
NULL
、またはIS NOT
NULL
演算子を使用した際の定数値の主部分の比較はrange状態です。
BTREE
インデックスにとって、>
、<
,
>=
、<=
,
BETWEEN
、!=
、<>
演算子、またはLIKE
'
(pattern
''
がワイルドカードで始まらないとき)を使用しての定数値の主部分の比較は、range状態です。
pattern
'
全インデックスの種類にとって、OR
またはAND
で結合されたマルチレンジ状態は、あるレンジ状態を形成します。
先の説明の 「定数値」は以下の1つを意味しています。
クエリ文字列の定数
同じ結合のconst
またはsystem
テーブルのカラム
相関しないサブクエリの結果
以前の型のサブ表現からのみ生成された表現
ここにWHERE
節内でレンジ状態のクエリの例を以下に示します。
SELECT * FROM t1 WHEREkey_col
> 1 ANDkey_col
< 10; SELECT * FROM t1 WHEREkey_col
= 1 ORkey_col
IN (15,18,20); SELECT * FROM t1 WHEREkey_col
LIKE 'ab%' ORkey_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
の抽出プロセスは以下のとおりです。
オリジナルのWHERE
節で始めてください。
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR (key1 < 'bar' AND nonkey = 4) OR (key1 < 'uux' AND key1 > 'z')
nonkey = 4
とkey1 LIKE
'%b'
はレンジスキャンに使用できませんので、取り除いてください。TRUE
と置き換えることが、正しく取り除く方法です。こうすることによって、レンジスキャン中に適合する行を見落としません。TRUE
で置き換えたとき、以下になります。
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR (key1 < 'bar' AND TRUE) OR (key1 < 'uux' AND key1 > 'z')
常に真か偽のコラップス状態
(key1 LIKE 'abcde%' OR
TRUE)
は常に真です
(key1 < 'uux' AND key1 >
'z')
は常に偽です
定数でこれらの状態を置き換えると、以下になります。
(key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)
不必要なTRUE
とFALSE
定数を取り除くことで、以下になります。
(key1 < 'abc') OR (key1 < 'bar')
重複するインターバルを1つに結合することでレンジスキャンで使用できる最終状態が生成されます。
(key1 < 'bar')
一般的にレンジスキャンで使用される状態は(そして前の例で説明されたとおり)、WHERE
節よりも制限されません。MySQLはレンジ条件を満たすが、WHERE
節を完全に満たさない行にフィルタをかけるために追加でチェックを行います。
レンジ条件抽出アルゴリズムは任意のデプスの入れ子
AND
/OR
生成子を取り扱うことができ、その出力はWHERE
節内でどの順序で条件が現れるかに影響されません。
複合パートインデックスのレンジ条件はシングルパートインデックスのレンジ条件の拡張です。複合パートインデックスのレンジ条件はインデックス行が1つもしくは複数のキータプルインターバルに含まれるよう制限します。キータプルインターバルはキータプルセット上で、インデックスからの順序づけを使用して定義されます。
例えば、複合パートインデックスがkey1(
として定義され、以下のキータプルのセットがキー順序でリストされたとします。
key_part1
、key_part2
、key_part3
)
key_part1
key_part2
key_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番目タプルをカバーし、レンジアクセスメソッドで使用できます。
それに引き換え、
条件はシングルインターバルを定義せず、レンジアクセスメソッドで使用できません。
key_part3
=
'abc'
以下はレンジ条件が複合パートインデックスでどのように働くかの詳細を説明します。
HASH
インデックスでは、同値を含む各インターバルが使用できます。これは以下のフォームをとる条件のためのみインターバルが生成できることを意味してます。
key_part1
cmp
const1
ANDkey_part2
cmp
const2
AND ... ANDkey_partN
cmp
constN
;
ここでは、const1
、const2
、…
は定数で、cmp
は=
、<=>
、またはIS
NULL
比較演算子の1つであり、条件は全てのインデックスパートをカバーします。(つまり、N
条件があり、各N
-パートインデックスごとに1つあります。.)例えば、以下は3パートHASH
インデックスのレンジ条件です。
key_part1
= 1 ANDkey_part2
IS NULL ANDkey_part3
= 'foo'
定数の定義は、項6.2.5.1. 「シングルパートインデックスのためのRangeアクセスメソッド」を参照してください。
BTREE
インデックスでは、インターバルはAND
と結合された条件に使用できることがあります。これは、各条件がキーパートと定数値を、=
,
<=>
、IS
NULL
、>
、<
、>=
、<=
,
!=
、<>
、BETWEEN
、またはLIKE
'
を使用して比較した場合です
(pattern
''
がワイルドカードで始まらない場合)。条件にマッチする行を全て含むシングルキータプルを判別できるかぎり、インターバルが使用できます。(あるいは、pattern
'<>
or
!=
が使用されたいれば2インターバル)。例えば、以下の条件では:
key_part1
= 'foo' ANDkey_part2
>= 10 ANDkey_part3
> 10
シングルインターバルは:
('foo',10,10) < (key_part1
,key_part2
,key_part3
) < ('foo',+inf,+inf)
作成されたインターバルが初期条件よりも行が多い可能性があります。例えば、初期条件を満たさない前インターバルが('foo',
11, 0)
の値を含んでいます。
インターバルに含まれる行のセットをカバーする条件がOR
で結合された場合、インターバルの結合に含まれる行のセットをカバーする条件を生成します。AND
で条件が結合された場合、インターバルの交差点に含まれる行のセットをカバーする条件を生成します。例えば、この2パートインデックスの条件について:
(key_part1
= 1 ANDkey_part2
< 2) OR (key_part1
> 5)
インターバルは:
(1,-inf) < (key_part1
,key_part2
) < (1,2) (5,-inf) < (key_part1
,key_part2
)
この例では、最初のラインのインターバルはレフトバウンドに1キーパートを使用し、ライトバウンドには2キーパートを使用します。2番目のラインのインターバルは1キーパートのみ使用します。EXPLAIN
出力のkey_len
カラムは、使用されたキープリフィックスの最大長を示しています。
場合によって、key_len
はキーパート使用されたことを示すことがありますが、ユーザが期待していたとおりではないかも知れません。key_part1
とkey_part2
がNULL
だったとします。key_len
カラム次の条件の2キーパート長を表示します。
key_part1
>= 1 ANDkey_part2
< 2
しかし、実際条件以下に変換されます。
key_part1
>= 1 ANDkey_part2
IS NOT NULL
項6.2.5.1. 「シングルパートインデックスのためのRangeアクセスメソッド」はシングルパートインデックスでレンジ条件のインターバルを結合か削除の際の最適化がどのように実行されるかを記述しています。マルチパートインデックスのレンジ条件にはアナログステップが実行されます。
Index
Mergeメソッドは、複数のrange
スキャンを有する行を取得と、それぞれの結果を1つに結合するのにするのに使用されます。この結合によって、基礎スキャンの結合、共通集合、あるいは交差点の結合が生成されます。
EXPLAIN
出力では、インデックスメソッドはtype
カラムでindex_merge
として現れます。この場合、key
カラムは使用されたインデックスのリストが含まれ、key_len
はインデックスの最長キー部分が含まれます。
例:
SELECT * FROMtbl_name
WHEREkey1
= 10 ORkey2
= 20; SELECT * FROMtbl_name
WHERE (key1
= 10 ORkey2
= 20) ANDnon_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
);
インデックス結合メソッドは複数のアクセスアルゴリズムがあります。(Extra
フィールドのEXPLAIN
出力で見られます。)
Using intersect(...)
Using union(...)
Using sort_union(...)
以下のセクションはこれらのメソッドの詳細を記述しています。
注:インデックス結合最適化アルゴリズムには以下の欠点があります。
あるキーでレンジスキャンが可能な場合、インデックス結合は考慮されません。例えば、以下のクエリでは:
SELECT * FROM t1 WHERE (goodkey1 < 10 OR goodkey2 < 20) AND badkey < 30;
このクエリでは、2つのプランが考えられます。
(goodkey1 < 10 OR goodkey2 <
20)
条件を使用したインデックス結合スキャン。
badkey <
30
条件を使用したレンジスキャン。
ただし、オプティマイザは2つ目のプランしか考慮しません。
ユーザのクエリに複雑なAND
/OR
入れ子を持つWHERE
節があり、MySQLが最適なプランを選択しない場合、以下のID法を使用して定義を分布してみてください。
(x
ANDy
) ORz
= (x
ORz
) AND (y
ORz
) (x
ORy
) ANDz
= (x
ANDz
) OR (y
ANDz
)
インデックス結合は、フルテキストインデックスには適用されません。将来的にリリースされるMySQLのバージョンでカバーできるよう、拡張する予定です。
種々のインデックス結合メソッドや他のアクセスメソッドの選択に関しては、選択肢のコスト予想によります。
このアクセスアルゴリズムが使用できるのは、WHERE
節が異なるキーの複数のレンジ条件に変換、AND
で結合され、各コンディションは以下の1つ:
このフォームでは、インデックスパーツはN
個あります(つまり、全てのインデックスパーツがカバーされています。)
key_part1
=const1
ANDkey_part2
=const2
... ANDkey_partN
=constN
InnoDB
テーブルのプライマリキーをカバーするレンジ条件。
例:
SELECT * FROMinnodb_table
WHEREprimary_key
< 10 ANDkey_col1
=20; SELECT * FROMtbl_name
WHERE (key1_part1
=1 ANDkey1_part2
=2) ANDkey2
=2;
インデックス共通集合アルゴリズムは全ての使用されたインデックスの同時スキャンを実行し、結合インデックススキャンから受信した行シーケンスの共通集合を生成します。
クエリで使用される全てのカラムが使用済みインデックスでカバーされている場合、完全なテーブル行は取得されません。(EXPLAIN
出力はExtra
フィールド内のUsing
index
を含んでいます)。そのようなクエリの例です:
SELECT COUNT(*) FROM t1 WHERE key1=1 AND key2=1;
クエリ内で使用されたカラムを使用されたインデックスがカバーしない場合、完全な行が取得されるのは全てのキーのレンジ条件が満たされたときのみです。
結合条件のうち1つがInnoDB
テーブルのプライマリキー上の条件である場合、行取得には使用されず、他の条件を使用して取得した行にフィルターをかけるのに使用されます。
このアルゴリズムの適用基準はインデックス結合メソッド共通集合アルゴリズムと似ています。このアクセスアルゴリズムが使用できるのは、WHERE
節が異なるキーの複数のレンジ条件に変換、OR
で結合され、各コンディションは以下の1つ:
このフォームでは、インデックスパーツはN
個あります(つまり、全てのインデックスパーツがカバーされています。)
key_part1
=const1
ANDkey_part2
=const2
... ANDkey_partN
=constN
InnoDB
テーブルのプライマリキーをカバーするレンジ条件。
インデックス結合共通集合アルゴリズムが適用できる条件
例:
SELECT * FROM t1 WHEREkey1
=1 ORkey2
=2 ORkey3
=3; SELECT * FROMinnodb_table
WHERE (key1
=1 ANDkey2
=2) OR (key3
='foo' ANDkey4
='bar') ANDkey5
=5;
このアクセスアルゴリズムは、OR
によりWHERE
節が複数のレンジ条件に変換されるが、インデックス結合メソッドユニオンアルゴリズムが適用できない場合に使用します。
例:
SELECT * FROMtbl_name
WHEREkey_col1
< 10 ORkey_col2
< 20; SELECT * FROMtbl_name
WHERE (key_col1
> 10 ORkey_col2
= 20) ANDnonkey_col
=30;
ソートユニオンアルゴリズムとユニオンアルゴリズムの違いは、ソートユニオンアルゴリズムはまず、行を返すまえに全ての行のIDを取得しソートしなければいけないところにあります。
MySQL では、col_name
=
constant_value
.の場合と同じ最適化を
col_name
IS
NULL
に対しても実行できます。
たとえば、MySQL
では、インデックスと範囲を使用して、IS
NULL
で
NULL
を検索できます。
例:
SELECT * FROMtbl_name
WHEREkey_col
IS NULL; SELECT * FROMtbl_name
WHEREkey_col
<=> NULL; SELECT * FROMtbl_name
WHEREkey_col
=const1
ORkey_col
=const2
ORkey_col
IS NULL;
WHERE
節内でcol_name
IS
NULL
で定義されたものをNOT
NULL
と使用する場合、その式は消去して最適化されます。この最適化は、結果的にカラムがNULL
を生成する場合には生じません。たとえば、LEFT
JOIN
の右側のテーブルからきている場合。
MySQL は
の組み合わせを最適化する機能が追加されています。これは解決されたサブクエリではよくあるフォームです。この最適化が使用される場合は、col_name
=
expr
AND
col_name
IS
NULLEXPLAIN
は ref_or_null
を表示します。
この最適化は、すべてのキー部分で IS
NULL
を 1 つ処理できます。
最適されたクエリのサンプルをいくつか紹介します(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);
DISTINCT
が ORDER
BY
と組み合わされて用いられると、多くの場合はテンポラリテーブルが必要になります。
DISTINCT
は GROUP
BY
をともなう可能性が高いので、選択されないカラムをORDER
BY
または
HAVING
した時に、どのように MySQL
が機能するかを認識しておく必要があります。
項11.11.3. 「非常時フィールドとの GROUP BY
および
HAVING
」を参照してください。
ほとんどの場合、
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
クエリ最適化の可能性については、項6.2.13. 「GROUP BY
最適化」を参照してください。
LIMIT
を
row_count
DISTINCT
とともに使用した場合、MySQL
は一意のレコードを
row_count
行検索するとただちに検索を停止します。
使用するテーブル内のカラムを使用しない場合、MySQL
は最初にマッチするレコードを検索するとただちに未使用テーブルのスキャンを停止します。
ここでは、t1
が
t2
の前に使用され(EXPLAIN
によるチェック)、t2
で最初のレコードが検索されると
t2
からの読み取り(t1
の特定のレコード)を停止します。
SELECT DISTINCT t1.a FROM t1, t2 where t1.a=t2.a;
MySQL の
は以下のように実装されます。
A
B
join_condition
テーブルB
はテーブル
A
と
A
が依存するすべてのテーブルに依存するように設定される。
テーブル A
は、LEFT
JOIN
条件で使用されるすべてのテーブル(B
を除く)に依存するように設定される。
LEFT JOIN
条件は、テーブル
B
からのレコードの取り出し方法の判定に使用される。(言い換えると、WHERE
節の条件はいずれも使用されない)。
あるテーブルが全てのテーブルの後に読み取られる場合を除き、通常の最適化全てが行われる。依存関係が循環している場合は、MySQL からエラーが出力される。
標準の
WHERE
最適化すべてが実行される。
A
にWHERE
節の条件にマッチするレコードがあり、B
に
ON
条件にマッチするレコードがない場合、B
のカラムの値が
NULL
に設定されたレコードが生成される。
テーブルのいずれかに存在しないレコードを検索する際に
LEFT JOIN
を使用した場合:
のcol_name
IS
NULLWHERE
節内で、NOT
NULL
と定義した
col_name
を column_name IS NULL
で評価した場合、 MySQL は LEFT
JOIN
条件に一致するレコードを 1
つ検索すると、その後はレコードの検索(特定のキー組み合わせの)を停止する。
RIGHT JOIN
の実装は LEFT
JOIN
と類似しています。
結合オプティマイザがテーブルの結合順序を計算します。テーブル読み取り順序は
LEFT JOIN
と
STRAIGHT_JOIN
によって強制されるため、チェック対象のテーブル順列が減少し、結合オプティマイザ(テーブルの結合順序を計算する)の動作の速度がさらに上がります。
LEFT JOIN
が
d
の前に読み取るように強制するため、MySQL
では
b
の完全スキャンが実行されることに注目してください。
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;
この場合の修正は逆さに行われ、a
とb
がFROM
節でリストされています。
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.column1
カラムが
NULL
であるとすると、以下のクエリの
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;
これでクエリが改善できる場合、MySQL
がテーブル
t1
を読み取る前にテーブル
t2
を使用できるようになるためスピードが向上します。テーブルの順序を指定して強制する場合は
STRAIGHT_JOIN
を使用します。
結合を表す構文は入れ子結合を許可します。以下は項12.2.7.1. 「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 JOIN
はINNER
JOIN
と構文上等価です
(置き換え可能です)。標準SQL上等価ではありません。INNER
JOIN
はON
節と一緒に使用されます。CROSS
JOIN
は他の使用方法があります。
一般的に、inner joinオペレーションを含むjoin 表現のかっこは無視できます。かっこを取り除きグルーピングオペレーションを左に移動させた後、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
しかし、二つの表現は等価ではありません。たとえば、t1
、t2
、そしてt3
が以下の状態であるとします。
テーブル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 | +------+------+------+------+
下記の例では、外側 join オペレーションが内側 join オペレーションと一緒に使用されます。
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 | +------+------+------+------+
よって、外側 join 演算子を含む join 表現のかっこを取り除いた場合、元の表現の結果セットを変える可能性があります。
正確には、左外側 join オペレーションの右演算子のかっこを、そして右側 join オペレーションの左演算子のかっこを無視することができません。言い換えれば、外側 join オペレーションの内側テーブル表現のかっこを無視することはできません。他のオペランド(外側テーブルのオペランド)のかっこは無視できます。
以下の表現:
(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
と条件P
属性t2.b
and t3.b
.
join 表現(join_table
)
のjoin オペレーション実行順序が左から右でない場合、入れ子
join の話が出てきます。以下のクエリを考慮してください。
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
上記クエリは以下の入れ子 join が含まれると考えられています。
t2 LEFT JOIN t3 ON t2.b=t3.b t2, t3
最初のクエリでは、左 join オペレーショを使用して入れ子 join が生成されます。二番目のクエリでは内側 join オペレーションで生成されます。
最初のクエリでは、かっこは取り除いてもかまいません。join
表現の文法構成はjoin
オペレーションと同じ実行順序を指令します。2番目のクエリでは、かっこなしでも join
表現があいまいに解釈されますが、かっこは取り除くことができません。(拡張された構文では、理論上はかっこなしでもパースされますが、2番目クエリの(t2,
t3)
のかっこは必要です。まだ、クエリはあいまいでない構文構成になります。これはLEFT
JOIN
とON
が左と右のデリミタの役割りを右の表現(t2,t3)
で果たすからです。)
前述の例でこれらの点を証明しています。
インナーjoins のみ関する表現(アウターjoins は不可)については、かっこは取り除けます。かっこを取り除いて左から右に評価を行うことができます(あるいは、テーブルの評価は好きな順序で行えます)。
一般的に、そとがわjoinや外がわjoin と併合された内側join にとっては、同じではありません。かっこを取り除くことで結果を変えることがあるかもしれません。
入れ子外側joins を含むクエリは内側join
を含むクエリと同じように、パイプライン形式で実行されます。正確には、入れ子ループjoinアルゴリズムが利用されます。入れ子ループjoinがクエリを実行する際利用するアルゴリズムスキーマを思い出してください。例えば、3つのテーブルT1,T2,T3
に関するjoinクエリが、以下のフォームであるとします。
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)
はjoin 条件です(表現につく)。それに引き換え、P(t1,t2,t3)
はテーブルT1,T2,T3
カラム上の条件です。
入れ子ループjoinアルゴリズムはこのクエリを次のように実行します。
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
を使用した表記法は「t1
、t2
、そしてt3
行を含むカラムを連鎖させることで作成された行」を意味します。以下の例では、行の名前があらわれる箇所にNULL
とある場合、NULL
はその行の各カラムに使用されることを意味します。例えば、t1||t2||NULL
は「t3
の各カラム毎のt1
、t2
、そしてNULL
行のカラムを連鎖させることで作成された行。」
入れ子のある外側join クエリを見てみましょう。
SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON P2(T2,T3)) ON P1(T1,T2) 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) { 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; } } }
一般的に、外側
joinオペレーションの最初の内側テーブル入れ子ループにとって、ループ前に消され、ループ後にチェックされるフラグが導入されます。フラグがオンになるのは、内側オペランドを表すテーブルから外側テーブルの現在行にマッチが見つかったときです。ループサイクルの最後でフラグがOFFの場合は、外側テーブルの現在行でマッチが見つからなかったときです。この場合、行がインナーテーブルのカラムNULL
値で補われています。結果行は次の入れ子ループか出力へ、最終確認のため渡されますが、これは行が組み込まれた全ての外側
joinの条件を満たしている場合のみです。
この例では、次の表現で表された外側joinテーブルは組み込まれています。
(T2 LEFT JOIN T3 ON P2(T2,T3))
内側joinを含むクエリにとって、オプティマイザは以下のような異なる順序の入れ子ループが選択できることに注目してください。
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; } } } }
外側テーブルを含むクエリに関しては、オプティマイザは外側テーブルのループが内側テーブルのループの前にくる順序のみ選択可能です。よって、外側joinのクエリにとって、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
は外側joinで使用されているため、外側ループでプロセスされなければいけません。
T2
とT3
は内側joinで使用されているため、そのjoinは内側ループで処理されなければいけません。ただし、joinが内側joinのため、T2
とT3
はどちらの順序でも処理できます。
内側joinの入れ子ループアルゴリズムについては、クエリ実行性能に関する、重大な詳細を省きました。いわゆる、「後入れ先出し」条件に関することには触れませんでした。例えば、WHERE
条件P(T1,T2,T3)
が接続法によって表されるとします。
P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3).
この場合、MySQLは内側joinを含むクエリの実行には以下の入れ子ループスキーマを使用します。
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
から内側ループに渡される行の数を大幅に減らします。結果的に、クエリの実行時間が大きく短縮できます。
外側joinを含むクエリについては、外側テーブルの現在行に内側テーブルからのマッチがあることが確認できてからWHERE
条件がチェックされます。よって、内側入れ子ループの後だし先入れ条件最適化は外側joinを含むクエリには直接適用できません。ここではマッチが見つかった時に起動するフラグに守られた、条件つき後出し先入れの述語を紹介しなければいけません。
例えば、外側joinでは
P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)
ガードされた後だし先入れ条件を使用した入れ子ループスキーマは以下のようになります。
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)
といったjoin条件から抽出できます。この場合、後だし先入れ述語は対応する外側joinオペレーションNULL
-に補われた行のチェックを妨げるフラグによって守られています。
ここで、1つの内側テーブルから同じ入れ子joinへのアクセスキーは、WHERE
条件からの述語に誘導されている場合、禁止されています。(この場合条件つきのキーアクセスを使用することはできますが、MySQLではこのテクニックはまだ使われていません5.1。)
クエリのFROM
節内テーブル表現は多くの場合単純化されています。
パーサ段階で、右外側joinオペレーションを含むクエリは左joinオペレーションを含む等価のクエリに変換されます。一般的には、変換は以下のルールに従って実行されます。
(T1, ...) RIGHT JOIN (T2,...) ON P(T1,...,T2,...) = (T2, ...) LEFT JOIN (T1,...) ON P(T1,...,T2,...)
T1 INNER JOIN T2 ON
P(T1,T2)
フォームの全ての内側join表現は
T1,T2
、P(T1,T2)
結合されたWHERE
条件によって置き換えられます。(あるいは、組み込まれたjoinのjoin条件が存在する場合は、それに置き換えられます)。
オプティマイザが外側joinオペレーションのjoinクエリ計画を評価する際、各オペレーション時、外側テーブルが内側テーブルより前にアクセスされます。そのようなプランは、入れ子ループスキーマによる外側joinオペレーションを含むクエリの実行のみ可能なため、オプティマイザ選択肢は制限されています。
例えば、以下のようなフォームのクエリがあるとします。
SELECT * T1 LEFT JOIN T2 ON P1(T1,T2) WHERE P(T1,T2) AND R(T2)
R(T2)
がテーブルT2
からマッチする行を大幅に狭めます。クエリをそのまま実行した場合、オプティマイザはT1
より前にT2
をアクセスする以外に選択肢が与えられず、非常に非効率的な実行プランになることがあります。
幸い、MySQL
WHERE
条件がnull-rejectedの場合、そのようなクエリを外側joinオペレーションを含まないクエリに変換します。もし外側join
オペレーションがNULL
-によって補われたオペレーションのために作成された行のFALSE
またはUNKNOWN
に評価される場合は、null-rejectedと呼ばれます。
よって、この外側joinでは:
T1 LEFT JOIN T2 ON T1.A=T2.A
以下のような条件はnull-rejectedです:
T2.B IS NOT NULL, T2.B > 3, T2.C <= T1.C, T2.B < 2 OR T2.C > 1
以下のような条件はnull-rejectedではありません:
T2.B IS NULL, T1.B < 3 OR T2.B IS NOT NULL, T1.B < 3 OR T2.B > 3
条件が外側joinオペレーションにとってnull-rejectedか否かを確認する一般的なルールは単純です。以下の場合、条件はnull-rejectedになります。
フォームがA IS NOT
NULL
で、A
が内側テーブルのどれかの属性である場合
UNKNOWN
のアーギュメントの内1つがNULL
で、且つUNKNOWN
と評価する内側テーブルへのリファレンスを含む述語である場合
null-rejected条件をコンジャンクトとして含んでいる結合子の場合。
null-rejected条件のディスジャンクションの場合。
条件は1つクエリでの外側joinオペレーションでnull-rejectedとなり、他のクエリでnot null-rejected になりえます。以下のクエリでは:
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つ目の外側joinオペレーションではnull-rejectedであり、1つ目の外側joinオペレーションではnot
null-rejected です。
もしWHERE
条件がクエリの外側joinオペレーションでnull-rejected
である場合、外側joinオペレーションは内側joinオペレーションに置き換えられます。
例えば、前のクエリは以下のクエリに置き換えられます。
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
アクセスシーケンスを追加で考慮します。
1つの外側joinオペレーションの変換は別のオペレーションの変換を引き起こす場合があります。よって、以下のクエリでは:
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-rejected
であることと、外側joinを含まないクエリを取得したため、残る外側joinオペレーションは内側joinに置き換えることができます。
SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3 WHERE T3.C > 0 AND T3.B=T2.B
時折、組み込まれた外側joinオペレーションを置き換えることができても、組み込まれた外側joinが変換できない場合があります。以下のクエリでは:
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,
それは組み込まれた外側joinオペレーションを含むフォームにのみ書き換えることができます。
SELECT * FROM T1 LEFT JOIN (T2,T3) ON (T2.A=T1.A AND T3.B=T2.B) WHERE T3.C > 0.
組み込まれた外側joinオペレーションをクエリに変換する場合、WHERE
条件とともに組み込まれた外側joinのjoin条件を考慮しなければいけません。以下のクエリでは:
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
は組み込まれた外側joinではnot null-rejected
ですが、組み込まれた外側joinT2.A=T1.A AND
T3.C=T1.C
のjoin条件はnull-rejectedになります。よって、クエリは以下に変換されます。
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
余分なソートを行わずに ORDER
BY
の要求に応じるために、MySQL
はインデックスを使用する場合があります。
全ての使用されていないインデックス部分と他の部分が
WHERE
節内で定数であるカラムである場合、ORDER
BY
がインデックスに完全にマッチしない場合でもこのインデックスを使用できます。次のクエリではインデックスを使用して
ORDER BY
部分を解決します。
SELECT * FROM t1 ORDER BYkey_part1
,key_part2
,... ; SELECT * FROM t1 WHEREkey_part1
=constant
ORDER BYkey_part2
; SELECT * FROM t1 ORDER BYkey_part1
DESC,key_part2
DESC; SELECT * FROM t1 WHEREkey_part1
=1 ORDER BYkey_part1
DESC,key_part2
DESC;
MySQL で ORDER
BY
の解決にインデックスを使用できない場合は以下のとおりです(この場合も
MySQL は
WHERE
節の条件に一致するレコードの検索にインデックスを使用します)。
複数のキーに対してORDER
BY
を実行する場合。
SELECT * FROM t1 ORDER BYkey1
,key2
;
連続しないキー部分に対してORDER
BY
を実行する場合。
SELECT * FROM t1 WHEREkey2
=constant
ORDER BYkey_part2
;
ASC
とDESC
が混在している場合。
SELECT * FROM t1 ORDER BYkey_part1
DESC,key_part2
ASC;
行の取り出しに使用されるキーが ORDER
BY
の実行に使用されるキーと異なる場合。
SELECT * FROM t1 WHEREkey2
=constant
ORDER BYkey1
;
ORDER
BY
で多くのテーブルとカラムを結合していて、それら全てがレコードの取り出しに使用される最初の非
const テーブルではない場合(これは
EXPLAIN
で出力される最初のテーブルで、かつ、const
メソッドを使用していないテーブル)。
ORDER BY
とGROUP
BY
式が異なる場合。
使用されたテーブルインデックスが、並び順にレコードを格納していないインデックスタイプの場合。(MEMORY
テーブルの
HASH
インデックスなど)。
EXPLAIN SELECT ... ORDER
BY
を使用すると、MySQL
でインデックスを使用してクエリを解決できるかどうかをチェックできます。Extra
カラムに
Using
filesort
が出力された場合は、MySQL で ORDER
BY の解決にインデックスを使用できません。
項6.2.1. 「EXPLAIN
を使用して、クエリを最適化する」を参照してください。
ソートキー値と行ポジションだけでなく、クエリに必要なカラムまで記憶するfilesort
最適化が使用されます。これにより行の2度読みを避けられます。filesort
アルゴリズムは以下のように実行されます。
WHERE
節とマッチする行を読む。
各行ごとに、クエリに必要なカラムとソートキー値と行ポジションを含むタプル値を記憶する。
ソートキー値でタプルを並べ替える
並べ替えられた順序で行を取得しますが、テーブルに2度アクセスするよりも、並べ替えられたタプルから必要なカラムを読み取ります。
MySQLの旧バージョンで使用されていたアルゴリズムよりも格段に改良されています。
遅滞を避けるため、この最適化はmax_length_for_sort_data
システム変数の値をソートタプル内の余分なカラムのトータルサイズが超えない場合使用されます。(この変数が高く設定されると、活発なディスクアク活動に対して低いCPU活動といった状態が発生します。)
ORDER
BY
速度を上げたい場合、MySQLが余分な並び替えフレーズよりもインデックスを使用できるか確認してください。これが不可能な場合、以下の手段を試してみてください。
sort_buffer_size
変数のサイズを大きくしてください。
read_rnd_buffer_size
変数のサイズを大きくしてください。
tmpdir
を変更することで、秋スペースの要領が多い専用のファイルシステムを示してください。この選択肢はラウンドロビン方式で複数のパスを受領します。Unixでは、パスはコロンを含む文字(‘:
’)
で分けられ、ウィンドウズ、Netware, そして
OS/2
ではセミコロンを含む文字(‘;
’)で分けられるべきです。
この特性を利用して複数のディレクトリに渡り負荷を分散することができます。注:パスは、同ディスクのパーティションで分けられた領域ではなく、異なる物理的なディスクのファイルシステム内のディレクトリに通じます。
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;
GROUP
BY
節を満たす最も一般的な方法は、テーブル全体をスキャンし、各グループの全ての行が連続する新しいテンポラリテーブルを作成することです。それにより、このテンポラリテーブルを使用してグループの発見や集約ファンクション(の適用が可能になります。場合により、MySQLはインデックスアクセスを使用することでテンポラリテーブルの作成を回避することが可能です。
GROUP
BY
インデックスを使用するための最も重要な前提条件は、全てのGROUP
BY
カラムは同じインデックスから属性を紹介することと、そしてインデックスがキーを正しい順序で保存することです。
(例えば、BTREE
はインデックスであり、HASH
インデックスではありません)。テンポラリテーブルの使用がインデックスアクセスに置き換えることが可能かは、クエリ内でどのインデックス部分が使用されるか、その部分を指定する条件、そして選択された集約ファンクションにもよります。
インデックスアクセスを通してGROUP
BY
クエリを実行するには以下に記述された、2つの方法があります。最初の方法では、グルーピングオペレーションは全てのレンジ前提(これが有る場合に限り)と共に適用されます。2つ目の方法は、まずレンジスキャンを実行し、その後結果タプルをグループします。
GROUP
BY
を処理する最も効率的な方法は、グループフィールドを直接取得するのにインデックスが使用されるときです。このアクセスメソッドで、MySQLはキーが順序づけられている、インデックス型の特性を利用します。(例えば、BTREE
)。この特性は全てのWHERE
条件を満たすインデックス内のキーを考慮せずとも、インデックス内のルックアップグループの仕様を可能にします。.このアクセスメソッドはインデックス内のほんの一部のキーを考慮するため、loose
index
scanと呼ばれています。WHERE
節がない場合、ルースインデックススキャン(loose
index
scan)はナンバーグループの数だけキーを読みます。これはキーの総数よりも少ない数かもしれません。もしWHERE
節がレンジ前提を含む場合、(項6.2.1. 「EXPLAIN
を使用して、クエリを最適化する」内に記述されたrange
join型に関するディスカッションを参照してください),
a loose index scan
はレンジ条件を満たす各グループの最初のキーを参照し、最低限のキーの数を読みます。これは以下の条件下で可能になります。
クエリはシングルテーブル上にある。
GROUP
BY
はインデックスの最初の連続部分を含む。(もし、GROUP
BY
の代わりにクエリがDISTINCT
節を含む場合、全ての異なる属性はインデックスの最初を参照する。)
使用される集約ファンクション(ある場合)はMIN()
とMAX()
であり、全て同じカラムを参照します。
クエリで参照されたGROUP
BY
以外のインデックス部分は定数でなければいけません(つまり、定数と等価であるよう参照されなければいけません)が、MIN()
あるいはMAX()
ファンクションは例外です。
そのようなクエリの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;
タイトインデックススキャンは、クエリ条件によって、フルインデックススキャンもしくはレンジインデックススキャンのいずれかとなります。
ルースインデックスに見合う条件がなければ、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;
HAVING
を使用するのではなく
LIMIT
を使用している場合、MySQL
によるクエリの処理方法が異なる場合があります。
row_count
LIMIT
を使用して数行しか選択していないと、フルテーブルスキャンが行われそうな場合に、MySQL
はインデックスを使うことがある。
ORDER BY
とともに LIMIT
を使用している場合、MySQL
ではすべてのテーブルがソートされるのではなく、最初の
row_count
row_count
行の検索が行われた時点でただちにソートを終了する。
インデックスを用いて整頓されている場合、これはとても速い方法です。ファイルソートが実行されなければならない場合、最初のrow_count
行が検索されたことを確認する前に、LIMIT
節を用いないクエリに当てはまる全ての行が選択されなければなりません。そして、それらのほとんど、もしくは全部がソートされなければなりません。いずれの場合でも最初の行が検索された後では、どの結果セットリマインダもソートする必要はありません。また、MySQLもその必要はありません。
LIMIT
を
row_count
DISTINCT
とあわせて使用した場合、MySQL は一意の
row_count
行を検索するとただちに停止します。
GROUP
BY
がキーを順番に読む(またはキーのソートを実行して読む)ことで解決でき、キーの値が変わるまで
サマリが計算される場合もあります。この場合、LIMIT
では不要な
row_count
GROUP BY
値の計算がすべて行われなくなります。
MySQL
が要求された行数をクライアントに送信すると、クエリが中止されます(SQL_CALC_FOUND_ROWS
を使用していない場合)。
LIMIT
0
は常に迅速に空のセットを返します。これは、クエリの妥当性チェックに役立ちます。MySQL
APIの1つを使用している場合、結果カラム型の獲得も可能です。(この方法は、MySQLモニター(mysqlプログラム)では、Empty
set
と表示されるだけで働きません。このため、代わりにSHOW
COLUMNS
またはDESCRIBE
を使用する必要があります。)
サーバでテンポラリテーブルを使用してクエリが解決される場合、LIMIT
節が必要な領域の計算に使用されます。
row_count
MySQLがクエリを解決するのにテーブルスキャンを使用すると、EXPLAIN
からの出力は、type
カラムのALL
を表示します。これはたいてい以下の条件下で生じます。
テーブルが小さすぎて、時間のかかるキールックアップよりもテーブルスキャンの実行のほうが遅くなります。このことは、10行以下の行や短い行をもつテーブルにはよく起こることです。
インデックスカラムに対して、ON
またはWHERE
節内に使用できる制限はありません。
インデックスカラムを定数値と比較し、MySQLは(インデックスツリーに基づいて)その定数がテーブルの大きすぎる部分をカバーしているか、またテーブルスキャンが高速に行われるかを計算します。項6.2.4. 「WHERE
節最適化」を参照してください。
他のカラムをとおして、低濃度(多数の行がキー値に当てはまる)でキーを使用します。この場合、MySQLは、 キーを使用して多数のキールックアップが実行され、またテーブルスキャンもより速く行われるであろうと認識します。
小さいテーブルに対しては、テーブルスキャンはたいてい適切であり、実行の際の影響は無視されます。大きいテーブルに対しては、オプティマイザが間違ったテーブルスキャンを選択しないように、以下の方法を試してください。
スキャンされたテーブルのキー配置を更新するには、ANALYZE
TABLE
を使用してください。項12.5.2.1. 「tbl_name
ANALYZE TABLE
構文」を参照してください。
MySQLに、テーブルスキャンは既存インデックスを使用するのに比べて大変時間がかかることを示すには、FORCE
INDEX
をスキャンされたテーブルに使用してください。
SELECT * FROM t1, t2 FORCE INDEX (index_for_column
) WHERE t1.col_name
=t2.col_name
;
項12.2.7. 「SELECT
構文」を参照してください。
オプティマイザに1,000キーシーク以上のキースキャンがないことを認識させるには、--max-seeks-for-key=1000
オプションで
mysqldを起動させるか、もしくはSET
max_seeks_for_key=1000
を使用してください。項4.2.3. 「システム変数」を参照してください。
行挿入の時間は、以下の要因によって決定されます。(数はおよその割合を示します。)
接続: (3)
サーバへのクエリの送信: (2)
クエリの解析: (2)
行挿入:(1 × 行サイズ)
エンデックス挿入:(1 × インデックス数)
クローズ: (1)
テーブルを開く初期オーバヘッドは算入されていません(これは同時実行クエリのそれぞれで 1 回実行されます)。
テーブルのサイズによって対数N
の分だけインデックス挿入の速度が低下します(B
ツリー)。
挿入の速度を上げる方法
1
つのクライアントから同時に多数の行を挿入する場合は、マルチプルVALUES
リストでINSERT
ステートメントを使用します。これで独立した
INSERT
ステートメントの使用時と比較して大幅に(場合によっては数倍)速度が上がります。空ではないテーブルにデータを追加する場合は、さらに速度を上げるためにbulk_insert_buffer_size
変数を調整します。項4.2.3. 「システム変数」を参照してください。
異なる複数のクライアントから大量のレコードを挿入する場合は、INSERT
DELAYED
ステートメントを使用すると速度を上げることができます。項12.2.4.2. 「INSERT DELAYED
構文」を参照してください。
MyISAM
テーブルでは、テーブルに削除された行がない場合、SELECT
の実行と同時に行を挿入できることに注意してください。項6.3.3. 「同時挿入」を参照してください。
テキストファイルからテーブルをロードする場合は
LOAD DATA
INFILE
を使用します。通常、これは
INSERT
ステートメントを使用する場合と比較して、20
倍速度が上がります。項12.2.5. 「LOAD DATA INFILE
構文」を参照してください。
テーブルにインデックスが多数ある場合、操作を少し追加するだけでMyISAM
テーブルのLOAD
DATA INFILE
の実行速度をさらに上げることができます。以下の手順を使用してください。
CREATE
TABLE
を使用して、テーブルを作成します。
FLUSH
TABLES
ステートメントまたはmysqladmin
flush-tablesコマンドを実行します。
myisamchk --keys-used=0 -rq
/path/to/db/tbl_name
を使用します。これでテーブルからすべてのインデックスの使用が削除されます。
LOAD DATA
INFILE
を使用して、テーブルにデータを挿入します。これはインデックスをまったく更新しないため、非常に高速になります。
テーブルを読み取り専用にする場合は、myisampackを実行してテーブルを小さくします。項13.4.3.3. 「圧縮テーブルの特徴」を参照してください。
myisamchk -rq
/path/to/db/tbl_name
を使用してインデックスを作成しなおします。これは、ディスクに書き込む前にメモリにインデックスツリーを作成してディスクシークを回避するため、LOAD
DATA
INFILE
中のインデックス更新が非常に高速になります。生成されたインデックスツリーは完全にバランスが取られています。
FLUSH
TABLES
ステートメントまたはmysqladmin
flush-tablesコマンドを実行します。
データを挿入したMyISAM
テーブルが空の場合は、LOAD
DATA
INFILE
は上記の最適化を自動的に実行します。上記手順との主な相違点は、LOAD
DATA
INFILE
ステートメントの実行中にサーバにインデックスの再作成を割り当てる場合より、myisamchkにインデックス作成用のテンポラリメモリ割り当てるほうが、より大幅に割り当てることができる点です。
myisamchkよりも以下のステートメントを使用して、MyISAM
のインデックス利用を可能にしたり、不可能にしたりできます。これらのステートメントを使用すると、FLUSH
TABLE
オペレーションをスキップできます。
ALTER TABLEtbl_name
DISABLE KEYS; ALTER TABLEtbl_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
で挿入できる場合はロックの必要がありません。
トランザクショナルテーブルの場合は、LOCK
TABLES
ではなく START
TRANSACTION
およびCOMMIT
を使用して速度の改善を図ります。
ロックは複数の同時接続テストの合計時間も短縮するが、一部のスレッドの最大待機時間は長くなります。(ロックの際に待機するため)例 :
接続1は1000行を挿入
接続2, 3,4は1行を挿入
接続5は1000行を挿入
ロックを使用しない場合、2、3、4 は 1 と 5 の前に終了します。ロックを使用した場合は、2、3、4 は 1 と 5 の前には終了しない確率が高くなりますが、合計時間は約 40% 短縮されます。
MySQL
では、INSERT
、UPDATE
、および
DELETE
の演算が非常に速いため、約
5 つより多い挿入や
更新をする前にロックを追加すると、総合的なパフォーマンスを改善できます。1
行で非常に多数の挿入を実行する場合は、ときどき(約
1,000 行ごと)LOCK
TABLES
にUNLOCK TABLES
を続けて実行して、他のスレッドからのテーブルへのアクセスを可能にすることができます。これでもパフォーマンの増加が得られます。
アウトラインストラテジー使用時でも、データのロードにはLOAD
DATA
INFILE
のほうがINSERT
よりも大幅に高速です。
MyISAM
テーブルおよびLOAD
DATA
INFILE
とINSERT
の両方に対するパフォーマンスの向上には、key_buffer_size
システム変数値を上げてキーキャッシュを拡張します。項6.5.2. 「サーバパラメータのチューニング」を参照してください。
MySQL Enterprise サーバのパフォーマンスを最適化するための詳しいアドバイスについては、MySQL Network Monitoring and Advisory Serviceを購読してください。多数のアドバイザーがパフォーマンス向上をサポートします。追加情報については http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
更新ステートメントは、SELECT
クエリと同様に最適化されますが、書き込みオーバヘッドが加算されます。書き込みの速度は更新対象のデータのサイズおよび更新対象のインデックス数によって異なります。変更がないインデックスは更新されません。
更新の速度を上げるもう 1 つの方法は、更新を遅延して 1 行で多数の更新を後から行うことです。1 行での多数の更新は、テーブルをロックすると同時に行う場合と比較して大幅に高速に実行できます。
可変長テーブルMyISAM
の場合は、合計の長さが今よりも長いものに行を更新すると、行が分割される場合があることに注意します。このため、頻繁にこれを実行する場合は、ときどき
OPTIMIZE
TABLE
することが重要になります。項12.5.2.5. 「OPTIMIZE TABLE
構文」を参照してください。
行の削除に要する時間は、完全にインデックス数に比例します。行削除の速度を上げるには、key_buffer_size
システム変数を上げて、キーキャッシュのサイズを拡大します。項6.5.2. 「サーバパラメータのチューニング」を参照してください。
テーブル内のすべての行を削除する場合は、TRUNCATE
TABLE
のほうがtbl_name
DELETE
FROM
を使用するより高速です。項12.2.9. 「tbl_name
TRUNCATE
構文」を参照してください。
このセクションでは、クエリ処理高速化のためのヒントを挙げます。
接続オーバヘッドを回避するには、データベースに対して永続的な接続を使用します。永続的な接続を使用せずにデータベースに対して多数の新規接続を実行する場合は、thread_cache_size
変数の値の変更が必要になることがあります。項6.5.2. 「サーバパラメータのチューニング」を参照してください。
常にすべてのクエリがテーブル内に作成したインデックスを実際に使用していることを確認します。MySQLでは、EXPLAIN
ステートメントでこれを実行できます。項6.2.1. 「EXPLAIN
を使用して、クエリを最適化する」を参照してください。
大量に更新された
MyISAM
テーブルに対して複雑な
SELECT
クエリを使用しないようにします。これで、読み手と書き手間の競合から生じるテーブルロックを回避します。
削除されたレコードがない
MyISAM
テーブルの場合は、別のクエリでそのテーブルからの読み取りが行われるのと同時に行を挿入できます。これがあなたにとって重要ならば、行削除の回避をおこなうテーブルの使用を検討します。また、大量の行削除後のOPTIMIZE
TABLE
の実行を検討します。項13.4. 「MyISAM
ストレージエンジン」を参照してください。
この動作は、concurrent_inserts
変数設定をとおして、変更されます。行が削除されたテーブル上であっても、新しい行を付加できます。(またその結果、同時に挿入することが可能です。)項6.3.3. 「同時挿入」を参照してください。
ARCHIVE
テーブルで生じるデータ圧縮問題を修復するのに、OPTIMIZE
TABLE
を使用できます。項13.10. 「ARCHIVE
ストレージエンジン」を参照してください。
通常
の順で行を読み取る場合は、expr1
、expr2
、
...ALTER
TABLE ... ORDER BY
を使用してください。テーブルが大幅に変更された後にこのオプションを使用すると、パフォーマンスを改善できます。
expr1
、expr2
、
...
他のカラムの情報を基にした 「ハッシュされた」カラムを導入することが役立つ場合があります。このカラムが短いもので、一意性がある場合は、多数のカラムに「大きな」インデックスを使用するより大幅に高速化できます。MySQLでは、追加カラムの使用が以下のように非常に容易です。
SELECT * FROMtbl_name
WHEREhash_col
=MD5(CONCAT(col1
,col2
)) ANDcol1
='constant
' ANDcol2
='constant
';
頻繁に変わるMyISAM
テーブルでは、全ての可変長カラム(VARCHAR
、BLOB
、そしてTEXT
)の使用を避けてください。たった1つの可変長カラムを含む場合でも、テーブルではダイナミック行フォーマットが使用されます。章?13. ストレージエンジンとテーブルタイプを参照してください。
一般に、1
つのテーブルを複数のテーブルに分割することは、行が大きくなるだけで高速化の役には立ちません。行にアクセスする際の、最も大きなパフォーマンス要因は、レコードの最初のバイトを見つけるためのディスクシークです。データの検索後、ほとんどの新規ディスクでは、大多数のアプリケーションに十分な速度で行全体を読み取ることができます。テーブルの分割が実際に有効な状況は、固定長テーブルへの変更が可能な可変長MyISAM
テーブルの場合か、テーブルのスキャンを非常に頻繁に必要としながらもほとんどのカラムに必要としない場合のみです。章?13. ストレージエンジンとテーブルタイプを参照してください。
多数の行の情報から計算する頻度を非常に高くする必要がある場合(カウントの場合など)、新たなテーブルを導入し、リアルタイムでカウンタを更新するほうがはるかに適しています。以下のような更新は非常に高速にできます。
UPDATEtbl_name
SETcount_col
=count_col
+1 WHEREkey_col
=constant
;
これは、MyISAM
のようにテーブルロック(複数リーダ/単一ライタ)のみの
MySQL
ストレージエンジンを使用する場合に、非常に重要です。また、このような場合は行ロックマネージャで必要な作業が少なくなるため、ほとんどのデータベースでパフォーマンスが改善されます。
大きなログテーブルから統計を収集する必要がある場合は、テーブル全体をスキャンするのではなく、サマリテーブルを使用します。サマリの管理は、「リアルタイム」で統計を実行する場合と比較して非常に高速になります。 何らかの変更がある(業務上の決定に応じて)場合は、ログから新規にサマリテーブルを再生成したほうが、実行アプリケーションの変更よりはるかに高速です。
可能であれば、レポートを「リアルタイム」か「集計」かのいずれかに分類するように推奨します。集計レポートに必要なデータは、サマリテーブルから生成され、サマリテーブルは実データから生成されます。
カラムにデフォルト値がある利点を生かします。挿入対象の値がデフォルト値と相違する場合のみ明示的に値を挿入します。これで、MySQL が要する解析作業が軽減され、挿入の速度が改善されます。
状況によっては、データをBLOB
にパックし、格納したほうが便利です。このような場合は、BLOB
へのパックおよびパック解除を行うコードをアプリケーションに追加する必要がありますが、あるステージにおける大量のアクセスを省略できることになります。これは、固定長テーブル構造に準拠しないデータがある場合に実用的です。
通常は、すべてのデータが冗長にならないようにする必要があります。(データベースセオリの第 3 正規化)。しかし、高速化を図る必要がある場合はデータなどの複製やサマリテーブルの作成をためらうべきではありません。
ストアドルーチンや UDF(ユーザ定義関数)はパフォーマンスの向上に役立つ手段です。詳しくは、章?17. ストアドプロシージャとファンクションおよび項25.3. 「Adding New Functions to MySQL」を参照してください。
アプリケーションのクエリと応答をキャッシュすること、および挿入と更新の同時実行を試行することは必ず高速化に役立ちます。データベースでロックテーブルがサポートされる場合(MySQL や Oracle など)は、これによって確実にすべての更新後にインデックスキャッシュが 1 回だけフラッシュされるようにできます。MySQLのクエリキャッシュも、同様の結果を得るために利用できます。詳しくは項4.13. 「MySQL クエリ キャッシュ」を参照してください。
データの書き込みするタイミングを知る必要がない場合はINSERT
DELAYED
を使用します。多数の行が 1
回のディスクへの書き込みで書き込まれるため、これで高速化が図れます。
SELECT
の優先を上げる場合は、INSERT
LOW_PRIORITY
を使用します。
キューをジャンプするようにする場合は、SELECT
HIGH_PRIORITY
を使用します。言い換えると、書き込み待機中のユーザがいる場合でも、SELECT
を実行できるようになる。
1 つの
SQLステートメントで多数の行を格納するには、複数行のINSERT
ステートメントを使用します。これは、MySQLを含む多数の
SQL でサポートされています。
大量のデータをロードする場合はLOAD
DATA
INFILE
を使用します。これは通常のINSERT
より高速になります。
一意の値にするには、AUTO_INCREMENT
カラムを使用します。
一定の間隔で OPTIMIZE TABLE
を使用して、動的MyISAM
テーブルの断片化を回避します。項13.4.3. 「MyISAM
テーブルストレージフォーマット」を参照してください。
さらに高速化が可能であれば、MEMORY
テーブルを使用します。詳しくは項13.7. 「MEMORY
(HEAP
)
ストレージエンジン」を参照してください。頻繁にアクセスされる非クリティカルデータ(クッキーなしでユーザに最後に表示されたバナーの情報など)にはMEMORY
テーブルを使用します。多くのWebアプリケーション環境では、
揮発性データの処理にユーザセッションも使用できます。
Webサーバでは、画像と他のバイナリアセットを通常ファイルとして格納します。言い換えると、データベース内にはファイル参照のみを格納します。この主な理由は、通常の Web サーバのほうがデータベースコンテンツと比較してファイルのキャッシュに優れているためです。このため、ファイルを使用したほうがシステムの高速化を容易に図れます。
別のテーブルで同一情報を扱うカラムは、同じ宣言をし、同じデータ型を持つようにします。この結果、一致カラムに基づく結合速度が速くなります。
カラム名はなるべく単純なものに保持します。たとえば、customer
テーブルではcustomer_name
ではなく name
を使用します。他の SQL
サーバに移植可能にすることを考慮するなら、名前を
18 文字未満にする。
高速化が大きく必要とされる場合は、複数の
SQL
サーバがサポートするデータストレージの低レベルインタフェースを調べる必要があります。たとえば、MySQL
MyISAM
ストレージエンジンに直接アクセスすることによって、SQL
インタフェース使用時と比較して 2〜5
倍の速度が得られることもあります。
これを実行可能にするには、データをアプリケーションと同じサーバに配置し、また通常は
1
プロセスのみからアクセスするようにする必要があります(外部ファイルロックが非常に低速なため)。上記の問題は、MySQL
サーバに低レベルの MyISAM
コマンドを導入することで解消できます(必要に応じてパフォーマンスを改善する容易な手段の
1
つとなるのです)。データベースインタフェースを慎重に設計することで、この種の最適化を容易にサポートできる。
多くの場合、テキストファイルにアクセスするのと比較して、データベースからデータにアクセスしたほうが高速である。この理由は一般にテキストファイル(数値データ使用時)よりデータベースのほうがよりコンパクトで、必要なディスクアクセスが少ないことによる。また、テキストファイルを解析してレコードとカラムの境界を検索する必要がないため、コードも節約できる。
レプリケーションはオペレーションによって、性能向上を図ります。負荷を分散させるため、クライアント修正をレプリケーションサーバに分布できる。バックアップを作成する間マスタの速度が低下するのを避けるため、スレーブサーバを作成することができる。章?5. レプリケーションを参照してください。
DELAY_KEY_WRITE=1
オプションでMyISAM
テーブルを定義すると、ファイルが閉じられるまでディスクにログが記録されないためインデックス更新の速度が上がる。
この欠点は、途中で mysqld
の強制終了が発生した場合にテーブルに問題がないことを確認するため、mysqld
を開始する前に、テーブルに対して
myisamchkを実行するか、--myisam-recover
でサーバを作動させる必要があるということである。キー情報は常にデータから生成可能であるため、DELAY_KEY_WRITE
を使用しても何も消失はしない。
MySQL はMyISAM
とMEMORY
テーブルにはテーブルレベルロックを使用し、InnoDB
テーブルには行レベルロックを使用します。
ほとんどの場合、どのロックタイプがアプリケーションに適しているか推察することが可能ですが、一概にどのロックタイプが優れているかを判断するのは困難です。全てはアプリケーションに依存しており、かつアプリケーションの部分毎に異なるロック型があります。
行レベルロックで保存エンジンを使用するか判断する場合、ユーザはアプリケーションの役割と、使用されているselect
と updateステートメントを確認する必要があります。例えば、大抵のWebアプリケーションは多くの選択を実行し、相対的に削除はほとんど行わず、主にキー値にもとづいた更新を行い、かつ特定のテーブルに挿入します。ベースMySQL
MyISAM
セットアップはよくチューニングされています。
MySQL Enterprise MySQL Network Monitoring and Advisory Service はテーブルレベルでの使用時および行レベルロックでの使用時について専門的なアドバイスを提供しています。購読を希望する場合は、http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
MySQLのテーブルロックは、テーブルレベルロックを使用するストレージエンジンに対して、デッドロックフリーです。デッドロックは、クエリの開始時に、同時に必要とされる全てのロックを常に要求すること、そして同じ順序のテーブルを常にロックすることで回避されます。
MySQLでのテーブルロックメソッドはWRITE
ロックを用いて以下のように機能します。
テーブルにロックがない場合、write ロックをつけてください。
もしくは、write ロックキューでロック要求を行ってください。
MySQLでのテーブルロックメソッドはREAD
ロックを用いて以下のように機能します。
テーブルに write ロックがない場合、read ロックをつけてください。
もしくは、read ロックキューでロック要求を行ってください。
ロックが開放されるとき、writeロックキューのスレッドに対してロックは有効であり、それから
read
ロックキューのスレッドに対しても有効です。これはテーブルの更新を頻繁に行う場合、SELECT
ステートメントは更新が行われなくなるまで待機することを意味します。
Table_locks_waited
およびTable_locks_immediate
ステータス変数をチェックすることでシステム上でテーブルロック競合を分析できます。
mysql> SHOW STATUS LIKE 'Table%';
+-----------------------+---------+
| Variable_name | Value |
+-----------------------+---------+
| Table_locks_immediate | 1151552 |
| Table_locks_waited | 15324 |
+-----------------------+---------+
MyISAM
テーブルが中心に空きブロックを持たない場合、行は必ずデータファイルの後部に挿入される。この場合、併発するINSERT
およびSELECT
ステートメントをロックなしでMyISAM
テーブルに対して自由に混合して使用できます。つまり、他のクライアントが読むのと同時にMyISAM
テーブルに行を挿入できます。テーブルの途中で行を更新もしくは削除した場合欠落が生じます。欠落がある場合、同時挿入はできませんが、全ての欠落が新しいデータで満たされた場合は、自動的に再使用可能となります。
この機能はconcurrent_inserts
システム変数によって変更されます。項6.3.3. 「同時挿入」を参照してください。
テーブルに対して多数の
INSERT
および
SELECT
操作を行う必要がある場合、このような待機を回避するには、テンポラリテーブルに行を挿入し、一定の間隔でテンポラリテーブルからの行で実テーブルを更新します。
これは以下のコードで実行できます。
mysql>LOCK TABLES real_table WRITE, insert_table WRITE;
mysql>INSERT INTO real_table SELECT * FROM insert_table;
mysql>TRUNCATE TABLE insert_table;
mysql>UNLOCK TABLES;
InnoDB
は行ロックを使用する。InnoDBでは、SQLステートメントのトランザクションの初めではなく、プロセス中に自動的に入手するため、デッドロックが可能です。
行レベルロックの利点
多数のスレッドで異なる行をアクセスする際に、ロックコンフリクトが少なくてすみます。
ロールバックの変更がすくなくてすみます。
1つの行を長時間ロックすることが可能です。
行レベルロックの欠点
テーブルレベルロックよりもメモリを要します。
テーブルの大部分で使用される際、より多くのロックが必要となるためテーブルレベルロックよりも処理速度が遅くなります。
データの大部分に対してGROUP
BY
オペレーションを実行する場合、もしくは全テーブルを頻繁にスキャンする場合他のロックよりもはるかに遅いです。
以下の場合、行レベルロックに対してテーブルロックが優勢になります。
テーブルのほとんどのステートメントは read です。
1つのキーリードで取得される1つの行に対して、write が更新もしくは削除される場合、read と write が混合となります。
UPDATEtbl_name
SETcolumn
=value
WHEREunique_key_col
=key_value
; DELETE FROMtbl_name
WHEREunique_key_col
=key_value
;
SELECT
が同時INSERT
ステートメントとごく少数のUPDATE
もしくはDELETE
ステートメントと混合されます。
writerを使用しない、全てのテーブルのGROUP
BY
オペレーションや多くのスキャン。
高レベルロックで、異なるタイプのロックをサポートすることによって、より簡単にアプリケーションのチューニングが行えます。というのも、ロックオーバーヘッドは低レベルロックに対して少ないからです。
行レベルロック以外のオプション
バージョニング(同時挿入でMySQLに使用されるような):同時に1writerと多数のreaderが存在する場合です。これはアクセス開始時に応じたデータに対して、データベースやテーブルは異なるビューをサポートします。これに対する他の共通用語は「タイムトラベル」、「writeのコピー」または「コピーオンデマンドです。」
多くの場合コピーオンデマンドは行レベルロックよりも優勢です。しかし、最悪の場合、通常のロックを使用するよりも、メモリ容量が多く必要となり得ます。
行レベルロックを使用する代わりに、MySQLではGET_LOCK()
やRELEASE_LOCK()
といったアプリケーションレベルロックを利用できます。これらはアドバイザリロックで、特に問題のないアプリケーションでのみ機能します。(詳しくは項11.10.4. 「その他の関数」をご確認ください。)
高速なロックスピードを実現するため、MySQL
InnoDB
以外の全てのテーブルロックを全てのストレージエンジンに使用します。
InnoDB
テーブルの場合は、MySQL
でLOCK
TABLES
によって明示的テーブルをロックした場合のみテーブルロックが使用されます。
InnoDB
は自動行レベルロックを使用してトランザクションの独立を確実にするため、これらのテーブル型には、LOCK
TABLES
をまったく使用しないように推奨します。
大きいテーブルには、ほとんどのアプリケーションでテーブルロックの方が行ロックより適切ですが、落とし穴もあります。
テーブルロックにより、同時に多数のスレッドがテーブルからの読み取りを行うことができますが、あるスレッドがテーブルへの書き込みを行うときは、まず排他処理をする必要があります。更新時は、特定のテーブルにアクセスしようとする他のすべてのスレッドが、更新の準備ができるまで待機します。
一般にテーブルの更新はテーブル検索より重要だと見なされるため、テーブルを更新するステートメントは優先度が高くなります。これにより、更新では特定のテーブルに対して重いSELECT
アクティビティが使用されるため、更新が
「資源枯渇」
にさらされないことが確実になります。
テーブルロックによって、ディスク容量がいっぱいのため、スレッドが待機するような問題が引き起こされます。またスレッドが処理される前に開きスペースを利用可能にする必要があります。この場合、問題テーブルにアクセスを求める全てのスレッドも、より多くのディスクスペースが利用可能になるまで待機中となります。
ただし、テーブルロックは以下のシナリオには適していません。
クライアントが実行に長時間かかる
SELECT
を使用します。
その後、別のクライアントが使用テーブルに対して
UPDATE
を使用する。
このクライアントは
SELECT
が完了するまで待機が必要になる。
別のクライアントが同一テーブルに対してさらに
SELECT
ステートメントを使用します。UPDATE
はSELECT
より優先度が高いため、この
SELECT
は
UPDATE
が完了するまで待機が必要になります。また、最初の
SELECT
の完了を待つ必要もあります。
以下のアイテムはテーブルロックによる競合を軽減または回避する方法を記述します。
SELECT
ステートメントの実行の高速化を試行する。これにはサマリテーブルの作成が必要な場合もあります。
--low-priority-updates
のオプションで
mysqldを開始する。これは、テーブルを更新(変更)するすべてのステートメントの優先度を
SELECT
ステートメントの優先度より低くします。
この場合、前シナリオの2つ目のSELECT
ステートメントはUPDATE
ステートメントの前に実行され、一番目のSELECT
が完了するまで待機する必要はありません。
SET
LOW_PRIORITY_UPDATES=1
ステートメントを使用すると、特定の接続からの更新すべてが低い優先度で実行されるように指定できます。項12.5.3. 「SET
構文」を参照してください。
LOW_PRIORITY
属性を使用して、特定のINSERT
、UPDATE
、または
DELETE
ステートメントの優先度を低く設定できます。
HIGH_PRIORITY
性を使用すると、特定の
SELECT
の重要度を高く指定できます。項12.2.7. 「SELECT
構文」を参照してください。
max_write_lock_count
システム変数の値を低くしてmysqldを開始し、MySQLに全てのSELECT
ステートメント(特定数をテーブルに挿入した後に待機しているステートメント)の優先度を一時的に引き上げさせます。これは、一定数のWRITE
ロックの後にREAD
ロックを設定します。
SELECT
と結合したINSERT
に問題がある場合は、SELECT
ステートメントとINSERT
ステートメントの同時サポートが可能になるため、新規の
MyISAM
テーブルを使用するように切り替えます。(詳しくは項6.3.3. 「同時挿入」をご確認ください。)
同じテーブル上で
insertとdeleteステートメントの混在が多い場合、INSERT
DELAYED
が非常に役立つ可能性があります。項12.2.4.2. 「INSERT DELAYED
構文」を参照してください。
SELECT
とDELETE
ステートメントに問題がある場合、DELETE
にLIMIT
オプションを使用すると解決できる場合があります。項12.2.1. 「DELETE
構文」を参照してください。
SELECT
ステートメントでSQL_BUFFER_RESULT
を使用すると、テーブルロックの持続を短縮することができます。項12.2.7. 「SELECT
構文」を参照してください。
単一クエリを使用するために、mysys/thr_lock.c
でロックコードを変更できることもあります。この場合、writeロックとreadロックは同等の優先度をもち、いくつかのアプリケーションにとって役立つものとなります。
以下はMySQLにおけるテーブルロックについてのヒントです。
更新と、同じテーブルの多くの行を調べるセレクトを混合しない限り、同時ユーザは問題を引き起こしません。
シングルロックをしようしてのアップデートはロックなしでのアップデートよりも速いため、LOCK
TABLES
を使用して速度を上げることができます。テーブルのコンテンツを別々のテーブルに分けるのも効果的です。
MySQLでテーブルロック関連の問題が生じたとき、テーブルをInnoDB
に変換することで性能向上を図ることができます。項13.5. 「InnoDB
ストレージ エンジン」を参照してください。
MySQL Enterprise ロックの競合は性能を著しく低下させます。MySQL Network Monitoring and Advisory Service はこの問題を回避するための専門的なアドバイスを提供します。購読を希望する場合は、http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
MyISAM
テーブルでは、テーブルに削除された行がない場合、SELECT
の実行と同時に行を挿入できることに注意してください。
上記がデフォルト作動で、concurrent_inserts
システム変数で制御できます。1
に設定された場合、削除された行を含むMyISAMテーブルで同時挿入が起こります。2
に設定された場合、削除された行があっても、全ての新しい行がテーブル後部に付加されることで同時挿入が強制的に行われます。???を参照してください。
同時挿入が使用できる状況下では、INSERT
ステートメントのDELAYED
修飾子を使用する必要はほとんどありません。項12.2.4.2. 「INSERT DELAYED
構文」を参照してください。
バイナリログを使用している場合、同時挿入はCREATE
... SELECT
もしくはINSERT ...
SELECT
ステートメントの一般的な挿入に変換されます。このことで、バックアップ演算中にログを適用することでテーブルの正確なコピーを再作成することができます。
LOAD DATA
INFILE
で同時挿入(つまり、途中に空きブロックを含まない)の条件を満たすMyISAM
テーブルを使用してCONCURRENT
を指定する場合、他のスレッドはLOAD
DATA
実行中にテーブルからデータを取得することができます。このオプションを使用することで、たとえ他のスレッドが同時にテーブルを使用しているとしても、LOAD
DATA
のパフォーマンスに幾分かの影響が与えられます。
MySQLはローデータとインデックスデータを別のファイルに格納します。その他のデータベースの多く(ほとんど)は、ローデータとインデックスデータが同じファイルに混在しています。現在の非常に多くのシステムで MySQL の選択のほうが優れていると確信しています。
行データの格納方法には、各カラムの情報を独立した領域に格納する方法もあります(例: SDBM、Focus など)。これは、複数のカラムにアクセスするすべてのクエリでパフォーマンスに影響を及ぼします。パフォーマンスは複数のコラムへのアクセスを開始するとただちに低速化するため、このようなモデルは汎用データベースには適さないと確信しています。
一般的にインデックスとデータが一緒に格納されている場合も多くあります(Oracle、Sybase などの場合)。この場合は、レコード情報をインデックスのリーフページで検索します。このレイアウトで優れている点は、多くの場合インデックスのキャッシュ方法次第でディスクの読み取りを節約できることにあります。このレイアウトの欠点は以下のとおりです。
データの取得時にインデックス全体を読み取る必要があるため、テーブルスキャンの速度が大幅に下がる。
クエリでデータを取り出す際にインデックステーブルのみの使用ができない。
ノードからインデックスを複製する必要があるため(レコードはノードに格納できないことによる)、大量の領域が消費される。
削除があるとテーブルの速度が次第に低下する(通常、削除ではノードのインデックスが更新されないため)。
インデックスデータのみのキャッシュが困難である。
最も基本的な最適化の 1 つにデータ(およびインデックス)が占めるディスク領域を可能な限り少なくすることがあります。これで、ディスクの読み取りが高速化し、使用メモリも一般に減少するため、大幅な改善が図れます。カラムが小さければインデックス作成で消費されるリソースも少なくなります。
MySQL では多様なテーブル型とレコード形式がサポートされます。 適切なテーブル形式を選択することで、パフォーマンスを大幅に改善できます。章?13. ストレージエンジンとテーブルタイプを参照してください。
ここで紹介する技法を使用すると、テーブルのパフォーマンス改善とストレージ領域の最小化を図ることができます。
できる限り効率性の高い(最小)の型を使用する。MySQL
にはディスク領域とメモリを節約できる専用の型がある。
可能な場合は、小さなテーブルの取得には小さな整数型を使用する。たとえば、INT
より、25%スペース使用量の少ないMEDIUMINT
のほうが適している場合もしばしばあります。
できる限り、カラムに NOT
NULL
を宣言します。これですべてが高速化され、1
カラム当たり 1
ビットを節約できます。アプリケーションで実際に
NULL
が必要な場合は、必ず使用する必要があるため、注意が必要です。デフォルトですべてのカラムにこれを設定することは避けます。
MyISAM
テーブルでは、可変長カラム(VARCHAR
、TEXT
、あるいはBLOB
など)がまったくない場合は固定長レコード形式を使用します。これで速度が上りますが、領域の消費も増えます。詳しくは項13.4.3. 「MyISAM
テーブルストレージフォーマット」を参照してください。たとえCREATE
TABLE
オプションROW_FORMAT=FIXED
が使用されたVARCHAR
カラムがあっても、固定長行が必要な場合のヒントが得られます。
InnoDB
はコンパクトストレージフォーマットを使用します。MySQL
5.0.3以前のバージョンでは、InnoDB
行は固定サイズカラムにたいしても、カラム数やカラム長さといった冗長な情報を含みます。
デフォルトでは、コンパクトフォーマット(ROW_FORMAT=COMPACT
)でテーブルが作成されます。MySQLの旧バージョンにダウングレードをしたい場合、ROW_FORMAT=REDUNDANT
で古いフォーマットを要求できます。
コンパクトInnoDB
フォーマットはUTF8データを含むCHAR
カラムがどのように格納されるかも変更します。最長UTF8エンコードキャラクタが3バイトであることを前提に、ROW_FORMAT=REDUNDANT
では、UTF-8
CHAR(
は3×
N
)N
バイトを使用します。多くの言語は主にシングルバイトUTF-8キャラクタを用いてかかれるため、固定保存長はスペースを無駄に使用します。ROW_FORMAT=COMPACT
フォーマットでは、
InnoDB
はN
から3×
N
バイトのストレージ可変容量をこれらカラムに割り当てます(必要であればトレールスペースを取り除いて行います)。最小のストレージ長は、ある場合に適切な更新が行われるように、N
バイトとして保持されます。
テーブルのプライマリインデックスを可能な限り短くします。これで、レコードの識別が容易になり効率化が図れます。
インデックスの作成は必要なものだけに限定します。インデックスは取り出しに優れていますが、高速保存が必要な場合は適されません。カラムの組み合わせを使用してテーブルを検索し、テーブルにアクセスする場合がほとんどであれば、インデックスを作成します。インデックスの最初の部分は、最も使用頻度の高いカラムにする必要があります。テーブルの検索時に、常に常に多数のカラムを使用する場合は、より重複しているカラムを先に使用するとインデックスの圧縮を改善できます。
文字列の最初の数文字に、一意のプリフィックスがあるカラムが多い場合は、このプリフィックスのみをインデックス化したほうがよりよくなります。MySQL
はカラムの最も左側の部分インデックス作成をサポートします(項12.1.7. 「CREATE INDEX
構文」を参照してください)。短いインデックスの速度が速い理由は、占有ディスク領域が小さいことだけではなく、インデックスキャッシュでのヒットが多くなり、所要ディスクシークが少なくなることにもよります。項6.5.2. 「サーバパラメータのチューニング」を参照してください。
状況によっては、スキャンの頻度が高いテーブルを 2 つに分割したほうが有利な場合もあります。これは特に、動的テーブルで、テーブルスキャンの際に対応する行の検索に小さな静的テーブルの使用が可能である場合にあてはまります。
MySQL
の全てのカラム型にはインデックスを張ることができます。SELECT
操作のパフォーマンスの改善には、対応するカラムにインデックスを使用することが最善の方法です。
テーブルあたりの最大インデックス数とインデックスの最大長は、ストレージエンジンごとに定義されます。章?13. ストレージエンジンとテーブルタイプを参照してください。ストレージエンジンのすべてで、1 テーブルあたり 16 以上のインデックスと 256 バイト以上のインデックス長がサポートされます。大抵のストレージエンジンでは、インデックス最大長がより高く設定されています。
インデックス指定で
構文を用いて、文字列カラムの最初のcol_name
(N
)N
キャラクタのみを使用したインデックスを作成できます。このようにカラム値のプレフィックスのみをインデックス化すると、インデックスファイルをかなり小さくすることができます。BLOB
もしくはTEXT
カラムにインデックスを張る場合、インデックスに対してプレフィックス長を指定しなければなりません。例
:
CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10)));
プレフィックスは、最大1000バイト長(InnoDB
テーブルに対しては767バイト)まで可能です。プレフィックスの最大長はバイトで評価されます。一方、CREATE
TABLE
ステートメント内のプレフィックス長は文字数として解釈されます。プレフィックス長を、マルチバイトキャラクタセットを使用するカラムに対して指定するときこのことを考慮に入れなければいけません。.
FULLTEXT
インデックスの作成も可能です。これらはフルテキスト検索に使用されます。FULLTEXT
インデックスをサポートするのはMyISAM
ストレージエンジンだけで、CHAR
、VARCHAR
、そしてTEXT
カラムについてのみサポートされます。インデックスの作成は常にカラム全体を対象として、先頭部分(プリフィックス)のインデックス化は行われません。詳細については、項11.7. 「全文検索関数」をご参照ください。
空間データ型上でインデックスの作成もできます。現在、MyISAM
のみが空間型R-treeインデックスをサポートしています。他のストレージエンジンは空間型のインデックス化にB-treesを使用します。
(ただしARCHIVE
やNDBCLUSTER
といった空間型インデックスをサポートしないものを除きます)
MEMORY
ストレージエンジンはデフォルトでHASH
インデックスを使用しますが、BTREE
インデックスもサポートしています。
MySQLでは複数のカラムに対するインデックスを作成できます。インデックスは最大 15 カラムで構成できます。.特定のデータ型に関しては、カラムのプリフィックスをインデックス上で検索することができます。(項6.4.3. 「カラムインデックス」を参照してください)
複数カラムのインデックス(複合インデックス)は、インデックス化されたカラムの値を連結することによって生成された値が含まれ、ソート化された配列と見なすことができます。
MySQL
では、WHERE
節内でインデックスの第
1
カラムを指定する場合、他のカラムの値を指定しなくても、クエリが高速化できるように複合インデックスが使用されます。
次のようなテーブルが定義されているとします。
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
の範囲、または last_name
と
first_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';
MySQL でインデックスを使用してクエリパフォーマンスを改善する方法の詳細については、項6.4.5. 「MySQLにおけるインデックスの使用」を参照してください。
インデックスは通常迅速に特定のカラム値の行を検索するのに使用されます。インデックスがない場合、関連する行を検索する場合MySQLは最初の行から始め全テーブルを読まなけれ場なりません。テーブルが大きいほど、コストがかかります。検索しているカラムのインデックスをテーブルが含んでいる場合、MySQLは全てのデータを検索せずに、データファイルの途中でシークポジションを迅速に決定します。テーブルに1000行ある場合、連続して読む場合よりも少なくとも100倍の速度で処理が行われます。行のほとんどにアクセスしなければならない場合、連続して読むほうがより高速です。というのも、これはディスクシークの最小化が行われるためです。
ほとんどのMySQLインデックスは(PRIMARY
KEY
、UNIQUE
、INDEX
、そしてFULLTEXT
)
はB-treesに保存されています。R-treesを使用する空間データ型インデックスは例外で、MEMORY
テーブルでもハッシュインデックスがサポートされています。
文字列のプリフィックスとエンドスペースは自動的に圧縮されます。項12.1.7. 「CREATE INDEX
構文」を参照してください。
一般的に、インデックスは以下のように使用されます。ハッシュインデックスの特徴は(MEMORY
テーブルで使用される)
このセクションの終わりに説明されています。
MySQLはこれらの演算についてインデックスを使用します。
WHERE
節にマッチする行を迅速に検索する場合。
行を考慮に入れない場合。複数のインデックス間を選択できる場合、MySQLは通常最小行数を検索するインデックスを使用します。
結合実行時に、他のテーブルから行を取得する場合。
特定のインデックス化されたカラムkey_col
に対して、MIN()
あるいはMAX()
値を検索する場合。これはインデックス内でkey_col
より前に発生する全てのキーパーツで、WHERE
が使用されているかをチェックするプリプロセッサによって最適化されます。この場合MySQLはkey_part_N
=
constant
MIN()
もしくはMAX()
表現それぞれに対して単一キールックアップを行い、定数で置き換えます。全ての表現が定数で置き換えられた場合、クエリは一度に返されます。例
:
SELECT MIN(key_part2
),MAX(key_part2
) FROMtbl_name
WHEREkey_part1
=10;
使用可能キーの最も左側のプリフィックスでグループ化やソート化が行われる際、テーブルをソートもしくはグループ化する場合に使用します。
(例えば ORDER BY
)全てのキー部分にkey_part1
、key_part2
DESC
が後続する場合、キーは逆の順序で読まれます。項6.2.12. 「ORDER BY
最適化」を参照してください。
データ行を参照せず値を取得するためにクエリが最適化される場合もあります。クエリがあるキーの最も左側のプリフィックスを形成し、かつテーブル内で数値のみのカラムを使用する場合、選択された値は高速化を図るため、インデックスツリーから取得されることもあります。
SELECTkey_part3
FROMtbl_name
WHEREkey_part1
=1
例えば次のSELECT
ステートメントを発行したとします。
mysql> SELECT * FROM tbl_name
WHERE col1=val1
AND col2=val2
;
col1
とcol2
上で複合カラムインデックスが存在する場合、適当な行は直接取得されます。col1
とcol2
に別々のシングルカラムインデックスが存在する場合、オプティマイザはどのインデックスがより少ない行を検索するかを決定することで、最も制限力のあるインデックスを検索します。また、そのインデックスを使用して行を取得します。
テーブルに複合インデックスがある場合、オプティマイザではインデックスの左端の先頭部分のいずれかをレコードの検索に使用できます。たとえば、(col1,
col2, col3)
に 3
カラムのインデックスがある場合、(col1)
、(col1,
col2)
そして(col1, col2,
col3)
に対して、インデックスの検索機能を使用できます。
カラムがインデックスの左端の先頭部分を構成していない場合、MySQL
では、部分インデックスを使用できなくなります。以下のSELECT
ステートメントがあります。
SELECT * FROMtbl_name
WHERE col1=val1
; SELECT * FROMtbl_name
WHERE col1=val1
AND col2=val2
; SELECT * FROMtbl_name
WHERE col2=val2
; SELECT * FROMtbl_name
WHERE col2=val2
AND col3=val3
;
インデックスが(col1, col2,
col3)
に存在する場合、最初のクエリだけがインデックスを使用できます。3つめと4つめのクエリには、インデックス化したカラムが必要ですが、(col2)
と(col2,
col3)
は (col1, col2,
col3)
の左端のプリフィックスではありません。
=
, >
,
>=
、<
,
<=
あるいはBETWEEN
演算子を使用する表現のカラム比較に、B-treeインデックスが使用可能です。LIKE
がワイルドカードキャラクタで始まらない定数文字列の場合、インデックスはLIKE
比較にも使用できます。例えば、以下のSELECT
ステートメントはインデックスを使用します。
SELECT * FROMtbl_name
WHEREkey_col
LIKE 'Patrick%'; SELECT * FROMtbl_name
WHEREkey_col
LIKE 'Pat%_ck%';
最初のステートメントでは、'Patrick'
<=
を含む行のみ考慮されます。2つ目のステートメントでは、key_col
<
'Patricl''Pat'
<=
を含む行のみ考慮されます。
key_col
<
'Pau'
以下のSELECT
ステートメントはインデックスを使用しません。
SELECT * FROMtbl_name
WHEREkey_col
LIKE '%Patrick%'; SELECT * FROMtbl_name
WHEREkey_col
LIKEother_col
;
最初のステートメントでは、LIKE
値はワイルドカードキャラクタで始まります。2つ目のステートメントでは、LIKE
値は定数ではありません。
もし... LIKE
'%
そして文字列
%'文字列
は3文字より長い場合、MySQLはTurbo
Boyer-Moore
アルゴリズムを使用して、文字列のパターンを初期化してから、このパターンを使用して検索をすばやく実行します。
を使用した検索では、col_name
IS
NULLcol_name
にインデックスが張られている場合にインデックスが使用されます。
WHERE
節内の全ての
AND
にかかっていないインデックスは、クエリの最適化に使用されません。言い換えると、インデックスの使用を可能にするには、インデックスの先頭部分がすべての
AND
グループで使用されている必要があります。
次のWHERE
節ではインデックスが使用されます。
... WHEREindex_part1
=1 ANDindex_part2
=2 ANDother_column
=3 /*index
= 1 ORindex
= 2 */ ... WHEREindex
=1 OR A=10 ANDindex
=2 /* optimized like "index_part1
='hello'" */ ... WHEREindex_part1
='hello' ANDindex_part3
=5 /* Can use index onindex1
but not onindex2
orindex3
*/ ... WHEREindex1
=1 ANDindex2
=2 ORindex1
=3 ANDindex3
=3;
次の
WHERE
節ではインデックスが使用されません。
/*index_part1
is not used */ ... WHEREindex_part2
=1 ANDindex_part3
=2 /* Index is not used in both parts of the WHERE clause */ ... WHEREindex
=1 OR A=10 /* No index spans all rows */ ... WHEREindex_part1
=1 ORindex_part2
=10
MySQL
では利用可能な場合でもインデックスが使用されない場合があることに注意してください。この一例として、インデックスの使用によって、MySQL
がテーブルの 30%
を超えるレコードにアクセスする必要が生じる場合が挙げられます(この場合は、必要なシークが大幅に減少するため、テーブルスキャンのほうが高速になる可能性が高くなります)。
ただしこのクエリに、レコードの一部のみを取り出す
LIMIT
が使用されている場合、結果で返される少数のレコードを迅速に検索できるため、MySQL
はインデックスを使用します。
ハッシュインデックスは先ほど挙げたインデックスとは特徴が異なります。
=
or
<=>
演算子を使用する等価比較にのみ使用されます。(ただし非常に
高速です)<
のように値の範囲を検索する比較演算子には使用されません。
オプティマイザはORDER
BY
オペレーション速度を上げるためにハッシュインデックスを使用することはできません。(このようなインデックスは順序どおりに次のエントリーを検索することはできません)
MySQLは2つの値の間にある行の数を判別することはできません。(どのインデックスを使用するかを決定する上、範囲オプティマイザによって使用されます)MyISAM
テーブルをハッシュインデックスを含むMEMORY
テーブルに変換した場合、これは一部のクエリに影響を与えるかもしれません。
行の検索には自然キーのみが使用できます。(B-tree インデックスでは、どのキーの左端の先頭部を使用しても行を検索できます。)
MySQL Enterprise どのインデックスが要求されるか、あるいは最も効率的な?実際のテーブル使用率が指標となるか、正確に推測するのは困難です。MySQL Network Monitoring and Advisory Service はこの問題に関する専門的なアドバイスを提供します。詳細は http://www-jp.mysql.com/products/enterprise/advisors.html をご覧ください。
ディスクI/Oを最小化するために、MyISAM
ストレージエンジンは多数のデータベースマネージメントシステムで使用される戦略を利用します。メモリ内で最も頻繁にアクセスされるテーブルブロックを保持するために、キャッシュメカニズムが使用されます。
インデックスブロックには、key cache (あるいはkey buffer)という特別な構造が保持されています。その構造には最も頻繁に使用されるインデックスがおかれた多数のブロックバッファが含まれます。
データブロックに対して、MySQLは特別なキャッシュを使用しません。代わりに、ネイティブオペレーティングシステムファイルシステムキャッシュに依存します。
このセクションでは最初にMyISAM
キーキャッシュの基本的な演算について説明しています。キーキャッシュパフォーマンスを向上させる特徴や、キャッシュオペレーションをよりよくコントロールできる特徴について、説明されています。
複合スレッドはキャッシュを同時にアクセスできます。
複合キーキャッシュのセットアップおよび特定のキャッシュに対するテーブルインデックスの割り当てが可能です。
キーキャッシュのサイズを制限するには、key_buffer_size
システム変数を使用します。この変数がゼロにセットされた場合、利用できるキーキャッシュはありません。key_buffer_size
値が小さすぎてブロックバッファの最小値が割り当てられない場合、キーキャッシュも使用できません(8)。
MySQL Enterprise
key_buffer_size
のサイズを最適化するための詳しいアドバイスについては、MySQL
Network Monitoring and Advisory
Serviceを購読してください。http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
キーキャッシュが作動していない場合、インデックスファイルはオペレーティングシステムによるネーティブファイルシステムバッファのみ使用してアクセスされます。(つまり、テーブルインデックスブロックはテーブルデータブロック利用と同様の方法でアクセスされます)
インデックスブロックはMyISAM
インデックスファイルにアクセスする隣接ユニットです。.通常、インデックスブロックのサイズは、インデックスB-treeのノードサイズと等価です。(インデックスはB-tree
データ構造を使用してディスク上で表現されます。ツリーの元にあるノードはリーフノードです。リーフノードの上層にあるノードは非リーフノードです。)
キーキャッシュ構造内のブロックバッファは全て同サイズです。このサイズは、テーブルインデックスブロックサイズと比べて、等しい/大きい/小さい場合があります。通常これら二つの値のうち一方は、他方の複合となっています。
あるテーブルインデックスブロックのデータアクセスが必要な場合、サーバはまずキーキャッシュのどれかのブロックバッファ内で利用可能かどうかをまずチェックします。可能である場合、サーバはディスク上ではなく、キーキャッシュのデータにアクセスします。つまり、ディスクからではなく、キャッシュから読むかもしくはキャッシュに書きこみます。でなければ、異なるテーブルインデックスブロックを含むキャッシュブロックバッファを選択し、そのデータを要求されたテーブルインデックスブロックのコピーと置き換えます。新しいインデックスブロックがキャッシュ内におかれるとすぐ、インデックスデータはアクセス可能となります。
たまたま置き換えのために選択されたブロックが改良されていた場合、ブロックは「汚染されたモノ」とみなされます。この場合、置き換えられる前に、内容は元のテーブルインデックスにフラッシュされます。
通常サーバはLRU (Least Recently Used)戦略に従います。置換のためブロックを選択した場合、最近使用した中で最も初期に使用されたインデックスブロックを選択します。この選択を簡単にするために、キーキャッシュモジュールは、使用された全てのブロックの特別なキュー(LRU chain) を保持します。ブロックがアクセスされた場合、キューの最後尾に置かれます。ブロックを置換する必要がある場合、キューの最前部にあるブロックは、最近使用した中で最も初期に使用されたものであり、かつ最初の除去対象となります。
スレッドはキーキャッシュバッファに同時にアクセス可能であり、以下の条件に従います。
更新されていないバッファは複合スレッドによってアクセス可能です。
更新中のバッファは更新が完了するまでスレッドを待機させます。
複合スレッドは、お互いに衝突しない限り、キャッシュブロック置換を引き起こす要求をします(つまり、異なるインデックスブロックを必要とする限り、異なるキャッシュブロックを置換します。)
キーキャッシュの共有アクセスによって、サーバ処理能力が大幅に向上します。
キーキャッシュへの共有アクセスはパフォーマンスを向上させますが、スレッド間の競合を完全には削除しません。キーキャッシュバッファへアクセスするためのコントロール構造をめぐって競合が行われます。キーキャッシュアクセス競合をさらに軽減するために、MySQLは複合キーキャッシュも提供しています。この特性によって、異なるキーキャッシュに対して各テーブルインデックスの割り当てが可能となります。
複合キーキャッシュがある場合、サーバは既存のMyISAM
テーブルに対してクエリ処理を行う際に、どのキャッシュを使用すべきか知らされていなければなりません。デフォルトでは、全てのMyISAM
テーブルインデックスはデフォルトキーキャッシュ内にキャッシュされます。テーブルインデックスを特定のキーキャッシュに割り当てるには、CACHE
INDEX
ステートメントを使用してください。(項12.5.5.1. 「CACHE INDEX
構文」を参照してください。)例えば、以下のステートメントはt1
、t2
、そして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
はキャッシュ部位です。構造キーキャッシュシステム変数に対して使用される構文の詳細については、項4.2.4.1. 「構造化システム変数」を参照してください。
デフォルトではテーブルインデックスは、サーバ起動時に、メイン(デフォルト)キーキャッシュに割り当てられます。.キーキャッシュが破壊された場合、それに割り当てられた全てのインデックスはデフォルトキーキャッシュに再度割り当てられます。
サーバが込んでいる場合、以下の3つのキーキャッシュを使用する戦略をお勧めします。
全てのキーキャッシュに割り当てられたスペースの20%以上を占める「hot」キーキャッシュ。 検索に使用される頻度が高く、かつ更新されていないテーブルに対してはこれを使用してください。
全てのキーキャッシュに割り当てられたスペースの20%以上を占める「cold」キーキャッシュ。 このキャッシュは、テンポラリテーブルのような中位の集中的に改良されたテーブルに使用してください。
キーキャッシュスペースの60%以上を占める「warm」キーキャッシュ。これをデフォルトで全ての他のテーブルに使用されるように、このキーキャッシュをデフォルト設定してください。
上記3つのキーキャッシュを使用する理由のひとつの利点は、1つのキーキャッシュ構造は他二つのキーキャッシュへのアクセスを阻害しない点です。あるキャッシュに割り当てられたテーブルにアクセスするステートメントは、他のキャッシュに割り当てられたテーブルにアクセスするステートメントとは競合しいません。.パフォーマンス向上には他の理由もあります。
hotキャッシュはクエリの取得にのみ使用されるため、内容は決して改良されません。結果、インデックスブロックがディスクから引き抜かれなければならない場合でも、置換のために選択されたキャッシュブロック内容は最初にフラッシュされる必要はありません。
キャッシュに割り当てられたインデックスにとって、インデックススキャンを必要とするクエリがなければ、インデックスB−treeの非リーフノードに一致するインデックスブロックがキャッシュに残っている可能性が高くなります。
テンポラリテーブルに対する最も頻度が高い更新オペレーションは、更新ノードがキャッシュ内にあり、最初にディスクから読まれる必要がない場合、処理速度がはるかに向上します。テンポラリテーブルのインデックスサイズがcoldキーキャッシュのサイズと同等の場合、更新ノードがキャッシュ内にある可能性が高くなります。
CACHE
INDEX
はテーブルとキーキャッシュの関連性を設けますが、サーバが再起動するたびにその関連性は失われます。サーバが再起動するたびに関連性を有効にしたい場合、オプションファイルを使用することで実行できます。キーキャッシュをコンフィギャする変数設定と、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
MySQL Enterprise
my.cnf/my.ini
オプションファイルに対する最適なコンフィギャを行う際のアドバイスを得るには、MySQL
Network Monitoring and Advisory
Serviceを購読してください。実際のテーブル使用量に基づいて行うことをお勧めします。追加情報については
http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
サーバが再起動するたびにmysqld_init.sql
でのステートメントは実行されている。ファイルはラインごとの1つのsqlのステートメントを含むべきである。次の例はhot_cache
と
cold_cache
に複数のテーブルをそれぞれ割り当てる。
CACHE INDEX db1.t1, db1.t2, db2.t3 IN hot_cache CACHE INDEX db1.t4, db2.t5, db2.t6 IN cold_cache
デフォルトでは、キーキャッシュマネージメントシステムは除去されるキーキャッシュブロックの選択に、LRU戦略が用いられますが、ミッドポイント挿入戦略と呼ばれるより複雑なメソッドもサポートします。
ミッドポイント挿入戦略を使用している間は、LRU
チェーンは2つに分かれます。hotサブチェインとwarmサブチェイン。2部位間の分け目は固定されていませんが、warm部位が「短すぎない」ように、最低でもkey_cache_division_limit
%のキーキャッシュブロックを含むようにします。key_cache_division_limit
は構成されたキーキャッシュ変数の構成要素であるため、その値はキャッシュ毎に設定可能なパラメータとなります。
インデックスブロックがテーブルからキーキャッシュに読まれる際、warmサブチェインの後尾に置かれます。ある特定のヒット数後(ブロックへのアクセス後)、hotサブチェインに昇格されます。現在では、ブロックを昇格させるために必要なヒット数は(3) 全てのインデックスブロックで同じです。
サブチェインに昇格されたブロックはチェインの後部に置かれます。それからブロックはこのsubチェイン内をめぐります。もしブロックが十分長い時間subチェインの前部にとどまった場合、warmチェインに降格されます。この時間はキーキャッシュのkey_cache_age_threshold
構成要素値によって決定されます。
境界値は、N
ブロックを含むキーキャッシュにとって、最後の
ヒットでアクセスされなかったhotサブチェイン前部のブロックは、warmサブチェインの前部に移動されます。そこから除去の第一候補となります。というのも、置換ブロックは常にwarmサブチェインの前部から取得されるからです。
N
× key_cache_age_threshold /
100
ミッドポイント挿入戦略によってキャッシュ内に常に高価値なブロックを保持することができます。プレーンLRU
戦略の使用を好む場合は、key_cache_division_limit
値をデフォルトである100に設定しておいてください。.
ミッドポイント挿入戦略によって、インデックススキャンで必要とされるクエリの実行が、高価値なハイレベルB−treeノードと一致するインデックスブロックを、効果的にキャッシュから押し出される際のパフォーマンスを向上させます。これを回避するには、100以下に設定されたkey_cache_division_limit
を用いた、ミッドポイント挿入戦略を使用しなければなりません。この結果、ノードに頻繁にヒットする変数は、インデックススキャンオペレーション中もhotサブチェインに保存されます。
全てのインデックスのブロックを保持するためのキーキャッシュ内に十分なブロックがある場合、もしくは少なくとも非リーフノードと一致するブロックがある場合、使用する前に、インデックスブロックでキーキャッシュをプレロードするのは理にかなっています。プレロードによって、テーブルインデックスブロックを最も効果的な方法でキーキャッシュバッファ内におくことができます。ディスクから連続してインデックスブロックを読む方法。
プレロードなしでは、ブロックは、クエリの必要に応じてキーキャッシュ内におかれます。ブロックはキャッシュ内にとどまりますが、バッファは足りているため、ディスクからランダムかつ不連続に取得されます。
インデックスをキャッシュにプレロードするには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
ステートメントを用いてキーキャッシュに割り当てられた場合、プレロードはインデックスブロックをキャッシュ内に置きます。もしくは、インデックスはデフォルトキーキャッシュにロードされます。
key_cache_block_size
変数を使用して、個々のキーキャッシュのブロックバッファサイズを指定することができます。.これによって、インデックスファイルに対して、I/O
オペレーションパフォーマンスのチューニングが許可されます。
I/O オペレーションに対する最適なパフォーマンスは、readバッファサイズがネーティブオペレーティングシステムI/Oバッファサイズと同等の場合に向上します。しかし、キーノードサイズをI/O バッファサイズと等しく設定しても、最適な全体的パフォーマンスを常に保証するわけではありません。ビッグリーフノードを読む場合、サーバは多数の不要なデータを吸収し、実質的に他のリーフノードが読まれるのを阻止します。
現在、テーブル内でインデックスブロックのサイズを変更することはできません。このサイズは、.MYI
インデックスファイルが作成される際に、サーバによって設定されます。これは、テーブル定義内に存在するインデックスのキーサイズに依存します。ほとんどの場合、I/O
バッファサイズと等価に設定されます。
キーキャッシュはパラメータ値を更新することで、随時、再構築されます。例 :
mysql> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024;
key_buffer_size
またはkey_cache_block_size
キーキャッシュ構成部位に構成部位の現在値と異なる値を割り当てた場合、サーバはキャッシュの旧構造を破壊し、新しい値に基づいた新構造を作成します。キャッシュに汚染されたブロックが存在する場合、サーバはキャッシュを破壊/再作成する前にディスクにそれを保存します。他のキーキャッシュパラメータを変更した場合再構築は起きません。
キーキャッシュを再構築する場合、サーバはディスクの汚染されたバッファ内容を最初にフラッシュします。その後、キャッシュ内容は無効となります。しかし、再構築はキャッシュに割り当てられたインデックス使用を必要とするクエリをブロックしません。その代わり、サーバはネーティブファイルシステムキャッシングを使用するテーブルインデックスに直接アクセスします。ファイルシステムキャッシュはキーキャッシュを使用した場合ほど、効率が良くありません。クエリの実行がなされますが、速度の低下が予期されます。キャッシュが再構築された後、キャッシュインデックスが割り当てられる毎に有効となり、インデックスのファイルシステムキャッシングの使用が停止します。
ストレージエンジンはオプティマイザに使用されるテーブルの統計を集めます。テーブル統計は値グループに基づき、値グループは同じキープリフィックス値を持つ行のセットです。オプティマイザの目的に関しては、標準グループサイズは重要な統計となります。
MySQLは標準値グループサイズを以下のように使用します。
各ref
アクセスごとに読まれなければならない行の数を推測
一部 joinが生成する行の数を推測するため:つまり、このフォームのオペレーションが生成する行の数になります。
(...) JOINtbl_name
ONtbl_name
.key
=expr
インデックスの標準値グループサイズが増加する毎に、その2つの目的に関してはインデックスは役に立たなくなります。というのも、ルックアップあたりの標準行数は増加するためです。最適化の目的のためにインデックスが有用であるため、各インデックス値はターゲットとするテーブル内の行の数は少なくなければいけません。既存のインデックス値が大量の行を生成する際、インデックスは役に立たなくなり、MySQLはそれを使用しなくなります。
標準値グループサイズはテーブルカーディナリティと関連しています。これは値グループの数を指します。SHOW
INDEX
ステートメントはN
/S
に基づくカーディナリティ値を表示します。これはN
がテーブル内の行数であり、S
が標準値グループサイズとなります。その比率はテーブル内の値グループの概数を生成します。
<=>
比較演算子に基づくjoinに対して、
NULL
は他の値と同様に扱われます。
NULL <=>
NULL
、ちょうど
が他のN
<=>
N
N
であるように。
ただし、=
演算子に基づくjoinに対して、NULL
は非NULL
値と異なります。
はexpr1
=
expr2
expr1
あるいはexpr2
(もしくは両方)がNULL
である場合、適切ではありません。このことは、フォーム
の比較に対するtbl_name.key
=
expr
ref
アクセスに影響を与えます。expr
の現在値がNULL
の場合、MySQLはテーブルにアクセスしません。というのも、比較は適切でないからです。
=
比較に対して、NULL
値の数がテーブル上にいくらであっても、関係ありません。最適化のためには、関連する値は非NULL
値グループの標準サイズにします。ただし、現在MySQLは標準サイズを収集もしくは使用することを許可しません。
MyISAM
テーブルに対して、myisam_stats_method
システム変数を使用することで、テーブル統計コレクションが管理されます。この変数には2つの可能値があり、これらは以下のとおり異なります。
myisam_stats_method
がnulls_equal
の場合、全てのNULL
値は等価として扱われます。(つまり、それらは全てシングル値グループを形成します。)
NULL
値グループサイズは標準非NULL
値グループサイズよりはるかに大きくなります。このメソッドは標準値グループサイズを大きくゆがませます。これによりオプティマイザから見たインデックスは非NULL
値を検索するjoinに対して役に立たないように見えます。結果的に、nulls_equal
メソッドはオプティマイザにref
アクセスに対してインデックスを使用すべきときでも使用しないままにする可能性があります。
myisam_stats_method
がnulls_unequal
の時、NULL
値は等価として扱われません。代わりに、各NULL
値はサイズ1の別値グループを生成します。
NULL
値が多い場合、このメソッドは標準値グループサイズを小さくゆがませます。もし標準非NULL
値グループサイズが大きい場合、NULL
値をサイズ1のグループとして取り扱うと、オプティマイザに非NULL
値を探させるjoinのインデックス値を過大評価します。結果的に、nulls_unequal
メソッドは、他のメソッドの方が適しているにもかかわらず、オプティマイザにこのインデックスをref
ルックアップに使用させることがあるかもしれません。
=
よりも<=>
を使用するjoinを多くユーザが使用している場合、NULL
値は比較では特別ではなく、1つのNULL
は他のものと等価です。この場合、nulls_equal
は適切な統計メソッドです。
myisam_stats_method
システム変数はグローバルとセッション値を保持しています。グローバル値の設定はMyISAM
テーブルに対するMyISAM
統計収集に影響を与えます。
セッション値を設定することで、現在のクライント接続のみに対する統計収集に影響が与えられます。これは、既存のメソッドで他のクライントに影響を与えず、テーブルの統計をセッション値myisam_stats_method
に設定することで再生することが可能です。
テーブル統計を再生するために、以下のメソッドを使用してください。
myisam_stats_method
を設定し、CHECK
TABLE
ステートメントを発行します。
myisamchk
--stats_method=method_name
--analyzeを実行します。
テーブルを変更し統計を旧値とし(例えば、行を挿入し、それから削除します)、そしてmyisam_stats_method
を設定しANALYZE
TABLE
ステートメントを発行します。
myisam_stats_method
の使用に関する警告。
以下で説明されているように、強制的にテーブル統計を明示的に収集させることができます。ただし、MySQLも自動的に統計を収集する可能性があります。例えば、テーブルステートメント実行時、ステートメントの中にはテーブルを改良するものもあり、MySQLは統計を収集する可能性があります。(例えば、これは大量挿入や削除時、もしくはALTER
TABLE
ステートメントの際に起こる可能性があります。)これが生じた場合、統計はmyisam_stats_method
がその時々で持っている値を使用して統計が収集されます。よって、あるメソッドで統計を収集したがmyisam_stats_method
が自動的にテーブル統計が収集された後他のメソッドに設定されている場合、他のメソッドが使用されます。
既存のMyISAM
テーブルに対してどのメソッドが統計生成に使用されたかを知ることはできません。
myisam_stats_method
はMyISAM
テーブルにのみ適用されます。他のストレージエンジンはテーブル統計を収集するメソッドが1つしかありません。通常は、nulls_equal
メソッドに近いです。
mysqladmin statusを実行すると、以下の出力が表示されます。
Uptime: 426 Running threads: 1 Questions: 11082 Reloads: 1 Open tables: 12
テーブルが 6 つしかない場合に Open
tables
値が 12
と表示されることに、当惑する場合もあります。
MySQL is MySQL
はマルチスレッド化されているため、多数のクライアントが同時に同じものに対してクエリを使用することがあります。2
つのクライアントスレッドで 1
つのファイルに異なるステータスが発生する問題を最小にするため、同時に実行しているスレッドがそれぞれで無関係にテーブルを開きます。これはメモリの消費を増やしますが、一般にパフォーマンスは向上します。MyISAM
テーブルの場合は、テーブルを開いたそれぞれのクライアントにデータファイルに対するファイル記述子が必要になります。このテーブル型では、インデックスファイルに対するファイル記述子がすべてのスレッドで共有されます。
table_open_cache
、max_connections
、および
max_tmp_tables
サーバ変数は、サーバが開いた状態で保持できるファイルの最大数に影響します。
これらの値の 1 つ以上を増加すると、OS
によって制限されている 1
プロセスが持つことができるファイル記述子の最大数まで実行が可能になります。システムごとに方法は多様ですが、多数のオペレーティングシステムでオープンファイルの制限値を上げることができます。
制限値の拡大が可能かどうかの判定、およびその実行方法については、使用するオペレーティングシステムの文書を参照してください。
table_open_cache
は
max_connections
と関係します。たとえば、同時接続数が
200 の場合、最低 200 ×
のテーブルキャッシュサイズが必要です。この
N
N
は結合で使用するテーブル数の最大値を示します。また、テンポラリテーブルとファイル用のファイル記述子も必要です。
あなたのオペレーティングシステムが
table_open_cache
の設定に従ったファイル記述子の数を処理できることを確認してください。table_open_cache
の設定が高すぎると、MySQL
がファイル記述子を使い果たして接続を拒否し、クエリの実行ができなくなり、信頼性が大幅に低下します。また、MyISAM
ストレージエンジンでは1つのテーブルごとに
2
つのファイル記述子が必要であることも考慮に入れる必要があります。--open-files-limit
スタートアップオプションを使用すると、mysqldで使用可能なファイル記述子数を拡大できます。
.項B.1.2.17. 「'File
' Not Found and
Similar Errors」を参照してください。
オープンテーブルのキャッシュは、table_open_cache
エントリレベルに保持されます。デフォルト値は
64
です。これは、--table_open_cache
オプション
mysqld
に与えることで変更できます。mysqldは一時的にさらに多くのテーブルを開いてクエリの実行を実現することがあります。
MySQL Enterprise
table_cache
設定が低すぎると、パフォーマンスに悪影響を及ぼします。この変数の最適値に関する詳しいアドバイスについては、MySQL
Network Monitoring and Advisory
Serviceを購読してください。追加情報については
http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
以下の状況では、MySQLは使用されていないテーブルが閉じられ、テーブルキャッシュから削除されます。
キャッシュが満杯のときに、キャッシュにないテーブルをスレッドが開こうとした場合。
キャッシュに
table_open_cache
を超えるエントリがあり、あるスレッドがテーブルの使用を終えた場合。
テーブルフラッシュオペレーションが起きたとき。いずれかのユーザが
FLUSH TABLES
、mysqladmin
flush-tablesまたは mysqladmin
refreshを実行した場合。
テーブルキャッシュが満杯になると、サーバでは以下の手順に従って使用するキャッシュエントリを割り当てます。
現在使用中でないテーブルは、最後に使用した時が古いものから順にリリースされる。
キャッシュが満杯でリリース可能なテーブルがなく、新たにテーブルを開く必要がある場合は、必要に応じてキャッシュが一時的に拡張される。
キャッシュが一時的に拡張された状況で、使用中のテーブルが使用されなくなったときは、そのテーブルが閉じられ、キャッシュからリリースされる。
テーブルは同時アクセスのそれぞれで開かれます。つまり、2
つのスレッドで同じテーブルにアクセスする場合、または
1 つのスレッドが同一クエリでテーブルに 2
回アクセスする場合(テーブルを同一テーブルに結合する場合など)は、テーブル
を 2 回開く必要があることになります。
いずれかのMyISAM
テーブルを最初に開く際に2
つのファイル記述子が割り当てられ、その後さらにそのテーブルを使用する場合はファイル記述子が
1
つのみ割り当てられます。最初のオープン時の
2
つめの記述子は、インデックスファイルに使用され、この記述子はすべてのスレッドで共有されます。
HANDLER
ステートメントを使用してテーブルを開く場合は、専用テーブルオブジェクトがスレッドに割り当てられます。
このテーブルオブジェクトは他のスレッドと共有されず、スレッドが
tbl_name
OPENHANDLER
を呼び出すか、スレッドが終了するまで閉じられません。この場合はテーブルがテーブルキャッシュに戻されます(キャッシュが満杯でない場合)。
項12.2.3. 「tbl_name
CLOSEHANDLER
構文」を参照してください。
テーブルキャッシュが小さすぎるかどうかは、mysqldの
Opened_tables
変数のチェックで確認できます。
mysql> SHOW GLOBAL STATUS LIKE 'Opened_tables';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Opened_tables | 2741 |
+---------------+-------+
たとえ多くの FLUSH
TABLES
を実行していない場合でも、この値が非常に大きい場合は、テーブルキャッシュサイズを拡張する必要があります。
項4.2.3. 「システム変数」、項4.2.5. 「ステータス変数」
を参照して下さい。
システムレベルの要素は、その一部を初期段階に決定する必要があるため、この話から始めます。これに該当しない場合は、システムを大きく変えることが重要でないのであれば、このセクションは簡単に目を通せば十分です。ただし、このレベルで変更を行うことでどの程度改善できるのかを自覚しておくことは必ず役に立ちます。
使用するオペレーティングシステムは非常に重要です。複数 CPU のコンピュータを使用するなら、Solaris(スレッド実装機能が優れている)または Linux(2.2 カーネルの SMP サポートが優れている)が良いでしょう。 また、旧バージョンの Linux カーネルのデフォルトには 2G ファイルサイズの制限があります。このカーネルで 2G より大きいファイルがどうしても必要な場合は、ext2 ファイルシステムの LFS (Large File System)パッチを入手する必要があります。 これ以外の ReiserFS や XFS などには 2G の制限がありません。
MySQL を本番稼働させていないため、可能であれば選択前に候補のプラットフォームのテストを実行することを推奨します。
他のヒント:
RAM が十分にある場合は、スワップデバイスすべてを削除できる。オペレーティングシステムによっては、空きメモリがある場合でもスワップデバイスが使用されることがある。
外部ロックを回避する。MySQL
4.0以降、外部ロックはデフォルトで全てのシステムで使用不可能となっています。--external-locking
と--skip-external-locking
オプションは外部ロックの使用可能および不可能を明示的に宣言します。
外部ロックを使用しなければ、1つのサーバのみを起動している場合、MySQLの機能性には影響しません。myisamchkを起動する前にサーバを停止してください(または関連するテーブルをロック/フラッシュしてください)。いくつかのシステム上では外部ロックを必ず使用不可に設定します。というのも、機能しないからです。
外部ロックが使用不可である唯一の状況は、同一データに対して複数の MySQL servers(クライアントではない)を実行している場合と、サーバに対して初めにテーブルのフラッシュとロックを行う指示を出さずに、テーブルに対して myisamchkを実行する場合に限られる。 MySQLクラスタを使用しているとき以外で、同じデータを同時にアクセスするのに複数のMySQLサーバを使用することは推奨されていません。
LOCK TABLES
とUNLOCK
TABLES
ステートメントは内部ロックを使用するため、外部ロックが不可となっている場合でも使用できます。
mysqldサーバで使用されるデフォルトのバッファサイズは次のコマンドで確認できます。
shell> mysqld --verbose --help
このコマンドによって、mysqldオプションと設定可能な変数すべての一覧が生成されます。この出力には、デフォルトの変数値も記載され、以下のように表示されます。
help TRUE abort-slave-event-count 0 allow-suspicious-udfs FALSE auto-increment-increment 1 auto-increment-offset 1 automatic-sp-privileges TRUE basedir /home/mysql/ bind-address (No default value) character-set-client-handshake TRUE character-set-server latin1 character-sets-dir /home/mysql/share/mysql/charsets/ chroot (No default value) collation-server latin1_swedish_ci completion-type 0 concurrent-insert 1 console FALSE datadir /home/mysql/var/ default-character-set latin1 default-collation latin1_swedish_ci default-time-zone (No default value) disconnect-slave-event-count 0 enable-locking FALSE enable-pstack FALSE engine-condition-pushdown FALSE external-locking FALSE gdb FALSE large-pages FALSE init-connect (No default value) init-file (No default value) init-slave (No default value) innodb TRUE innodb_checksums TRUE innodb_data_home_dir (No default value) innodb_doublewrite TRUE innodb_fast_shutdown 1 innodb_file_per_table FALSE innodb_flush_log_at_trx_commit 1 innodb_flush_method (No default value) innodb_locks_unsafe_for_binlog FALSE innodb_log_arch_dir (No default value) innodb_log_group_home_dir (No default value) innodb_max_dirty_pages_pct 90 innodb_max_purge_lag 0 innodb_status_file FALSE innodb_table_locks TRUE innodb_support_xa TRUE isam FALSE language /home/mysql/share/mysql/english local-infile TRUE log /home/mysql/var/master1.log log-bin /home/mysql/var/master1 log-bin-index (No default value) log-bin-trust-routine-creators FALSE log-error /home/mysql/var/master1.err log-isam myisam.log log-queries-not-using-indexes FALSE log-short-format FALSE log-slave-updates FALSE log-slow-admin-statements FALSE log-slow-queries (No default value) log-tc tc.log log-tc-size 24576 log-update (No default value) log-warnings 1 low-priority-updates FALSE master-connect-retry 60 master-host (No default value) master-info-file master.info master-password (No default value) master-port 3306 master-retry-count 86400 master-ssl FALSE master-ssl-ca (No default value) master-ssl-capath (No default value) master-ssl-cert (No default value) master-ssl-cipher (No default value) master-ssl-key (No default value) master-user test max-binlog-dump-events 0 memlock FALSE myisam-recover OFF ndbcluster FALSE ndb-connectstring (No default value) ndb-mgmd-host (No default value) ndb-nodeid 0 ndb-autoincrement-prefetch-sz 32 ndb-distibution KEYHASH ndb-force-send TRUE ndb_force_send TRUE ndb-use-exact-count TRUE ndb_use_exact_count TRUE ndb-shm FALSE ndb-optimized-node-selection TRUE ndb-cache-check-time 0 ndb-index-stat-enable TRUE ndb-index-stat-cache-entries 32 ndb-index-stat-update-freq 20 new FALSE old-alter-table FALSE old-passwords FALSE old-style-user-limits FALSE pid-file /home/mysql/var/hostname.pid1 port 3306 relay-log (No default value) relay-log-index (No default value) relay-log-info-file relay-log.info replicate-same-server-id FALSE report-host (No default value) report-password (No default value) report-port 3306 report-user (No default value) rpl-recovery-rank 0 safe-user-create FALSE secure-auth FALSE server-id 1 show-slave-auth-info FALSE skip-grant-tables FALSE skip-slave-start FALSE slave-load-tmpdir /tmp/ socket /tmp/mysql.sock sporadic-binlog-dump-fail FALSE sql-mode OFF symbolic-links TRUE tc-heuristic-recover (No default value) temp-pool TRUE timed_mutexes FALSE tmpdir (No default value) use-symbolic-links TRUE verbose TRUE warnings 1 back_log 50 binlog_cache_size 32768 bulk_insert_buffer_size 8388608 connect_timeout 5 date_format (No default value) datetime_format (No default value) default_week_format 0 delayed_insert_limit 100 delayed_insert_timeout 300 delayed_queue_size 1000 expire_logs_days 0 flush_time 0 ft_max_word_len 84 ft_min_word_len 4 ft_query_expansion_limit 20 ft_stopword_file (No default value) group_concat_max_len 1024 innodb_additional_mem_pool_size 1048576 innodb_autoextend_increment 8 innodb_buffer_pool_awe_mem_mb 0 innodb_buffer_pool_size 8388608 innodb_concurrency_tickets 500 innodb_file_io_threads 4 innodb_force_recovery 0 innodb_lock_wait_timeout 50 innodb_log_buffer_size 1048576 innodb_log_file_size 5242880 innodb_log_files_in_group 2 innodb_mirrored_log_groups 1 innodb_open_files 300 innodb_sync_spin_loops 20 innodb_thread_concurrency 20 innodb_commit_concurrency 0 innodb_thread_sleep_delay 10000 interactive_timeout 28800 join_buffer_size 131072 key_buffer_size 8388600 key_cache_age_threshold 300 key_cache_block_size 1024 key_cache_division_limit 100 long_query_time 10 lower_case_table_names 0 max_allowed_packet 1048576 max_binlog_cache_size 4294967295 max_binlog_size 1073741824 max_connect_errors 10 max_connections 100 max_delayed_threads 20 max_error_count 64 max_heap_table_size 16777216 max_join_size 4294967295 max_length_for_sort_data 1024 max_relay_log_size 0 max_seeks_for_key 4294967295 max_sort_length 1024 max_tmp_tables 32 max_user_connections 0 max_write_lock_count 4294967295 multi_range_count 256 myisam_block_size 1024 myisam_data_pointer_size 6 myisam_max_extra_sort_file_size 2147483648 myisam_max_sort_file_size 2147483647 myisam_repair_threads 1 myisam_sort_buffer_size 8388608 myisam_stats_method nulls_unequal net_buffer_length 16384 net_read_timeout 30 net_retry_count 10 net_write_timeout 60 open_files_limit 0 optimizer_prune_level 1 optimizer_search_depth 62 preload_buffer_size 32768 query_alloc_block_size 8192 query_cache_limit 1048576 query_cache_min_res_unit 4096 query_cache_size 0 query_cache_type 1 query_cache_wlock_invalidate FALSE query_prealloc_size 8192 range_alloc_block_size 2048 read_buffer_size 131072 read_only FALSE read_rnd_buffer_size 262144 div_precision_increment 4 record_buffer 131072 relay_log_purge TRUE relay_log_space_limit 0 slave_compressed_protocol FALSE slave_net_timeout 3600 slave_transaction_retries 10 slow_launch_time 2 sort_buffer_size 2097144 sync-binlog 0 sync-frm TRUE sync-replication 0 sync-replication-slave-id 0 sync-replication-timeout 10 table_open_cache 64 table_lock_wait_timeout 50 thread_cache_size 0 thread_concurrency 10 thread_stack 196608 time_format (No default value) tmp_table_size 33554432 transaction_alloc_block_size 8192 transaction_prealloc_size 4096 updatable_views_with_limit 1 wait_timeout 28800
現在実行中の mysqldサーバがある場合は、次のステートメントで変数に実際に使用されている値を調べることができます。
mysql> SHOW VARIABLES;
また、次のステートメントでは、実行中のサーバの統計やステータスインジケータを調べることができます。
mysql> SHOW STATUS;
システム変数とステータス情報は、mysqladminでも入手できます。
shell>mysqladmin variables
shell>mysqladmin extended-status
全てのシステムとステータス変数については、本マニュアルの項4.2.3. 「システム変数」と項4.2.5. 「ステータス変数」を参照してください。
MySQL は非常にスケーラブルなアルゴリズムを使用しているため、通常は実行時のメモリ消費が非常に小さくなります。しかし、MySQL に対するメモリを多く割り当てると、通常はパフォーマンスが向上します。
MySQL
サーバをチューニングする際に使用される最も重要な変数は
key_buffer_size
と
table_open_cache
の 2
つです。他の変数の変更を行う前にこの変数をあらかじめ適切に設定しておくことで自信がつきます。
以下に典型的な変数を実行時に設定している例を示します。
最小 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
または GROUP
BY
を実行する場合は
read_rnd_buffer_size
の値を大きくしてソート操作後のレコードの読み取りの速度を上げる必要があります。
ユーザのMySQLディストリビューションで、例オプションファイルを使用する場合は、項3.3.2.1. 「あらかじめ形成されたオプション・ファイル」を参照してください。
mysqldまたは mysqld_safeのコマンドラインでオプションを指定した場合、そのサーバの呼び出しでしか有効性が保持されないことに注意してください。 サーバ実行のたびにオプションを使用する場合は、オプション設定ファイルに配置します。
パラメータ変更の有効性を調べるには、次のように実行します。
shell> mysqld --key_buffer_size=32M --verbose --help
変数値は出力の最後付近で一覧表示されます。--verbose
と--help
オプションが残っていることを確認してください。でなければ、コマンドラインでそれらの後に表示されるオプションの効果は出力に反映されません。
InnoDB
ストレージエンジンのチューニングに関する情報は、項13.5.11. 「InnoDB
パフォーマンス チューニング
ヒント」を参照してください。
MySQL Enterprise チューニングシステムパラメータに関する専門的なアドバイスを受けるには、MySQL Network Monitoring and Advisory Serviceを購読してください。追加情報については http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
クエリオプティマイザの役割はSQLクエリの実行に際し、最適なプランを検索することです。「良い」と「悪い」プランのパフォーマンス性の違いは、マグニチュードの順序(つまり、秒に対する時間、または日にち)であるため、MySQLを含む大抵のクエリオプティマイザは全ての評価可能なプランの中から最適なプランを検索します。joinクエリに対して、MySQLオプティマイザによって確認される可能なプランの数は、クエリで参照されるテーブル数と共に、急激に増加します。少ない数のテーブル(通常から7?10以下)に対して、これは問題とはなりません。 しかし、大きなクエリが提出されたとき、クエリの最適化に要する時間はサーバのパフォーマンスを低下させる主な原因となります。
フレキシブルなクエリ最適化メソッドは、最適なクエリ評価プランを、オプティマイザがどれほど徹底的に検索するかを管理します。基本的には、オプティマイザによって確認されるプランが少なければ少ないほど、クエリをコンパイルするのに要する時間も少なくてすむと考えられています。一方で、オプティマイザはいくつかのプランをスキップするため、最適プランを見逃す可能性もあります。
評価するプラン数に対するオプティマイザの動作は二つのシステム変数を通して管理されます。
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に設定することで、オプティマイザに自動的に値を決定させることができます。
以下のテストのほとんどは、MySQL ベンチマークを使用した Linux で実行されていますが、これ以外のオペレーティングシステムおよびワークロードに対しても一定の指針になります。
-static
とリンクした場合に最速のバイナリが得られます。
Linux 上では、pgccおよび
-O3
でコンパイルした場合に最速のコードが得られます。これらのオプションで
sql_yacc.cc
をコンパイルする場合は、gcc/pgccで関数のすべてをインラインにする際に大量のメモリが要求されるため約
200M のメモリが必要です。MySQL
のコンフィギャ時に CXX=gcc
も設定して、libstdc++
ライブラリ(これは不要です)が含まれないようにします。pgccの一部のバージョンでは、生成されたコードを
x586 タイプのプロセッサ(AMD
など)すべてで動作可能にするコンパイラオプションを使用しても、コードが純正
Pentium
プロセッサでしか実行できないため注意が必要です。
適切なコンパイラおよびコンパイラオプションを使用することで、アプリケーションの速度が 10?30% 改善されます。これは各自で SQL サーバをコンパイルする場合に特に重要です。
Cygnus CodeFusion と Fujitsu コンパイラの両方をテストしましたが、いずれもバグフリーではなく、最適化をオンにして MySQL をコンパイルするには不十分でした。
標準の MySQL
バイナリディストリビューションは、すべてのキャラクタセットをサポートするようにコンパイルされています。MySQL
のコンパイル時は、使用するキャラクタセットのサポートのみを含めます。
これは--with-charset
オプションからconfigureによって管理されます。
以下に実施した測定結果の一部を紹介します。
pgccを使用し、すべてを
-O6
でコンパイルした場合、mysqldサーバは
gcc2.95.2と比較して 1%
速度が上がる。
動的にリンクした場合(-static
なし)は、結果が
Linux 上で 13%
遅くなった。クライアントアプリケーションには動的リンクの
MySQL
ライブラリを使用できることに注意する。これは、サーバのパフォーマンス上重大である。
同一ホスト上で実行されるクライアントからサーバへの接続で、Unix
ソケットファイルではなく、TCP/IP
で接続すると、7.5%
パフォーマンスが遅くなった(Unixではlocalhost
に接続する場合、MySQL
ではデフォルトでソケットファイルが使用される)。
TCPクライアントからサーバへの TCP/IP 接続で別のホストにあるリモートサーバに接続した場合、100M イーサネットによる接続でも、同一ホスト上のローカルサーバに接続した場合と比較して、8?11% 遅くなった。
暗号化した接続(内部 SSL サポートによるすべてのデータの暗号化)を使用してベンチマークテストを実行した場合、パフォーマンスが 55% 遅くなった。
--with-debug=full
でコンパイルすると、ほとんどのクエリが
20% 遅くなる。
一部のクエリはかなり長くかかった(たとえば
MySQL ベンチマークは 35%
の速度低下)。--with-debug
(=full
なしで)を使用すると、この速度低下は
15%
で済む。--with-debug=full
でコンパイルされた
mysqldバージョンは、--skip-safemalloc
オプションで起動すると実行時のメモリチェックを無効化できる。この場合の最終的な結果は、--with-debug
で構成した場合に非常に近くなる。
Sun UltraSPARC-IIe, Forte 5.0 は、gcc3.2 より 4% 速度が上がった。
Sun UltraSPARC-IIe, Forte 5.0 では、64 ビットモードより 32 ビットモードのほうが 4% 速かった。
gcc2.95.2 for UltraSPARC
にオプション -mcpu=v8
-Wa,-xarch=v8plusa
を付けてコンパイルすると、パフォーマンスが
4% 改善した。
Solaris 2.5.1, MIT-pthreads は、単一プロセッサ上で Solaris ネイティブスレッドより 8?12% 遅かった。CPU の負荷が増加するとこの差はさらに拡大する。
フレームポインタ
-fomit-frame-pointer
または
-fomit-frame-pointer -ffixed-ebp
なしで
gccを使用して Linux-x86
でコンパイルすると、mysqldが
1?4% 速くなった。
pgccによるコンパイルに MySQL AB 提供の バイナリMySQL-Linux ディストリビューションを使用したが、AMD で実行されないコードを生成するバグが pgccにあったため、通常の gcc の使用に戻さざるを得ませんでした。このバグが解決されるまで gccの使用を続行します。 ただし、AMD 以外のコンピュータを使用する場合は、pgccでコンパイルすると高速なバイナリが得られます。標準の MySQL Linux バイナリは、速度および移植性を高めるため静的にリンクされています。
以下の一覧は、mysqldサーバでのメモリの使用方法の一部を示しています。可能な場合は、メモリ使用に関連するサーバ変数名も記載されています。
The キーバッファ(変数
key_buffer_size
)はすべてのスレッドで共有される。サーバが使用するこれ以外のバッファは必要に応じて割り当てられる。
項6.5.2. 「サーバパラメータのチューニング」を参照してください。
それぞれの接続はいくつかのスレッド固有領域を使用します。以下にそれらの領域およびどの変数がサイズをコントロールするかをリストアップします。
スタック(デフォルト192KB,
変数thread_stack
)
接続バッファ(変数net_buffer_length
)
結果バッファ(変数net_buffer_length
)
接続バッファと結果バッファ(ともにnet_buffer_length
で与えられるサイズで始まる)は必要に応じて
max_allowed_packet
まで動的に拡張される。結果バッファは各SQLステートメントの後net_buffer_length
に縮小される。クエリの実行中は現在のクエリ文字列のコピーも割り当てられる。
すべてのスレッドで同じベースメモリが共有される。
スレッドが必要ない場合、それに割り当てられたメモリはリリースされ、スレッドがスレッドキャッシュに戻るまでシステムに返されます。この場合、メモリは割り当てられた状態のままです。
MySQL
5.1.4以前では、圧縮されたMyISAM
テーブルのみがメモリーマップされました。MySQL
5.1.4以降では、myisam_use_mmap
システム変数は全てのMyISAM
テーブルに対してメモリマッピングを可能にするために、1に設定できます。
項4.2.3. 「システム変数」.
テーブルの順次スキャンを行う要求はそれぞれ、read
buffer(変数
read_buffer_size
)を割り当てる。
レコードを ``ランダムな''
順序で読み取る場合(ソート後など)、random-read
bufferが割り当てられディスクシークが回避される(変数
read_rnd_buffer_size
)。
結合はすべて 1
回の受け渡しで実行され、ほとんどの結合はテンポラリテーブルを使用せずに実行される。テンポラリテーブルのほとんどはメモリベース(HEAP)テーブルである。レコード長の大きなテンポラリテーブル(すべてのカラム長の合計として算出)や
BLOB
カラムが含まれるテンポラリテーブルはディスク上に格納される。
メモリ内のheap
テーブルのサイズがtmp_table_size
を超えた場合、
MySQLはディスクベース
MyISAM
テーブルに変更されることで自動的に処理される。
この問題を回避するには、tmp_table_size
オプションを
mysqldに設定するか、クライアントプログラムで
SQL_BIG_TABLES
を設定することで、テンポラリテーブルのサイズを拡張する。
項12.5.3. 「SET
構文」を参照してください。
MySQL Enterprise
MySQL Network Monitoring and Advisory
Service購読者はテンポラリテーブルサイズがtmp_table_size
を超えたときに警告を受けます。実際のテーブル使用に基づいてtmp_table_size
の最適値のアドバイスを提供します。MySQL
Network Monitoring and Advisory
Serviceの詳細については、http://www-jp.mysql.com/products/enterprise/advisors.htmlを参照してください。
ソートを実行する要求のほとんどで、ソートバッファおよび結果セットサイズに応じた 0 から 2 つのテンポラリファイルが割り当てられる。項B.1.4.4. 「Where MySQL Stores Temporary Files」を参照してください。
解析および計算のほとんどすべてが、ローカルメモリストアで実行される。小さいアイテムにはメモリオーバヘッドが不要で、通常の低速メモリの割り当ておよび解放は回避される。メモリは、予測外の規模の文字列の場合のみ割り当てられ、これは、malloc()
および
free()
で実行される。
開かれるMyISAM
テーブルにはそれぞれ
1
回開かれ、データファイルは、同時実行スレッドごとに
1
回開かれる。同時スレッドのそれぞれに対して、テーブル構造、各カラムのカラム構造、サイズ
3 ×
のバッファが割り当てられる(N
N
は、レコードの最大長、ただし
BLOB
カラムは計算外)。BLOB
カラムは、5
から 8 バイトに
BLOB
データの長さを加算したバイト数を使用する。MyISAM
ストレージエンジンは、内部使用のための追加レコードを
1 つ使用する。
BLOB
カラムがあるテーブルのそれぞれで、大きな
BLOB
値を読み込むためにバッファが動的に拡張される。テーブルをスキャンする場合は、最大
BLOB
値と同じ大きさのバッファが割り当てられる。
使用中テーブルすべてのハンドラ構造がキャッシュに保存され、FIFO 形式で管理される。一般にキャッシュには 64 のエントリがある。テーブルが同時に 2 つの実行スレッドで使用されている場合、キャッシュにはそのテーブルのエントリが 2 つ配置される。項6.4.8. 「MySQL でのテーブルのオープンとクローズの方法」を参照してください。
FLUSH TABLES
コマンド(または
mysqladmin
flush-tablesステートメント)によって、使用中でないテーブルすべてが閉じられ、現在実行中のスレッドの終了時に使用中のテーブルすべてが閉じられるように指定される。これで効率的に使用中メモリに空きを作ることができる。
FLUSH
TABLES
は全てのテーブルが閉じられるまで返されない。
ps
およびその他のステータスプログラムによって、mysqldが大量のメモリを使用していることを示すレポートが行われることがあります。これは、複数のメモリアドレスでのスレッドスタックによって発生します。たとえば、Solaris
バージョンの
psではスタック間の使用していないメモリが使用メモリにカウントされます。これは、swap
-s
で使用可能スワップをチェックすることで検証できます。市販のメモリリーク検出装置で
mysqldをテストし、メモリリークがないと判明しています。
新たなクライアントが mysqldに接続すると、mysqldによって要求を処理する新規のスレッドが作成されます。このスレッドでは、まずホスト名がホスト名キャッシュにあるかどうかがチェックされます。ない場合は、ホスト名の解決が試行されます。
オペレーティングシステムがスレッドセーフの
gethostbyaddr_r()
とgethostbyname_r()
の呼び出しをサポートしている場合、スレッドではこれを使用してホスト名の解決が実行される。
オペレーティングシステムがスレッドセーフの呼び出しをサポートしていない場合、スレッドでは相互排除ロックを行い、代わりに
gethostbyaddr()
と
gethostbyname()
が呼び出される。この場合、他のスレッドでは最初のスレッドが相互排除ロックを解除するまでホスト名キャッシュ内のホスト名を解決できなくなることに注意する。
--skip-name-resolve
を
mysqldオプションを指定して起動すると、DNS
ホスト名ルックアップを無効化できます。ただし、この場合は、MySQL
権限テーブルで IP
番号しか使用できなくなります。
非常に低速の DNS
と多数のホストがある場合は、--skip-name-resolve
で
DNS
ルックアップを無効化するか、HOST_CACHE_SIZE
の定義(デフォルト値:
128)を拡張し、mysqldを再コンパイルすることで、パフォーマンスを改善できます。
--skip-host-cache
オプションを使用してサーバを起動すると、ホスト名キャッシュを無効化できます。ホスト名のキャッシュをクリアするには、FLUSH
HOSTS
ステートメントを使用するか、mysqladmin
flush-hostsコマンドを実行します。
TCP/IP
接続すべてを認めない場合は、--skip-networking
オプションを指定して
mysqldを開始します。
ディスクシークはパフォーマンスに対する大きなボトルネックである。この問題は、データが拡大し、効率的なキャッシュが実行不能になるほど大きくなるにつれて明白になる。大規模データベースで、事実上ランダムにデータにアクセスする場合、読み取りでは最低 1 回、書き込みでは最低 2 回のディスクシークが必要になることがわかる。この問題を最小にするには、シーク回数を減らすようにディスクを使用する。
複数のディスクに対してファイルをシンボリックリンクするか、ストライピングを行って、利用可能なディスクスピンドル数を増加する(およびそれによるシークのオーバヘッドを軽減する)。
シンボリックリンクの使用
MyISAM
テーブルの場合、通常のデータディレクトリ内の位置から別のディスクへのインデックスファイルやデータファイルのシンボリックリンクを行う(ストライピングも可能)。これによって、ディスクが他の用途に使用されていなければ、シークと読み取り時間がいずれも改善される。
項6.6.1. 「シンボリックリンクの使用」を参照してください。
ストライピングは、ディスクが多数ある場合に、第
1 ブロックを第 1 ディスクに、第 2
ブロックは第 2 ディスクに、第
N
ブロックは(
)ディスクにといった配置を意味する。これにより、通常のデータサイズがストライプサイズより小さい(または完全に一致している)場合にパフォーマンスが大幅に改善する。ストライピングはオペレーティングシステムとストライプサイズへの依存性が非常に高いため、ストライプサイズをさまざまに変えながらアプリケーションのベンチマークを行う。項6.1.5. 「独自のベンチマークの使用」を参照してください。
N
MOD
number_of_disks
ストライピングの速度はパラメータによって大きく異なることに注意する。ストライピングパラメータの設定方法とディスク数によって桁ちがいの差異が発生する。ランダムアクセスか順次アクセスのいずれの最適化を行うかの選択が必要なことに注意する。
信頼性を高めるため、RAID 0+1(ストライピング
+
ミラーリング)の使用が必要な場合、N
個のドライブのデータの保持に2×
N
個のドライブが必要になる。財務上の余裕がある場合はこれが最適な選択肢になる。しかし、処理の効率化にボリューム管理ソフトウェアへの投資が必要なこともある。
データの種類の重大性に応じて RAID
レベルを変える選択も推奨される。たとえば、ホスト情報やログなどの重要度の高いデータは、RAID
0+1 または RAID
N
ディスクに格納し、再生成が可能で重要性が中程度のデータは
RAID 0
ディスクに格納することなどができる。RAID
N
は、パリティビットの更新に時間がかかるため、書き込みが多いと問題になる場合がある。
Linux
の場合、hdparm
を使用してディスクのインタフェースを構成することでパフォーマンスを大幅に改善できる(負荷時に
100%
改善できることも珍しくない)。次の例は、MySQL(およびその他の多数のアプリケーション)に非常に適した
hdparm
オプションである。
hdparm -m 16 -d 1
上記を使用した場合のパフォーマンスと信頼性は使用ハードウェアに依存するため、hdparm
の使用後はシステムを総合的にテストするように強く推奨する。詳細については、hdparm
のページを参照。hdparm
の使用が適切でない場合は、ファイルシステムの損傷が発生することがあるため、テストの際はあらかじめすべてのバックアップを取っておく必要がある。
データベースが使用するファイルシステムのパラメータを設定することもできる。
If
ファイルへの最終アクセス時を認識する必要がない(データベースサーバでは重要度が低い)場合、-o
noatime
オプションを使用してファイルシステムをマウントできる。これで、ファイルシステムの
i
ノードへの最終アクセス時間の更新がスキップされ、一部のディスクシークを回避できる。
多数のオペレーティングシステムで、-o
async
オプションを使用してディスクをマウントし、ファイルシステムが非同期で更新されるように設定できる。使用しているコンピュータが適度に安定している場合は、信頼性を損なわずにさらにパフォーマンスを改善できる(Linux
ではこのフラグがデフォルトでオンになっている)。
テーブルとデータベースをデータベースディレクトリから他の位置に移動し、新しい位置へのシンボリックリンクに置換することができます。 これは、たとえば、データベースを空き領域の多いファイルシステムに移動し、テーブルを別のディスクに分散することでシステムの速度を上げる場合などに実行できます。
この推奨される実行方法は、データベースだけ別のディスクへのシンボリックリンクを行い、最後の手段としてのみテーブルのシンボリックリンクを行うことです。
Unix の場合、データベースのシンボリックリンクは、まず、空き領域のあるディスクにディレクトリを作成し、次に MySQL データベースディレクトリからそのディレクトリへのシンボリックリンクを作成します。
shell>mkdir /dr1/databases/test
shell>ln -s /dr1/databases/test
/path/to/datadir
MySQL は、1
つのディレクトリに対して複数のデータベースをリンクさせることをサポートしていません。データベースディレクトリをシンボリックリンクに置換すると、複数のデータベースへシンボリックリンクを張らない限り、問題なく機能します。
仮に MySQL データディレクトリにデータベース
db1
がある場合に、db1
を指すシンボリックリンク
db2
を作成するとします。
shell>cd
shell>/path/to/datadir
ln -s db1 db2
これで、db1
のテーブル
tbl_a
が、db2 のテーブル tbl_a
としても表示されます。あるスレッドで
db1.tbl_a が更新され、他のクライアントが
db2.tbl_a
に更新すると、問題が発生します。
ただし、実際にこれを実行しなければいけないとき、ソースファイルであるmysys/my_symlink.c
を変更することで可能となります。その場合、以下のステートメントを参照してください。
if (!(MyFlags & MY_RESOLVE_LINK) || (!lstat(filename,&stat_buff) && S_ISLNK(stat_buff.st_mode)))
このステートメントを以下に変えます。
if (1)
realpath()
の呼び出しの機能が完全でないシステムではテーブルのシンボリックリンクを行わないでください。(少なくとも、Linux
と Solaris では
realpath()
がサポートされています)。
SHOW VARIABLES LIKE
'have_symlink'
ステートメントを発行することで、ユーザのシステムがシンボリックリンクをサポートするかチェックできます。.
MySQL 4.0 では
MyISAM
テーブルでのみシンボリックリンクが完全サポートされています。
これ以外のテーブル型で上記のコマンドを使用すると、予想外の問題の発生の恐れがあります。
MySQL 4.0
でのMyISAM
テーブルのシンボリックリンクの処理は次のように機能します。
データディレクトリには常にテーブル定義ファイル(.frm
)
、データファイル(.MYD
)
およびインデックスファイル(.MYI
)
がある。データファイルとインデックスファイルは、別の場所に移動し、データディレクトリ内でシンボリックリンクによって置換できる。定義ファイルはこれができない。
データファイルとインデックスファイルは、それぞれ独立して別のディレクトリにシンボリックリンクを作成できる。
シンボリックリンクは、オペレーティングシステムレベル(mysqld
が実行されていない場合)、または SQL で
CREATE TABLE
に DATA
DIRECTORY
および INDEX
DIRECTORY
オプションを指定して実行できる。
詳しくは項12.1.8. 「CREATE TABLE
構文」を参照してください。あるいは、シンボリックリンクはln
-s
を使用してコマンドラインから手動で行えますが、
これはmysqldが作動していない場合に限ります。
myisamchkは、データファイルやインデックスファイルのシンボリックリンクを置き換えない。myisamchk
はリンクで指し示されているファイルに直接作用する。テンポラリファイルはすべてデータファイルやインデックスファイルが配置されているのと同じディレクトリに作成されます。
同様のことがALTER
TABLE
、OPTIMIZE
TABLE
、そしてREPAIR
TABLE
ステートメントでいえます。
注:シンボリックリンクを使用しているテーブルをドロップすると、シンボリックリンクとシンボリックリンクが指しているファイルの両方がドロップされる。このため、root
として
mysqldを実行すべきではなく、また、MySQL
データベースディレクトリへの書き込みアクセスをユーザに許可するべきでもない。
ALTER TABLE ...
RENAME
を使用してテーブルの名前を変更し、テーブルを他のデータベースに移動しない場合、データベースディレクトリのシンボリックリンクの名前が新しい名前に変更され、データファイルとインデックスファイルもそれに従って名前が変更される。
ALTER TABLE ...
RENAME
を使用してテーブルを別のデータベースに移動すると、テーブルが別のデータベースディレクトリに移動され、それまであったシンボリックリンクとそれが指すファイルが削除される(新規テーブルのシンボリックリンクは作成されない)。
シンボリックリンクを使用していない場合は、mysqldに
--skip-symbolic-links
オプションを指定して使用し、確実に誰もデータディレクトリの外でファイルのドロップや名前の変更を行う
mysqldを使用できないようにする。
サポートされていないテーブルシンボリックリンクオペレーション
ALTER TABLE
では DATA
DIRECTORY
と INDEX DIRECTORY
テーブルオプションが無視される。
BACKUP TABLE
とRESTORE
TABLE
ではシンボリックリンクが考慮されない。
.frm
ファイルはシンボリックリンクにすることがまったくできない(前述のように、データファイルとインデックスファイルのみシンボリックリンクにできる)。
これを実行した場合(シノニム作成など)、正しい結果が得られなくなる。
MySQL データディレクトリにデータベース
db1
があり、このデータベースにはテーブル
tbl1
が、db1
ディレクトリには
tbl1
を指すシンボリックリンク
tbl2
があるとする。
shell>cd
shell>/path/to/datadir
/db1ln -s tbl1.frm tbl2.frm
shell>ln -s tbl1.MYD tbl2.MYD
shell>ln -s tbl1.MYI tbl2.MYI
あるスレッドで
db1.tbl1
が読み取られ、別のスレッドで
db1.tbl2
が更新されると、問題が発生する。
クエリキャッシュが「欺かれ」(tbl1
が更新されていないと判断され、最新でない結果が返される)。
tbl2
に対するALTER
ステートメントもエラーになる。
シンボリックリンクはデフォルトで全てのWindowsサーバのため有効になっています。これにより、シンボリックリンクを設定することでデータベースディレクトリを別のディスクにセットすることが可能となります。リンクを設定するプロシージャは異なりますが、データベースシンボリックリンクがUnix上で作動するのと似ています。シンボリックリンクが必要ない場合、--skip-symbolic-links
オプションを使用して動作しないように設定できます。
On Windows
では、対象ディレクトリへのパスが記載されたファイルを作成して
MySQL
データベースに対するシンボリックリンクを作成します。ファイル名
を使用してデータディレクトリにファイルを保存します。この
db_name
.symdb_name
はデータベース名です。
たとえば、MySQL データディレクトリが
C:\mysql\data
で、データベース
foo
を
D:\data\foo
に配置したい場合。このプロシージャを使用してsymlinkをセットしてください。
この作業には
D:\data\foo
ディレクトリが存在している必要があります。
言い換えると、すでに
foo
という名前のデータベースディレクトリがデータディレクトリにある場合、これを
D:\data
に移動しないとシンボリックリンクが有効にならないことになります(問題を回避するため、データベースディレクトリの移動時はサーバを実行しないでください)。
D:\data\foo\
パスネームを含むテキストファイルC:\mysql\data\foo.sym
を作成してください。
この後、foo
データベースで作成される全てのテーブルはD:\data\foo
内に作成されます。MySQLデータディレクトリに同じ名前のディレクトリがある場合、シンボリックリンクは使用されません。注意してください。.