第5章 MySQL の最適化

目次

5.1. 最適化の概要
5.1.1. MySQL の設計上の制約とトレードオフ
5.1.2. 移植性
5.1.3. MySQL 使用実績
5.1.4. MySQL ベンチマークスィート
5.1.5. 独自のベンチマークの使用
5.2. SELECT ステートメントおよびその他のクエリの最適化
5.2.1. EXPLAIN 構文(SELECT に関する情報の取得)
5.2.2. クエリパフォーマンスの推定
5.2.3. SELECT クエリの速度
5.2.4. MySQL による WHERE 節の最適化
5.2.5. MySQL による IS NULL の最適化
5.2.6. MySQL による DISTINCT の最適化
5.2.7. MySQL による LEFT JOINRIGHT JOIN の最適化
5.2.8. MySQL による ORDER BY の最適化
5.2.9. MySQL による LIMIT の最適化
5.2.10. INSERT クエリの速度
5.2.11. UPDATE クエリの速度
5.2.12. DELETE クエリの速度
5.2.13. その他の最適化のヒント
5.3. ロック関連の問題
5.3.1. MySQL のテーブルロック方法
5.3.2. テーブルロック関連の問題
5.4. データベース構造の最適化
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 つのデータベースに大量のテーブルを作成した場合の欠点
5.5. MySQL サーバの最適化
5.5.1. システム、コンパイル時間およびスタートアップパラメータのチューニング
5.5.2. サーバパラメータのチューニング
5.5.3. MySQL の速度に対するコンパイルとリンクの影響
5.5.4. MySQL でのメモリの使用
5.5.5. MySQL の DNS の使用
5.5.6. SET 構文
5.6. ディスク関連の問題
5.6.1. シンボリックリンクの使用

最適化は、システム全体の理解が必要であるため、複雑な作業です。システムやアプリケーションに関する知識が豊富でなくても部分的なローカルの最適化は可能ですが、より高度な最適化が必要になるほど求められる知識も高度になります。

この章では、MySQL 最適化の方法説明し、その例もいくつか紹介します。ただし、常にシステムの速度をさらに上げる補足的な方法もありますが、難度も高くなることを覚えておいてください。

5.1. 最適化の概要

言うまでもなく、システムの速度を上げる際に最も重要な要素は基本設計です。また、使用するシステムの用途およびそのボトルネックを認識しておく必要もあります。

最も一般的なボトルネックは下記のとおりです。

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

  • ディスクの読み取りと書き込み。 ディスクが適切な位置にある場合、データの読み取りが必要になる。1999 年の最新のディスクでは、1 つのディスクで約 10 - 20 MB の読み取りが可能になる。これは、複数のディスクから並行した読み取りが可能であるため、シークに比較して最適化が容易である。

  • CPU サイクル。 メインメモリにデータがある場合(またはすでにそこに存在している場合)、結果を得るためには処理が必要になる。メモリと比較してテーブルが小さい場合は、最も一般的な制限要因になる。しかし、テーブルが小さくても、一般に速度上の問題は発生しない。

  • メモリ帯域幅。 CPU キャッシュの適正量より多く CPU がデータを必要とする場合、メインメモリの帯域幅がボトルネックになる。これは、ほとんどのシステムに一般的な問題ではないが、認識しておく必要がある。

5.1.1. MySQL の設計上の制約とトレードオフ

MyISAM ストレージエンジンの使用時に、MySQL では非常に高速のテーブルロック(複数リーダ/単一ライタ)が使用されます。このテーブル型の最大の問題は、同じテーブルに対して複数の UPDATE と遅い SELECT が混在する場合に発生します。テーブルでこのような問題が発生した場合は、別のテーブル型を使用してもかまいません。 See 章?7. MySQL のテーブル型

MySQL はトランザクションテーブル、非トランザクションテーブルの両方で機能します。 非トランザクションテーブル(何らかのエラー発生した場合にロールバックができない)での動作をスムーズにするため、MySQL には次のルールがあります。

  • すべてのカラムにデフォルト値がある。

  • NOT NULL カラムに対して NULL などの '正しくない' 値を挿入した場合や、数値列の数値が大きすぎる場合、MySQL ではエラーを発生するのではなく、'とりうる可能な値のうちの最適値' に値を設定する。数値の場合は 0 で、可能な最小値か最大値になる。文字列の場合は、空白文字かカラムの最大長さにあわせた文字列になる。

  • 計算式はすべて、エラー状態を表示するのではなく、使用可能な値を返す。たとえば、1/0 の場合は、NULL を返す。

この詳細については、See 項1.8.5. 「MySQL における制約の処理」 を参照してください。

このことは、フィールド内容のチェックに MySQL を使用するのではなく、このチェックをアプリケーションで実行する必要があることを意味します。

5.1.2. 移植性

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 アプリケーションのように正確性よりパフォーマンスが重視される場合は、すべての結果をキャッシュするアプリケーションレイヤを作成すると、さらにパフォーマンスを改善できます。一定の期間後に古い結果を '期限切れ' することで、キャッシュを適度に最新の状態に保持できます。これにより、キャッシュを動的に拡大し、通常の状況に戻るまでタイムアウト期限を高速に設定して、高負荷のスパイクを処理するメソッドが提供されます。

この場合、テーブル作成情報にキャッシュの初期サイズと通常時にテーブルがリフレッシュされる頻度に関する情報が組み込まれます。

5.1.3. MySQL 使用実績

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 カーネルではこの処理が改善されるでしょう。

