1:nの関係で50のフィードを含むテーブルにリンクされた、およそ100.000のブログ投稿を含むテーブルがあります。両方のテーブルを、postingテーブルのdatetimeフィールドで並べられたselectステートメントでクエリすると、MySQLは常にfilesortを使用するため、クエリ時間が非常に遅くなります(> 1秒)。 postings
テーブルのスキーマは次のとおりです(簡略化)。
+---------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| feed_id | int(11) | NO | MUL | NULL | |
| crawl_date | datetime | NO | | NULL | |
| is_active | tinyint(1) | NO | MUL | 0 | |
| link | varchar(255) | NO | MUL | NULL | |
| author | varchar(255) | NO | | NULL | |
| title | varchar(255) | NO | | NULL | |
| excerpt | text | NO | | NULL | |
| long_excerpt | text | NO | | NULL | |
| user_offtopic_count | int(11) | NO | MUL | 0 | |
+---------------------+--------------+------+-----+---------+----------------+
そして、これがfeed
テーブルです。
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| type | int(11) | NO | MUL | 0 | |
| title | varchar(255) | NO | | NULL | |
| website | varchar(255) | NO | | NULL | |
| url | varchar(255) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
実行に1秒以上かかるクエリを次に示します。 post_date
フィールドにはインデックスがありますが、MySQLはそれを使用して投稿テーブルをソートしていないことに注意してください。
SELECT
`postings`.`id`,
UNIX_TIMESTAMP(postings.post_date) as post_date,
`postings`.`link`,
`postings`.`title`,
`postings`.`author`,
`postings`.`excerpt`,
`postings`.`long_excerpt`,
`feeds`.`title` AS feed_title,
`feeds`.`website` AS feed_website
FROM
(`postings`)
JOIN
`feeds`
ON
`feeds`.`id` = `postings`.`feed_id`
WHERE
`feeds`.`type` = 1 AND
`postings`.`user_offtopic_count` < 10 AND
`postings`.`is_active` = 1
ORDER BY
`postings`.`post_date` desc
LIMIT
15
このクエリに対するexplain extended
コマンドの結果は、MySQLがfilesortを使用していることを示しています。
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+
| 1 | SIMPLE | postings | ref | feed_id,is_active,user_offtopic_count | is_active | 1 | const | 30996 | Using where; Using filesort |
| 1 | SIMPLE | feeds | eq_ref | PRIMARY,type | PRIMARY | 4 | feedian.postings.feed_id | 1 | Using where |
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+
order by
の部分を削除すると、MySQLはfilesortの使用を停止します。このクエリを最適化してMySQLでインデックスを使用してデータをソートおよび選択する方法について何かご意見がありましたら、お知らせください。いくつかのブログ投稿で示唆されているように、すべてのwhere/order byフィールドに結合インデックスを作成するなど、いくつかのことをすでに試しましたが、これも機能しませんでした。
postings (is_active, post_date)
に(この順序で)複合インデックスを作成します。
_is_active
_でのフィルタリングと_post_date
_による順序付けの両方に使用されます。
MySQL
は、_EXPLAIN EXTENDED
_のこのインデックスに対するREF
アクセスメソッドを表示する必要があります。
_user_offtopic_count
_に対してRANGE
フィルタリング条件があることに注意してください。そのため、フィルタリングと他のフィールドによるソートの両方で、このフィールドのインデックスを使用できません。
_user_offtopic_count
_がどの程度選択的であるか(つまり、_user_offtopic_count < 10
_を満たす行数)に応じて、_user_offtopic_count
_にインデックスを作成し、post_datesをソートする方が便利な場合があります。
これを行うには、postings (is_active, user_offtopic_count)
に複合インデックスを作成し、このインデックスに対してRANGE
アクセスメソッドが使用されていることを確認します。
どのインデックスがより高速になるかは、データの分散によって異なります。両方のインデックスを作成し、それらをFORCE
して、どちらが高速かを確認します。
_CREATE INDEX ix_active_offtopic ON postings (is_active, user_offtopic_count);
CREATE INDEX ix_active_date ON postings (is_active, post_date);
SELECT
`postings`.`id`,
UNIX_TIMESTAMP(postings.post_date) as post_date,
`postings`.`link`,
`postings`.`title`,
`postings`.`author`,
`postings`.`excerpt`,
`postings`.`long_excerpt`,
`feeds`.`title` AS feed_title,
`feeds`.`website` AS feed_website
FROM
`postings` FORCE INDEX (ix_active_offtopic)
JOIN
`feeds`
ON
`feeds`.`id` = `postings`.`feed_id`
WHERE
`feeds`.`type` = 1 AND
`postings`.`user_offtopic_count` < 10 AND
`postings`.`is_active` = 1
ORDER BY
`postings`.`post_date` desc
LIMIT
15
/* This should show RANGE access with few rows and keep the FILESORT */
SELECT
`postings`.`id`,
UNIX_TIMESTAMP(postings.post_date) as post_date,
`postings`.`link`,
`postings`.`title`,
`postings`.`author`,
`postings`.`excerpt`,
`postings`.`long_excerpt`,
`feeds`.`title` AS feed_title,
`feeds`.`website` AS feed_website
FROM
`postings` FORCE INDEX (ix_active_date)
JOIN
`feeds`
ON
`feeds`.`id` = `postings`.`feed_id`
WHERE
`feeds`.`type` = 1 AND
`postings`.`user_offtopic_count` < 10 AND
`postings`.`is_active` = 1
ORDER BY
`postings`.`post_date` desc
LIMIT
15
/* This should show REF access with lots of rows and no FILESORT */
_
また、注文している列に関数が適用されている場合、MySQLはインデックスを使用しないことを覚えておくことも重要です。
また、postings.post_dateのエイリアスを別の方法で試してください。これにより、MySQLは変更されていない列で並べ替えるようになり、UNIXタイムスタンプを選択します。
MySQLには2つのファイルソートアルゴリズムがあります。ディスク上のレコードをソートする古いファイルソートと、メモリ内で機能する新しいバージョンです。
結合の最初のテーブルのインデックスを使用してクエリをソートできない場合は、ファイルソートを実行する必要があります。固定幅フォーマットに変換されたソート前の結果セットがソートバッファーよりも大きい場合[〜#〜] or [〜#〜]テキストフィールドが含まれている場合、低速のオンを使用する必要がありますdisk filesortアルゴリズム(クエリにテキストフィールドがあるため、2番目の条件が満たされます)。
MySQLは、他の結合とwhere条件を続行する前に行を削除するときに列が最も選択的であると考えているため、is_active列の使用を選択しています。最初にお勧めするのは、post_date、feed_id、およびwhere条件の列を使用して複合インデックスを作成してみることです。 (is_active、user_offtopic_count、post_date、feed_id)。