余分なソートを行わずに ORDER
BY
の要求に応じるために、MySQL
はインデックスを使用する場合があります。
すべての使用されていないインデックス部分とほかの部分が
WHERE
節内で定数であるカラムである場合、ORDER
BY
がインデックスに完全にマッチしない場合でもこのインデックスを使用できます。次のクエリーではインデックスを使用して
ORDER BY
部分を解決します。
SELECT * FROM t1 ORDER BYkey_part1
,key_part2
,... ; SELECT * FROM t1 WHEREkey_part1
=constant
ORDER BYkey_part2
; SELECT * FROM t1 ORDER BYkey_part1
DESC,key_part2
DESC; SELECT * FROM t1 WHEREkey_part1
=1 ORDER BYkey_part1
DESC,key_part2
DESC;
MySQL で ORDER BY
の解決にインデックスを使用できない場合
(この場合も MySQL は
WHERE
節の条件に一致するレコードの検索にインデックスを使用します)。これには、次のような場合が含まれます。
複数のキーに対して ORDER
BY
を実行する場合。
SELECT * FROM t1 ORDER BYkey1
,key2
;
連続しないキー部分に対して
ORDER BY
を実行する場合。
SELECT * FROM t1 WHEREkey2
=constant
ORDER BYkey_part2
;
ASC
と
DESC
が混在している場合。
SELECT * FROM t1 ORDER BYkey_part1
DESC,key_part2
ASC;
行の取り出しに使用されるキーが
ORDER BY
の実行に使用されるキーと異なる場合。
SELECT * FROM t1 WHEREkey2
=constant
ORDER BYkey1
;
キーカラム名以外の項を含む式で
ORDER BY
を使用する場合。
SELECT * FROM t1 ORDER BY ABS(key
); SELECT * FROM t1 ORDER BY -key
;
多数のテーブルを結合する場合で、行の取得に使用される最初の非定数テーブルのものでないカラムが
ORDER BY
に含まれているとき。(これは
EXPLAIN
で出力される最初の、const
結合型でないテーブル。)
ORDER BY
と
GROUP BY
式が異なる場合。
ORDER BY
節に指定されたカラムの接頭辞だけにインデックスが設定されている場合。この場合、インデックスを使用してソート順序を完全に解決することはできません。たとえば、CHAR(20)
のカラムがあり、その先頭の 10
バイトだけにインデックスが設定されている場合、10
バイト目よりあとの値はインデックスで区別できないため、filesort
が必要になります。
使用されたテーブルインデックスが、並び順にレコードを格納していないインデックスタイプの場合。(MEMORY
テーブルの HASH
インデックスなど。)
インデックスをソートに使用できるかどうかは、カラムエイリアスの使用によって影響を受けることがあります。カラム
t1.a
にインデックスが設定されていると仮定します。次のステートメントで、選択リスト内のカラム名は
a
です。これは
t1.a
を指しているため、ORDER
BY
内での a
の参照にはインデックスを使用できます。
SELECT a FROM t1 ORDER BY a;
次のステートメントでも、選択リスト内のカラム名は
a
ですが、これはエイリアスです。これは
ABS(a)
を指しているため、ORDER
BY
内での a
の参照にはインデックスを使用できません。
SELECT ABS(a) AS a FROM t1 ORDER BY a;
次のステートメントでは、選択リスト内のカラム名とは異なる名前を
ORDER BY
で参照しています。ただし、t1
には a
というカラムがあるため、ORDER
BY
ではそれが使用され、インデックスは使用できます。(当然ながら、結果として
ABS(a)
の順序とはまったく異なるソート順序になります。)
SELECT ABS(a) AS b FROM t1 ORDER BY a;
MySQL はデフォルトで、GROUP BY
の全クエリーを
col1
、col2
,
...ORDER BY
で指定したかのように、クエリーをソートします。同じカラムリストを含む
col1
、col2
,
...ORDER BY
節を明示的に取り入れた場合、ソートが実行されるとはいえ、MySQL
は速度ペナルティなしに最適化します。クエリーに
GROUP BY
が含まれていながら、結果のソートに費やすオーバーヘッドを避けたい場合、ORDER
BY NULL
を指定することでソートを実行しないようにすることができます。例
:
INSERT INTO foo SELECT a, COUNT(*) FROM bar GROUP BY a ORDER BY NULL;
EXPLAIN SELECT ... ORDER BY
を使用すると、MySQL
でインデックスを使用してクエリーを解決できるかどうかを確認できます。Extra
カラムに Using filesort
が出力された場合は、MySQL で ORDER BY
の解決にインデックスを使用できません。項4.2.1. 「EXPLAIN
を使用して、クエリーを最適化する」
を参照してください。
MySQL には、結果をソートして取り出すための
filesort
アルゴリズムが 2 つあります。元のメソッドは
ORDER BY
のカラムだけを使用します。変更されたメソッドは、ORDER
BY
のカラムだけでなく、クエリーで使用されているすべてのカラムを使用します。
どちらの filesort
アルゴリズムを使用するかはオプティマイザが選択します。通常は変更されたアルゴリズムが使用されますが、BLOB
カラムや TEXT
カラムが対象となる場合には元のアルゴリズムが使用されます。
元の filesort
アルゴリズムは次のように動作します。
キーに従って、あるいはテーブルスキャンによって、すべての行を読み取ります。WHERE
節に一致しない行はスキップされます。
各行について、1 組の値
(ソートキーと行ポインタ)
をバッファーに格納します。sort_buffer_size
システム変数の値がバッファーのサイズになります。
バッファーが満杯になると、バッファーに qsort (クイックソート) を実行し、その結果を一時ファイルに格納します。ソートされたブロックのポインタを保存します。(すべての組がソートバッファーに収まる場合、一時ファイルは作成されません。)
すべての行が読み取られるまで、上記の手順を繰り返します。
最大 MERGEBUFF
(7)
領域までのマルチマージを実行して 1
つのブロックにし、別の一時ファイルに格納します。最初のファイルのすべてのブロックが
2
番目のファイルに格納されるまで、この処理を繰り返します。
残りのブロック数が
MERGEBUFF2
(15)
未満になるまで、次の処理を繰り返します。
最後のマルチマージでは、行のポインタ (ソートキーの最後の部分) だけが結果ファイルに書き込まれます。
結果ファイル内の行ポインタを使用して、ソートされた順序で行を読み取ります。これを最適化するには、行ポインタを大きなブロックで読み込んでソートし、ソートした行ポインタを使用して、ソートされた順序で行を行バッファーに読み込みます。sort_buffer_size
システム変数の値がバッファーのサイズになります。この手順のコードは
sql/records.cc
ソースファイルにあります。
このアプローチの問題の 1 つは、行を
WHERE
節の評価時に 1
回、値の組をソートしたあとにもう 1 回、合計
2 回読み取ることです。また、1
回目は行に連続してアクセスできても
(テーブルスキャンを実行する場合など)、2
回目のアクセスはランダムになります。(ソートキーは順に並べ替えられますが、行の位置は変更されないため。)
変更された filesort
アルゴリズムは、ソートキーの値と行の位置だけでなく、クエリーに必要なカラムも記録するように最適化されています。これにより行の
2 度読みを避けられます。変更された
filesort
アルゴリズムは次のように動作します。
WHERE
節とマッチする行を読む。
各行ごとに、クエリーに必要なカラムとソートキー値と行ポジションを含むタプル値を記憶する。
ソートキー値でタプルを並べ替える
並べ替えられた順序で行を取得しますが、テーブルに 2 度アクセスするよりも、並べ替えられたタプルから必要なカラムを読み取ります。
変更された filesort
アルゴリズムを使用すると、元のアルゴリズムで使用される組よりもタプルが長くなり、ソートバッファーに収まるタプル数は少なくなります
(ソートバッファーのサイズは
sort_buffer_size
で指定される)。その結果、追加の I/O
が発生して、変更されたアルゴリズムの速度は逆に低下する可能性があります。速度低下を避けるため、この最適化は、ソートタプル内の追加カラムの合計サイズが
max_length_for_sort_data
システム変数の値を超えない場合にかぎり使用されます。(この変数が高く設定されると、活発なディスクアク活動に対して低い
CPU 活動といった状態が発生します)。
filesort
が使用されていない低速のクエリーでは、filesort
が発生するように
max_length_for_sort_data
を小さくしてみることもできます。
ORDER BY
速度を上げたい場合、MySQL
が余分な並べ替えフレーズよりもインデックスを使用できるか確認してください。これが不可能な場合、次の手段を試してみてください。
sort_buffer_size
変数のサイズを大きくしてください。
read_rnd_buffer_size
変数のサイズを大きくしてください。
値を保持するために必要な最小限のサイズでカラムを宣言することにより、行あたりに使用する
RAM を減らしてください。たとえば、値が 16
文字を超えることがない場合は、CHAR(16)
の方が CHAR(200)
よりも適切です。
tmpdir
を変更して、空き領域が多い専用のファイルシステムを指定してください。また、このオプションはラウンドロビン方式で複数のパスを受け入れるため、この機能を使用して複数のディレクトリに負荷を分散できます。パスは、Unix
ではコロン
(「:
」)
で区切り、Windows、NetWare、および OS/2
ではセミコロン
(「;
」)
で区切るようにしてください。パスは、同じディスク上の異なるパーティションではなく、異なる
物理
ディスク上にあるファイルシステム内のディレクトリを指すようにします。