5.1.4. MySQL ベンチマークスィート

このセクションでは、MySQL ベンチマークスィート(および crash-me)の技術的記述を記載する予定ですが、この記述はまだ作成されていません。現状では、MySQL ソースディストリビューションの sql-bench ディレクトリにあるコードと結果を確認することでベンチマークに関するヒントが得られます。

このベンチマークスィートは、SQL 実装のパフォーマンスを向上または低下させる操作をユーザに示すことを目的としています。

このベンチマークはシングルスレッドであるため、実行される操作の最短時間が測定されていることに注意してください。将来はこのベンチマークスィートにマルチスレッドのテストも多数追加する予定です。

下表は、Windows NT 4.0 コンピュータ上で ODBC を介していくつかのデータベースサーバにアクセスした場合のベンチマーク結果の比較を示しています。

インデックスごとに 2,000,000 レコードの読み取り
mysql367249
mysql_odbc464?
db2_odbc1206?
informix_odbc121126?
ms-sql_odbc1634?
oracle_odbc20800?
solid_odbc877?
sybase_odbc17614?
350,768 レコードの挿入
mysql381206
mysql_odbc619?
db2_odbc3460?
informix_odbc2692?
ms-sql_odbc4012?
oracle_odbc11291?
solid_odbc1801?
sybase_odbc4802?

最初のテストでは、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 のサイトにあります。

5.1.5. 独自のベンチマークの使用

確実にアプリケーションとデータベースのベンチマークを行い、ボトルネックを検出しておく必要があります。これを修正(または、ボトルネックを ``ダミーモジュール'' に置換)することによって、次のボトルネック(など)の確認が容易になります。現在のアプリケーションの総合的なパフォーマンスが許容できるものであっても、実際にパフォーマンスの強化が迫られる場合に備えて、少なくともボトルネックそれぞれに対して計画を立て解決方法を判定しておく必要があります。

移植可能なベンチマークプログラムの例として、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 = 打ちこわし)のとおり、システムに限界まで負荷をかけることができるため、必ず開発システムでのみ使用するようにしてください。

5.2. 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) はクエリに関数上の問題があるかどうかを調べる際に最適のツールです。

5.2.1. EXPLAIN 構文(SELECT に関する情報の取得)

    EXPLAIN tbl_name
か  EXPLAIN SELECT select_options

EXPLAIN tbl_name は、DESCRIBE tbl_name または SHOW COLUMNS FROM tbl_name のシノニムです。

キーワード EXPLAINSELECT ステートメントの前に置いた場合、MySQL によってテーブルの結合状況と順序に関する情報が提供され、テーブルの SELECT の処理方法が説明されます。

EXPLAIN を利用すると、より速くレコードを検索する SELECT を得るために、どの時テーブルにインデックスを追加しなければならないかを確認できます。

最適化方法の選択に影響を及ぼすキーの、カーディナリティなどのテーブル統計を更新するために、ANALYZE TABLE を定期的に実行する必要があります。 See 項4.6.2. 「ANALYZE TABLE 構文」

また、オプティマイザが、テーブルを最適な順序で結合しているかどうかも確認することができます。 オプティマイザが特定の順番で結合を行うように強制するには、SELECT ステートメントに STRAIGHT_JOIN 節を追加します。

非単純結合の場合、EXPLAINSELECT ステートメントで使用される各テーブルに関する情報を返します。 テーブルは、読み取られた順序に従って一覧表示されます。 MySQL は、単一スイープ多結合メソッドを使用してすべての結合を解決します。これは、MySQL が最初のテーブルからレコードを読み取ってから、第 2 のテーブル、第 3 のテーブルといった順序で、一致するレコードの検索を行うことを意味します。 すべてのテーブルの処理が終わると、選択したカラムと、さらに一致レコードがあるテーブルが検索されるまでのテーブル一覧のバックトラックが出力されます。 次のレコードはこのテーブルから読み取られ、処理が次のテーブルから続行されます。

MySQL バージョン 4.1 では、EXPLAIN 出力が変更され、UNION ステートメント、サブクエリ、派生テーブルなどの構造での機能が改善されています。最も重要なことは、idselect_type という 2 つの新しいカラムが追加されたことです。

EXPLAIN の出力は、次のカラムで構成されます。

  • id

    SELECT に割り当てられた ID。クエリ内におけるこの SELECT の順序番号。

  • select_type

    SELECT 節の種類、次のいずれかが示される。

    • SIMPLE

      単純な SELECTUNION やサブクエリを使用しない)。

    • PRIMARY

      最外部の SELECT

    • UNION

      UNION 内の第 2 およびそれ以降の SELECT ステートメント。

    • DEPENDENT UNION

      UNION 内の第 2 およびそれ以降の SELECT ステートメント、外側のサブクエリに依存する。

    • SUBQUERY

      サブクエリ内の第 1 SELECT

    • DEPENDENT SUBQUERY

      第 1 SELECT、外側のサブクエリに依存する。

    • DERIVED

      派生テーブル SELECTFROM 節内のサブクエリ)。

  • 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_tableeq_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 は、インデックスの左端の先頭部分のみが結合で使用される場合、またはインデックスが UNIQUEPRIMARY KEY ではない場合(すなわち、この結合において、インデックス値から1つのレコードをSELECTできない場合)に使用される。この結合型は、使用されるインデックスと一致するレコードが数レコードしかない場合に適している。

      = 演算子と比較されるインデックスが張られたカラムには、ref が使用される。

      下記の例では、ref_tableref が示される。

      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 が使用を決定したキーの長さを示す。keyNULL の場合、この長さは 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.idNOT NULL で定義されているとする。この場合、MySQL で t1 がスキャンされ、t1.idt2 内のレコードのルックアップが行われる。MySQL によって t2 内のマッチするレコードが検索されると、t2t2.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 filesortUsing 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;

