web-dev-qa-db-ja.com

MySQL COUNTクエリの実行に時間がかかりすぎる

実行に7秒かかる以下のクエリがあります

SELECT COUNT(*) AS `count` 
FROM `yuldi`.`businesses` AS `Business` 
  LEFT JOIN `yuldi`.`businesses_categories` AS `BusinessesCategory` 
    ON (`Business`.`id` = `BusinessesCategory`.`business_id`) 
  LEFT JOIN `yuldi`.`categories` AS `Category` 
    ON (`BusinessesCategory`.`category_id` = `Category`.`id`) 
WHERE `Category`.`slug` = 'building-construction'

2回目に実行すると160ミリ秒かかります。毎回1回目の実行で時間がかかる理由

説明:

| id | select_type | table              | type   | possible_keys                                                                                   | key                                          | key_len | ref      | rows  | Extra                    |
|  1 | SIMPLE      | Category           | const  | PRIMARY,UNIQUE_SLUG,index_lug                                                                   | UNIQUE_SLUG                                  | 302     | const    |     1 | Using index              |
|  1 | SIMPLE      | BusinessesCategory | ref    | PRIMARY,fk_businesses_has_categories_categories1_idx,fk_businesses_has_categorie_businesses_idx | fk_businesses_has_categories_categories1_idx | 4       | const    | 49630 | Using where; Using index |
|  1 | SIMPLE      | Business           | eq_ref | PRIMARY                                                                                         | PRIMARY                                      | 4       | yuldi.BusinessesCategory.business_id |     1 | Using index              |
3
Surjit Sidhu

やり直す必要がある3つの側面があります

側面#1:クエリ

クエリをご覧ください

SELECT COUNT(*) AS `count`
FROM `yuldi`.`businesses` AS `Business`
LEFT JOIN `yuldi`.`businesses_categories` AS `BusinessesCategory`
    ON (`Business`.`id` = `BusinessesCategory`.`business_id`)
LEFT JOIN `yuldi`.`categories` AS `Category`
    ON (`BusinessesCategory`.`category_id` = `Category`.`id`)
WHERE `Category`.`slug` = 'building-construction';

LEFT JOINを実行しています。これは、COUNTを実行する場合には必要ありません。どうして ?

ビジネスの数を数える場合は、1つのテーブルから数えるだけで済みます

SELECT COUNT(*) AS `count`
FROM `yuldi`.`businesses` AS `Business`;

Category.slugにWHERE句があるため、JOINが必要です。 LEFT JOINからINNER JOINに切り替える必要があります。これにより、内部の一時テーブルが小さくなります。その一時テーブルは、スラッグ「building-construction」を検索します。

側面2:インデックス

EXPLAINプランから、同じ名前の2つのインデックスが見られます

  • fk_businesses_has_categories_categories1_idx
  • fk_businesses_has_categories_businesses_idx

それらの列を見て、列リストが同一でないことを確認する必要があります。

2つのインデックスを作成することができます

ALTER TABLE BusinessesCategory
    ADD INDEX bus_cat_ndx (business_id,category_id),
    ADD INDEX cat_bus_ndx (category_id,business_id)
;

複合インデックスを使用すると、JOIN情報のテーブルでのカリングが少なくなります。

データの主要な分布がわからないため、この提案は盲目的です。

側面#3:キャッシング

インデックス統計の収集は、クエリの評価が遅いクエリにすぐに変換されないためです。その遅さは本当の犯人によって明らかにされています:[〜#〜]キャッシング[〜#〜]。 2回目にクエリが速く実行される理由は、最初に読み取ったデータに関係しています。クエリを発行したときに、MySQLのキャッシュに必要なデータが含まれていない可能性があります。

MySQLの2つのメインキャッシュは、InnoDBバッファープール(InnoDBテーブルにヒットするクエリ用)とMyISAMキーキャッシュ(MyISAMテーブルにヒットするクエリ用)です。

InnoDBはデータとインデックスページをキャッシュしますが、MyISAMはインデックスページのみをキャッシュします。

比較のためにデータを取得するとき、mysqldは必要なデータがRAMに最初にあるかどうかを判断しようとします。これを監視するサーバーステータス変数があります:

InnoDBキャッシュ

MyISAMキャッシュ

  • Key_read_requests :MyISAMキーキャッシュからキーブロックを読み取るリクエストの数。
  • Key_reads :ディスクからMyISAMキーキャッシュへのキーブロックの物理読み取りの数。 Key_readsが大きい場合は、key_buffer_sizeの値が小さすぎる可能性があります。キャッシュミス率は、Key_reads/Key_read_requestsとして計算できます。

クエリが初めて遅い場合は、クエリに必要なデータがキャッシュになかったことを意味します。これは、 Innodb_buffer_pool_reads または Key_reads の増分によって示されます。

2回目は、同じクエリのデータページまたはインデックスページ、あるいはその両方がRAMにあり、クエリや他のクエリからより多く利用できます。

InnoDBバッファープールおよび/またはMyISAMキーキャッシュ のサイズを確認することをお勧めします。

5
RolandoMySQLDBA