| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
| 5.1 最適化の概要 | ||
5.2 SELECT ステートメントおよびその他のクエリの最適化 | ||
| 5.3 ロック関連の問題 | ||
| 5.4 データベース構造の最適化 | ||
| 5.5 MySQL サーバの最適化 | ||
| 5.6 ディスク関連の問題 |
最適化は、システム全体の理解が必要であるため、複雑な作業です。システムやアプリケーションに関する知識が豊富でなくても部分的なローカルの最適化は可能ですが、より高度な最適化が必要になるほど求められる知識も高度になります。
この章では、MySQL 最適化の方法説明し、その例もいくつか紹介します。ただし、常にシステムの速度をさらに上げる補足的な方法もありますが、難度も高くなることを覚えておいてください。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
言うまでもなく、システムの速度を上げる際に最も重要な要素は基本設計です。また、使用するシステムの用途およびそのボトルネックを認識しておく必要もあります。
最も一般的なボトルネックは下記のとおりです。
| 5.1.1 MySQL の設計上の制約とトレードオフ | ||
| 5.1.2 移植性 | ||
| 5.1.3 MySQL 使用実績 | ||
| 5.1.4 MySQL ベンチマークスィート | ||
| 5.1.5 独自のベンチマークの使用 |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
MyISAM ストレージエンジンの使用時に、MySQL では非常に高速のテーブルロック(複数リーダ/単一ライタ)が使用されます。このテーブル型の最大の問題は、同じテーブルに対して複数の UPDATE と遅い SELECT が混在する場合に発生します。テーブルでこのような問題が発生した場合は、別のテーブル型を使用してもかまいません。 「7. MySQL のテーブル型」 節 参照 。
MySQL はトランザクションテーブル、非トランザクションテーブルの両方で機能します。 非トランザクションテーブル(何らかのエラー発生した場合にロールバックができない)での動作をスムーズにするため、MySQL には次のルールがあります。
NOT NULL カラムに対して NULL などの '正しくない' 値を挿入した場合や、数値列の数値が大きすぎる場合、MySQL ではエラーを発生するのではなく、'とりうる可能な値のうちの最適値' に値を設定する。数値の場合は 0 で、可能な最小値か最大値になる。文字列の場合は、空白文字かカラムの最大長さにあわせた文字列になる。
NULL を返す。
この詳細については、 「1.8.5 MySQL における制約の処理」 節 参照 を参照してください。
このことは、フィールド内容のチェックに MySQL を使用するのではなく、このチェックをアプリケーションで実行する必要があることを意味します。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
SQL サーバは SQL のさまざまな部分を実装しているので、移植可能な SQL アプリケーションの作成が可能となります。非常に単純な SELECT や INSERT は容易ですが、必要なことが増えれば増えるほど、作成が難しくなります。多数のデータベースを使用しながら素早い速度が要求されるアプリケーションの場合は、さらに難度が上がります。
複雑なアプリケーションを移植可能にするには、ともに稼動する必要のある SQL サーバ数を選択する必要があります。
MySQL crash-me プログラム(Web ページ http://www.mysql.com/information/crash-me.php )を使用すると、データベースサーバの選択に使用できる関数、データ型、制約を調べることができます。現在の Crash-me は可能なことすべてのテストを実行できるとは決して言えませんが、約 450 項目のテストが幅広く行われています。
たとえば、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 アプリケーションのように正確性よりパフォーマンスが重視される場合は、すべての結果をキャッシュするアプリケーションレイヤを作成すると、さらにパフォーマンスを改善できます。一定の期間後に古い結果を '期限切れ' することで、キャッシュを適度に最新の状態に保持できます。これにより、キャッシュを動的に拡大し、通常の状況に戻るまでタイムアウト期限を高速に設定して、高負荷のスパイクを処理するメソッドが提供されます。
この場合、テーブル作成情報にキャッシュの初期サイズと通常時にテーブルがリフレッシュされる頻度に関する情報が組み込まれます。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 カーネルではこの処理が改善されるでしょう。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
このセクションでは、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 ソースディストリビューションの `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 のサイトにあります。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
確実にアプリケーションとデータベースのベンチマークを行い、ボトルネックを検出しておく必要があります。これを修正(または、ボトルネックを "ダミーモジュール" に置換)することによって、次のボトルネック(など)の確認が容易になります。現在のアプリケーションの総合的なパフォーマンスが許容できるものであっても、実際にパフォーマンスの強化が迫られる場合に備えて、少なくともボトルネックそれぞれに対して計画を立て解決方法を判定しておく必要があります。
移植可能なベンチマークプログラムの例として、MySQL ベンチマークスィートを取り上げます。 「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 = 打ちこわし)のとおり、システムに限界まで負荷をかけることができるため、必ず開発システムでのみ使用するようにしてください。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
SELECT ステートメントおよびその他のクエリの最適化 第 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) はクエリに関数上の問題があるかどうかを調べる際に最適のツールです。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
EXPLAIN 構文(SELECT に関する情報の取得)
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 を定期的に実行する必要があります。
「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
SELECT。
DEPENDENT SUBQUERY
SELECT、外側のサブクエリに依存する。
DERIVED
SELECT(FROM 節内のサブクエリ)。
table
type
system
const 結合型の特殊なケースである。
const
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
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 を使用したレコードの補足検索も追加で実行される。
「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 を使用してクエリをチェックする。
「6.5.4 ALTER TABLE 構文」 節 参照 。
テーブルにあるインデックスを調べるには SHOW INDEX FROM tbl_name を使用する。
key
key カラムは、MySQL が実際に使用を決定したキー(インデックス)を示す。選択されたインデックスがない場合、このキーは NULL になる。MySQL で possible_keys カラムに記載されたキーが使用されるように強制するには、クエリで USE KEY/IGNORE KEY を使用する。
「6.4.1 SELECT 構文」 節 参照 。
また、テーブルで myisamchk --analyze( 「4.5.6.1 myisamchk 起動構文」 節 参照 )または ANALYZE TABLE( 「4.6.2 ANALYZE TABLE 構文」 節 参照 )を実行することも、オプティマイザでより適したインデックスを選択する際に役立つ。
key_len
key_len カラムは、MySQL が使用を決定したキーの長さを示す。key が NULL の場合、この長さは NULL になる。これによって、複合キーで MySQL が実際に使用するパート数が示されることに注意する。
ref
ref カラムは、テーブルからレコードを選択する際に key とともに使用されるカラムまたは定数を示す。
rows
rows カラムは、クエリの実行に際して調べる必要があると MySQL によって判定されたレコードの数を示す。
Extra
Distinct
Not exists
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: #)
Using filesort
join type に従ってすべてのレコードをスキャンし、WHERE 条件に一致する全てのレコードに、ソートキー + 行ポインタを格納して、ソートは実行される。
その後キーがソートされる。
最後に、ソートされた順にレコードが取り出される。
Using index
Using temporary
GROUP BY を実行したカラムセットと異なるカラムセットに対して ORDER BY を実行した場合に発生する。
Using where
WHERE 節が使用されることを示す。この情報がなく、テーブルの型が ALL または index である場合はクエリが正常に実行されないことがある(テーブルのすべてのレコードの取得や検査を意図していない場合)。
クエリを最大限高速に実行する必要がある場合は、Using filesort と Using temporary に注意する必要がある。
EXPLAIN 出力の rows カラムのすべての値を掛け算することで、結合がどの程度適しているかを示す指針を取得できます。これは、クエリの実行時に MySQL で調べる必要があるレコード数の概要を示します。この数値は、max_join_size 変数でクエリを制限する際にも使用されます。
「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 節でテーブルの順序を変えて一覧表示してみるとパフォーマンスを改善できます。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
ほとんどの場合、ディスクシークを数えることでパフォーマンスを推定できます。
小さいテーブルの場合は一般に 1 つのディスクシークでレコードを検索できます(インデックスがキャッシュされることが多いため)。大きいテーブルの場合の推定では、(B++ ツリーインデックスを使用して)log(row_count) / log(index_block_length / 3 * 2 / (index_length + data_pointer_length)) + 1 のシークがレコードの検索に必要になります。
MySQL では、インデックスブロックが通常 1024 バイトで、データポインタは通常 4 バイトです。インデックスの長さが 3(中位の整数)の 500,000 レコードのテーブルの場合は以下のようになります。
log(500000)/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 の分だけ増加する)によって最終的にアプリケーションがバインドされるまで大幅に速度の低下が始まります。これを回避するには、データの増加に合わせてインデックスキャッシュも拡大します。 「5.5.2 サーバパラメータのチューニング」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
SELECT クエリの速度
一般に、低速の SELECT ... WHERE の速度を上げる必要がある場合は、まず、インデックスを追加できるかどうかをチェックします。 「5.4.3 MySQL でのインデックスの使用」 節 参照 。一般に複数のテーブル間の参照はすべてインデックスを使用して実行する必要があります。EXPLAIN コマンドを使用して、SELECT に使用されるインデックスを判定できます。
「5.2.1 EXPLAIN 構文(SELECT に関する情報の取得)」 節 参照 。
一般的なヒント
myisamchk --analyze を実行する。これはインデックスのために、同じ値があるレコードの平均値を更新する(ユニークインデックスの場合、これは常に 1 になる)。MySQL はこれを使用して、2 つのテーブルを '非定数式' で接続する際に選択するインデックスを判定する。
SHOW INDEX FROM table_name を実行し Cardinality カラムを調べると、analyze の実行結果をチェックできる。
myisamchk --sort-index --sort-records=1(インデックス 1 でソートする場合)を使用する。速度を上げるには、すべてのレコードの読み取りにユニークインデックスを使用し、そのインデックスに従った順序で読み取りを行うように推奨される。ただし、このソートでは書き込みの最適化はできず、テーブルが大きい場合は時間がかかる。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 |
WHERE がない単一テーブルの COUNT(*) は、MyISAM と HEAP テーブルのテーブル情報から直接取り出される。
これは、テーブル 1 つのみで使用する場合はすべての NOT NULL 式でも実行される。
SELECT ステートメントがある場合、それを迅速に検出し、結果としてレコードを返さない。
GROUP BY またはグループ関数(COUNT()、MIN()。..)を使用しない場合、HAVING は WHERE とマージされる。
WHERE が構造化され、サブ結合ごとに迅速に WHERE 評価を取得し、可能な限り迅速にレコードをスキップする。
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 ではメモリ内のテンポラリテーブルが使用される。
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;
|
次のクエリは、ソートのパスを分けることなく、ソートしたレコードを取り出すためにインデックスを使用します。
mysql> SELECT ... FROM tbl_name
-> ORDER BY key_part1,key_part2,... ;
mysql> SELECT ... FROM tbl_name
-> ORDER BY key_part1 DESC,key_part2 DESC,... ;
|
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
IS NULL の最適化
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 のキー部分は使用できません。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
DISTINCT の最適化
DISTINCT が ORDER BY と組み合わされて用いられると、多くの場合はテンポラリテーブルが必要になります。
DISTINCT は GROUP BY をともなう可能性が高いので、SELECT されないフィールドを ORDER BY または HAVING した時に、どのように MySQL が機能するかを認識しておく必要があります。 「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 の特定のレコード)を停止します。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
LEFT JOIN と RIGHT JOIN の最適化
MySQL の A LEFT JOIN B join_condition は以下のように実装されます。
B はテーブル A と A が依存するすべてのテーブルに依存するように設定される。
A は、LEFT JOIN 条件で使用されるすべてのテーブル(B を除く)に依存するように設定される。
LEFT JOIN 条件は、テーブル B からのレコードの取り出し方法の判定に使用される(言い換えると、WHERE 節の条件はいずれも使用されない)。
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 を使用します。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
ORDER BY の最適化
余分なソートを行わずに 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)に格納される。
MERGEBUFF(7)領域まで、別のテンポラリファイルの 1 ブロックにマルチマージが実行される。最初のファイルの全ブロックが 2 つめのファイルに配置されるまで反復される。
MERGEBUFF2(15)ブロック未満になるまで、以下が反復される。
read_rnd_buffer_size)。
EXPLAIN SELECT ... ORDER BY を使用すると、MySQL でインデックスを使用してクエリを解決できるかどうかをチェックできます。extra カラムに Using filesort が出力された場合は、MySQL で ORDER BY の解決にインデックスを使用できません。 「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; |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
LIMIT の最適化
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 値の計算がすべて行われなくなる。
# レコードをクライアントに送信すると、クエリが中止される(SQL_CALC_FOUND_ROWS を使用していない場合)。
LIMIT 0 は常に迅速に空のセットを返す。これは、クエリのチェックおよび結果として返るカラムのカラム型の取得に役立つ。
LIMIT row_count が必要な領域の計算に使用される。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
INSERT クエリの速度 レコード挿入の時間構成の概要は次のとおりです。
ここに示した数値は、時間全体を比例的に配分したものです。テーブルを開く初期オーバヘッドは算入されていません(これは同時実行クエリのそれぞれで 1 回実行されます)。
テーブルのサイズによって対数 N の分だけインデックス挿入の速度が低下します(B ツリー)。
挿入の速度を上げる方法
INSERT ステートメントを使用する。これで独立した INSERT ステートメントの使用時と比較して大幅に(場合によっては数倍)速度が上がる。空ではないテーブルにデータを追加する場合は、さらに速度を上げるために bulk_insert_buffer_size 変数を調整する。
「4.6.8.4 SHOW VARIABLES」 節 参照 。
INSERT DELAYED ステートメントを使用すると速度を上げることができる。 「6.4.3 INSERT 構文」 節 参照 。
MyISAM テーブルでは、テーブルに削除されたレコードがない場合、SELECT の実行と同時にレコードを挿入できることに注意する。
LOAD DATA INFILE を使用する。通常、これは INSERT ステートメントを多数使用する場合と比較して、20 倍速度が上がる。
「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 を実行してテーブルを小さくする。 「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 の両方の速度をさらに改善するには、キーバッファを拡張します。 「5.5.2 サーバパラメータのチューニング」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
UPDATE クエリの速度
更新クエリは、SELECT クエリと同様に最適化されますが、書き込みオーバヘッドが加算されます。書き込みの速度は更新対象のデータのサイズおよび更新対象のインデックス数によって異なります。変更がないインデックスは更新されません。
更新の速度を上げるもう 1 つの方法は、更新を遅延して 1 行で多数の更新を後から行うことです。1 行での多数の更新は、テーブルをロックすると同時に行う場合と比較して大幅に高速に実行できます。
可変長レコードの場合は、合計の長さが今よりも長いものにレコードを更新すると、レコードが分割される場合があることに注意します。このため、頻繁にこれを実行する場合は、ときどき OPTIMIZE TABLE することが重要になります。
「4.6.1 OPTIMIZE TABLE 構文」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
DELETE クエリの速度
テーブル内のすべてのレコードを削除する場合は、TRUNCATE TABLE table_name を使用します。 「6.4.6 TRUNCATE 構文」 節 参照 。
レコード削除に要する時間は、完全にインデックス数に比例します。レコード削除の速度を上げるには、インデックスキャッシュのサイズを拡大します。 「5.5.2 サーバパラメータのチューニング」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
システム高速化のためのヒント(順不同)
thread_cache_size 変数の値の変更が必要になることがある。 「5.5.2 サーバパラメータのチューニング」 節 参照 。
EXPLAIN コマンドでこれを実行できる。 「5.2.1 EXPLAIN 構文(SELECT に関する情報の取得)」 節 参照 。
MyISAM テーブルに対して複雑な SELECT クエリを使用しないようにする。これでテーブルロックを回避する。
MyISAM テーブルの場合は、別のクエリでそのテーブルからの読み取りが行われるのと同時にレコードを挿入できる。これがあなたにとって重要ならば、レコードの削除が不要なメソッドや、大量のレコード削除後の OPTIMIZE TABLE の実行を検討する。
expr1,expr2... の順に従って頻繁にレコードを読み取る場合は、ALTER TABLE ... ORDER BY expr1,expr2... を使用する。テーブルが大幅に変更された後にこのオプションを使用すると、パフォーマンスを改善できる。
SELECT * FROM table_name WHERE hash=MD5(CONCAT(col1,col2))
AND col_1='constant' AND col_2='constant'
VARCHAR や BLOB のカラムを使用しないようにする。VARCHAR または BLOB カラムを 1 つ使用するとレコードがただちに可変長になってしまう。 「7. MySQL のテーブル型」 節 参照 。
UPDATE table SET count=count+1 WHERE index_column=constant のような更新は非常に高速にできる。
実際これは、MyISAM や ISAM のようにテーブルロック(複数リーダ/単一ライタ)のみの MySQL テーブル型を使用する場合に非常に重要である。また、このような場合は行ロックマネージャで必要な作業が少なくなるため、ほとんどのデータベースでパフォーマンスが改善される。
INSERT /*! DELAYED */ を使用する。多数のレコードが 1 回のディスクへの書き込みで書き込まれるため、これで高速化が図れる。
SELECT の優先を上げる場合は、INSERT /*! LOW_PRIORITY */ を使用する。
SELECT がキューをジャンプするようにする場合は、SELECT /*! HIGH_PRIORITY */ を使用する。言い換えると、書き込み待機中のユーザがいる場合でも、SELECT を実行できるようになる。
INSERT ステートメントを使用する(これは多数の SQL でサポートされている)。
LOAD DATA INFILE を使用する。これは通常の挿入より高速になる。
AUTO_INCREMENT カラムを使用する。
OPTIMIZE TABLE を使用して、動的テーブルの断片化を回避する。 「4.6.1 OPTIMIZE TABLE 構文」 節 参照 。
HEAP を使用して高速化を図れるようにする。 「7. MySQL のテーブル型」 節 参照 。
名前はなるべく単純なものに保持する(カスタマテーブルでは customer_name ではなく name を使用する)。他の SQL サーバに移植可能にすることを考慮するなら、名前を 18 文字未満にする。
MyISAM に直接アクセスすることによって、SQL インタフェース使用時と比較して 2-5 倍の速度が得られることもある。
これを実行可能にするには、データをアプリケーションと同じサーバに配置し、また通常は 1 プロセスのみからアクセスするようにする必要がある(外部ファイルロックが非常に低速なため)。上記の問題は、MySQL サーバに低レベルの MyISAM コマンドを導入することで解消できる(必要に応じてパフォーマンスを改善する容易な手段の 1 つ)。データベースインタフェースを慎重に設計することで、この種の最適化を容易にサポートできる。
DELAY_KEY_WRITE=1 オプションでテーブルを定義すると、ファイルが閉じられるまでディスクにログが記録されないためインデックス更新の速度が上がる。
この欠点は、途中で mysqld の強制終了が発生した場合にテーブルに問題がないことを確認するため、mysqld を開始する前に、テーブルに対して myisamchk を実行する必要があるということである。キー情報は常にデータから生成可能であるため、DELAY_KEY_WRITE を使用しても何も消失はしない。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
| 5.3.1 MySQL のテーブルロック方法 | ||
| 5.3.2 テーブルロック関連の問題 |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
ロックメソッドそれぞれについての説明は付録にあります。 「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 の使用もテーブルロックを短縮するのに役立ちます。
「6.4.1 SELECT 構文」 節 参照 。
さらに、1 つのキューを使用するように `mysys/thr_lock.c' のロックコードを変更することもできます。この場合は、書き込みロックと読み取りロックの優先度が同じになり、アプリケーションによっては高速化に役立ちます。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 ロックを設定する。
SET LOW_PRIORITY_UPDATES=1 を使用すると、特定のスレッドからの更新すべてが低い優先度で実行されるように指定できる。
「5.5.6 SET 構文」 節 参照 。
HIGH_PRIORITY 属性を使用すると、特定の SELECT の重要度を高く指定できる。 「6.4.1 SELECT 構文」 節 参照 。
SELECT と結合した INSERT に問題がある場合は、SELECT ステートメントと INSERT ステートメントの同時サポートが可能になるため、新規の MyISAM テーブルを使用するように切り替える。
INSERT ステートメントと SELECT ステートメントの混在が多い場合、INSERT の DELAYED 属性によって問題が解決される確率が高い。
「6.4.3 INSERT 構文」 節 参照 。
SELECT と DELETE に問題がある場合、DELETE に LIMIT オプションを使用すると解決できる場合がある。 「6.4.5 DELETE 構文」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
| 5.4.1 設計上の選択 | ||
| 5.4.2 データの小型化 | ||
| 5.4.3 MySQL でのインデックスの使用 | ||
| 5.4.4 カラムインデックス | ||
| 5.4.5 複合インデックス | ||
| 5.4.6 MySQL のオープンテーブルのカウント方法 | ||
| 5.4.7 MySQL でのテーブルのオープンとクローズの方法 | ||
| 5.4.8 1 つのデータベースに大量のテーブルを作成した場合の欠点 |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
MySQL はローデータとインデックスデータを別のファイルに格納します。その他のデータベースの多く(ほとんど)は、ローデータとインデックスデータが同じファイルに混在しています。現在の非常に多くのシステムで MySQL の選択のほうが優れていると確信しています。
ローデータの格納方法には、各カラムの情報を独立した領域に格納する方法もあります(例: SDBM、Focus など)。これは、複数のカラムにアクセスするすべてのクエリでパフォーマンスに影響を及ぼします。パフォーマンスは複数のコラムへのアクセスを開始するとただちに低速化するため、このようなモデルは汎用データベースには適さないと確信しています。
一般的にインデックスとデータが一緒に格納されている場合も多くあります(Oracle、Sybase などの場合)。この場合は、レコード情報をインデックスのリーフページで検索します。このレイアウトで優れている点は、多くの場合インデックスのキャッシュ方法次第でディスクの読み取りを節約できることにあります。このレイアウトの欠点は以下のとおりです。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
最も基本的な最適化の 1 つにデータ(およびインデックス)が占めるディスク領域を可能な限り少なくすることがあります。これで、ディスクの読み取りが高速化し、使用メモリも一般に減少するため、大幅な改善が図れます。カラムが小さければインデックス作成で消費されるリソースも少なくなります。
MySQL では多様なテーブル型とレコード形式がサポートされます。 適切なテーブル形式を選択することで、パフォーマンスを大幅に改善できます。 「7. MySQL のテーブル型」 節 参照 。
ここで紹介する技法を使用すると、テーブルのパフォーマンス改善とストレージ領域の最小化を図ることができます。
INT より、MEDIUMINT のほうが適している場合もしばしばある。
NOT NULL を宣言する。これですべてが高速化され、1 カラム当たり 1 ビットを節約できる。アプリケーションで実際に NULL が必要な場合は、必ず使用する必要があるため、注意が必要である。デフォルトですべてのカラムにこれを設定することは避ける。
VARCHAR、TEXT、BLOB など)がまったくない場合は固定長レコード形式を使用する。これで速度が上がるが、領域の消費も増える。
「7.1.2 MyISAM テーブル形式」 節 参照
CHAR 型カラムの部分インデックスをサポートする。短いインデックスの速度が速い理由は、占有ディスク領域が小さいことだけではなく、インデックスキャッシュでのヒットが多くなり、所要ディスクシークが少なくなることにもよる。 「5.5.2 サーバパラメータのチューニング」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
インデックスは、カラムが特定の値をもつレコードの迅速な検索に使用されます。インデックスがないと、MySQL がレコードを見つけるために、最初のレコードから開始し、テーブル全体を読み取るとが必要になります。テーブルが大きくなると、これにコストがかかります。クエリ対象のカラムにインデックスがあると、MySQL は全てのデータを探すことなく、データファイルの途中にあるシーク対象ポジションを迅速に取得することができます。テーブルに 1000 レコードある場合、シーケンシャルに読み取る場合と比較して少なくとも 100 倍は高速化できます。1000 レコードのほとんどすべてにアクセスする必要がある場合は、ディスクシークが最小になるため、シーケンシャルに読むほうが速くなることに注意してください。
MySQL インデックスのすべて(PRIMARY KEY、UNIQUE、および INDEX)は、B ツリーに格納されます。文字列の頭にある空白と最後にある空白は自動的に圧縮されます。 「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 がある場合は、キーが逆の順序で読み取られる。 「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 はインデックスを使用します。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
MySQL の全てのカラム型にはインデックスを張ることができます。SELECT 操作のパフォーマンスの改善には、対応するカラムにインデックスを使用することが最善の方法です。
テーブルあたりの最大インデックス数とインデックスの最大長は、ストレージエンジンごとに定義されます。 「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 全文検索」 を参照してください。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 でのインデックスの使用」 を参照してください。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
mysqladmin status を実行すると、以下の出力が表示されます。
Uptime: 426 Running threads: 1 Questions: 11082 Reloads: 1 Open tables: 12 |
テーブルが 6 つしかない場合に Open tables 値が 12 と表示されることに、当惑する場合もあります。
MySQL はマルチスレッド化されているため、多数のクライアントが同時に同じものに対してクエリを使用することがあります。2 つのクライアントスレッドで 1 つのファイルに異なるステータスが発生する問題を最小にするため、同時に実行しているスレッドがそれぞれで無関係にテーブルを開きます。これはメモリの消費を増やしますが、一般にパフォーマンスは向上します。ISAM テーブルと MyISAM テーブルの場合は、テーブルを開いたそれぞれのクライアントにデータファイルに対するファイル記述子が必要になります。このテーブル型では、インデックスファイルに対するファイル記述子がすべてのスレッドで共有されます。
次のセクションでもこのトピックについてさらに説明します。 「5.4.7 MySQL でのテーブルのオープンとクローズの方法」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 で使用可能なファイル記述子数を拡大できます。 「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 を呼び出すか、スレッドが終了するまで閉じられません。
「6.4.9 HANDLER 構文」 節 参照 。 この場合はテーブルがテーブルキャッシュに戻されます(キャッシュが満杯でない場合)。
テーブルキャッシュが小さすぎるかどうかは、mysqld の Opened_tables 変数のチェックで確認できます。たとえ多くの FLUSH TABLES を実行していない場合でも、この値が非常に大きい場合は、テーブルキャッシュサイズを拡張する必要があります。 「4.6.8.3 SHOW STATUS」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
ディレクトリにファイルが多数ある場合、オープン、クローズ、および作成の動作が低速になります。多数のテーブルに対して SELECT ステートメントを実行した場合、必要なテーブルを開くごとに、他のテーブルを閉じることが必要になるため、テーブルキャッシュが満杯の場合にオーバヘッドが少し発生します。このオーバヘッドは、テーブルキャッシュを拡大することで軽減できます。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
| 5.5.1 システム、コンパイル時間およびスタートアップパラメータのチューニング | ||
| 5.5.2 サーバパラメータのチューニング | ||
| 5.5.3 MySQL の速度に対するコンパイルとリンクの影響 | ||
| 5.5.4 MySQL でのメモリの使用 | ||
| 5.5.5 MySQL の DNS の使用 | ||
5.5.6 SET 構文 |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
システムレベルの要素は、その一部を初期段階に決定する必要があるため、この話から始めます。これに該当しない場合は、システムを大きく変えることが重要でないのであれば、このセクションは簡単に目を通せば十分です。ただし、このレベルで変更を行うことでどの程度改善できるのかを自覚しておくことは必ず役に立ちます。
使用するオペレーティングシステムは非常に重要です。複数 CPU のコンピュータを使用するなら、Solaris(スレッド実装機能が優れている)または Linux(2.2 カーネルの SMP サポートが優れている)が良いでしょう。 また、旧バージョンの Linux カーネルのデフォルトには 2G ファイルサイズの制限があります。このカーネルで 2G より大きいファイルがどうしても必要な場合は、ext2 ファイルシステムの LFS (Large File System)パッチを入手する必要があります。 これ以外の ReiserFS や XFS などには 2G の制限がありません。
多くのプラットフォーム上で、MySQL を本番稼働させていないため、可能であれば選択前に候補のプラットフォームのテストを実行することを推奨します。
--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 は使用できる。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 セクションを参照してください。 「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 を最後に指定します。最後にしないと、コマンドラインのそれ以降に記載されたオプションの効果が出力に反映されません。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
以下のテストのほとんどは、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% まで上げられる。
localhost に接続する場合、MySQL ではデフォルトでソケットファイルが使用される)。
--with-debug=full でコンパイルすると、ほとんどのクエリが 20% 遅くなる。
一部のクエリはかなり長くかかった(たとえば MySQL ベンチマークは 35% の速度低下)。--with-debug を使用すると、この速度低下は 15% で済む。--with-debug=full でコンパイルされた mysqld バージョンは、--skip-safemalloc オプションで起動すると実行時のメモリチェックを無効化できる。この場合の最終的な結果は、--with-debug で構成した場合に非常に近くなる。
gcc 3.2 より 4% 速度が上がった。
gcc 2.95.2 for UltraSPARC にオプション -mcpu=v8 -Wa,-xarch=v8plusa を付けてコンパイルすると、パフォーマンスが 4% 改善した。
--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 バイナリは、速度および移植性を高めるため静的にリンクされています。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
以下の一覧は、mysqld サーバでのメモリの使用方法の一部を示しています。可能な場合は、メモリ使用に関連するサーバ変数名も記載されています。
key_buffer_size)はすべてのスレッドで共有される。サーバが使用するこれ以外のバッファは必要に応じて割り当てられる。 「5.5.2 サーバパラメータのチューニング」 節 参照 。
thread_stack)、接続バッファ(変数 net_buffer_length)、および結果バッファ(net_buffer_length)のスレッド固有領域を使用する。接続バッファと結果バッファは必要に応じて max_allowed_packet まで動的に拡張される。クエリの実行中は現在のクエリ文字列のコピーも割り当てられる。
ISAM および MyISAM テーブルのみがメモリにマップされる。これは、4 GB の 32 ビットメモリ領域では大型のほとんどのテーブルに十分なほどは大きくないことによる。64 ビットアドレス領域のあるシステムが一般的になれば、メモリマップの一般サポートの追加が可能になる。
read_buffer_size)を割り当てる。
read_rnd_buffer_size)。
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 を設定することで、テンポラリテーブルのサイズを拡張する。 「5.5.6 SET 構文」 節 参照 。MySQL バージョン 3.20 では、テンポラリテーブルの最大サイズが record_buffer*16 であった。このバージョンを使用している場合は、record_buffer の値を拡大する必要がある。また、--big-tables オプションで mysqld を起動して、常にテンポラリテーブルをディスクに格納することもできる。ただし、これは複雑なクエリのほとんどで処理速度に影響を及ぼす。
malloc() および free() で実行される。
3 * n のバッファが割り当てられる(n は、レコードの最大長、ただし BLOB カラムは計算外)。BLOB カラムは、5 から 8 バイトに BLOB データの長さを加算したバイト数を使用する。ISAM および MyISAM ストレージエンジンは、内部使用のための追加レコードを 1 つ使用する。
BLOB カラムがあるテーブルのそれぞれで、大きな BLOB 値を読み込むためにバッファが動的に拡張される。テーブルをスキャンする場合は、最大 BLOB 値と同じ大きさのバッファが割り当てられる。
mysqladmin flush-tables コマンド(または FLUSH TABLES ステートメント)によって、使用中でないテーブルすべてが閉じられ、現在実行中のスレッドの終了時に使用中のテーブルすべてが閉じられるように指定される。これで効率的に使用中メモリに空きを作ることができる。
ps およびその他のステータスプログラムによって、mysqld が大量のメモリを使用していることを示すレポートが行われることがあります。これは、複数のメモリアドレスでのスレッドスタックによって発生します。たとえば、Solaris バージョンの ps ではスタック間の使用していないメモリが使用メモリにカウントされます。これは、swap -s で使用可能スワップをチェックすることで検証できます。市販のメモリリーク検出装置で mysqld をテストし、メモリリークがないと判明しています。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
新たなクライアントが 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 を開始します。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
SET 構文
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 構文とその他のデータベースとの互換性を保持を目的にサポートされています。
このマニュアルのシステム変数のセクションに設定可能な多様なシステム変数に関する説明があります。 「6.1.5 システム変数」 節 参照 。
SESSION(デフォルト)を使用している場合、現在のセッションを終了するまで、あるいはこのオプションに別の値を設定するまで、設定したオプションが有効になります。SUPER 特権を必要とする GLOBAL を使用した場合、サーバの再起動が行われるまでオプションが記憶され、新規接続時も使用されます。オプションを永続的にする場合は、オプション設定ファイルに設定します。
「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 コマンドラインオプションを使用して最大値を指定できます。 「4.1.1 mysqld コマンドラインオプション」 節 参照 。
SHOW VARIABLES を使用すると、ほとんどの変数の一覧が出力されます。
「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 の説明に記載されています。 「6.1.5 システム変数」 節 参照 。 「4.1.1 mysqld コマンドラインオプション」 節 参照 。 「4.6.8.4 SHOW VARIABLES」 節 参照 。
AUTOCOMMIT= 0 | 1
1 に設定すると、テーブルに対する変更すべてがただちに実行される。トランザクションを有効にする場合は、BEGIN ステートメントを使用する必要がある。 「6.7.1 START TRANSACTION、COMMIT、ROLLBACK の各構文」 節 参照 。0 に設定した場合は、そのトランザクションを COMMIT で受け入れるか、ROLLBACK で取り消す必要がある。
「6.7.1 START TRANSACTION、COMMIT、ROLLBACK の各構文」 節 参照 。
AUTOCOMMIT モードを 0 から 1 に変更すると、開いているすべてのトランザクションに対して MySQL が COMMIT を自動実行するため注意が必要である。
BIG_TABLES = 0 | 1
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() 関数の使用が便利である。
「6.3.4 日付と時刻関数」 節 参照 を参照。
DATETIME_FORMAT = format_str
DATETIME 値を文字列に変換する方法を設定する。この変数は、グローバルオプション、ローカルオプション、コマンドラインオプションのいずれでも使用できる。format_str の指定には GET_FORMAT() 関数の使用が便利である。
「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 = PASSWORD('パスワード')
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() 関数の使用が便利である。
「6.3.4 日付と時刻関数」 節 参照 を参照。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
MyISAM テーブルの場合、通常のデータディレクトリ内の位置から別のディスクへのインデックスファイルやデータファイルのシンボリックリンクを行う(ストライピングも可能)。これによって、ディスクが他の用途に使用されていなければ、シークと読み取り時間がいずれも改善される。 「5.6.1 シンボリックリンクの使用」 節 参照 。
ストライピングの速度はパラメータによって大きく異なることに注意する。ストライピングパラメータの設定方法とディスク数によって桁ちがいの差異が発生する。ランダムアクセスか順次アクセスのいずれの最適化を行うかの選択が必要なことに注意する。
hdparm を使用してディスクのインタフェースを構成することでパフォーマンスを大幅に改善できる(負荷時に 100% 改善できることも珍しくない)。次の例は、MySQL(およびその他の多数のアプリケーション)に非常に適した hdparm オプションである。
hdparm -m 16 -d 1 |
上記を使用した場合のパフォーマンスと信頼性は使用ハードウェアに依存するため、hdparm の使用後はシステムを総合的にテストするように強く推奨する。詳細については、hdparm のページを参照。hdparm の使用が適切でない場合は、ファイルシステムの損傷が発生することがあるため、テストの際はあらかじめすべてのバックアップを取っておく必要がある。
-o noatime オプションを使用してファイルシステムをマウントできる。これで、ファイルシステムの i ノードへの最終アクセス時間の更新がスキップされ、一部のディスクシークを回避できる。
-o async オプションを使用してディスクをマウントし、ファイルシステムが非同期で更新されるように設定できる。使用しているコンピュータが適度に安定している場合は、信頼性を損なわずにさらにパフォーマンスを改善できる(Linux ではこのフラグがデフォルトでオンになっている)。
| 5.6.1 シンボリックリンクの使用 |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
テーブルとデータベースをデータベースディレクトリから他の位置に移動し、新しい位置へのシンボリックリンクに置換することができます。 これは、たとえば、データベースを空き領域の多いファイルシステムに移動し、テーブルを別のディスクに分散することでシステムの速度を上げる場合などに実行できます。
この推奨される実行方法は、データベースだけ別のディスクへのシンボリックリンクを行い、最後の手段としてのみテーブルのシンボリックリンクを行うことです。
| 5.6.1.1 Unix 上のデータベースに対するシンボリックリンクの使用 | ||
| 5.6.1.2 Unix 上のテーブルに対するシンボリックリンクの使用 | ||
| 5.6.1.3 Windows 上のデータベースに対するシンボリックリンクの使用 |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 をコンパイルして、ディレクトリへの内部シンボリックリンクを使用できます。これによって、複数のデータベースを複数のディスクに配置できるようになります。 「5.6.1.3 Windows 上のデータベースに対するシンボリックリンクの使用」 節 参照 。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 オプションを指定して実行できる。
「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 テーブルオプションが無視される。
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 コマンドもエラーになる。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |
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 オプションで無効化できます。
| [ << ] | [ >> ] | [Top ] | [Contents ] | [Index] | [ ? ] |