この例では以下のように想定しています。

  • 比較対象のカラムは以下のように宣言されます。

    テーブルカラムカラムの型
    ttActualPCCHAR(10)
    ttAssignedPCCHAR(10)
    ttClientIDCHAR(10)
    etEMPLOYIDCHAR(15)
    doCUSTNMBRCHAR(15)
  • テーブルには以下のインデックスがあります。

    テーブルインデックス
    ttActualPC
    ttAssignedPC
    ttClientID
    etEMPLOYID(主キー)
    doCUSTNMBR(主キー)
  • tt.ActualPC 値の分布が均一ではない。

当初、最適化の実行前は、EXPLAIN ステートメントで次の情報が生成されました。

table type possible_keys                key  key_len ref  rows  Extra
et    ALL  PRIMARY                      NULL NULL    NULL 74
do    ALL  PRIMARY                      NULL NULL    NULL 2135
et_1  ALL  PRIMARY                      NULL NULL    NULL 74
tt    ALL  AssignedPC,ClientID,ActualPC NULL NULL    NULL 3872
      range checked for each record (key map: 35)

各テーブルで typeALL であるため、この出力は MySQL がすべてのテーブルのデカルト積を生成すると示しています。各テーブルのレコードの数の積の分量を調べる必要があるため、これは非常に時間がかかります。この例の場合は、レコードの数が 74 * 2135 * 74 * 3872 = 45,268,558,720 になります。テーブルがこれより大きい場合は、さらに時間がかかると考えられます。

ここでの問題の 1 つは、宣言の方法が異なると MySQL でカラムのインデックスを効率的に使用できないことにあります。この例では、VARCHARCHAR が異なる長さで宣言されていなければ同じになります。tt.ActualPCCHAR(10) として、et.EMPLOYIDCHAR(15) として宣言されているため、長さの不一致が発生します。

カラムの長さの不一致を修正するため、ALTER TABLE を使用して ActualPC を 10 文字から 15 文字にします。

mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

これで tt.ActualPCet.EMPLOYID はいずれも VARCHAR(15) になりました。 ここでまた EXPLAIN を実行してみると、以下の結果が得られました。

table type   possible_keys   key     key_len ref         rows    Extra
tt    ALL    AssignedPC,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.EMPLOYIDtt.ClientID = do.CUSTNMBR の比較でのカラム長の不一致を解消できます。

mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
    ->                MODIFY ClientID   VARCHAR(15);

ここでは、EXPLAIN から以下の出力が生成されます。

table type   possible_keys   key      key_len ref           rows Extra
et    ALL    PRIMARY         NULL     NULL    NULL          74
tt    ref    AssignedPC,     ActualPC 15      et.EMPLOYID   52   Using 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 節でテーブルの順序を変えて一覧表示してみるとパフォーマンスを改善できます。

5.2.2. クエリパフォーマンスの推定

ほとんどの場合、ディスクシークをカウントしてパフォーマンスを推定できます。 小さいテーブルの場合は一般に 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. 「サーバパラメータのチューニング」

5.2.3. SELECT クエリの速度

一般に、低速の 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 でソートする場合)を使用する。速度を上げるには、すべてのレコードの読み取りにユニークインデックスを使用し、そのインデックスに従った順序で読み取りを行うように推奨される。ただし、このソートでは書き込みの最適化はできず、テーブルが大きい場合は時間がかかる。

5.2.4. MySQL による WHERE 節の最適化

WHERE の最適化は、ほとんどの場合 SELECT とともに使用されるため、SELECT 部分に適用されますが、DELETEUPDATE のステートメントの 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(*) は、MyISAMHEAP テーブルのテーブル情報から直接取り出される。 これは、テーブル 1 つのみで使用する場合はすべての NOT NULL 式でも実行される。

  • 無効定数式の早期検出。MySQL は実行不可能な SELECT ステートメントがある場合、それを迅速に検出し、結果としてレコードを返さない。

  • GROUP BY またはグループ関数(COUNT()MIN()。..)を使用しない場合、HAVINGWHERE とマージされる。

  • サブ結合のそれぞれに、単純な 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,... ;

5.2.5. MySQL による IS NULL の最適化

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

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 の組み合わせを最適化する機能が追加されています。この最適化が使用される場合は、EXPLAINref_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 のキー部分は使用できません。

5.2.6. MySQL による DISTINCT の最適化

DISTINCTORDER BY と組み合わされて用いられると、多くの場合はテンポラリテーブルが必要になります。

DISTINCTGROUP BY をともなう可能性が高いので、SELECT されないフィールドを ORDER BY または HAVING した時に、どのように MySQL が機能するかを認識しておく必要があります。 See 項6.3.7.3. 「非表示のフィールドに対する GROUP BY

LIMIT row_countDISTINCT とともに使用した場合、MySQL は一意のレコードを row_count 行検索するとただちに検索を停止します。

使用するテーブル内のカラムを使用しない場合、MySQL は最初にマッチするレコードを検索するとただちに未使用テーブルのスキャンを停止します。

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

