目次
SELECT
ステートメントおよびその他のクエリの最適化EXPLAIN
構文(SELECT
に関する情報の取得)SELECT
クエリの速度WHERE
節の最適化IS NULL
の最適化DISTINCT
の最適化LEFT JOIN
と RIGHT
JOIN
の最適化ORDER BY
の最適化LIMIT
の最適化INSERT
クエリの速度UPDATE
クエリの速度DELETE
クエリの速度最適化は、システム全体の理解が必要であるため、複雑な作業です。システムやアプリケーションに関する知識が豊富でなくても部分的なローカルの最適化は可能ですが、より高度な最適化が必要になるほど求められる知識も高度になります。
この章では、MySQL 最適化の方法説明し、その例もいくつか紹介します。ただし、常にシステムの速度をさらに上げる補足的な方法もありますが、難度も高くなることを覚えておいてください。
言うまでもなく、システムの速度を上げる際に最も重要な要素は基本設計です。また、使用するシステムの用途およびそのボトルネックを認識しておく必要もあります。
最も一般的なボトルネックは下記のとおりです。
ディスクシーク。 ディスクが 1 つのデータを検索するには時間がかかる。1999 年の最新のディスクでは、通常これにかかる平均時間が 10 ms 未満であるため、理論的には 1 秒間に 100 のシークを実行できることになる。新しいディスクではこの時間の改善が緩やかで、1 つのテーブルの最適化が非常に困難である。これを最適化する方法として、複数のディスクにデータを分散することが挙げられる。
ディスクの読み取りと書き込み。 ディスクが適切な位置にある場合、データの読み取りが必要になる。1999 年の最新のディスクでは、1 つのディスクで約 10 - 20 MB の読み取りが可能になる。これは、複数のディスクから並行した読み取りが可能であるため、シークに比較して最適化が容易である。
CPU サイクル。 メインメモリにデータがある場合(またはすでにそこに存在している場合)、結果を得るためには処理が必要になる。メモリと比較してテーブルが小さい場合は、最も一般的な制限要因になる。しかし、テーブルが小さくても、一般に速度上の問題は発生しない。
メモリ帯域幅。 CPU キャッシュの適正量より多く CPU がデータを必要とする場合、メインメモリの帯域幅がボトルネックになる。これは、ほとんどのシステムに一般的な問題ではないが、認識しておく必要がある。
MyISAM ストレージエンジンの使用時に、MySQL では非常に高速のテーブルロック(複数リーダ/単一ライタ)が使用されます。このテーブル型の最大の問題は、同じテーブルに対して複数の UPDATE と遅い SELECT が混在する場合に発生します。テーブルでこのような問題が発生した場合は、別のテーブル型を使用してもかまいません。 See 章?7. MySQL のテーブル型。
MySQL はトランザクションテーブル、非トランザクションテーブルの両方で機能します。 非トランザクションテーブル(何らかのエラー発生した場合にロールバックができない)での動作をスムーズにするため、MySQL には次のルールがあります。
すべてのカラムにデフォルト値がある。
NOT NULL
カラムに対して
NULL
などの '正しくない'
値を挿入した場合や、数値列の数値が大きすぎる場合、MySQL
ではエラーを発生するのではなく、'とりうる可能な値のうちの最適値'
に値を設定する。数値の場合は 0
で、可能な最小値か最大値になる。文字列の場合は、空白文字かカラムの最大長さにあわせた文字列になる。
計算式はすべて、エラー状態を表示するのではなく、使用可能な値を返す。たとえば、1/0
の場合は、NULL
を返す。
この詳細については、See 項1.8.5. 「MySQL における制約の処理」 を参照してください。
このことは、フィールド内容のチェックに MySQL を使用するのではなく、このチェックをアプリケーションで実行する必要があることを意味します。
SQL サーバは SQL のさまざまな部分を実装しているので、移植可能な SQL アプリケーションの作成が可能となります。非常に単純な SELECT や INSERT は容易ですが、必要なことが増えれば増えるほど、作成が難しくなります。多数のデータベースを使用しながら素早い速度が要求されるアプリケーションの場合は、さらに難度が上がります。
複雑なアプリケーションを移植可能にするには、ともに稼動する必要のある SQL サーバ数を選択する必要があります。
たとえば、Infomix や DB2 の使用を可能にするには、18 文字を超えるカラム名は使用できません。
MySQL ベンチマークと crash-me
プログラムはいずれもデータベースへの依存度が非常に低くなっています。これらのプログラムがどのように処理されているかを調べることによって、データベースに依存しないアプリケーションを作成する際に必要なことに関する感覚を得ることができます。ベンチマーク自体は、MySQL
ソースディストリビューションの
sql-bench
ディレクトリにあります。これは Perl - DBI
データベースインタフェース(問題のアクセス部分を解決する)で作成されています。
このベンチマークの結果については、http://www.mysql.com/information/benchmarks.html を参照してください。
これらの結果からもわかるように、データベースのすべてに何らかの弱点があります。言い換えると、動作の相違を招くさまざまな設計上の障害があります。
データベースの独立性の獲得を目指す場合は、SQL サーバそれぞれのボトルネックを正しく理解する必要があります。MySQL では、非常に高速にレコードの取り出しと更新が行われますが、1 つのテーブル上に低速のリーダとライタが混在することに問題があります。これとは異なり、Oracle では、更新直後のレコードがディスクに保存される前にそのレコードにアクセスしようとする際に大きな問題があります。一般にトランザクションデータベースの場合、ログテーブルからのサマリテーブルの生成時は行ロックがほとんど役に立たず、問題が生じやすくなっています。
アプリケーションを実際にデータベース非依存にするには、データ操作に使用する簡単な拡張可能インタフェースを定義する必要があります。ほとんどのシステムでは C++ が使用できるため、データベースに C++ クラスインタフェースを使用することは道理にかなっています。
あるデータベースに固有の機能を使用する場合(MySQL
の REPLACE
コマンドなど)は、他の
SQL
サーバでその機能を実装できるようにするメソッドをコード化する必要があります(ただし低速化します)。MySQL
を使用すると、/*! */
構文を使用して MySQL
固有のキーワードをクエリに追加できます。/**/
内のコードは、その他の SQL
サーバのほとんどでコメントとして処理(無視)されます。
一部の Web アプリケーションのように正確性よりパフォーマンスが重視される場合は、すべての結果をキャッシュするアプリケーションレイヤを作成すると、さらにパフォーマンスを改善できます。一定の期間後に古い結果を '期限切れ' することで、キャッシュを適度に最新の状態に保持できます。これにより、キャッシュを動的に拡大し、通常の状況に戻るまでタイムアウト期限を高速に設定して、高負荷のスパイクを処理するメソッドが提供されます。
この場合、テーブル作成情報にキャッシュの初期サイズと通常時にテーブルがリフレッシュされる頻度に関する情報が組み込まれます。
MySQL の初期開発当時は、最大顧客に合わせて MySQL の機能が開発されてきました。この機能は、スウェーデンの最大小売商数社向けにデータウェアハウスを処理するものです。
すべての店舗からボーナスカード取引すべてのサマリを毎週取得し、店舗の所有者が顧客に対する広告キャンペーンの効果を調べる際に役立つ情報を提供するように求められています。
このデータは非常に大量(1 か月に約 700 万のサマリ取引)で、ユーザへの提示に必要な 4-10 年間のデータを保有しています。 このデータから新しいレポートに '即時' アクセスできるようにしたいという顧客からの要求が毎週ありました。
1 か月ごとにすべての情報を圧縮 'トランザクション' テーブルに格納することでこの要求を解決しました。トランザクションテーブルからさまざまな基準(製品グループ、顧客 ID、店舗など)によって分類されたサマリテーブルを生成する単純なマクロ(スクリプト)セットを開発しています。レポートは Web ページ形式で、Web ページを解析し、SQL ステートメントを実行して、結果を挿入する、短い Perl スクリプトから動的に生成されます。PHP か mod_perl の使用のほうが適しているとも言えますが、その当時は利用できませんでした。
グラフィカルデータについては、SQL
クエリの結果(この結果に処理を加えて)から
GIF を生成する簡単なツールを C
で作成しました。これも HTML
ファイルを解析する Perl
スクリプトから動的に実行されます。
ほとんどの場合、既存のスクリプトをコピーし、その SQL クエリを修正することで新規のレポートを簡単に実行することができます。状況によっては、既存のサマリテーブルにフィールドを追加したり、新規のテーブルを生成することが必要な場合もありますが、これもディスク上にすべてのトランザクションテーブルを保存しているため非常に容易なことです(現在、少なくとも 50 G のトランザクションテーブルとその他の 200 G の顧客データを保持しています)。
顧客は、ODBC によってサマリテーブルに直接アクセスすることができ、上級ユーザであれば各自でデータを処理することができます。
非常に適度な規模の Sun Ultra SPARCstation(2x200 Mhz)を使用した処理では何も問題が発生していません。最近サーバの 1 つを 2 CPU 400 Mhz UltraSPARC にアップグレードし、製品レベルでのトランザクション処理の開始を計画しています。この処理ではデータが 10 倍増加することになります。システムにディスクを追加するだけでこれに対応できると考えています。
安価に CPU 能力を増強できるように Intel-Linux でも実験を行っています。現在、バイナリの移植可能データベースフォーマット(バージョン 3.23 の新機能)があり、アプリケーションの一部への使用を開始することになっています。
当初、Linux では低から中程度の負荷でのパフォーマンスに優れ、Solaris ではディスク IO が非常に高いため高負荷を達成しようとする際のパフォーマンスに優れているという感触を得ましたが、現在のところこれに関する結論は出ていません。Linux カーネルの開発者との協議の結果、これは、Linux のバッチジョブに割り当てられるリソースが多すぎると対話的なパフォーマンスが非常に低くなる副作用の可能性もあリます。これによって大量のバッチが進行中に非常に低速になり、応答不可の状態が発生します。将来の Linux カーネルではこの処理が改善されるでしょう。
このセクションでは、MySQL
ベンチマークスィート(および
crash-me
)の技術的記述を記載する予定ですが、この記述はまだ作成されていません。現状では、MySQL
ソースディストリビューションの
sql-bench
ディレクトリにあるコードと結果を確認することでベンチマークに関するヒントが得られます。
このベンチマークスィートは、SQL 実装のパフォーマンスを向上または低下させる操作をユーザに示すことを目的としています。
このベンチマークはシングルスレッドであるため、実行される操作の最短時間が測定されていることに注意してください。将来はこのベンチマークスィートにマルチスレッドのテストも多数追加する予定です。
下表は、Windows NT 4.0 コンピュータ上で ODBC を介していくつかのデータベースサーバにアクセスした場合のベンチマーク結果の比較を示しています。
インデックスごとに 2,000,000 レコードの読み取り | 秒 | 秒 |
mysql | 367 | 249 |
mysql_odbc | 464 | ? |
db2_odbc | 1206 | ? |
informix_odbc | 121126 | ? |
ms-sql_odbc | 1634 | ? |
oracle_odbc | 20800 | ? |
solid_odbc | 877 | ? |
sybase_odbc | 17614 | ? |
350,768 レコードの挿入 | 秒 | 秒 |
mysql | 381 | 206 |
mysql_odbc | 619 | ? |
db2_odbc | 3460 | ? |
informix_odbc | 2692 | ? |
ms-sql_odbc | 4012 | ? |
oracle_odbc | 11291 | ? |
solid_odbc | 1801 | ? |
sybase_odbc | 4802 | ? |
最初のテストでは、8M のインデックスキャッシュサイズで MySQL が実行されました。
これ以外にもベンチマーク結果を http://www.mysql.com/information/benchmarks.html のサイトに収集しています。
Oracle は削除の依頼があったため含まれていません。Oracle のベンチマークはすべて、Oracle から提供されます。上記のベンチマークは標準のインストールが 1 クライアントに対して実行できることを示すことを想定しているため、Oracle のベンチマークは非常に偏りがあると確信しています。
ベンチマークスィートを使用するには、以下の要件を満たす必要があります。
ベンチマークスィートは、MySQL ソースディストリビューションによって提供されるため、ソースディストリビューションが必要である。http://www.mysql.com/downloads/ のサイトからリリースされているディストリビューションをダウンロードするか、現在の開発ツリー(see 項2.3.4. 「開発ソースツリーからのインストール」)を使用できる。
ベンチマークスクリプトは Perl で作成され、データベースサーバへのアクセスには Perl DBI モジュールを使用しているため、DBI のインストールが必要である。テスト対象のサーバのそれぞれにサーバ専用の DBD ドライバも必要である。 たとえば、MySQL、PostgreSQL、DB2 をテストするには、DBD::mysql、DBD::Pg、DBD::DB2 のモジュールをインストールする必要がある。
ベンチマークスィートは、MySQL
ソースディストリビューションの
sql-bench
ディレクトリにあります。
ベンチマークテストを実行するには、ロケーションをそのディレクトリに変更し、run-all-tests
スクリプトを実行します。
shell>cd sql-bench
shell>perl run-all-tests --server=server_name
server_name
はサポートされるサーバの 1
つを表します。run-all-tests --help
を呼び出すと、すべてのオプションとサポート対象サーバの一覧を取得できます。
crash-me
では、データベースがサポートする機能と、実際のクエリを実行した場合の機能と制約の判定が試行されます。たとえば、以下についての判定が行われます。
サポートされるカラム型
サポートされるインデックス数
サポートされる関数
使用可能なクエリのサイズ
使用可能な VARCHAR
カラムのサイズ
多様なデータベースに関する
crash-me
の結果は、http://www.mysql.com/information/crash-me.php
のサイトにあります。
確実にアプリケーションとデータベースのベンチマークを行い、ボトルネックを検出しておく必要があります。これを修正(または、ボトルネックを ``ダミーモジュール'' に置換)することによって、次のボトルネック(など)の確認が容易になります。現在のアプリケーションの総合的なパフォーマンスが許容できるものであっても、実際にパフォーマンスの強化が迫られる場合に備えて、少なくともボトルネックそれぞれに対して計画を立て解決方法を判定しておく必要があります。
移植可能なベンチマークプログラムの例として、MySQL ベンチマークスィートを取り上げます。See 項5.1.4. 「MySQL ベンチマークスィート」。このスィートから任意のプログラムを選び、必要に合わせて修正することができます。これによって、それぞれの問題に対して複数の解決方法を試行して、実際に最も高速が得られるのはどれであるかについてテストすることができます。
これ以外の無料のベンチマークスィートに
Open Source Database Benchmark
があり、これは
http://osdb.sourceforge.net/
で入手できます。
一般的には、システムの負荷が非常に高い状況にのみ問題が発生します。負荷の問題が(テスト済の)本稼動のシステムで発生したと問い合わせてくる顧客が多数いました。ほとんどの場合、パフォーマンスに関わる問題は基本的な設計上の問題(高負荷時のテーブルスキャンの不良)かオペレーティングシステムやライブラリの問題が原因だと判明しています。たいていは、システムがまだ本稼動に入っていない場合のほうが問題の修正がはるかに容易です。
このような問題を回避するには、想定可能な最悪の負荷でアプリケーション全体のベンチマークにある程度力を注ぐ必要があります。これには Super Smack を使用できます。これは、http://www.mysql.com/Downloads/super-smack/super-smack-1.0.tar.gz で入手できます。 その名(Smack = 打ちこわし)のとおり、システムに限界まで負荷をかけることができるため、必ず開発システムでのみ使用するようにしてください。
EXPLAIN
構文(SELECT
に関する情報の取得)SELECT
クエリの速度WHERE
節の最適化IS NULL
の最適化DISTINCT
の最適化LEFT JOIN
と RIGHT
JOIN
の最適化ORDER BY
の最適化LIMIT
の最適化INSERT
クエリの速度UPDATE
クエリの速度DELETE
クエリの速度第 1 にすべてのクエリに影響を及ぼすことが 1 つあります。アクセス権システムのセットアップの複雑性が増すほど、オーバヘッドも増加します。
GRANT
ステートメントを何も実行していない場合は、MySQL
によってアクセス権チェックが多少最適化されます。大量の処理が必要なときは、GRANT
を使用しないことで時間を節約できる場合もあります。GRANT
を使用した場合は、アクセス権チェックが多くなり、オーバヘッドが増加します。
明示的な MySQL 関数に関わる問題がある場合は、常に MySQL クライアントでこの関数の計時を行うことができます。
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(loop_count,expression)
はクエリに関数上の問題があるかどうかを調べる際に最適のツールです。
EXPLAIN tbl_name か EXPLAIN SELECT select_options
EXPLAIN tbl_name
は、DESCRIBE
tbl_name
または SHOW COLUMNS FROM
tbl_name
のシノニムです。
キーワード EXPLAIN
を
SELECT
ステートメントの前に置いた場合、MySQL
によってテーブルの結合状況と順序に関する情報が提供され、テーブルの
SELECT
の処理方法が説明されます。
EXPLAIN
を利用すると、より速くレコードを検索する
SELECT
を得るために、どの時テーブルにインデックスを追加しなければならないかを確認できます。
最適化方法の選択に影響を及ぼすキーの、カーディナリティなどのテーブル統計を更新するために、ANALYZE
TABLE
を定期的に実行する必要があります。 See
項4.6.2. 「ANALYZE TABLE
構文」。
また、オプティマイザが、テーブルを最適な順序で結合しているかどうかも確認することができます。
オプティマイザが特定の順番で結合を行うように強制するには、SELECT
ステートメントに STRAIGHT_JOIN
節を追加します。
非単純結合の場合、EXPLAIN
は
SELECT
ステートメントで使用される各テーブルに関する情報を返します。
テーブルは、読み取られた順序に従って一覧表示されます。
MySQL
は、単一スイープ多結合メソッドを使用してすべての結合を解決します。これは、MySQL
が最初のテーブルからレコードを読み取ってから、第
2 のテーブル、第 3
のテーブルといった順序で、一致するレコードの検索を行うことを意味します。
すべてのテーブルの処理が終わると、選択したカラムと、さらに一致レコードがあるテーブルが検索されるまでのテーブル一覧のバックトラックが出力されます。
次のレコードはこのテーブルから読み取られ、処理が次のテーブルから続行されます。
MySQL バージョン 4.1 では、EXPLAIN
出力が変更され、UNION
ステートメント、サブクエリ、派生テーブルなどの構造での機能が改善されています。最も重要なことは、id
と select_type
という 2
つの新しいカラムが追加されたことです。
EXPLAIN
の出力は、次のカラムで構成されます。
id
SELECT
に割り当てられた
ID。クエリ内におけるこの
SELECT
の順序番号。
select_type
SELECT
節の種類、次のいずれかが示される。
SIMPLE
単純な
SELECT
(UNION
やサブクエリを使用しない)。
PRIMARY
最外部の SELECT
UNION
UNION
内の第 2
およびそれ以降の SELECT
ステートメント。
DEPENDENT UNION
UNION
内の第 2
およびそれ以降の SELECT
ステートメント、外側のサブクエリに依存する。
SUBQUERY
サブクエリ内の第 1 SELECT
。
DEPENDENT SUBQUERY
第 1
SELECT
、外側のサブクエリに依存する。
DERIVED
派生テーブル
SELECT
(FROM
節内のサブクエリ)。
table
結果を得るために参照するテーブル。
type
結合型。各結合型を最適なものから順に紹介する。
system
1 レコードのみで構成されるテーブル(=
システムテーブル)。これは、const
結合型の特殊なケースである。
const
テーブルに、一致するレコードが最大で 1
つあり、クエリの開始時に読み取られる。レコードが
1
つしかないため、このレコードのカラムの値はオプティマイザによって定数と見なされる。const
テーブルは、1
回しか読み取られないため、非常に高速である。
const
は、PRIMARY
/UNIQUE
キーを定数と比較する場合に使用される。
SELECT * FROM const_table WHERE primary_key=1; SELECT * FROM const_table WHERE primary_key_part1=1 AND primary_key_part2=2;
eq_ref
前のテーブルのレコードの組み合わせのそれぞれに対して、このテーブルから
1
レコードずつ読み取られる。これは、const
型以外で最適な結合型である。結合でインデックスのすべての部分が使用され、このインデックスが
UNIQUE
または PRIMARY
KEY
である場合に使用される。
=
演算子と比較されるインデックスの張られたカラムには、eq_ref
を使用できる。比較対象のアイテムは定数でも、このテーブル以前に読み取られたテーブルのカラムを使用する式でもかまわない。
下記の例では、ref_table
で
eq_ref
が使用される。
SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
ref
前のテーブルのレコードの組み合わせのそれぞれに対して、インデックス値にマッチするすべてのレコードがこのテーブルから読み取られる。ref
は、インデックスの左端の先頭部分のみが結合で使用される場合、またはインデックスが
UNIQUE
や PRIMARY
KEY
ではない場合(すなわち、この結合において、インデックス値から1つのレコードをSELECTできない場合)に使用される。この結合型は、使用されるインデックスと一致するレコードが数レコードしかない場合に適している。
=
演算子と比較されるインデックスが張られたカラムには、ref
が使用される。
下記の例では、ref_table
で
ref
が示される。
SELECT * FROM ref_table WHERE key_column=expr; SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
ref_or_null
ref
と同様だが、NULL
を使用したレコードの補足検索も追加で実行される。
See 項5.2.5. 「MySQL による IS NULL
の最適化」。
SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;
この結合型の最適化は、MySQL 4.1.1 の新機能で、主としてサブクエリを解決する場合に使用される。
range
インデックスを使用して、一定の範囲にあるレコードのみが取り出される。key
カラムに使用されるインデックスが示される。
key_len
には使用される最長のインデックス部分が記載される。
この型では、ref
カラムが
NULL
になる。
range
は、インデックスを張っているカラムが
=
、<>
、>
、>=
、<
、<=
、IS
NULL
、<=>
、BETWEEN
、および
IN
を使用して定数と比較される場合に使用される。
SELECT * FROM range_table WHERE key_column = 10; SELECT * FROM range_table WHERE key_column BETWEEN 10 and 20; SELECT * FROM range_table WHERE key_column IN (10,20,30); SELECT * FROM range_table WHERE key_part1= 10 and key_part2 IN (10,20,30);
index
これは、インデックスツリーのみがスキャンされる点を除いて
ALL
と同じである。一般にインデックスファイルはデータファイルより小さいため、通常は
ALL
より高速である。
これは、クエリで 1 インデックスの構成部分であるカラムのみが使用される場合にのみ使用される。
ALL
前のテーブルのレコードの組み合わせのそれぞれに対して、フルテーブルスキャンが実行される。一般に、テーブルが
const
の指定がない第 1
テーブルの場合には適さず、その他の場合はすべて非常に不適である。通常は、さらにインデックスを追加することで
ALL
を回避し、定数値または以前のテーブルのカラム値を基準にレコードを取り出すようにすることができる。
possible_keys
possible_keys
カラムは、このテーブル内のレコードの検索に
MySQL
で使用可能なインデックスを示す。このカラムはテーブルの順序にはまったく依存しないことに注意する。すなわち、possible_keys
のキーの一部は、生成されたテーブルの順序では事実上使用できないことになる。
このカラムが NULL
の場合は、対応するインデックスがない。この場合は、WHERE
節でインデックス作成に適するカラムを 1
つ以上参照しているかどうかを調べることでクエリのパフォーマンスを改善できる。参照している場合は適切なインデックスを作成し、再度
EXPLAIN
を使用してクエリをチェックする。 See
項6.5.4. 「ALTER TABLE
構文」。
テーブルにあるインデックスを調べるには
SHOW INDEX FROM tbl_name
を使用する。
key
key
カラムは、MySQL
が実際に使用を決定したキー(インデックス)を示す。選択されたインデックスがない場合、このキーは
NULL
になる。MySQL で
possible_keys
カラムに記載されたキーが使用されるように強制するには、クエリで
USE KEY/IGNORE KEY
を使用する。 See
項6.4.1. 「SELECT
構文」。
また、テーブルで myisamchk
--analyze
(see
項4.5.6.1. 「myisamchk
起動構文」)または
ANALYZE TABLE
(see
項4.6.2. 「ANALYZE TABLE
構文」)を実行することも、オプティマイザでより適したインデックスを選択する際に役立つ。
key_len
key_len
カラムは、MySQL
が使用を決定したキーの長さを示す。key
が NULL
の場合、この長さは
NULL
になる。これによって、複合キーで MySQL
が実際に使用するパート数が示されることに注意する。
ref
ref
カラムは、テーブルからレコードを選択する際に
key
とともに使用されるカラムまたは定数を示す。
rows
rows
カラムは、クエリの実行に際して調べる必要があると
MySQL
によって判定されたレコードの数を示す。
Extra
このカラムには、MySQL でどのようにクエリが解決されるかに関する追加情報が記載される。以下は、このカラムに記載できる各種テキスト文字列の説明である。
Distinct
マッチした最初のレコードが検索されると、MySQL は現在のレコードの組み合わせによるその後のレコード検索を続行しないことを示す。
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
は t2.id
ではないと認識され、t2
内の同じ id
を持つ残りのレコードのスキャンは行われない。言い換えると、t2
にあるマッチするレコードの数に関わらず、MySQL
で実行が必要なことは t1
のレコードのそれぞれに対して、t2
のルックアップを 1
回実行することだけである。
range checked for each record (index map:
#)
MySQL で使用に適した実際のインデックスを検索できなかったことを示す。代替として、先行テーブルのレコードの組み合わせのそれぞれに対して、使用するインデックス(存在する場合)のチェックが実行され、このインデックスがテーブルからのレコードの取り出しに使用される。非常に高速ではないが、インデックスなしの結合と比較すると高速である。
Using filesort
レコードをソートして取り出す方法を決定するには、MySQL
はパスを余分に実行しなくてはならないことを示す。
join type
に従ってすべてのレコードをスキャンし、WHERE
条件に一致する全てのレコードに、ソートキー
+
行ポインタを格納して、ソートは実行される。
その後キーがソートされる。
最後に、ソートされた順にレコードが取り出される。
Using index
インデックスツリーの情報のみを使用してカラム情報がテーブルから取り出され、実際のレコードを読み取るその後の検索を実行する必要がないことを示す。これは、そのテーブルで使用されたカラムがすべて同一インデックスの構成部分である場合に実行できる。
Using temporary
クエリの解決に MySQL
で結果を保持するテンポラリテーブルの作成が必要であることを示す。これは一般に、GROUP
BY
を実行したカラムセットと異なるカラムセットに対して
ORDER BY
を実行した場合に発生する。
Using where
次のテーブルとの一致が調べられるレコードまたはクライアントに送信されるレコードの限定に
WHERE
節が使用されることを示す。この情報がなく、テーブルの型が
ALL
または
index
である場合はクエリが正常に実行されないことがある(テーブルのすべてのレコードの取得や検査を意図していない場合)。
クエリを最大限高速に実行する必要がある場合は、Using
filesort
と Using temporary
に注意する必要がある。
EXPLAIN
出力の rows
カラムのすべての値を掛け算することで、結合がどの程度適しているかを示す指針を取得できます。これは、クエリの実行時に
MySQL
で調べる必要があるレコード数の概要を示します。この数値は、max_join_size
変数でクエリを制限する際にも使用されます。
See 項5.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,ClientID,ActualPC NULL NULL NULL 3872 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,ClientID,ActualPC NULL NULL NULL 3872 Using where 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 where ClientID, 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 に示すことができます。
shell>myisamchk --analyze PATH_TO_MYSQL_DATABASE/tt
shell>mysqladmin refresh
これで結合が完全になり、EXPLAIN
で以下の結果が生成されます。
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC NULL NULL NULL 3872 Using where ClientID, 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
結合オプティマイザの学習による推測であることに注意してください。クエリを最適化するには、この数値が実際に近いものであるかどうかを確認する必要があります。実際とかけ離れている場合は、SELECT
ステートメントで STRAIGHT_JOIN
を使用し、FROM
節でテーブルの順序を変えて一覧表示してみるとパフォーマンスを改善できます。
ほとんどの場合、ディスクシークをカウントしてパフォーマンスを推定できます。
小さいテーブルの場合は一般に 1
つのディスクシークでレコードを検索できます(インデックスがキャッシュされることが多いため)。大きいテーブルの場合の推定では、(B++
ツリーインデックスを使用して)log(row_count)
/ log(index_block_length / 3 * 2 / (index_length +
data_pointer_length)) + 1
のシークがレコードの検索に必要になります。
MySQL では、インデックスブロックが通常 1,024
バイトで、データポインタは通常 4
バイトです。インデックスの長さが
3(中位の整数)の 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 シークが必要になります。
このことは、アプリケーションが対数 N の分だけ低速になるという意味ではないことに注意してください。OS または SQL サーバですべてがキャッシュされている限り、テーブルが拡大しても速度の低下はわずかです。データがキャッシュできないほど増加すると、ディスクシーク(対数 N の分だけ増加する)によって最終的にアプリケーションがバインドされるまで大幅に速度の低下が始まります。これを回避するには、データの増加に合わせてインデックスキャッシュも拡大します。 See 項5.5.2. 「サーバパラメータのチューニング」。
一般に、低速の SELECT ... WHERE
の速度を上げる必要がある場合は、まず、インデックスを追加できるかどうかをチェックします。See
項5.4.3. 「MySQL でのインデックスの使用」。一般に複数のテーブル間の参照はすべてインデックスを使用して実行する必要があります。EXPLAIN
コマンドを使用して、SELECT
に使用されるインデックスを判定できます。 See
項5.2.1. 「EXPLAIN
構文(SELECT
に関する情報の取得)」。
一般的なヒント
MySQL
によるクエリの最適化を容易にするには、関連データをロードした後にテーブルに対して
myisamchk --analyze
を実行する。これはインデックスのために、同じ値があるレコードの平均値を更新する(ユニークインデックスの場合、これは常に
1 になる)。MySQL はこれを使用して、2
つのテーブルを '非定数式'
で接続する際に選択するインデックスを判定する。
SHOW INDEX FROM table_name
を実行し
Cardinality
カラムを調べると、analyze
の実行結果をチェックできる。
インデックスに従ってインデックスとデータをソートするには
myisamchk --sort-index
--sort-records=1
(インデックス 1
でソートする場合)を使用する。速度を上げるには、すべてのレコードの読み取りにユニークインデックスを使用し、そのインデックスに従った順序で読み取りを行うように推奨される。ただし、このソートでは書き込みの最適化はできず、テーブルが大きい場合は時間がかかる。
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 回に限られる。
WHERE
がない単一テーブルの
COUNT(*)
は、MyISAM
と HEAP
テーブルのテーブル情報から直接取り出される。
これは、テーブル 1
つのみで使用する場合はすべての NOT
NULL
式でも実行される。
無効定数式の早期検出。MySQL は実行不可能な
SELECT
ステートメントがある場合、それを迅速に検出し、結果としてレコードを返さない。
GROUP BY
またはグループ関数(COUNT()
、MIN()
。..)を使用しない場合、HAVING
は WHERE
とマージされる。
サブ結合のそれぞれに、単純な
WHERE
が構造化され、サブ結合ごとに迅速に
WHERE
評価を取得し、可能な限り迅速にレコードをスキップする。
クエリ内の他のすべてのテーブルの前に、まず、すべての定数テーブルが読み込まれる。 定数テーブルとは以下のものを指す。
空白テーブルまたは 1 レコードのみのテーブル。
UNIQUE
インデックスまたは
PRIMARY KEY
を使う
WHERE
節とともに使用されるテーブルで、インデックス部分のすべてが定数式とともに使用され、そのインデックス部分が
NOT NULL
として定義されている場合。
以下のテーブルはすべて定数テーブルとして使用される。
mysql>SELECT * FROM t WHERE primary_key=1;
mysql>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% 未満である最適インデックスが使用される。このようなインデックスが検索されない場合は、クイックテーブルスキャンが使用される。
状況によっては、MySQL でデータファイルの参照もせずにインデックスからレコードを読み取れる場合もある。インデックスから使用されるカラムのすべてが数値型の場合、クエリの解決にはインデックスツリーのみが使用される。
レコードのそれぞれが出力される前に、HAVING
節と一致しないレコードはスキップされる。
非常に高速なクエリのサンプルをいくつか紹介します。
mysql>SELECT COUNT(*) FROM tbl_name;
mysql>SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;
mysql>SELECT MAX(key_part2) FROM tbl_name
->WHERE key_part_1=constant;
mysql>SELECT ... FROM tbl_name
->ORDER BY key_part1,key_part2,... LIMIT 10;
mysql>SELECT ... FROM tbl_name
->ORDER BY key_part1 DESC,key_part2 DESC,... LIMIT 10;
次のクエリは、インデックスツリーのみを使用して解決されます(インデックスのあるカラムが数値型であると想定)。
mysql>SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;
mysql>SELECT COUNT(*) FROM tbl_name
->WHERE key_part1=val1 AND key_part2=val2;
mysql>SELECT key_part2 FROM tbl_name GROUP BY key_part1;
The following queries use indexing to retrieve the rows in sorted order without a separate sorting pass: 次のクエリは、ソートのパスを分けることなく、ソートしたレコードを取り出すためにインデックスを使用します。
mysql>SELECT ... FROM tbl_name
->ORDER BY key_part1,key_part2,... ;
mysql>SELECT ... FROM tbl_name
->ORDER BY key_part1 DESC,key_part2 DESC,... ;
MySQL では、column = constant_value
の場合と同じ最適化を column IS NULL
に対しても実行できます。たとえば、MySQL
では、インデックスと範囲を使用して、IS
NULL
で NULL
を検索できます。
SELECT * FROM table_name WHERE key_col IS NULL; SELECT * FROM table_name WHERE key_col <=> NULL; SELECT * FROM table_name WHERE key_col=# OR key_col=# OR key_col IS NULL
OUTER JOIN
に使用されないテーブル上で、WHERE
節内で column_name IS NULL
で定義された物を NOT NULL
と使用する場合、その式は消去して最適化されます。
MySQL 4.1.1 では、column = expr AND column IS
NULL
の組み合わせを最適化する機能が追加されています。この最適化が使用される場合は、EXPLAIN
は 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
レベルしか処理できないことに注意が必要です。
SELECT * FROM t1,t2 where (t1.a=t2.a AND t2.a IS NULL) OR (t1.b=t2.b AND t2.b IS NULL);
この状況で MySQL は (t1.a=t2.a AND t2.a IS
NULL)
の部分に対してキーのルックアップを実行するのみで、b
のキー部分は使用できません。
DISTINCT
が ORDER BY
と組み合わされて用いられると、多くの場合はテンポラリテーブルが必要になります。
DISTINCT
は GROUP BY
をともなう可能性が高いので、SELECT
されないフィールドを ORDER BY
または HAVING
した時に、どのように MySQL
が機能するかを認識しておく必要があります。
See 項6.3.7.3. 「非表示のフィールドに対する GROUP BY
」。
LIMIT row_count
を
DISTINCT
とともに使用した場合、MySQL
は一意のレコードを row_count
行検索するとただちに検索を停止します。
使用するテーブル内のカラムを使用しない場合、MySQL は最初にマッチするレコードを検索するとただちに未使用テーブルのスキャンを停止します。
SELECT DISTINCT t1.a FROM t1,t2 WHERE t1.a=t2.a;
ここでは、t1
が t2
の前に使用され(EXPLAIN
によるチェック)、t2
で最初のレコードが検索されると
t2
からの読み取り(t1
の特定のレコード)を停止します。
MySQL の A LEFT JOIN B join_condition
は以下のように実装されます。
テーブル B
はテーブル
A
と A
が依存するすべてのテーブルに依存するように設定される。
テーブル A
は、LEFT
JOIN
条件で使用されるすべてのテーブル(B
を除く)に依存するように設定される。
LEFT JOIN
条件は、テーブル B
からのレコードの取り出し方法の判定に使用される(言い換えると、WHERE
節の条件はいずれも使用されない)。
あるテーブルが全てのテーブルの後に読み取られる場合を除き、通常の最適化全てが行われる。依存関係が循環している場合は、MySQL からエラーが出力される。
標準の WHERE
最適化すべてが実行される。
A
に WHERE
節の条件にマッチするレコードがあり、B
に ON
条件にマッチするレコードがない場合、B
のカラムの値が NULL
に設定されたレコードが生成される。
テーブルのいずれかに存在しないレコードを検索する際に
LEFT JOIN
を使用していて、かつ、WHERE
節内で、NOT NULL
と定義した
column_name
を column_name IS
NULL
で評価した場合、 MySQL は
LEFT JOIN
条件に一致するレコードを 1
つ検索すると、その後はレコードの検索(特定のキー組み合わせの)を停止する。
RIGHT JOIN
の実装は LEFT
JOIN
と類似しています。
テーブル読み取り順序は LEFT JOIN
と STRAIGHT JOIN
によって強制されるため、チェック対象のテーブル順列が減少し、結合オプティマイザ(テーブルの結合順序を計算する)の動作の速度がさらに上がります。
上記は、該当する種類のクエリを実行した場合であることに注意してください。
SELECT * FROM a,b LEFT JOIN c ON (c.key=a.key) LEFT JOIN d (d.key=a.key) WHERE b.key=d.key
LEFT JOIN
が d
の前に読み取るように強制するため、MySQL では
b
の完全スキャンが実行されます。
この状況はクエリを以下のように変更して修正します。
SELECT * FROM b,a LEFT JOIN c ON (c.key=a.key) LEFT JOIN d (d.key=a.key) WHERE b.key=d.key
4.0.14 以降、MySQL では以下の LEFT
JOIN
最適化が行われます。
生成された NULL
レコードで
WHERE
条件が常に false
である場合、LEFT JOIN
は通常の結合に変更されます。
たとえば、t2
カラムが
NULL
であるとすると、以下のクエリの
WHERE
節は false
になるため、通常の結合に変換しても問題ありません。
SELECT * FROM t1 LEFT t2 ON (column) WHERE t2.column2 =5; -> SELECT * FROM t1,t2 WHERE t2.column2=5 AND t1.column=t2.column;
これでクエリが改善できる場合、MySQL
がテーブル t1
を読み取る前にテーブル t2
を使用できるようになるためスピードが向上します。テーブルの順序を指定して強制する場合は
STRAIGHT JOIN
を使用します。
余分なソートを行わずに ORDER BY
または GROUP BY
の要求に応じるために、MySQL
はインデックスを使用する場合があります。
全ての使用されていないインデックス部分と他の部分が
WHERE
節内で定数であるカラムである場合、ORDER
BY
がインデックスに完全にマッチしない場合でもこのインデックスを使用できます。
次のクエリではインデックスを使用して
ORDER BY
/ GROUP BY
部分を解決します。
SELECT * FROM t1 ORDER BY key_part1,key_part2,... SELECT * FROM t1 WHERE key_part1=constant ORDER BY key_part2 SELECT * FROM t1 WHERE key_part1=constant GROUP BY key_part2 SELECT * FROM t1 ORDER BY key_part1 DESC,key_part2 DESC SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC,key_part2 DESC
MySQL で ORDER BY
の解決にインデックスを使用できない場合は以下のとおりです(この場合も
MySQL は WHERE
節の条件に一致するレコードの検索にインデックスを使用します)。
複数のキーに対して ORDER BY
を実行する場合。
SELECT * FROM t1 ORDER BY key1,key2
連続しないキー部分に対して ORDER
BY
を実行する場合。
SELECT * FROM t1 WHERE key2=constant ORDER BY
key_part2
ASC
と DESC
が混在している場合。
SELECT * FROM t1 ORDER BY key_part1 DESC,key_part2
ASC
レコードの取り出しに使用されるキーが
ORDER BY
の実行に使用されるキーと異なる場合。
SELECT * FROM t1 WHERE key2=constant ORDER BY
key1
ORDER BY
で多くのテーブルとカラムを結合していて、それら全てがレコードの取り出しに使用される最初の非
const
テーブルではない場合(これは
EXPLAIN
で出力される最初のテーブルで、かつ、const
メソッドを使用していないテーブル)。
ORDER BY
と GROUP BY
式が異なる場合。
使用されたテーブルインデックスが、並び順にレコードを格納していないインデックスタイプの場合(HEAP
テーブルの HASH
インデックスなど)。
MySQL で結果のソートが必要な場合は、以下のアルゴリズムが使用されます。
キーまたはテーブルスキャンに従ってすべてのレコードが読み取られる。
WHERE
節に一致しないレコードはスキップされる。
ソートキーがバッファ(サイズ
sort_buffer
)に格納される。
バッファが満杯になると、qsort が実行され結果がテンポラリファイルに格納される。ポインタはソートブロックに保存される(すべてのレコードがソートバッファに適合する場合は、テンポラリファイルが作成されない)。
すべてのレコードが読み取られるまで上記項目が反復される。
MERGEBUFF
(7)領域まで、別のテンポラリファイルの
1
ブロックにマルチマージが実行される。最初のファイルの全ブロックが
2
つめのファイルに配置されるまで反復される。
残りが
MERGEBUFF2
(15)ブロック未満になるまで、以下が反復される。
最終マルチマージでは、レコードに対するポインタ(ソートキーの最終部分)のみが結果ファイルに書き込まれる。
次に、sql/records.cc
のコードが使用され、結果ファイルのポインタによってソートされた順序で読み取りが行われる。これを最適化するためローポインタの大きなブロックを読み込み、そのソートを行ってからソートされた順序でレコードバッファにレコードを読み取る(read_rnd_buffer_size
)。
EXPLAIN SELECT ... ORDER BY
を使用すると、MySQL
でインデックスを使用してクエリを解決できるかどうかをチェックできます。extra
カラムに Using filesort
が出力された場合は、MySQL で ORDER
BY
の解決にインデックスを使用できません。 See
項5.2.1. 「EXPLAIN
構文(SELECT
に関する情報の取得)」。
さらに ORDER BY
の速度を上げる必要がある場合はまず、ソートフェーズを実行する必要なく
MySQL
でインデックスを使用できるかどうかを調べます。これが不可能な場合は、以下を実行できます。
sort_buffer_size
変数の値を増やす。
read_rnd_buffer_size
変数の値を増やす。
tmpdir
に空き領域が大量にある専用ディスク上のディレクトリを指定する。
MySQL 4.1
以降を使用している場合、tmpdir
に対してコロン :
(Windows
の場合はセミコロン
;
)で区切ったパスの一覧を設定することで、複数の物理ディスク間の負荷を分散させることができる。この物理ディスクは、ラウンドロビン方法で使用される。
注意:
これらのパスは、同一ディスクの複数のパーティションではなく、異なる物理ディスクである必要がある。
デフォルトでは、クエリで ORDER BY
x,y[,...]
と指定した場合と同様に MySQL
によってすべての GROUP BY x,y[,...]
クエリがソートされます。ORDER BY
節を明示的に記述した場合、ソートは発生するものの、MySQL
はスピードを損なうことなくそれを最適化します。
クエリに GROUP BY
が含まれていて、もし結果のソートのオーバヘッドを回避したいならば、ORDER
BY NULL
を指定することでソートを抑止できます。
INSERT INTO foo SELECT a,COUNT(*) FROM bar GROUP BY a ORDER BY NULL;
HAVING
を使用するのではなく
LIMIT row_count
を使用している場合、MySQL
によるクエリの処理方法が異なる場合があります。
LIMIT
を使用して数レコードしか選択していないと、フルテーブルスキャンが行われそうな場合に、MySQL
はインデックスを使うことがある。
ORDER BY
とともに LIMIT
row_count
を使用している場合、MySQL
ではすべてのテーブルがソートされるのではなく、最初の
row_count
レコードの検索が行われた時点でただちにソートを終了する。
LIMIT row_count
を
DISTINCT
とあわせて使用した場合、MySQL は一意の
row_count
行のレコードを検索するとただちに停止する。
GROUP BY
がキーを順番に読む(またはキーのソートを実行して読む)ことで解決でき、キーの値が変わるまで
サマリが計算される場合もある。この場合、LIMIT
row_count
では不要な GROUP
BY
値の計算がすべて行われなくなる。
MySQL が最初の #
レコードをクライアントに送信すると、クエリが中止される(SQL_CALC_FOUND_ROWS
を使用していない場合)。
LIMIT 0
は常に迅速に空のセットを返す。これは、クエリのチェックおよび結果として返るカラムのカラム型の取得に役立つ。
サーバでテンポラリテーブルを使用してクエリが解決される場合、LIMIT
row_count
が必要な領域の計算に使用される。
レコード挿入の時間構成の概要は次のとおりです。
接続:(3)
サーバへのクエリの送信:(2)
クエリの解析:(2)
レコード挿入:(1 x レコードサイズ)
インデックス挿入:(1 x インデックス数)
クローズ:(1)
ここに示した数値は、時間全体を比例的に配分したものです。テーブルを開く初期オーバヘッドは算入されていません(これは同時実行クエリのそれぞれで 1 回実行されます)。
テーブルのサイズによって対数 N の分だけインデックス挿入の速度が低下します(B ツリー)。
挿入の速度を上げる方法
1
つのクライアントから同時に多数のレコードを挿入する場合はマルチプル
INSERT
ステートメントを使用する。これで独立した
INSERT
ステートメントの使用時と比較して大幅に(場合によっては数倍)速度が上がる。空ではないテーブルにデータを追加する場合は、さらに速度を上げるために
bulk_insert_buffer_size
変数を調整する。 See
項4.6.8.4. 「SHOW VARIABLES
」。
異なる複数のクライアントから大量のレコードを挿入する場合は、INSERT
DELAYED
ステートメントを使用すると速度を上げることができる。
See 項6.4.3. 「INSERT
構文」。
MyISAM
テーブルでは、テーブルに削除されたレコードがない場合、SELECT
の実行と同時にレコードを挿入できることに注意する。
テキストファイルからテーブルをロードする場合は
LOAD DATA INFILE
を使用する。通常、これは
INSERT
ステートメントを多数使用する場合と比較して、20
倍速度が上がる。 See 項6.4.8. 「LOAD DATA INFILE
構文」。
テーブルにインデックスが多数ある場合、操作を少し追加するだけで
LOAD DATA INFILE
の実行速度をさらに上げることができる。以下の手順を使用する。
CREATE TABLE
を使用して、テーブルを作成する。mysql
や Perl-DBI などを使用する。
FLUSH TABLES
ステートメントまたはシェルコマンド
mysqladmin flush-tables
を実行する。
myisamchk --keys-used=0 -rq
/path/to/db/tbl_name
を使用する。これでテーブルからすべてのインデックスの使用が削除される。
LOAD DATA INFILE
を使用し、テーブルにデータを挿入する。これはインデックスをまったく更新しないため、非常に高速になる。
テーブルを読み取り専用にする場合は、myisampack
を実行してテーブルを小さくする。 See
項7.1.2.3. 「圧縮テーブルの特性」。
myisamchk -r -q /path/to/db/tbl_name
を使用してインデックスを作成しなおす。これは、ディスクに書き込む前にメモリにインデックスツリーを作成して、ディスクシークを回避するため非常に高速になる。生成されたインデックスツリーは完全にバランスが取られている。
FLUSH TABLES
ステートメントまたはシェルコマンド
mysqladmin flush-tables
を実行する。
空のテーブルへ挿入する場合は、LOAD
DATA INFILE
は上記の最適化を実行します。上記手順との主な相違点は、myisamchk
にインデックス作成用のテンポラリメモリを大幅に割り当てることができる点です。
MySQL 4.0 以降は、myisamchk --keys-used=0 -rq
/path/to/db/tbl_name
の代わりに
ALTER TABLE tbl_name DISABLE KEYS
を、また myisamchk -r -q
/path/to/db/tbl_name
の代わりに
ALTER TABLE tbl_name ENABLE KEYS
を使用することもできます。このようにすると、FLUSH
TABLES
ステップをスキップすることもできます。
複数ステートメントを使用して実行される挿入の速度を、テーブルをロックすることによって上げることができる。
mysql>LOCK TABLES a WRITE;
mysql>INSERT INTO a VALUES (1,23),(2,34),(4,33);
mysql>INSERT INTO a VALUES (8,26),(6,29);
mysql>UNLOCK TABLES;
主な速度の相違点は、すべての
INSERT
ステートメントの完了後にインデックスバッファが
1
回のみディスクにフラッシュされることである。通常は、INSERT
ステートメントの数と同じだけ、インデックスバッファのフラッシュが行われる。すべてのレコードを
1
つのステートメントで挿入できる場合はロックの必要がない。
トランザクションテーブルの場合は、LOCK
TABLES
ではなく
BEGIN/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
行で非常に多数の挿入を実行する場合は、ときどき(約
1,000 レコードごと)LOCK TABLES
に UNLOCK TABLES
を続けて実行して、他のスレッドからのテーブルへのアクセスを可能にすることができる。これでもパフォーマンの増加が得られる。
言うまでもなく、データのロードには
LOAD DATA INFILE
のほうが大幅に高速である。
LOAD DATA INFILE
と
INSERT
の両方の速度をさらに改善するには、キーバッファを拡張します。
See 項5.5.2. 「サーバパラメータのチューニング」。
更新クエリは、SELECT
クエリと同様に最適化されますが、書き込みオーバヘッドが加算されます。書き込みの速度は更新対象のデータのサイズおよび更新対象のインデックス数によって異なります。変更がないインデックスは更新されません。
更新の速度を上げるもう 1 つの方法は、更新を遅延して 1 行で多数の更新を後から行うことです。1 行での多数の更新は、テーブルをロックすると同時に行う場合と比較して大幅に高速に実行できます。
可変長レコードの場合は、合計の長さが今よりも長いものにレコードを更新すると、レコードが分割される場合があることに注意します。このため、頻繁にこれを実行する場合は、ときどき
OPTIMIZE TABLE
することが重要になります。 See
項4.6.1. 「OPTIMIZE TABLE
構文」。
テーブル内のすべてのレコードを削除する場合は、TRUNCATE
TABLE table_name
を使用します。 See
項6.4.6. 「TRUNCATE
構文」。
レコード削除に要する時間は、完全にインデックス数に比例します。レコード削除の速度を上げるには、インデックスキャッシュのサイズを拡大します。 See 項5.5.2. 「サーバパラメータのチューニング」。
システム高速化のためのヒント(順不同)
接続オーバヘッドを回避するには、データベースに対して永続的な接続を使用する。永続的な接続を使用せずにデータベースに対して多数の新規接続を実行する場合は、thread_cache_size
変数の値の変更が必要になることがある。
See 項5.5.2. 「サーバパラメータのチューニング」。
常にすべてのクエリがテーブル内に作成したインデックスを実際に使用していることを確認する。MySQL
では、EXPLAIN
コマンドでこれを実行できる。 See
項5.2.1. 「EXPLAIN
構文(SELECT
に関する情報の取得)」。
大量に更新された MyISAM
テーブルに対して複雑な SELECT
クエリを使用しないようにする。これでテーブルロックを回避する。
削除されたレコードがない
MyISAM
テーブルの場合は、別のクエリでそのテーブルからの読み取りが行われるのと同時にレコードを挿入できる。これがあなたにとって重要ならば、レコードの削除が不要なメソッドや、大量のレコード削除後の
OPTIMIZE TABLE
の実行を検討する。
expr1,expr2...
の順に従って頻繁にレコードを読み取る場合は、ALTER
TABLE ... ORDER BY expr1,expr2...
を使用する。テーブルが大幅に変更された後にこのオプションを使用すると、パフォーマンスを改善できる。
他のカラムの情報を基にした
'ハッシュされた'
カラムを導入することが役立つ場合がある。このカラムが短いもので、一意性がある場合は、多数のカラムに大きなインデックスを使用するより大幅に高速化できる。MySQL
では、追加カラムの使用が以下のように非常に容易である。
SELECT * FROM table_name WHERE
hash=MD5(CONCAT(col1,col2)) AND col_1='constant' AND
col_2='constant'
大量に変更があるテーブルはすべて
VARCHAR
や BLOB
のカラムを使用しないようにする。VARCHAR
または BLOB
カラムを 1
つ使用するとレコードがただちに可変長になってしまう。
See 章?7. MySQL のテーブル型。
一般に、1 つのテーブルを複数のテーブルに分割することは、レコードが '大きく' なるだけで高速化の役には立たない。レコードにアクセスする際の、最も大きなパフォーマンス要因は、レコードの最初のバイトを見つけるためのディスクシークである。データの検索後、ほとんどの新規ディスクでは、大多数のアプリケーションに十分な速度でレコード全体を読み取ることができる。テーブルの分割が実際に有効な状況は、固定長テーブルへの変更が可能な可変長テーブル(上記参照)の場合か、テーブルのスキャンを非常に頻繁に必要としながらもほとんどのカラムを結果に必要としない場合のみである。 See 章?7. MySQL のテーブル型。
多数のレコードの情報から計算する頻度を非常に高くする必要がある場合(カウントの場合など)、新たなテーブルを導入し、リアルタイムでカウンタを更新するほうがはるかに適している。UPDATE
table SET count=count+1 WHERE
index_column=constant
のような更新は非常に高速にできる。
実際これは、MyISAM
や
ISAM
のようにテーブルロック(複数リーダ/単一ライタ)のみの
MySQL
テーブル型を使用する場合に非常に重要である。また、このような場合は行ロックマネージャで必要な作業が少なくなるため、ほとんどのデータベースでパフォーマンスが改善される。
大きなログテーブルから統計を収集する必要がある場合は、テーブル全体をスキャンするのではなく、サマリテーブルを使用する。サマリの管理は、リアルタイムで統計を実行する場合と比較して非常に高速になる。何らかの変更がある(業務上の決定に応じて)場合は、ログから新規にサマリテーブルを再生成したほうが、実行アプリケーションの変更よりはるかに高速である。
可能であれば、レポートをリアルタイムか集計かのいずれかに分類するように推奨する。集計レポートに必要なデータは、サマリテーブルから生成され、サマリテーブルは実データから生成される。
カラムにデフォルト値がある利点を生かす。挿入対象の値がデフォルト値と相違する場合のみ明示的に値を挿入する。これで、MySQL が要する解析作業が軽減され、挿入の速度が改善される。
状況によっては、データを BLOB にパックし、格納したほうが便利である。このような場合は、BLOB へのパックおよびパック解除を行うコードをアプリケーションに追加する必要があるが、あるステージにおける大量のアクセスを省略できることになる。 これは、固定長テーブル構造に準拠しないデータがある場合に実用的である。
通常は、すべてのデータが冗長にならないようにする必要がある(データベースセオリの第 3 正規化)が、高速化を図る必要がある場合はデータなどの複製やサマリテーブルの作成をためらうべきではない。
ストアドプロシージャや UDF(ユーザ定義関数)はパフォーマンスの向上に役立つ手段である。ただし、これをサポートしないデータベースを使用する場合は、常に代替の(速度が遅い)方法も用意する必要がある。
アプリケーションのクエリと応答をキャッシュすること、および挿入と更新の同時実行を試行することは必ず高速化に役立つ。データベースでロックテーブルがサポートされる場合(MySQL や Oracle など)は、これによって確実にすべての更新後にインデックスキャッシュが 1 回だけフラッシュされるようにできる。
データの書き込みするタイミングを知る必要がない場合は、INSERT
/*! DELAYED */
を使用する。多数のレコードが 1
回のディスクへの書き込みで書き込まれるため、これで高速化が図れる。
SELECT
の優先を上げる場合は、INSERT /*!
LOW_PRIORITY */
を使用する。
SELECT
がキューをジャンプするようにする場合は、SELECT
/*! HIGH_PRIORITY */
を使用する。言い換えると、書き込み待機中のユーザがいる場合でも、SELECT
を実行できるようになる。
1 つの SQL
コマンドで多数のレコードを格納するには、複数行の
INSERT
ステートメントを使用する(これは多数の
SQL でサポートされている)。
大量のデータをロードする場合は LOAD
DATA INFILE
を使用する。これは通常の挿入より高速になる。
一意の値にするには
AUTO_INCREMENT
カラムを使用する。
一定の間隔で OPTIMIZE TABLE
を使用して、動的テーブルの断片化を回避する。
See 項4.6.1. 「OPTIMIZE TABLE
構文」。
可能ならば HEAP
を使用して高速化を図れるようにする。 See
章?7. MySQL のテーブル型。
通常の Web サーバセットアップを使用する場合は、画像をファイルとして格納する。言い換えると、データベース内にはファイル参照のみを格納する。この主な理由は、通常の Web サーバのほうがデータベースコンテンツと比較してファイルのキャッシュに優れているためである。このため、ファイルを使用したほうがシステムの高速化を容易に図れる。
頻繁にアクセスされる非クリティカルデータ(クッキーなしでユーザに最後に表示されたバナーの情報など)にはメモリテーブルを使用する。
別のテーブルで同一情報を扱うカラムは、同じ宣言をし、同じ名前を付ける。バージョン 3.23 以前はこのようにしないと結合の速度が遅くなる。
名前はなるべく単純なものに保持する(カスタマテーブルでは
customer_name
ではなく
name
を使用する)。他の SQL
サーバに移植可能にすることを考慮するなら、名前を
18 文字未満にする。
高速化が大きく必要とされる場合は、複数の
SQL
サーバがサポートするデータストレージの低レベルインタフェースを調べる必要がある。たとえば、MySQL
MyISAM
に直接アクセスすることによって、SQL
インタフェース使用時と比較して 2-5
倍の速度が得られることもある。
これを実行可能にするには、データをアプリケーションと同じサーバに配置し、また通常は
1
プロセスのみからアクセスするようにする必要がある(外部ファイルロックが非常に低速なため)。上記の問題は、MySQL
サーバに低レベルの MyISAM
コマンドを導入することで解消できる(必要に応じてパフォーマンスを改善する容易な手段の
1
つ)。データベースインタフェースを慎重に設計することで、この種の最適化を容易にサポートできる。
多くの場合、テキストファイルにアクセスするのと比較して、データベースからデータにアクセスしたほうが高速である。この理由は一般にテキストファイル(数値データ使用時)よりデータベースのほうがよりコンパクトで、必要なディスクアクセスが少ないことによる。また、テキストファイルを解析してレコードとカラムの境界を検索する必要がないため、コードも節約できる。
レプリケーションでも高速化を図ることができる。 See 項4.11. 「MySQL のレプリケーション」。
DELAY_KEY_WRITE=1
オプションでテーブルを定義すると、ファイルが閉じられるまでディスクにログが記録されないためインデックス更新の速度が上がる。
この欠点は、途中で mysqld
の強制終了が発生した場合にテーブルに問題がないことを確認するため、mysqld
を開始する前に、テーブルに対して
myisamchk
を実行する必要があるということである。キー情報は常にデータから生成可能であるため、DELAY_KEY_WRITE
を使用しても何も消失はしない。
ロックメソッドそれぞれについての説明は付録にあります。 See 項E.4. 「ロック方法」。
InnoDB
型と BDB
型のテーブルを除き、MySQL
のロックはすべてデッドロックフリーです。
これは、常にクエリの開始時に必要なすべてのロックを要求し、また、常に同じ順序でテーブルをロックすることによって管理されます。
InnoDB
型のテーブルは、行ロックを自動的に取得し、BDB
型のテーブルは、トランザクションの開始時ではなく
SQL
ステートメントの処理時にページロックを自動取得します。
MySQL は WRITE
ロックに以下のロック方法を使用します。
テーブルにロックがない場合は、書き込みロックを配置する。
その他の場合は、書き込みロックキューにロック要求を配置する。
MySQL は READ
ロックに以下のロック方法を使用します。
テーブルに書き込みロックがない場合は、読み取りロックを配置する。
その他の場合は、読み取りロックキューにロック要求を配置する。
ロックが解除されると、まず書き込みロックキューのスレッドでロックが使用可能になり、その後読み取りロックキューのスレッドで利用可能になります。
これは、1
つのテーブルに対して更新が多数ある場合に、更新がすべてなくなるまで
SELECT
ステートメントが待機することを意味します。
テーブルに対して多数の 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;
特定の状況で取り出しに優先順位を設定するには、LOW_PRIORITY
オプションを
INSERT
、UPDATE
または
DELETE
に、あるいは
HIGH_PRIORITY
オプションを
SELECT
に使用します。また、--low-priority-updates
オプションで mysqld
を開始しても同じ効果が得られます。
SQL_BUFFER_RESULT
の使用もテーブルロックを短縮するのに役立ちます。
See 項6.4.1. 「SELECT
構文」。
さらに、1 つのキューを使用するように
mysys/thr_lock.c
のロックコードを変更することもできます。この場合は、書き込みロックと読み取りロックの優先度が同じになり、アプリケーションによっては高速化に役立ちます。
MySQL のテーブルロックコードはデッドロックフリーです。
MySQL は、InnoDB
テーブルと
BDB
テーブルを除くすべてのテーブル型にテーブルロックを使用して、非常に高速なロックを実現します。大型のテーブルの場合、ほとんどのアプリケーションで行ロックと比較してテーブルロックのほうがはるかに優れていますが、これには危険もあります。
InnoDB
テーブルと BDB
テーブルの場合は、MySQL で LOCK
TABLES
によって明示的テーブルをロックした場合のみテーブルロックが使用されます。
InnoDB
は自動行レベルロックを使用し、BDB
はページレベルロックを使用してトランザクションの独立を確実にするため、これらのテーブル型には、LOCK
TABLES
をまったく使用しないように推奨します。
MySQL バージョン 3.23.7
以降は、MyISAM
テーブルへのレコードの挿入を、他のスレッドが同一テーブルから読み取りを行うのと同時に実行できるようになりました。現在のところ、挿入実行時にテーブルのレコード削除後のホールがない場合にのみ、この機能が使用可能になるため注意が必要です。すべてのホールに新規のデータが入力されると、同時挿入が自動的に再度可能になります。
テーブルロックにより、同時に多数のスレッドがテーブルからの読み取りを行うことができますが、あるスレッドがテーブルへの書き込みを行うときは、まず排他処理をする必要があります。更新時は、特定のテーブルにアクセスしようとする他のすべてのスレッドが、更新の準備ができるまで待機します。
一般にテーブルの更新は SELECT
より重要だと見なされるため、テーブルを更新するステートメントはすべて、テーブルから情報を取り出すステートメントより優先度が高くなります。これにより、更新では特定のテーブルに対して大量の重いクエリが使用されるため、更新が
'資源枯渇'
にさらされないことが確実になります(これは、更新を実行するステートメントを
LOW_PRIORITY
とともに使用するか、SELECT
ステートメントとともに
HIGH_PRIORITY
を使用することで変更できます)。
MySQL バージョン 3.23.7
以降は、max_write_lock_count
変数を使用して、テーブルに対する挿入が一定数行われた後に、MySQL
によってテーブルの使用を待機している
SELECT
ステートメントのすべてに高い優先度を強制的に設定できるようになりました。
ただし、テーブルロックは以下のシナリオには適していません。
クライアントが実行に長時間かかる
SELECT
を使用する。
その後、別のクライアントが使用テーブルに対して
UPDATE
を使用する。このクライアントは
SELECT
が完了するまで待機が必要になる。
別のクライアントが同一テーブルに対してさらに
SELECT
ステートメントを使用する。UPDATE
は SELECT
より優先度が高いため、この
SELECT
は UPDATE
が完了するまで待機が必要になる。また、最初の
SELECT
の完了を待つ必要もある。
full disk
などによってスレッドが待機中の場合、そのテーブルへのアクセスが必要なすべてのスレッドが追加のディスク容量が使用可能になるまで待機状態に置かれる。
この問題に対応する解決策は以下のとおりです。
SELECT
ステートメントの実行の高速化を試行する。これにはサマリテーブルの作成が必要な場合もある。
--low-priority-updates
のオプションで mysqld
を開始する。これは、テーブルを更新(変更)するすべてのステートメントの優先度を
SELECT
ステートメントの優先度より低くする。この場合、前のシナリオの最後の
SELECT
ステートメントが
INSERT
ステートメントより前に実行されることになる。
LOW_PRIORITY
属性を使用して、特定の
INSERT
、UPDATE
、または
DELETE
ステートメントの優先度を低く設定できる。
max_write_lock_count
の値を低くして mysqld
を開始し、一定数の WRITE
ロックの後に READ
ロックを設定する。
SQL コマンド SET
LOW_PRIORITY_UPDATES=1
を使用すると、特定のスレッドからの更新すべてが低い優先度で実行されるように指定できる。
See 項5.5.6. 「SET
構文」。
HIGH_PRIORITY
属性を使用すると、特定の
SELECT
の重要度を高く指定できる。 See
項6.4.1. 「SELECT
構文」。
SELECT
と結合した
INSERT
に問題がある場合は、SELECT
ステートメントと INSERT
ステートメントの同時サポートが可能になるため、新規の
MyISAM
テーブルを使用するように切り替える。
INSERT
ステートメントと
SELECT
ステートメントの混在が多い場合、INSERT
の DELAYED
属性によって問題が解決される確率が高い。
See 項6.4.3. 「INSERT
構文」。
SELECT
と DELETE
に問題がある場合、DELETE
に
LIMIT
オプションを使用すると解決できる場合がある。
See 項6.4.5. 「DELETE
構文」。
MySQL はローデータとインデックスデータを別のファイルに格納します。その他のデータベースの多く(ほとんど)は、ローデータとインデックスデータが同じファイルに混在しています。現在の非常に多くのシステムで MySQL の選択のほうが優れていると確信しています。
ローデータの格納方法には、各カラムの情報を独立した領域に格納する方法もあります(例: SDBM、Focus など)。これは、複数のカラムにアクセスするすべてのクエリでパフォーマンスに影響を及ぼします。パフォーマンスは複数のコラムへのアクセスを開始するとただちに低速化するため、このようなモデルは汎用データベースには適さないと確信しています。
一般的にインデックスとデータが一緒に格納されている場合も多くあります(Oracle、Sybase などの場合)。この場合は、レコード情報をインデックスのリーフページで検索します。このレイアウトで優れている点は、多くの場合インデックスのキャッシュ方法次第でディスクの読み取りを節約できることにあります。このレイアウトの欠点は以下のとおりです。
データの取得時にインデックス全体を読み取る必要があるため、テーブルスキャンの速度が大幅に下がる。
クエリでデータを取り出す際にインデックステーブルのみの使用ができない。
ノードからインデックスを複製する必要があるため(レコードはノードに格納できないことによる)、大量の領域が消費される。
削除があるとテーブルの速度が次第に低下する(通常、削除ではノードのインデックスが更新されないため)。
インデックスデータのみのキャッシュが困難である。
最も基本的な最適化の 1 つにデータ(およびインデックス)が占めるディスク領域を可能な限り少なくすることがあります。これで、ディスクの読み取りが高速化し、使用メモリも一般に減少するため、大幅な改善が図れます。カラムが小さければインデックス作成で消費されるリソースも少なくなります。
MySQL では多様なテーブル型とレコード形式がサポートされます。 適切なテーブル形式を選択することで、パフォーマンスを大幅に改善できます。 See 章?7. MySQL のテーブル型。
ここで紹介する技法を使用すると、テーブルのパフォーマンス改善とストレージ領域の最小化を図ることができます。
できる限り効率性の高い(最小)の型を使用する。MySQL にはディスク領域とメモリを節約できる専用の型がある。
可能な場合は、小さなテーブルの取得には小さな整数型を使用する。たとえば、INT
より、MEDIUMINT
のほうが適している場合もしばしばある。
できる限り、カラムに NOT NULL
を宣言する。これですべてが高速化され、1
カラム当たり 1
ビットを節約できる。アプリケーションで実際に
NULL
が必要な場合は、必ず使用する必要があるため、注意が必要である。デフォルトですべてのカラムにこれを設定することは避ける。
可変長カラム(VARCHAR
、TEXT
、BLOB
など)がまったくない場合は固定長レコード形式を使用する。これで速度が上がるが、領域の消費も増える。
See 項7.1.2. 「MyISAM
テーブル形式」
テーブルのプライマリインデックスを可能な限り短くする。これで、レコードの識別が容易になり効率化が図れる。
テーブルごとに使用するストレージとインデックスの方法を設定する必要がある。 See 章?7. MySQL のテーブル型。
インデックスの作成は必要なものだけに限定する。インデックスは取り出しに優れているが、高速保存が必要な場合は適さない。カラムの組み合わせを使用してテーブルを検索し、テーブルにアクセスする場合がほとんどであれば、インデックスを作成する。インデックスの最初の部分は、最も使用頻度の高いカラムにする必要がある。常に多数のカラムを使用する場合は、より重複しているカラムを先に使用するとインデックスの圧縮を改善できる。
文字列の最初の数文字に、一意のプリフィックスがあるカラムが多い場合は、このプリフィックスのみをインデックス化したほうがよい。MySQL
は CHAR
型カラムの部分インデックスをサポートする。短いインデックスの速度が速い理由は、占有ディスク領域が小さいことだけではなく、インデックスキャッシュでのヒットが多くなり、所要ディスクシークが少なくなることにもよる。
See 項5.5.2. 「サーバパラメータのチューニング」。
状況によっては、スキャンの頻度が高いテーブルを 2 つに分割したほうが有利な場合もある。これは特に、動的テーブルで、テーブルスキャンの際に対応するレコードの検索に小さな静的テーブルの使用が可能である場合にあてはまる。
インデックスは、カラムが特定の値をもつレコードの迅速な検索に使用されます。インデックスがないと、MySQL がレコードを見つけるために、最初のレコードから開始し、テーブル全体を読み取るとが必要になります。テーブルが大きくなると、これにコストがかかります。クエリ対象のカラムにインデックスがあると、MySQL は全てのデータを探すことなく、データファイルの途中にあるシーク対象ポジションを迅速に取得することができます。テーブルに 1000 レコードある場合、シーケンシャルに読み取る場合と比較して少なくとも 100 倍は高速化できます。1000 レコードのほとんどすべてにアクセスする必要がある場合は、ディスクシークが最小になるため、シーケンシャルに読むほうが速くなることに注意してください。
MySQL インデックスのすべて(PRIMARY
KEY
、UNIQUE
、および
INDEX
)は、B
ツリーに格納されます。文字列の頭にある空白と最後にある空白は自動的に圧縮されます。
See 項6.5.7. 「CREATE INDEX
構文」。
インデックスの用途は以下のとおりです。
WHERE
節の条件に一致するレコードを迅速に検索する。
結合実行時に他のテーブルのレコードを取り出す。
インデックス化された特定のカラムの
MAX()
値や MIN()
値を検索する。これは、WHERE
key_part_# = すべてのキー部分の定数 < N
を使用しているかどうかをチェックするプリプロセッサによって最適化される。この場合、MySQL
では単一キーのルックアップを実行し、MIN()
式を定数に置換する。すべての式が定数に置換されると、ただちにクエリの応答が返される。
SELECT MIN(key_part2),MAX(key_part2) FROM table_name WHERE key_part1=10
使用可能キーの左端の先頭部分でソートやグループ化が実行されている場合、テーブルのソートやグループ化を実行する(例:
ORDER BY key_part_1,key_part_2
)。すべてのキー部分の後ろに
DESC
がある場合は、キーが逆の順序で読み取られる。
See 項5.2.8. 「MySQL による ORDER BY
の最適化」。
状況によっては、データファイルを参照せずに値を取り出すようにクエリを最適化できる。あるテーブルで使用カラムのすべてが数値型で、キーの左端の先頭部分を構成している場合は、インデックスツリーから非常に高速に値を取り出すことができる。
SELECT key_part3 FROM table_name WHERE key_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
ステートメントがあるとします。
mysql>SELECT * FROM tbl_name WHERE col1=val1;
mysql>SELECT * FROM tbl_name WHERE col2=val2;
mysql>SELECT * FROM tbl_name WHERE col2=val2 AND col3=val3;
インデックスが (col1, col2, col3)
に存在する場合、最初のクエリだけがインデックスを使用できます。2
つめと 3
つめのクエリには、インデックス化したカラムが必要ですが、(col2)
と (col2, col3)
は (col1, col2,
col3)
の左端のプリフィックスではありません
MySQL は、LIKE
の引数がワイルドカード文字で始まらない文字列定数である場合に、LIKE
比較にもインデックスを使用します。たとえば、以下の
SELECT
ステートメントではインデックスが使用されます。
mysql>SELECT * FROM tbl_name WHERE key_col LIKE "Patrick%";
mysql>SELECT * FROM tbl_name WHERE key_col LIKE "Pat%_ck%";
最初のステートメントでは "Patrick" <=
key_col < "Patricl"
のあるレコードだけが考慮されます。2
つめのステートメントでは "Pat" <=
key_col < "Pau"
のあるレコードだけが考慮されます。
以下の SELECT
ステートメントではインデックスが使用されません。
mysql>SELECT * FROM tbl_name WHERE key_col LIKE "%Patrick%";
mysql>SELECT * FROM tbl_name WHERE key_col LIKE other_col;
最初のステートメントでは LIKE
がワイルドカード文字で始まっています。2
つめのステートメントでは LIKE
値が定数ではありません。
MySQL 4.0 ではこれ以外の LIKE
の最適化も実行されます。... LIKE
"%string%"
を使用し、string
が 3 文字より長い場合、MySQL は Turbo
Boyer-Moore
アルゴリズムを使用して、文字列のパターンを初期化してから、このパターンを使用して検索を素早く実行します。
column_name IS NULL
を使用した検索では、column_name
にインデックスが張られている場合にインデックスが使用されます。
通常 MySQL
は、検索するレコードを少なくするために、インデックスを使用します。インデックスは、以下の演算子で比較するカラムに使用されます。
=
、>
、>=
、<
、<=
、BETWEEN
、または、'something%'
などのワイルドカード以外のプリフィックスで始まるパターンに対する
LIKE
。
WHERE
節内の全ての
AND
にかかっていないインデックスは、クエリの最適化に使用されません。言い換えると、インデックスの使用を可能にするには、インデックスの先頭部分がすべての
AND
グループで使用されている必要があります。
次の WHERE
節ではインデックスが使用されます。
... WHERE index_part1=1 AND index_part2=2 AND other_column=3 ... WHERE index=1 OR A=10 AND index=2 /* index = 1 OR index = 2 */ ... WHERE index_part1='hello' AND index_part_3=5 /* optimized like "index_part1='hello'" */ ... WHERE index1=1 AND index2=2 OR index1=3 AND index3=3; /* Can use index on index1 but not on index2 or index 3 */
次の WHERE
節ではインデックスが使用されません。
... WHERE index_part2=1 AND index_part3=2 /* index_part_1 is not used */ ... WHERE index=1 OR A=10 /* Index is not used in both AND parts */ ... WHERE index_part1=1 OR index_part2=10 /* No index spans all rows */
MySQL
では利用可能な場合でもインデックスが使用されない場合があることに注意してください。この一例として、インデックスの使用によって、MySQL
がテーブルの 30%
を超えるレコードにアクセスする必要が生じる場合が挙げられます(この場合は、必要なシークが大幅に減少するため、テーブルスキャンのほうが高速になる可能性が高くなります)。
ただしこのクエリに、レコードの一部のみを取り出す
LIMIT
が使用されている場合、結果で返される少数のレコードを迅速に検索できるため、MySQL
はインデックスを使用します。
MySQL
の全てのカラム型にはインデックスを張ることができます。SELECT
操作のパフォーマンスの改善には、対応するカラムにインデックスを使用することが最善の方法です。
テーブルあたりの最大インデックス数とインデックスの最大長は、ストレージエンジンごとに定義されます。See 章?7. MySQL のテーブル型。ストレージエンジンのすべてで、1 テーブルあたり 16 以上のインデックスと 256 バイト以上のインデックス長がサポートされます。
CHAR
型および VARCHAR
型のカラムでは、カラムの先頭部分をインデックス化できます。これは、カラム全体をインデックス化する場合と比較して大幅に高速になり、所要ディスク領域も少なくて済みます。カラムの先頭部分をインデックス化する
CREATE TABLE
ステートメント構文は次のようになります。
INDEX index_name (col_name(length))
この例では、name
カラムの最初の
10 文字のインデックスが作成されます。
mysql>CREATE TABLE test (
->name CHAR(200) NOT NULL,
->INDEX index_name (name(10)));
BLOB
型および TEXT
型のカラムでは、カラムの先頭部分をインデックス化する必要があります。インデックスが張れる部分の最大長は
255 バイトです。
MySQL バージョン 3.23.23 以降は、特殊な
FULLTEXT
インデックスも作成できます。これは全文検索に使用されます。FULLTEXT
インデックスは、MyISAM
テーブル型でのみ、CHAR
、VARCHAR
、および
TEXT
カラムに限ってサポートされます。
フルテキストインデックスの作成は常にカラム全体を対象として、先頭部分(プリフィックス)のインデックス化は行われません。詳細については、項6.8. 「MySQL 全文検索」
を参照してください。
MySQL
では複数のカラムに対するインデックスを作成できます。インデックスは最大
15 カラムで構成できます(CHAR
および VARCHAR
カラムではカラムの先頭部分をインデックスの部分として使用することもできます)。
複数カラムのインデックス(複合インデックス)は、インデックス化されたカラムの値を連結することによって生成された値が含まれ、ソート化された配列と見なすことができます。
MySQL では、WHERE
節内でインデックスの第 1
カラムを指定する場合、他のカラムの値を指定しなくても、クエリが高速化できるように複合インデックスが使用されます。
次のようなテーブルが定義されているとします。
mysql>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
インデックスは次のようなクエリに使用されます。
mysql>SELECT * FROM test WHERE last_name="Widenius";
mysql>SELECT * FROM test WHERE last_name="Widenius"
->AND first_name="Michael";
mysql>SELECT * FROM test WHERE last_name="Widenius"
->AND (first_name="Michael" OR first_name="Monty");
mysql>SELECT * FROM test WHERE last_name="Widenius"
->AND first_name >="M" AND first_name < "N";
しかし、次のクエリには name
インデックスが使用されません。
mysql>SELECT * FROM test WHERE first_name="Michael";
mysql>SELECT * FROM test WHERE last_name="Widenius"
->OR first_name="Michael";
MySQL でインデックスを使用してクエリパフォーマンスを改善する方法の詳細については、項5.4.3. 「MySQL でのインデックスの使用」を参照してください。
mysqladmin status
を実行すると、以下の出力が表示されます。
Uptime: 426 Running threads: 1 Questions: 11082 Reloads: 1 Open tables: 12
テーブルが 6 つしかない場合に Open
tables
値が 12
と表示されることに、当惑する場合もあります。
MySQL
はマルチスレッド化されているため、多数のクライアントが同時に同じものに対してクエリを使用することがあります。2
つのクライアントスレッドで 1
つのファイルに異なるステータスが発生する問題を最小にするため、同時に実行しているスレッドがそれぞれで無関係にテーブルを開きます。これはメモリの消費を増やしますが、一般にパフォーマンスは向上します。ISAM
テーブルと MyISAM
テーブルの場合は、テーブルを開いたそれぞれのクライアントにデータファイルに対するファイル記述子が必要になります。このテーブル型では、インデックスファイルに対するファイル記述子がすべてのスレッドで共有されます。
次のセクションでもこのトピックについてさらに説明します。 See 項5.4.7. 「MySQL でのテーブルのオープンとクローズの方法」。
table_cache
、max_connections
、および
max_tmp_tables
サーバ変数は、サーバが開いた状態で保持できるファイルの最大数に影響します。
これらの値の 1 つ以上を増加すると、OS
によって制限されている 1
プロセスが持つことができるファイル記述子の最大数まで実行が可能になります。システムごとに方法は多様ですが、多数のオペレーティングシステムでオープンファイルの制限値を上げることができます。
制限値の拡大が可能かどうかの判定、およびその実行方法については、使用するオペレーティングシステムの文書を参照してください。
table_cache
は
max_connections
と関係します。たとえば、同時接続数が 200
の場合、最低 200 * n
のテーブルキャッシュサイズが必要です。この
n
は結合で使用するテーブル数の最大値を示します。また、テンポラリテーブルとファイル用のファイル記述子も必要です。
あなたのオペレーティングシステムが
table_cache
の設定に従ったファイル記述子の数を処理できることを確認してください。table_cache
の設定が高すぎると、MySQL
がファイル記述子を使い果たして接続を拒否し、クエリの実行ができなくなり、信頼性が大幅に低下します。また、MyISAM
ストレージエンジンでは1つのテーブルごとに 2
つのファイル記述子が必要であることも考慮に入れる必要があります。--open-files-limit=#
スタートアップオプションを使用すると、MySQL
で使用可能なファイル記述子数を拡大できます。
See 項A.2.17. 「File Not Found エラー」。
オープンテーブルのキャッシュは、table_cache
エントリレベルに保持されます。デフォルト値は
64 です。これは、-O table_cache=#
オプション mysqld
に与えることで変更できます。MySQL
は一時的にさらに多くのテーブルを開いてクエリの実行を実現することがあります。
以下の状況では、使用されていないテーブルが閉じられ、テーブルキャッシュから削除されます。
キャッシュが満杯のときに、キャッシュにないテーブルをスレッドが開こうとした場合。
キャッシュに table_cache
を超えるエントリがあり、あるスレッドがテーブルの使用を終えた場合。
いずれかのユーザが mysqladmin
refresh
または mysqladmin
flush-tables
を実行した場合。
いずれかのユーザが FLUSH TABLES
ステートメントを実行した場合。
テーブルキャッシュが満杯になると、サーバでは以下の手順に従って使用するキャッシュエントリを割り当てます。
現在使用中でないテーブルは、最後に使用した時が古いものから順にリリースされる。
キャッシュが満杯でリリース可能なテーブルがなく、新たにテーブルを開く必要がある場合は、必要に応じてキャッシュが一時的に拡張される。
キャッシュが一時的に拡張された状況で、使用中のテーブルが使用されなくなったときは、そのテーブルが閉じられ、キャッシュからリリースされる。
テーブルは同時アクセスのそれぞれで開かれます。つまり、2 つのスレッドで同じテーブルにアクセスする場合、または 1 つのスレッドが同一クエリでテーブルに 2 回アクセスする場合(テーブルを同一テーブルに結合する場合など)は、テーブル を 2 回開く必要があることになります。 いずれかのテーブルを最初に開く際に 2 つのファイル記述子が割り当てられ、その後さらにそのテーブルを使用する場合はファイル記述子が 1 つのみ割り当てられます。最初のオープン時の 2 つめの記述子は、インデックスファイルに使用され、この記述子はすべてのスレッドで共有されます。
HANDLER table_name OPEN
ステートメントを使用してテーブルを開く場合は、専用テーブルオブジェクトがスレッドに割り当てられます。
このテーブルオブジェクトは他のスレッドと共有されず、スレッドが
HANDLER table_name CLOSE
を呼び出すか、スレッドが終了するまで閉じられません。
See 項6.4.9. 「HANDLER
構文」。
この場合はテーブルがテーブルキャッシュに戻されます(キャッシュが満杯でない場合)。
テーブルキャッシュが小さすぎるかどうかは、mysqld
の Opened_tables
変数のチェックで確認できます。たとえ多くの
FLUSH TABLES
を実行していない場合でも、この値が非常に大きい場合は、テーブルキャッシュサイズを拡張する必要があります。
See 項4.6.8.3. 「SHOW STATUS
」。
システムレベルの要素は、その一部を初期段階に決定する必要があるため、この話から始めます。これに該当しない場合は、システムを大きく変えることが重要でないのであれば、このセクションは簡単に目を通せば十分です。ただし、このレベルで変更を行うことでどの程度改善できるのかを自覚しておくことは必ず役に立ちます。
使用するオペレーティングシステムは非常に重要です。複数 CPU のコンピュータを使用するなら、Solaris(スレッド実装機能が優れている)または Linux(2.2 カーネルの SMP サポートが優れている)が良いでしょう。 また、旧バージョンの Linux カーネルのデフォルトには 2G ファイルサイズの制限があります。このカーネルで 2G より大きいファイルがどうしても必要な場合は、ext2 ファイルシステムの LFS (Large File System)パッチを入手する必要があります。 これ以外の ReiserFS や XFS などには 2G の制限がありません。
多くのプラットフォーム上で、MySQL を本番稼働させていないため、可能であれば選択前に候補のプラットフォームのテストを実行することを推奨します。
その他のヒント:
RAM が十分にある場合は、スワップデバイスすべてを削除できる。オペレーティングシステムによっては、空きメモリがある場合でもスワップデバイスが使用されることがある。
--skip-external-locking
MySQL
オプションを使用して、外部ロックを回避する。実行しているのが
1 サーバだけである限り、これによる MySQL
の機能に対する影響はない。myisamchk
を実行する前にサーバの記録を取る(または対応するテーブルをロックし、フラッシュする)ことを忘れないようにする。一部のシステムは、外部ロックがまったく機能しないため、このオプションが必須になる。
MySQL 4.0 以降、--skip-external-locking
オプションはデフォルトでオンになっている。
それ以前は、MIT-pthread
によるコンパイル時にデフォルトでオンになっている。これは
flock()
がすべてのプラットフォームで MIT-pthread
により完全にサポートされているわけではないことによる。Linux
ファイルロックは安全ではないため、Linux
でもデフォルトでオンになっている。
--skip-external-locking
を使用できない状況は、同一データに対して複数の
MySQL
サーバ(クライアントではない)を実行している場合と、サーバに対して初めにテーブルのフラッシュとロックを行う指示を出さずに、テーブルに対して
myisamchk
を実行する場合に限られる。
--skip-external-locking
を使用している場合でもLOCK
TABLES
/UNLOCK TABLES
は使用できる。
mysqld
サーバで使用されるデフォルトのバッファサイズは次のコマンドで確認できます。
shell> mysqld --help
このコマンドによって、mysqld
オプションと設定可能な変数すべての一覧が生成されます。この出力には、デフォルトの変数値も記載され、以下のように表示されます。
back_log current value: 5 bdb_cache_size current value: 1048540 binlog_cache_size current value: 32768 connect_timeout current value: 5 delayed_insert_timeout current value: 300 delayed_insert_limit current value: 100 delayed_queue_size current value: 1000 flush_time current value: 0 interactive_timeout current value: 28800 join_buffer_size current value: 131072 key_buffer_size current value: 1048540 lower_case_table_names current value: 0 long_query_time current value: 10 max_allowed_packet current value: 1048576 max_binlog_cache_size current value: 4294967295 max_connections current value: 100 max_connect_errors current value: 10 max_delayed_threads current value: 20 max_heap_table_size current value: 16777216 max_join_size current value: 4294967295 max_sort_length current value: 1024 max_tmp_tables current value: 32 max_write_lock_count current value: 4294967295 myisam_sort_buffer_size current value: 8388608 net_buffer_length current value: 16384 net_retry_count current value: 10 net_read_timeout current value: 30 net_write_timeout current value: 60 read_buffer_size current value: 131072 read_rnd_buffer_size current value: 262144 slow_launch_time current value: 2 sort_buffer current value: 2097116 table_cache current value: 64 thread_concurrency current value: 10 tmp_table_size current value: 1048576 thread_stack current value: 131072 wait_timeout current value: 28800
現在実行中の mysqld
サーバがある場合は、次のステートメントで変数に実際に使用されている値を調べることができます。
mysql> SHOW VARIABLES;
また、次のステートメントでは、実行中のサーバの統計やステータスインジケータを調べることができます。
mysql> SHOW STATUS;
すべての変数の詳細説明については、本マニュアルの
SHOW VARIABLES
セクションを参照してください。 See
項4.6.8.4. 「SHOW VARIABLES
」。
ステータス変数詳細については、項4.6.8.3. 「SHOW STATUS
」
を参照してください。
サーバ変数とステータス情報は、mysqladmin
でも入手できます。
shell>mysqladmin variables
shell>mysqladmin extended-status
MySQL は非常にスケーラブルなアルゴリズムを使用しているため、通常は実行時のメモリ消費が非常に小さくなります。しかし、MySQL に対するメモリを多く割り当てると、通常はパフォーマンスが向上します。
MySQL
サーバをチューニングする際に使用される最も重要な変数は
key_buffer_size
と
table_cache
の 2
つです。他の変数の変更を行う前にこの変数をあらかじめ適切に設定しておくことで自信がつきます。
以下に典型的な変数を実行時に設定している例を示します。この例は
mysqld_safe
スクリプトを使用し、--name=value
構文で変数 name
を値
value
に設定しています。この構文は、MySQL 4.0
から利用できます。旧バージョンの MySQL
の場合は、以下の相違点を考慮してください。
mysqld_safe
ではなく、safe_mysqld
を使用する。
--set-variable=name=value
または
-O name=value
構文を使用して変数を設定する。
_size
で終わる変数名は
_size
なしでの指定が必要な場合がある。たとえば、sort_buffer_size
の旧名は sort_buffer
である。read_buffer_size
の旧名は
record_buffer
である。サーババージョンで認識される変数を調べるときは
mysqld --help
を使用する。
最小 256M のメモリで多数のテーブルがあり、中程度のクライアントで最大のパフォーマンスを得るには、次のように使用します。
shell>mysqld_safe --key_buffer_size=64M --table_cache=256 \
--sort_buffer_size=4M --read_buffer_size=1M &
メモリが 128M で、テーブルは少数で大量のソートの実行が必要な場合は、次のように使用できます。
shell> mysqld_safe --key_buffer_size=16M --sort_buffer_size=1M
メモリがほとんどなく大量の接続がある場合は、次のように使用します。
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_cache=32 --read_buffer_size=8K -O net_buffer_length=1K &
使用可能メモリより大幅に大きいテーブルで
GROUP BY
または ORDER
BY
を実行する場合は
read_rnd_buffer_size
の値を大きくしてソート操作後のレコードの読み取りの速度を上げる必要があります。
MySQL
をインストールしたときは、support-files
ディレクトリに複数の my.cnf
サンプルファイルの、my-huge.cnf
、my-large.cnf
、my-medium.cnf
、および
my-small.cnf
が格納され、システム最適化のベースとして使用できます。
同時接続が非常に多い場合、接続ごとに
mysqld
で使用されるメモリを非常に小さくしていないとスワップの問題が発生することがあります。言うまでもなく、すべての接続に使用可能なメモリが十分ある場合は
mysqld
のパフォーマンスが向上します。
mysqld
または
mysqld_safe
のコマンドラインでオプションを指定した場合、そのサーバの呼び出しでしか有効性が保持されないことに注意してください。
サーバ実行のたびにオプションを使用する場合は、オプション設定ファイルに配置します。
パラメータ変更の有効性を調べるには、次のように実行します。
shell> mysqld --key_buffer_size=32m --help
必ず --help
を最後に指定します。最後にしないと、コマンドラインのそれ以降に記載されたオプションの効果が出力に反映されません。
以下のテストのほとんどは、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
のコンパイル時は、使用するキャラクタセットのサポートのみを含めます(オプション
--with-charset=xxx
)。 標準の MySQL
バイナリディストリビューションは、すべてのキャラクタセットをサポートするようにコンパイルされています。
以下に実施した測定結果の一部を紹介します。
pgcc
を使用し、すべてを
-O6
でコンパイルした場合、mysqld
サーバは gcc
2.95.2 と比較して
1% 速度が上がる。
動的にリンクした場合(-static
なし)は、結果が Linux 上で 13%
遅くなった。クライアントアプリケーションには動的リンクの
MySQL
ライブラリを使用できることに注意する。これは、サーバのパフォーマンス上重大である。
strip libexec/mysqld
を使用して
mysqld
バイナリをストリップすると、生成されたバイナリの速度を
4% まで上げられる。
同一ホスト上で実行されるクライアントからサーバへの接続で、Unix
ソケットファイルではなく、TCP/IP
で接続すると、7.5%
パフォーマンスが遅くなった(ホスト名
localhost
に接続する場合、MySQL
ではデフォルトでソケットファイルが使用される)。
クライアントからサーバへの TCP/IP 接続で別のホストにあるリモートサーバに接続した場合、100M イーサネットによる接続でも、同一ホスト上のローカルサーバに接続した場合と比較して、8-11% 遅くなった。
暗号化した接続(内部 SSL サポートによるすべてのデータの暗号化)を使用してベンチマークテストを実行した場合、パフォーマンスが 55% 遅くなった。
--with-debug=full
でコンパイルすると、ほとんどのクエリが
20% 遅くなる。
一部のクエリはかなり長くかかった(たとえば
MySQL ベンチマークは 35%
の速度低下)。--with-debug
を使用すると、この速度低下は 15%
で済む。--with-debug=full
でコンパイルされた mysqld
バージョンは、--skip-safemalloc
オプションで起動すると実行時のメモリチェックを無効化できる。この場合の最終的な結果は、--with-debug
で構成した場合に非常に近くなる。
Sun UltraSPARC-IIe, Forte 5.0 は、gcc
3.2 より 4% 速度が上がった。
Sun UltraSPARC-IIe, Forte 5.0 では、64 ビットモードより 32 ビットモードのほうが 4% 速かった。
gcc
2.95.2 for UltraSPARC
にオプション -mcpu=v8
-Wa,-xarch=v8plusa
を付けてコンパイルすると、パフォーマンスが
4% 改善した。
Solaris 2.5.1, MIT-pthreads は、単一プロセッサ上で Solaris ネイティブスレッドより 8-12% 遅かった。CPU の負荷が増加するとこの差はさらに拡大する。
--log-bin
を使用して実行すると
mysqld
が 1% 遅くなった。
フレームポインタ
-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
サーバでのメモリの使用方法の一部を示しています。可能な場合は、メモリ使用に関連するサーバ変数名も記載されています。
キーバッファ(変数
key_buffer_size
)はすべてのスレッドで共有される。サーバが使用するこれ以外のバッファは必要に応じて割り当てられる。
See 項5.5.2. 「サーバパラメータのチューニング」。
それぞれの接続は、スタック(デフォルト
64K、変数
thread_stack
)、接続バッファ(変数
net_buffer_length
)、および結果バッファ(net_buffer_length
)のスレッド固有領域を使用する。接続バッファと結果バッファは必要に応じて
max_allowed_packet
まで動的に拡張される。クエリの実行中は現在のクエリ文字列のコピーも割り当てられる。
すべてのスレッドで同じベースメモリが共有される。
圧縮 ISAM
および
MyISAM
テーブルのみがメモリにマップされる。これは、4
GB の 32
ビットメモリ領域では大型のほとんどのテーブルに十分なほどは大きくないことによる。64
ビットアドレス領域のあるシステムが一般的になれば、メモリマップの一般サポートの追加が可能になる。
テーブルの順次スキャンを行う要求はそれぞれ、読み取りバッファ(変数
read_buffer_size
)を割り当てる。
レコードを ``ランダムな''
順序で読み取る場合(ソート後など)、ランダム読み取りバッファが割り当てられディスクシークが回避される(変数
read_rnd_buffer_size
)。
結合はすべて 1
回の受け渡しで実行され、ほとんどの結合はテンポラリテーブルを使用せずに実行される。テンポラリテーブルのほとんどはメモリベース(HEAP
)テーブルである。レコード長の大きなテンポラリテーブル(すべてのカラム長の合計として算出)や
BLOB
カラムが含まれるテンポラリテーブルはディスク上に格納される。
バージョン 3.23.2 より前の MySQL
には、メモリ内の HEAP
テーブルが tmp_table_size
のサイズを超えた場合にエラー The
table tbl_name is full
が出力される問題があった。3.23.2
以降、この問題は必要に応じてメモリ内
HEAP
テーブルをディスクベース
MyISAM
テーブルに変更されることで自動的に処理される。この問題を回避するには、tmp_table_size
オプションを mysqld
に設定するか、クライアントプログラムで
SQL オプション BIG_TABLES
を設定することで、テンポラリテーブルのサイズを拡張する。See
項5.5.6. 「SET
構文」。MySQL バージョン 3.20
では、テンポラリテーブルの最大サイズが
record_buffer*16
であった。このバージョンを使用している場合は、record_buffer
の値を拡大する必要がある。また、--big-tables
オプションで mysqld
を起動して、常にテンポラリテーブルをディスクに格納することもできる。ただし、これは複雑なクエリのほとんどで処理速度に影響を及ぼす。
ソートを実行する要求のほとんどで、ソートバッファおよび結果セットサイズに応じた 0 から 2 つのテンポラリファイルが割り当てられる。 See 項A.4.4. 「MySQL がテンポラリファイルを格納する場所」。
解析および計算のほとんどすべてが、ローカルメモリストアで実行される。小さいアイテムにはメモリオーバヘッドが不要で、通常の低速メモリの割り当ておよび解放は回避される。メモリは、予測外の規模の文字列の場合のみ割り当てられ、これは、malloc()
および free()
で実行される。
インデックスファイルはそれぞれ 1
回開かれ、データファイルは、同時実行スレッドごとに
1
回開かれる。同時スレッドのそれぞれに対して、テーブル構造、各カラムのカラム構造、サイズ
3 * n
のバッファが割り当てられる(n
は、レコードの最大長、ただし
BLOB
カラムは計算外)。BLOB
カラムは、5 から 8 バイトに
BLOB
データの長さを加算したバイト数を使用する。ISAM
および MyISAM
ストレージエンジンは、内部使用のための追加レコードを
1 つ使用する。
BLOB
カラムがあるテーブルのそれぞれで、大きな
BLOB
値を読み込むためにバッファが動的に拡張される。テーブルをスキャンする場合は、最大
BLOB
値と同じ大きさのバッファが割り当てられる。
使用中テーブルすべてのハンドラ構造がキャッシュに保存され、FIFO 形式で管理される。一般にキャッシュには 64 のエントリがある。テーブルが同時に 2 つの実行スレッドで使用されている場合、キャッシュにはそのテーブルのエントリが 2 つ配置される。 See 項5.4.7. 「MySQL でのテーブルのオープンとクローズの方法」。
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
を開始します。
SET [GLOBAL | SESSION] sql_variable=expression, [[GLOBAL | SESSION] sql_variable=expression] ...
SET
は、サーバやクライアントの動作に影響を及ぼすさまざまなオプションを設定します。
以下の例は、変数の設定に使用できる各種の構文を示しています。
旧バージョンの MySQL では、SET
OPTION
構文の使用を許可していましたが、今は廃止されています。
MySQL 4.0.3 では、GLOBAL
オプション、SESSION
オプション、および最も重要なスタートアップ変数へのアクセスを追加しています。
LOCAL
は、SESSION
のシノニムとして使用できます。
1
つのコマンドラインに複数の変数を設定する場合は、最後の
GLOBAL | SESSION
モードが使用されます。
SET sort_buffer_size=10000; SET @@local.sort_buffer_size=10000; SET GLOBAL sort_buffer_size=1000000, SESSION sort_buffer_size=1000000; SET @@sort_buffer_size=1000000; SET @@global.sort_buffer_size=1000000, @@local.sort_buffer_size=1000000;
@@variable_name
構文は、MySQL
構文とその他のデータベースとの互換性を保持を目的にサポートされています。
このマニュアルのシステム変数のセクションに設定可能な多様なシステム変数に関する説明があります。 See 項6.1.5. 「システム変数」。
SESSION
(デフォルト)を使用している場合、現在のセッションを終了するまで、あるいはこのオプションに別の値を設定するまで、設定したオプションが有効になります。SUPER
特権を必要とする GLOBAL
を使用した場合、サーバの再起動が行われるまでオプションが記憶され、新規接続時も使用されます。オプションを永続的にする場合は、オプション設定ファイルに設定します。
See 項4.1.2. 「my.cnf
オプション設定ファイル」。
不適切な使用を防ぐため、SET
SESSION
でしか使用できない変数とともに
SET GLOBAL
を使用した場合や、グローバル変数に SET
GLOBAL
を使用しない場合には MySQL
からエラーが出力されます。
SESSION
変数を GLOBAL
値に、あるいは GLOBAL
値を MySQL
のデフォルト値に設定する場合は、DEFAULT
として設定することができます。
SET max_join_size=DEFAULT;
これは以下と等しいことになります。
SET @@session.max_join_size=@@global.max_join_size;
SET
コマンドで設定可能なサーバ変数に最大値を設定して制限する場合、--maximum-variable-name
コマンドラインオプションを使用して最大値を指定できます。
See 項4.1.1. 「mysqld
コマンドラインオプション」。
SHOW VARIABLES
を使用すると、ほとんどの変数の一覧が出力されます。
See 項4.6.8.4. 「SHOW VARIABLES
」。
@@[global.|local.]variable_name
構文を使用すると特定の変数の値を取得できます。
SHOW VARIABLES like "max_join_size"; SHOW GLOBAL VARIABLES like "max_join_size"; SELECT @@max_join_size, @@global.max_join_size;
以下に、非標準 SET
構文を使用する変数およびその他の変数の一部について説明します。これ以外の変数定義は、システム変数セクションのスタートアップオプションの部分または
SHOW VARIABLES
の説明に記載されています。 See
項6.1.5. 「システム変数」。 See
項4.1.1. 「mysqld
コマンドラインオプション」。 See
項4.6.8.4. 「SHOW VARIABLES
」。
AUTOCOMMIT= 0 | 1
1
に設定すると、テーブルに対する変更すべてがただちに実行される。トランザクションを有効にする場合は、BEGIN
ステートメントを使用する必要がある。See
項6.7.1. 「START
TRANSACTION
、COMMIT
、ROLLBACK
の各構文」。0
に設定した場合は、そのトランザクションを
COMMIT
で受け入れるか、ROLLBACK
で取り消す必要がある。 See
項6.7.1. 「START
TRANSACTION
、COMMIT
、ROLLBACK
の各構文」。 AUTOCOMMIT
モードを 0
から
1
に変更すると、開いているすべてのトランザクションに対して
MySQL が COMMIT
を自動実行するため注意が必要である。
1
に設定すると、テンポラリテーブルのすべてがメモリではなくディスクに格納される。これによって速度が少し低下するが、大きなテンポラリテーブルを必要とする大規模な
SELECT
操作でもエラー The
table tbl_name is full
が出力されなくなる。新たに接続した場合のデフォルト値は
0
(メモリ内テンポラリテーブルを使用)である。
この変数は旧称 SQL_BIG_TABLES
であった。MySQL 4.0 では、MySQL
によって必要に応じてメモリ内テーブルがディスクベーステーブルに自動変換されるため、通常この変数の設定が必要な状況はない。
CHARACTER SET character_set_name |
DEFAULT
これは、クライアントとの間でやり取りされるすべての文字列に指定のマッピングを行う。
現在、character_set_name
の唯一のオプションは
cp1251_koi8
のみであるが、MySQL
ソースディストリビューションの
sql/convert.cc
ファイルを編集して容易に新規のマッピングを追加できる。デフォルトのマッピングは、DEFAULT
の character_set_name
値を使用してリストアできる。
CHARACTER SET
オプションを設定する構文は、他のオプションを設定する構文とは異なるため注意が必要である。
DATE_FORMAT = format_str
サーバで DATE
値を文字列に変換する方法を設定する。この変数は、グローバルオプション、ローカルオプション、コマンドラインオプションのいずれでも使用できる。format_str
の指定には GET_FORMAT()
関数の使用が便利である。 See
項6.3.4. 「日付と時刻関数」 を参照。
DATETIME_FORMAT = format_str
サーバで DATETIME
値を文字列に変換する方法を設定する。この変数は、グローバルオプション、ローカルオプション、コマンドラインオプションのいずれでも使用できる。format_str
の指定には GET_FORMAT()
関数の使用が便利である。 See
項6.3.4. 「日付と時刻関数」 を参照。
INSERT_ID = #
次の INSERT
や ALTER
TABLE
コマンドで使用される
AUTO_INCREMENT
値を設定する。これは主としてバイナリログとともに使用される。
LAST_INSERT_ID = #
LAST_INSERT_ID()
から返される値を設定する。これは、テーブルを更新するコマンドで
LAST_INSERT_ID()
関数を使用した場合にバイナリログに格納される。
LOW_PRIORITY_UPDATES = 0 | 1
1
に設定した場合、全ての
INSERT
、UPDATE
、DELETE
、および
LOCK TABLE WRITE
ステートメントが、同じテーブルに対して実行されている
SELECT
や LOCK TABLE
READ
がなくなるまで待機する。
この変数は旧称
SQL_LOW_PRIORITY_UPDATES
であった。
MAX_JOIN_SIZE = value | DEFAULT
value
を超えるレコードの組み合わせを調べることが必要な
SELECT
ステートメント、または
value
を超えるディスクシークの実行が見込まれる
SELECT
ステートメントを許可しない。この値を設定すると、キーの使用が不適切で長時間かかると見込まれる
SELECT
ステートメントを捕捉できる。DEFAULT
以外の値に設定すると、SQL_BIG_SELECTS
値が 0
にリセットされる。SQL_BIG_SELECTS
値を設定しなおすと、SQL_MAX_JOIN_SIZE
変数は無視される。mysqld
を
--max_join_size=value
オプションを指定して起動すると、この変数にデフォルト値を設定できる。この変数は旧称
SQL_MAX_JOIN_SIZE
であった。
クエリ結果がすでにクエリキャッシュにある場合は、結果がすでに計算されており、クライアントへの送信による負荷がサーバにかからないため、結果サイズのチェックは実行されない。
現在のユーザのパスワードを設定する。すべての非匿名ユーザは各自のパスワードを変更できる。
PASSWORD FOR user =
PASSWORD('パスワード')
現在のサーバホストの特定ユーザのパスワードを設定する。この実行は、mysql
データベースへのアクセスがあるユーザに限られる。ユーザは
user@hostname
の形式で指定する必要がある。user
と hostname
は、mysql.user
テーブルエントリの User
カラムと Host
カラムの記載どおりにする必要がある。たとえば、User
フィールドと Host
フィールドのエントリが 'bob'
および '%.loc.gov'
の場合は次のように入力する。
mysql> SET PASSWORD FOR 'bob'@'%.loc.gov' = PASSWORD('newpass');
これは以下と等しいことになる。
mysql>UPDATE mysql.user SET Password=PASSWORD('newpass')
->WHERE User='bob' AND Host='%.loc.gov';
mysql>FLUSH PRIVILEGES;
QUERY_CACHE_TYPE = OFF | ON | DEMAND
,
QUERY_CACHE_TYPE = 0 | 1 | 2
スレッドにクエリキャッシュ設定を行う。
オプション | 説明 |
0 または OFF | 結果のキャッシュや取り出しを行わない。 |
1 または ON | SELECT SQL_NO_CACHE ...
クエリを除くすべての結果をキャッシュする。 |
2 または DEMAND は、 | SELECT SQL_CACHE ...
クエリのみをキャッシュする。 |
SQL_AUTO_IS_NULL = 0 | 1
1
(デフォルト)に設定すると、WHERE
auto_increment_column IS NULL
の構造を使用して AUTO_INCREMENT
カラムが含まれるテーブルで最後に挿入されたレコードを検索できる。これは、Access
などの ODBC プログラムの一部で使用される。
SQL_BIG_SELECTS = 0 | 1
0
に設定すると、長時間要すると見込まれる(オプティマイザによって
MAX_JOIN_SIZE
の値を超えるレコード数が調べられると見込まれる)SELECT
ステートメントが MySQL
によって中止される。 これは不適切な
WHERE
ステートメントが使用された場合に役立つ。新たに接続した場合のデフォルト値は
1
で、すべての 1
ステートメントが許可される。
MAX_JOIN_SIZE
を
DEFAULT
以外の値に設定すると、SQL_BIG_SELECTS
が 0
に設定される。
SQL_BUFFER_RESULT = 0 | 1
SQL_BUFFER_RESULT
は
SELECT
ステートメントからの結果を強制的にテンポラリテーブルに入れる。これは、MySQL
によるテーブルロック解除の早期化と、結果セットのクライアントへの送信に長時間かかる場合に役立つ。
SQL_LOG_BIN = 0 | 1
0
に設定すると、クライアントに
SUPER
特権がある場合、そのクライアントに関するバイナリログへのログ記録が行われなくなる。
SQL_LOG_OFF = 0 | 1
1
に設定すると、クライアントに
SUPER
特権がある場合、そのクライアントに関する標準ログへのログ記録が行われなくなる。
SQL_LOG_UPDATE = 0 | 1
0
に設定すると、クライアントに
SUPER
特権がある場合、そのクライアントに関する更新ログへのログ記録が行われなくなる。
バージョン 5.0
以降はこの変数が廃止されている。
SQL_QUOTE_SHOW_CREATE = 0 | 1
1
に設定すると、SHOW
CREATE TABLE
でテーブル名とカラム名がクオートされる。これはデフォルトでオンになっており、マルチバイト文字などを使用したカラム名をもつテーブルのレプリケーションを可能にする。
項4.6.8.8. 「SHOW CREATE TABLE
」。
SQL_SAFE_UPDATES = 0 | 1
1
に設定すると、MySQL によって
WHERE
節でキーや
LIMIT
を使用しない
UPDATE
または
DELETE
ステートメントが中止される。これで、手入力で
SQL
ステートメントを作成した場合に誤った更新の捕捉が実現される。
SQL_SELECT_LIMIT = value | DEFAULT
SELECT
ステートメントから返されるレコードの最大数。SELECT
に LIMIT
節がある場合、SQL_SELECT_LIMIT
の値よりも LIMIT
のほうが優先される。新たに接続した場合のデフォルト値は
``無制限''
である。この制限を変更した場合、DEFAULT
の SQL_SELECT_LIMIT
値を使用してデフォルト値をリストアできる。
TIMESTAMP = timestamp_value | DEFAULT
クライアントに時間を設定する。これは、レコードのリストアにバイナリログを使用する場合、オリジナル(ログが記録された時点)のタイムスタンプにするために使用される。timestamp_value
は MySQL タイムスタンプではなく Unix
基準時点のタイムスタンプにする必要がある。
TIME_FORMAT = format_str
サーバで TIME
値を文字列に変換する方法を設定する。この変数は、グローバルオプション、ローカルオプション、コマンドラインオプションのいずれでも使用できる。format_str
の指定には GET_FORMAT()
関数の使用が便利である。 See
項6.3.4. 「日付と時刻関数」 を参照。
前述のように、ディスクシークはパフォーマンスに対する大きなボトルネックである。この問題は、データが拡大し、効率的なキャッシュが実行不能になるほど大きくなるにつれて明白になる。大規模データベースで、事実上ランダムにデータにアクセスする場合、読み取りでは最低 1 回、書き込みでは最低 2 回のディスクシークが必要になることがわかる。この問題を最小にするには、シーク回数を減らすようにディスクを使用する。
複数のディスクに対してファイルをシンボリックリンクするか、ストライピングを行って、利用可能なディスクスピンドル数を増加する(およびそれによるシークのオーバヘッドを軽減する)。
シンボリックリンクの使用
MyISAM
テーブルの場合、通常のデータディレクトリ内の位置から別のディスクへのインデックスファイルやデータファイルのシンボリックリンクを行う(ストライピングも可能)。これによって、ディスクが他の用途に使用されていなければ、シークと読み取り時間がいずれも改善される。
See 項5.6.1. 「シンボリックリンクの使用」。
ストライピング
ストライピングは、ディスクが多数ある場合に、第 1 ブロックを第 1 ディスクに、第 2 ブロックは第 2 ディスクに、第 N ブロックは(N mod number_of_disks)ディスクにといった配置を意味する。これにより、通常のデータサイズがストライプサイズより小さい(または完全に一致している)場合にパフォーマンスが大幅に改善する。ストライピングはオペレーティングシステムとストライプサイズへの依存性が非常に高いため、ストライプサイズをさまざまに変えながらアプリケーションのベンチマークを行う。 See 項5.1.5. 「独自のベンチマークの使用」。
ストライピングの速度はパラメータによって大きく異なることに注意する。ストライピングパラメータの設定方法とディスク数によって桁ちがいの差異が発生する。ランダムアクセスか順次アクセスのいずれの最適化を行うかの選択が必要なことに注意する。
信頼性を高めるため、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
の使用が適切でない場合は、ファイルシステムの損傷が発生することがあるため、テストの際はあらかじめすべてのバックアップを取っておく必要がある。
データベースが使用するファイルシステムのパラメータを設定することもできる。
ファイルへの最終アクセス時を認識する必要がない(データベースサーバでは重要度が低い)場合、-o
noatime
オプションを使用してファイルシステムをマウントできる。これで、ファイルシステムの
i
ノードへの最終アクセス時間の更新がスキップされ、一部のディスクシークを回避できる。
多数のオペレーティングシステムで、-o
async
オプションを使用してディスクをマウントし、ファイルシステムが非同期で更新されるように設定できる。使用しているコンピュータが適度に安定している場合は、信頼性を損なわずにさらにパフォーマンスを改善できる(Linux
ではこのフラグがデフォルトでオンになっている)。
テーブルとデータベースをデータベースディレクトリから他の位置に移動し、新しい位置へのシンボリックリンクに置換することができます。 これは、たとえば、データベースを空き領域の多いファイルシステムに移動し、テーブルを別のディスクに分散することでシステムの速度を上げる場合などに実行できます。
この推奨される実行方法は、データベースだけ別のディスクへのシンボリックリンクを行い、最後の手段としてのみテーブルのシンボリックリンクを行うことです。
Unix の場合、データベースのシンボリックリンクは、まず、空き領域のあるディスクにディレクトリを作成し、次に MySQL データベースディレクトリからそのディレクトリへのシンボリックリンクを作成します。
shell>mkdir /dr1/databases/test
shell>ln -s /dr1/databases/test mysqld-datadir
MySQL は、1
つのディレクトリに対して複数のデータベースをリンクさせることをサポートしていません。データベースディレクトリをシンボリックリンクに置換すると、複数のデータベースへシンボリックリンクを張らない限り、問題なく機能します。
仮に MySQL データディレクトリにデータベース
db1
がある場合に、db1
を指すシンボリックリンク db2
を作成するとします。
shell>cd /path/to/datadir
shell>ln -s db1 db2
これで、db1
のテーブル
tbl_a
が、db2
のテーブル tbl_a
としても表示されます。あるスレッドで
db1.tbl_a
が更新され、別のスレッドで
db2.tbl_a
が更新されると、問題が発生します。
このようにすることが実際に必要な場合は、mysys/mf_format.c
で次のコードを変更する必要があります。
if (flag & 32 || (!lstat(to,&stat_buff) && S_ISLNK(stat_buff.st_mode)))
これを次のようにします。
if (1)
Windows では、-DUSE_SYMDIR
を使用して MySQL
をコンパイルして、ディレクトリへの内部シンボリックリンクを使用できます。これによって、複数のデータベースを複数のディスクに配置できるようになります。
See 項5.6.1.3. 「Windows
上のデータベースに対するシンボリックリンクの使用」。
MySQL 4.0
より前は、テーブルのシンボリックリンクの実行を非常に慎重に行う必要がありました。シンボリックリンクが行われているテーブルで、ALTER
TABLE
、REPAIR
TABLE
、あるいは OPTIMIZE
TABLE
を実行する際に、シンボリックリンクが削除され、オリジナルファイルに置換されるという問題がありました。これらのステートメントは、データベースディレクトリにテンポラリファイルを作成し、ステートメントの操作が完了するとオリジナルファイルとテンポラリファイルの置換が行われる仕様であるため、この問題が発生しました。
realpath()
の呼び出しの機能が完全でないシステムではテーブルのシンボリックリンクを行わないでください(少なくとも、Linux
と Solaris では realpath()
がサポートされています)。
MySQL 4.0 では MyISAM
テーブルでのみシンボリックリンクが完全サポートされています。これ以外のテーブル型で上記のコマンドを使用すると、予想外の問題の発生の恐れがあります。
MySQL 4.0
でのシンボリックリンクの処理は、次のように機能します(ほとんどが
MyISAM
テーブルのみに適しています)。
データディレクトリには常にテーブル定義ファイル、データファイルおよびインデックスファイルがある。データファイルとインデックスファイルは、別の場所に移動し、データディレクトリ内でシンボリックリンクによって置換できる。定義ファイルはこれができない。
データファイルとインデックスファイルは、それぞれ独立して別のディレクトリにシンボリックリンクを作成できる。
シンボリックリンクは、オペレーティングシステムレベル(mysqld
が実行されていない場合)、または SQL で
CREATE TABLE
に DATA
DIRECTORY
および INDEX
DIRECTORY
オプションを指定して実行できる。 See
項6.5.3. 「CREATE TABLE
構文」。
myisamchk
は、データファイルやインデックスファイルのシンボリックリンクを置き換えない。
myisamchk
はリンクで指し示されているファイルに直接作用する。テンポラリファイルはすべてデータファイルやインデックスファイルが配置されているのと同じディレクトリに作成される。
シンボリックリンクを使用しているテーブルをドロップすると、シンボリックリンクとシンボリックリンクが指しているファイルの両方がドロップされる。このため、root
として mysqld
を実行すべきではなく、また、MySQL
データベースディレクトリへの書き込みアクセスをユーザに許可するべきでもない。
ALTER TABLE RENAME
を使用してテーブルの名前を変更し、テーブルを他のデータベースに移動しない場合、データベースディレクトリのシンボリックリンクの名前が新しい名前に変更され、データファイルとインデックスファイルもそれに従って名前が変更される。
ALTER TABLE RENAME
を使用してテーブルを別のデータベースに移動すると、テーブルが別のデータベースディレクトリに移動され、それまであったシンボリックリンクとそれが指すファイルが削除される(新規テーブルのシンボリックリンクは作成されない)。
シンボリックリンクを使用していない場合は、mysqld
に --skip-symlink
オプションを指定して使用し、確実に誰もデータディレクトリの外でファイルのドロップや名前の変更を行う
mysqld
を使用できないようにする。
サポートされていない事項
ALTER TABLE
では DATA
DIRECTORY
と INDEX DIRECTORY
テーブルオプションが無視される。
MySQL 4.0.15
より前は、テーブルにシンボリックリンクがある場合、SHOW
CREATE TABLE
でレポートが行われない。これは、SHOW
CREATE TABLE
を使用して CREATE
TABLE
ステートメントを生成する
mysqldump
についても同様である。
BACKUP TABLE
と RESTORE
TABLE
ではシンボリックリンクが考慮されない。
frm
ファイルはシンボリックリンクにすることがまったくできない(前述のように、データファイルとインデックスファイルのみシンボリックリンクにできる)。
これを実行した場合(シノニム作成など)、正しい結果が得られなくなる。
MySQL データディレクトリにデータベース
db1
があり、このデータベースにはテーブル
tbl1
が、db1
ディレクトリには tbl1
を指すシンボリックリンク
tbl2
があるとする。
shell>cd /path/to/datadir/db1
shell>ln -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
コマンドもエラーになる。
MySQL バージョン 3.23.16 から、MySQL
ディストリビューションの
mysqld-max
および
mysql-max-nt
サーバが
-DUSE_SYMDIR
オプションでコンパイルされるようになりました。これにより、シンボリックリンクを設定して別のディスクにデータベースディレクトリを配置できます。
リンクの設定手順は異なりますが、機能は Unix
のシンボリックリンクと同様です。
Windows
では、対象ディレクトリへのパスが記載されたファイルを作成して
MySQL
データベースに対するシンボリックリンクを作成します。ファイル名
db_name.sym
を使用してデータディレクトリにファイルを保存します。この
db_name
はデータベース名です。
たとえば、MySQL データディレクトリが
C:\mysql\data
で、データベース
foo
を D:\data\foo
に配置する場合、パス名
D:\data\foo\
が記載されたファイル
C:\mysql\data\foo.sym
を作成する必要があります。このようにすると、データベース
foo
に作成されているすべてのテーブルが
D:\data\foo
に作成されます。
この作業には D:\data\foo
ディレクトリが存在している必要があります。また、データベース名のディレクトリが
MySQL
データディレクトリにあるとシンボリックリンクが使用されなくなるため、注意が必要です。言い換えると、すでに
foo
という名前のデータベースディレクトリがデータディレクトリにある場合、これを
D:\data
に移動しないとシンボリックリンクが有効にならないことになります(問題を回避するため、データベースディレクトリの移動時はサーバを実行しないでください)。
どのテーブルを開く場合でも速度が低下するため、これをサポートするように
MySQL
をコンパイルした場合でも、デフォルトでは有効化されていません。シンボリックリンクを有効化するには、my.cnf
または my.ini
ファイルを次のエントリに入力する必要があります。
[mysqld] symbolic-links
MySQL 4.0
では、シンボリックリンクがデフォルトで有効化されています。不要の場合は、skip-symbolic-links
オプションで無効化できます。
This is a translation of the MySQL Reference Manual that can be found at dev.mysql.com. The original Reference Manual is in English, and this translation is not necessarily as up to date as the English version.