web-dev-qa-db-ja.com

MySQLパフォーマンスの最適化:日時フィールドによる順序付け

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フィールドに結合インデックスを作成するなど、いくつかのことをすでに試しましたが、これも機能しませんでした。

37
Dennis G.

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 */
_
39
Quassnoi

また、注文している列に関数が適用されている場合、MySQLはインデックスを使用しないことを覚えておくことも重要です。

また、postings.post_dateのエイリアスを別の方法で試してください。これにより、MySQLは変更されていない列で並べ替えるようになり、UNIXタイムスタンプを選択します。

3
Chris Henry

MySQLには2つのファイルソートアルゴリズムがあります。ディスク上のレコードをソートする古いファイルソートと、メモリ内で機能する新しいバージョンです。

結合の最初のテーブルのインデックスを使用してクエリをソートできない場合は、ファイルソートを実行する必要があります。固定幅フォーマットに変換されたソート前の結果セットがソートバッファーよりも大きい場合[〜#〜] or [〜#〜]テキストフィールドが含まれている場合、低速のオンを使用する必要がありますdisk filesortアルゴリズム(クエリにテキストフィールドがあるため、2番目の条件が満たされます)。

MySQLは、他の結合とwhere条件を続行する前に行を削除するときに列が最も選択的であると考えているため、is_active列の使用を選択しています。最初にお勧めするのは、post_date、feed_id、およびwhere条件の列を使用して複合インデックスを作成してみることです。 (is_active、user_offtopic_count、post_date、feed_id)。

3
ʞɔıu