ここでは、t1t2 の前に使用され(EXPLAIN によるチェック)、t2 で最初のレコードが検索されると t2からの読み取り(t1 の特定のレコード)を停止します。

5.2.7. MySQL による LEFT JOINRIGHT JOIN の最適化

MySQL の A LEFT JOIN B join_condition は以下のように実装されます。

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

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

  • LEFT JOIN 条件は、テーブル B からのレコードの取り出し方法の判定に使用される(言い換えると、WHERE 節の条件はいずれも使用されない)。

  • あるテーブルが全てのテーブルの後に読み取られる場合を除き、通常の最適化全てが行われる。依存関係が循環している場合は、MySQL からエラーが出力される。

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

  • AWHERE 節の条件にマッチするレコードがあり、BON 条件にマッチするレコードがない場合、B のカラムの値が NULL に設定されたレコードが生成される。

  • テーブルのいずれかに存在しないレコードを検索する際に LEFT JOIN を使用していて、かつ、WHERE 節内で、NOT NULL と定義した column_namecolumn_name IS NULL で評価した場合、 MySQL は LEFT JOIN 条件に一致するレコードを 1 つ検索すると、その後はレコードの検索(特定のキー組み合わせの)を停止する。

RIGHT JOIN の実装は LEFT JOIN と類似しています。

テーブル読み取り順序は LEFT JOINSTRAIGHT 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 JOINd の前に読み取るように強制するため、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 を使用します。

5.2.8. MySQL による 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

  • ASCDESC が混在している場合。

    SELECT * FROM t1 ORDER BY key_part1 DESC,key_part2 ASC

  • レコードの取り出しに使用されるキーが ORDER BY の実行に使用されるキーと異なる場合。

    SELECT * FROM t1 WHERE key2=constant ORDER BY key1

  • ORDER BY で多くのテーブルとカラムを結合していて、それら全てがレコードの取り出しに使用される最初の非 const テーブルではない場合(これは EXPLAIN で出力される最初のテーブルで、かつ、const メソッドを使用していないテーブル)。

  • ORDER BYGROUP 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;

5.2.9. MySQL による LIMIT の最適化

HAVING を使用するのではなく LIMIT row_count を使用している場合、MySQL によるクエリの処理方法が異なる場合があります。

  • LIMIT を使用して数レコードしか選択していないと、フルテーブルスキャンが行われそうな場合に、MySQL はインデックスを使うことがある。

  • ORDER BY とともに LIMIT row_count を使用している場合、MySQL ではすべてのテーブルがソートされるのではなく、最初の row_count レコードの検索が行われた時点でただちにソートを終了する。

  • LIMIT row_countDISTINCT とあわせて使用した場合、MySQL は一意の row_count 行のレコードを検索するとただちに停止する。

  • GROUP BY がキーを順番に読む(またはキーのソートを実行して読む)ことで解決でき、キーの値が変わるまで サマリが計算される場合もある。この場合、LIMIT row_count では不要な GROUP BY 値の計算がすべて行われなくなる。

  • MySQL が最初の # レコードをクライアントに送信すると、クエリが中止される(SQL_CALC_FOUND_ROWS を使用していない場合)。

  • LIMIT 0 は常に迅速に空のセットを返す。これは、クエリのチェックおよび結果として返るカラムのカラム型の取得に役立つ。

  • サーバでテンポラリテーブルを使用してクエリが解決される場合、LIMIT row_count が必要な領域の計算に使用される。

5.2.10. INSERT クエリの速度

レコード挿入の時間構成の概要は次のとおりです。

  • 接続:(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 の実行速度をさらに上げることができる。以下の手順を使用する。

    1. CREATE TABLE を使用して、テーブルを作成する。mysql や Perl-DBI などを使用する。

    2. FLUSH TABLES ステートメントまたはシェルコマンド mysqladmin flush-tables を実行する。

    3. myisamchk --keys-used=0 -rq /path/to/db/tbl_name を使用する。これでテーブルからすべてのインデックスの使用が削除される。

    4. LOAD DATA INFILE を使用し、テーブルにデータを挿入する。これはインデックスをまったく更新しないため、非常に高速になる。

    5. テーブルを読み取り専用にする場合は、myisampack を実行してテーブルを小さくする。 See 項7.1.2.3. 「圧縮テーブルの特性」

    6. myisamchk -r -q /path/to/db/tbl_name を使用してインデックスを作成しなおす。これは、ディスクに書き込む前にメモリにインデックスツリーを作成して、ディスクシークを回避するため非常に高速になる。生成されたインデックスツリーは完全にバランスが取られている。

    7. 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 では、INSERTUPDATE、および DELETE の演算が非常に速いため、約 5 つより多い挿入や 1 レコード更新する前にロックを追加すると総合的なパフォーマンスを改善できる。1 行で非常に多数の挿入を実行する場合は、ときどき(約 1,000 レコードごと)LOCK TABLESUNLOCK TABLES を続けて実行して、他のスレッドからのテーブルへのアクセスを可能にすることができる。これでもパフォーマンの増加が得られる。

    言うまでもなく、データのロードには LOAD DATA INFILE のほうが大幅に高速である。

LOAD DATA INFILEINSERT の両方の速度をさらに改善するには、キーバッファを拡張します。 See 項5.5.2. 「サーバパラメータのチューニング」

5.2.11. UPDATE クエリの速度

更新クエリは、SELECT クエリと同様に最適化されますが、書き込みオーバヘッドが加算されます。書き込みの速度は更新対象のデータのサイズおよび更新対象のインデックス数によって異なります。変更がないインデックスは更新されません。

更新の速度を上げるもう 1 つの方法は、更新を遅延して 1 行で多数の更新を後から行うことです。1 行での多数の更新は、テーブルをロックすると同時に行う場合と比較して大幅に高速に実行できます。

可変長レコードの場合は、合計の長さが今よりも長いものにレコードを更新すると、レコードが分割される場合があることに注意します。このため、頻繁にこれを実行する場合は、ときどき OPTIMIZE TABLE することが重要になります。 See 項4.6.1. 「OPTIMIZE TABLE 構文」

5.2.12. DELETE クエリの速度

テーブル内のすべてのレコードを削除する場合は、TRUNCATE TABLE table_name を使用します。 See 項6.4.6. 「TRUNCATE 構文」

レコード削除に要する時間は、完全にインデックス数に比例します。レコード削除の速度を上げるには、インデックスキャッシュのサイズを拡大します。 See 項5.5.2. 「サーバパラメータのチューニング」

5.2.13. その他の最適化のヒント

システム高速化のためのヒント(順不同)

  • 接続オーバヘッドを回避するには、データベースに対して永続的な接続を使用する。永続的な接続を使用せずにデータベースに対して多数の新規接続を実行する場合は、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'

  • 大量に変更があるテーブルはすべて VARCHARBLOB のカラムを使用しないようにする。VARCHAR または BLOB カラムを 1 つ使用するとレコードがただちに可変長になってしまう。 See 章?7. MySQL のテーブル型

  • 一般に、1 つのテーブルを複数のテーブルに分割することは、レコードが '大きく' なるだけで高速化の役には立たない。レコードにアクセスする際の、最も大きなパフォーマンス要因は、レコードの最初のバイトを見つけるためのディスクシークである。データの検索後、ほとんどの新規ディスクでは、大多数のアプリケーションに十分な速度でレコード全体を読み取ることができる。テーブルの分割が実際に有効な状況は、固定長テーブルへの変更が可能な可変長テーブル(上記参照)の場合か、テーブルのスキャンを非常に頻繁に必要としながらもほとんどのカラムを結果に必要としない場合のみである。 See 章?7. MySQL のテーブル型

  • 多数のレコードの情報から計算する頻度を非常に高くする必要がある場合(カウントの場合など)、新たなテーブルを導入し、リアルタイムでカウンタを更新するほうがはるかに適している。UPDATE table SET count=count+1 WHERE index_column=constant のような更新は非常に高速にできる。

    実際これは、MyISAMISAM のようにテーブルロック(複数リーダ/単一ライタ)のみの 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 を使用しても何も消失はしない。

5.3. ロック関連の問題

5.3.1. MySQL のテーブルロック方法

ロックメソッドそれぞれについての説明は付録にあります。 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 オプションを INSERTUPDATE または DELETE に、あるいは HIGH_PRIORITY オプションを SELECT に使用します。また、--low-priority-updates オプションで mysqld を開始しても同じ効果が得られます。

SQL_BUFFER_RESULT の使用もテーブルロックを短縮するのに役立ちます。 See 項6.4.1. 「SELECT 構文」

さらに、1 つのキューを使用するように mysys/thr_lock.c のロックコードを変更することもできます。この場合は、書き込みロックと読み取りロックの優先度が同じになり、アプリケーションによっては高速化に役立ちます。

5.3.2. テーブルロック関連の問題

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 ステートメントを使用する。UPDATESELECT より優先度が高いため、この SELECTUPDATE が完了するまで待機が必要になる。また、最初の SELECT の完了を待つ必要もある。

  • full disk などによってスレッドが待機中の場合、そのテーブルへのアクセスが必要なすべてのスレッドが追加のディスク容量が使用可能になるまで待機状態に置かれる。

この問題に対応する解決策は以下のとおりです。

  • SELECT ステートメントの実行の高速化を試行する。これにはサマリテーブルの作成が必要な場合もある。

  • --low-priority-updates のオプションで mysqld を開始する。これは、テーブルを更新(変更)するすべてのステートメントの優先度を SELECT ステートメントの優先度より低くする。この場合、前のシナリオの最後の SELECT ステートメントが INSERT ステートメントより前に実行されることになる。

  • LOW_PRIORITY 属性を使用して、特定の INSERTUPDATE、または 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 ステートメントの混在が多い場合、INSERTDELAYED 属性によって問題が解決される確率が高い。 See 項6.4.3. 「INSERT 構文」

  • SELECTDELETE に問題がある場合、DELETELIMIT オプションを使用すると解決できる場合がある。 See 項6.4.5. 「DELETE 構文」

5.4. データベース構造の最適化

5.4.1. 設計上の選択

MySQL はローデータとインデックスデータを別のファイルに格納します。その他のデータベースの多く(ほとんど)は、ローデータとインデックスデータが同じファイルに混在しています。現在の非常に多くのシステムで MySQL の選択のほうが優れていると確信しています。

ローデータの格納方法には、各カラムの情報を独立した領域に格納する方法もあります(例: SDBM、Focus など)。これは、複数のカラムにアクセスするすべてのクエリでパフォーマンスに影響を及ぼします。パフォーマンスは複数のコラムへのアクセスを開始するとただちに低速化するため、このようなモデルは汎用データベースには適さないと確信しています。

一般的にインデックスとデータが一緒に格納されている場合も多くあります(Oracle、Sybase などの場合)。この場合は、レコード情報をインデックスのリーフページで検索します。このレイアウトで優れている点は、多くの場合インデックスのキャッシュ方法次第でディスクの読み取りを節約できることにあります。このレイアウトの欠点は以下のとおりです。

  • データの取得時にインデックス全体を読み取る必要があるため、テーブルスキャンの速度が大幅に下がる。

  • クエリでデータを取り出す際にインデックステーブルのみの使用ができない。

  • ノードからインデックスを複製する必要があるため(レコードはノードに格納できないことによる)、大量の領域が消費される。

  • 削除があるとテーブルの速度が次第に低下する(通常、削除ではノードのインデックスが更新されないため)。

  • インデックスデータのみのキャッシュが困難である。

5.4.2. データの小型化

最も基本的な最適化の 1 つにデータ(およびインデックス)が占めるディスク領域を可能な限り少なくすることがあります。これで、ディスクの読み取りが高速化し、使用メモリも一般に減少するため、大幅な改善が図れます。カラムが小さければインデックス作成で消費されるリソースも少なくなります。

MySQL では多様なテーブル型とレコード形式がサポートされます。 適切なテーブル形式を選択することで、パフォーマンスを大幅に改善できます。 See 章?7. MySQL のテーブル型

ここで紹介する技法を使用すると、テーブルのパフォーマンス改善とストレージ領域の最小化を図ることができます。

  • できる限り効率性の高い(最小)の型を使用する。MySQL にはディスク領域とメモリを節約できる専用の型がある。

  • 可能な場合は、小さなテーブルの取得には小さな整数型を使用する。たとえば、INT より、MEDIUMINT のほうが適している場合もしばしばある。

  • できる限り、カラムに NOT NULL を宣言する。これですべてが高速化され、1 カラム当たり 1 ビットを節約できる。アプリケーションで実際に NULL が必要な場合は、必ず使用する必要があるため、注意が必要である。デフォルトですべてのカラムにこれを設定することは避ける。

  • 可変長カラム(VARCHARTEXTBLOB など)がまったくない場合は固定長レコード形式を使用する。これで速度が上がるが、領域の消費も増える。 See 項7.1.2. 「MyISAM テーブル形式」

  • テーブルのプライマリインデックスを可能な限り短くする。これで、レコードの識別が容易になり効率化が図れる。

  • テーブルごとに使用するストレージとインデックスの方法を設定する必要がある。 See 章?7. MySQL のテーブル型

  • インデックスの作成は必要なものだけに限定する。インデックスは取り出しに優れているが、高速保存が必要な場合は適さない。カラムの組み合わせを使用してテーブルを検索し、テーブルにアクセスする場合がほとんどであれば、インデックスを作成する。インデックスの最初の部分は、最も使用頻度の高いカラムにする必要がある。常に多数のカラムを使用する場合は、より重複しているカラムを先に使用するとインデックスの圧縮を改善できる。

  • 文字列の最初の数文字に、一意のプリフィックスがあるカラムが多い場合は、このプリフィックスのみをインデックス化したほうがよい。MySQL は CHAR 型カラムの部分インデックスをサポートする。短いインデックスの速度が速い理由は、占有ディスク領域が小さいことだけではなく、インデックスキャッシュでのヒットが多くなり、所要ディスクシークが少なくなることにもよる。 See 項5.5.2. 「サーバパラメータのチューニング」

  • 状況によっては、スキャンの頻度が高いテーブルを 2 つに分割したほうが有利な場合もある。これは特に、動的テーブルで、テーブルスキャンの際に対応するレコードの検索に小さな静的テーブルの使用が可能である場合にあてはまる。

5.4.3. MySQL でのインデックスの使用

インデックスは、カラムが特定の値をもつレコードの迅速な検索に使用されます。インデックスがないと、MySQL がレコードを見つけるために、最初のレコードから開始し、テーブル全体を読み取るとが必要になります。テーブルが大きくなると、これにコストがかかります。クエリ対象のカラムにインデックスがあると、MySQL は全てのデータを探すことなく、データファイルの途中にあるシーク対象ポジションを迅速に取得することができます。テーブルに 1000 レコードある場合、シーケンシャルに読み取る場合と比較して少なくとも 100 倍は高速化できます。1000 レコードのほとんどすべてにアクセスする必要がある場合は、ディスクシークが最小になるため、シーケンシャルに読むほうが速くなることに注意してください。

MySQL インデックスのすべて(PRIMARY KEYUNIQUE、および 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;

col1col2 に複合インデックスが存在する場合、対応するレコードを直接読み取れます。col1col2 に独立した単一カラムインデックスが存在する場合、検索されるレコードの少ないインデックスを判定し、そのインデックスをレコードの読み取りに使用して、最も制限性の高いインデックスの検索が試行されます。

テーブルに複合インデックスがある場合、オプティマイザではインデックスの左端の先頭部分のいずれかをレコードの検索に使用できます。たとえば、(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 はインデックスを使用します。

5.4.4. カラムインデックス

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 テーブル型でのみ、CHARVARCHAR、および TEXT カラムに限ってサポートされます。 フルテキストインデックスの作成は常にカラム全体を対象として、先頭部分(プリフィックス)のインデックス化は行われません。詳細については、項6.8. 「MySQL 全文検索」 を参照してください。

5.4.5. 複合インデックス

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_namefirst_name に対するインデックスです。このインデックスは、last_name の範囲、または last_namefirst_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 でのインデックスの使用」を参照してください。

5.4.6. 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 でのテーブルのオープンとクローズの方法」

5.4.7. MySQL でのテーブルのオープンとクローズの方法

table_cachemax_connections、および max_tmp_tables サーバ変数は、サーバが開いた状態で保持できるファイルの最大数に影響します。 これらの値の 1 つ以上を増加すると、OS によって制限されている 1 プロセスが持つことができるファイル記述子の最大数まで実行が可能になります。システムごとに方法は多様ですが、多数のオペレーティングシステムでオープンファイルの制限値を上げることができます。 制限値の拡大が可能かどうかの判定、およびその実行方法については、使用するオペレーティングシステムの文書を参照してください。

table_cachemax_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 構文」。 この場合はテーブルがテーブルキャッシュに戻されます(キャッシュが満杯でない場合)。

テーブルキャッシュが小さすぎるかどうかは、mysqldOpened_tables 変数のチェックで確認できます。たとえ多くの FLUSH TABLES を実行していない場合でも、この値が非常に大きい場合は、テーブルキャッシュサイズを拡張する必要があります。 See 項4.6.8.3. 「SHOW STATUS

5.4.8. 1 つのデータベースに大量のテーブルを作成した場合の欠点

ディレクトリにファイルが多数ある場合、オープン、クローズ、および作成の動作が低速になります。多数のテーブルに対して SELECT ステートメントを実行した場合、必要なテーブルを開くごとに、他のテーブルを閉じることが必要になるため、テーブルキャッシュが満杯の場合にオーバヘッドが少し発生します。このオーバヘッドは、テーブルキャッシュを拡大することで軽減できます。

5.5. MySQL サーバの最適化

5.5.1. システム、コンパイル時間およびスタートアップパラメータのチューニング

システムレベルの要素は、その一部を初期段階に決定する必要があるため、この話から始めます。これに該当しない場合は、システムを大きく変えることが重要でないのであれば、このセクションは簡単に目を通せば十分です。ただし、このレベルで変更を行うことでどの程度改善できるのかを自覚しておくことは必ず役に立ちます。

使用するオペレーティングシステムは非常に重要です。複数 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 は使用できる。

5.5.2. サーバパラメータのチューニング

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_sizetable_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.cnfmy-large.cnfmy-medium.cnf、および my-small.cnf が格納され、システム最適化のベースとして使用できます。

同時接続が非常に多い場合、接続ごとに mysqld で使用されるメモリを非常に小さくしていないとスワップの問題が発生することがあります。言うまでもなく、すべての接続に使用可能なメモリが十分ある場合は mysqld のパフォーマンスが向上します。

mysqld または mysqld_safe のコマンドラインでオプションを指定した場合、そのサーバの呼び出しでしか有効性が保持されないことに注意してください。 サーバ実行のたびにオプションを使用する場合は、オプション設定ファイルに配置します。

パラメータ変更の有効性を調べるには、次のように実行します。

shell> mysqld --key_buffer_size=32m --help

必ず --help を最後に指定します。最後にしないと、コマンドラインのそれ以降に記載されたオプションの効果が出力に反映されません。

5.5.3. MySQL の速度に対するコンパイルとリンクの影響

以下のテストのほとんどは、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 バイナリは、速度および移植性を高めるため静的にリンクされています。

5.5.4. MySQL でのメモリの使用

以下の一覧は、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 をテストし、メモリリークがないと判明しています。

5.5.5. MySQL の DNS の使用

新たなクライアントが mysqld に接続すると、mysqld によって要求を処理する新規のスレッドが作成されます。このスレッドでは、まずホスト名がホスト名キャッシュにあるかどうかがチェックされます。ない場合は、ホスト名の解決が試行されます。

  • オペレーティングシステムがスレッドセーフの gethostbyaddr_r()gethostbyname_r() の呼び出しをサポートしている場合、スレッドではこれを使用してホスト名の解決が実行される。

  • オペレーティングシステムがスレッドセーフの呼び出しをサポートしていない場合、スレッドでは相互排除ロックを行い、代わりに gethostbyaddr()gethostbyname() が呼び出される。この場合、他のスレッドでは最初のスレッドが相互排除ロックを解除するまでホスト名キャッシュ内のホスト名を解決できなくなることに注意する。

--skip-name-resolvemysqld オプションを指定して起動すると、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 を開始します。

5.5.6. 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 構文とその他のデータベースとの互換性を保持を目的にサポートされています。

このマニュアルのシステム変数のセクションに設定可能な多様なシステム変数に関する説明があります。 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 TRANSACTIONCOMMITROLLBACK の各構文」0 に設定した場合は、そのトランザクションを COMMIT で受け入れるか、ROLLBACK で取り消す必要がある。 See 項6.7.1. 「START TRANSACTIONCOMMITROLLBACK の各構文」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 ファイルを編集して容易に新規のマッピングを追加できる。デフォルトのマッピングは、DEFAULTcharacter_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 = #

    次の INSERTALTER TABLE コマンドで使用される AUTO_INCREMENT 値を設定する。これは主としてバイナリログとともに使用される。

  • LAST_INSERT_ID = #

    LAST_INSERT_ID() から返される値を設定する。これは、テーブルを更新するコマンドで LAST_INSERT_ID() 関数を使用した場合にバイナリログに格納される。

  • LOW_PRIORITY_UPDATES = 0 | 1

    1 に設定した場合、全ての INSERTUPDATEDELETE、および LOCK TABLE WRITE ステートメントが、同じテーブルに対して実行されている SELECTLOCK 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 の形式で指定する必要がある。userhostname は、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 または ONSELECT 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_SIZEDEFAULT 以外の値に設定すると、SQL_BIG_SELECTS0 に設定される。

  • SQL_BUFFER_RESULT = 0 | 1

    SQL_BUFFER_RESULTSELECT ステートメントからの結果を強制的にテンポラリテーブルに入れる。これは、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 ステートメントから返されるレコードの最大数。SELECTLIMIT 節がある場合、SQL_SELECT_LIMIT の値よりも LIMIT のほうが優先される。新たに接続した場合のデフォルト値は ``無制限'' である。この制限を変更した場合、DEFAULTSQL_SELECT_LIMIT 値を使用してデフォルト値をリストアできる。

  • TIMESTAMP = timestamp_value | DEFAULT

    クライアントに時間を設定する。これは、レコードのリストアにバイナリログを使用する場合、オリジナル(ログが記録された時点)のタイムスタンプにするために使用される。timestamp_value は MySQL タイムスタンプではなく Unix 基準時点のタイムスタンプにする必要がある。

  • TIME_FORMAT = format_str

    サーバで TIME 値を文字列に変換する方法を設定する。この変数は、グローバルオプション、ローカルオプション、コマンドラインオプションのいずれでも使用できる。format_str の指定には GET_FORMAT() 関数の使用が便利である。 See 項6.3.4. 「日付と時刻関数」 を参照。

5.6. ディスク関連の問題

  • 前述のように、ディスクシークはパフォーマンスに対する大きなボトルネックである。この問題は、データが拡大し、効率的なキャッシュが実行不能になるほど大きくなるにつれて明白になる。大規模データベースで、事実上ランダムにデータにアクセスする場合、読み取りでは最低 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 ではこのフラグがデフォルトでオンになっている)。

5.6.1. シンボリックリンクの使用

テーブルとデータベースをデータベースディレクトリから他の位置に移動し、新しい位置へのシンボリックリンクに置換することができます。 これは、たとえば、データベースを空き領域の多いファイルシステムに移動し、テーブルを別のディスクに分散することでシステムの速度を上げる場合などに実行できます。

この推奨される実行方法は、データベースだけ別のディスクへのシンボリックリンクを行い、最後の手段としてのみテーブルのシンボリックリンクを行うことです。

5.6.1.1. Unix 上のデータベースに対するシンボリックリンクの使用

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 上のデータベースに対するシンボリックリンクの使用」

5.6.1.2. Unix 上のテーブルに対するシンボリックリンクの使用

MySQL 4.0 より前は、テーブルのシンボリックリンクの実行を非常に慎重に行う必要がありました。シンボリックリンクが行われているテーブルで、ALTER TABLEREPAIR TABLE、あるいは OPTIMIZE TABLE を実行する際に、シンボリックリンクが削除され、オリジナルファイルに置換されるという問題がありました。これらのステートメントは、データベースディレクトリにテンポラリファイルを作成し、ステートメントの操作が完了するとオリジナルファイルとテンポラリファイルの置換が行われる仕様であるため、この問題が発生しました。

realpath() の呼び出しの機能が完全でないシステムではテーブルのシンボリックリンクを行わないでください(少なくとも、Linux と Solaris では realpath() がサポートされています)。

MySQL 4.0 では MyISAM テーブルでのみシンボリックリンクが完全サポートされています。これ以外のテーブル型で上記のコマンドを使用すると、予想外の問題の発生の恐れがあります。

MySQL 4.0 でのシンボリックリンクの処理は、次のように機能します(ほとんどが MyISAM テーブルのみに適しています)。

  • データディレクトリには常にテーブル定義ファイル、データファイルおよびインデックスファイルがある。データファイルとインデックスファイルは、別の場所に移動し、データディレクトリ内でシンボリックリンクによって置換できる。定義ファイルはこれができない。

  • データファイルとインデックスファイルは、それぞれ独立して別のディレクトリにシンボリックリンクを作成できる。

  • シンボリックリンクは、オペレーティングシステムレベル(mysqld が実行されていない場合)、または SQL で CREATE TABLEDATA 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 DIRECTORYINDEX DIRECTORY テーブルオプションが無視される。

  • MySQL 4.0.15 より前は、テーブルにシンボリックリンクがある場合、SHOW CREATE TABLE でレポートが行われない。これは、SHOW CREATE TABLE を使用して CREATE TABLE ステートメントを生成する mysqldump についても同様である。

  • BACKUP TABLERESTORE 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 コマンドもエラーになる。

5.6.1.3. Windows 上のデータベースに対するシンボリックリンクの使用

MySQL バージョン 3.23.16 から、MySQL ディストリビューションの mysqld-max および mysql-max-nt サーバが -DUSE_SYMDIR オプションでコンパイルされるようになりました。これにより、シンボリックリンクを設定して別のディスクにデータベースディレクトリを配置できます。 リンクの設定手順は異なりますが、機能は Unix のシンボリックリンクと同様です。

Windows では、対象ディレクトリへのパスが記載されたファイルを作成して MySQL データベースに対するシンボリックリンクを作成します。ファイル名 db_name.sym を使用してデータディレクトリにファイルを保存します。この db_name はデータベース名です。

たとえば、MySQL データディレクトリが C:\mysql\data で、データベース fooD:\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.

アダルトレンタルサーバー