5

ログ エントリの表と、考えられる約 100 のログ コードの説明表があります。

CREATE TABLE `log_entries` (
  `logentry_id` int(11) NOT NULL AUTO_INCREMENT,
  `date` datetime NOT NULL,
  `partner_id` smallint(4) NOT NULL,
  `log_code` smallint(4) NOT NULL,
  PRIMARY KEY (`logentry_id`),
  KEY `IX_code` (`log_code`),
  KEY `IX_partner_code` (`partner_id`,`log_code`)
) ENGINE=MyISAM ;

CREATE TABLE IF NOT EXISTS `log_codes` (
  `log_code` smallint(4) NOT NULL DEFAULT '0',
  `log_desc` varchar(255) DEFAULT NULL,
  `category_overview` tinyint(1) NOT NULL DEFAULT '0',
  `category_error` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`log_code`),
  KEY `IX_overview_code` (`category_overview`,`log_code`),
  KEY `IX_error_code` (`category_error`,`log_code`)
) ENGINE=MyISAM ;

次のクエリ (20,000 行のうち 10,000 行に一致) は 0.0034 秒で実行されます (を使用LIMIT 0,20):

SELECT log_entries.date, log_codes.log_desc FROM log_entries 
INNER JOIN log_codes ON log_codes.log_code = log_entries.log_code 
WHERE log_entries.partner_id = 1 AND log_codes.category_overview = 1;

しかしORDER BY log_entries.logentry_id DESC、もちろん必要な を追加すると、0.6 秒まで遅くなります。おそらく、log_codes テーブルで「Using temporary」が使用されているためでしょうか。インデックスを削除すると、実際にはクエリの実行が速くなりますが、それでも遅くなります (0.3 秒)。

ORDER BY を使用しないクエリの EXPLAIN 出力:

+----+-------------+-------------+------+--------- -------------------+------------------+---------------------+- --------------+------+-------------+
| | ID | select_type | テーブル | タイプ | 可能な_キー | キー | key_len | 参照 | 行 | 行 エクストラ |
+----+-------------+-------------+------+--------- -------------------+------------------+---------------------+- --------------+------+-------------+
| | 1 | シンプル | ログコード | 参照 | PRIMARY,IX_overview_code | IX_overview_code | 1 | 定数 | 56 | | |
| | 1 | シンプル | log_entries | 参照 | IX_code,IX_partner_code | IX_パートナー_コード | 7 | const,log_codes.log_code | 25 | where | の使用
+----+-------------+-------------+------+--------- -------------------+------------------+---------------------+- --------------+------+-------------+

ORDER BY を含む:

+----+-------------+-------------+------+--------- -------------------+------------------+---------------------+- --------------+--------------------+----------------- ----------------+
| | ID | select_type | テーブル | タイプ | 可能な_キー | キー | key_len | 参照 | 行 | 行 エクストラ |
+----+-------------+-------------+------+--------- -------------------+------------------+---------------------+- --------------+--------------------+----------------- ----------------+
| | 1 | シンプル | ログコード | 参照 | PRIMARY,IX_overview_code | IX_overview_code | 1 | 定数 | 56 | 一時的な使用; ファイルソートの使用 |
| | 1 | シンプル | log_entries | 参照 | IX_code,IX_partner_code | IX_パートナー_コード | 7 | const,log_codes.log_code | 25 | where | の使用
+----+-------------+-------------+------+--------- -------------------+------------------+---------------------+- --------------+--------------------+----------------- ----------------+

このクエリをより高速に実行する方法に関するヒントはありますか? 適切なログエントリを取得してソートする前にログコードを選択する必要があるため、「一時的な使用」が必要な理由がわかりませんか?

更新@Eugen Rieck

SELECT log_entries.date, lc.log_desc FROM log_entries INNER JOIN (SELECT log_desc, log_code FROM log_codes WHERE category_overview = 1) AS lc ON lc.log_code = log_entries.log_code WHERE log_entries.partner_id = 1 ORDER BY log_entries.logentry_id;
+----+-------------+-------------+------+--------- -+--------------------+---------------------+---- ---------------+------+--------------------------- ------+
| | ID | select_type | テーブル | タイプ | 可能な_キー | キー | key_len | 参照 | 行 | 行 エクストラ |
+----+-------------+-------------+------+--------- -+--------------------+---------------------+---- ---------------+------+--------------------------- ------+
| | 1 | プライマリ | <派生2> | すべて | ヌル | ヌル | ヌル | ヌル | 57 | 一時的な使用; ファイルソートの使用 |
| | 1 | プライマリ | log_entries | 参照 | IX_code,IX_partner_code | IX_パートナー_コード | 7 | const,lc.log_code | 25 | where | の使用
| | 2 | 派生 | ログコード | 参照 | IX_overview_code | IX_overview_code | 1 | | | 56 | | |
+----+-------------+-------------+------+--------- -+--------------------+---------------------+---- ---------------+------+--------------------------- ------+

更新 @RolandoMySQLDBA :

私の元のインデックスでは、ORDER BY date DESC:

SELECT log_entries.date, log_codes.log_desc FROM (SELECT log_code,date FROM log_entries WHERE partner_id = 1) log_entries INNER JOIN (SELECT log_code,log_desc FROM log_codes WHERE category_overview = 1) log_codes USING (log_code) ORDER BY log_entries.date DESC;
+----+-------------+-------------+------+--------- ------+------------------+---------+------+---- ---+---------------------------------+
| | ID | select_type | テーブル | タイプ | 可能な_キー | キー | key_len | 参照 | 行 | 行 エクストラ |
+----+-------------+-------------+------+--------- ------+------------------+---------+------+---- ---+---------------------------------+
| | 1 | プライマリ | <派生3> | すべて | ヌル | ヌル | ヌル | ヌル | 57 | 一時的な使用; ファイルソートの使用 |
| | 1 | プライマリ | <派生2> | すべて | ヌル | ヌル | ヌル | ヌル | 21937 | where を使用します。結合バッファーの使用 |
| | 3 | 派生 | ログコード | 参照 | IX_overview_code | IX_overview_code | 1 | | | 56 | | |
| | 2 | 派生 | log_entries | すべて | IX_パートナー_コード | ヌル | ヌル | ヌル | 22787 | where | の使用
+----+-------------+-------------+------+--------- ------+------------------+---------+------+---- ---+---------------------------------+

あなたのインデックスでは、順序付けはありません:

SELECT log_entries.date, log_codes.log_desc FROM (SELECT log_code,date FROM log_entries WHERE partner_id = 1) log_entries INNER JOIN (SELECT log_code,log_desc FROM log_codes WHERE category_overview = 1) log_codes USING (log_code);
+----+-------------+-------------+-------+-------- ---------------+-----------------------+---------+ ------+----------------------+--------------------------------+
| | ID | select_type | テーブル | タイプ | 可能な_キー | キー | key_len | 参照 | 行 | 行 エクストラ |
+----+-------------+-------------+-------+-------- ---------------+-----------------------+---------+ ------+----------------------+--------------------------------+
| | 1 | プライマリ | <派生3> | すべて | ヌル | ヌル | ヌル | ヌル | 57 | | |
| | 1 | プライマリ | <派生2> | すべて | ヌル | ヌル | ヌル | ヌル | 21937 | where を使用します。結合バッファーの使用 |
| | 3 | 派生 | ログコード | インデックス | IX_overview_code_desc | IX_overview_code_desc | 771 | ヌル | 80 | where を使用します。インデックスの使用 |
| | 2 | 派生 | log_entries | インデックス | IX_partner_code_date | IX_partner_code_date | 15 | ヌル | 22787 | where を使用します。インデックスの使用 |
+----+-------------+-------------+-------+-------- ---------------+-----------------------+---------+ ------+----------------------+--------------------------------+

インデックスを使用すると、ORDER BY date DESC:

SELECT log_entries.date, log_codes.log_desc FROM (SELECT log_code,date FROM log_entries WHERE partner_id = 1) log_entries INNER JOIN (SELECT log_code,log_desc FROM log_codes WHERE category_overview = 1) log_codes USING (log_code) ORDER BY log_entries.date DESC;
+----+-------------+-------------+-------+-------- ---------------+-----------------------+---------+ ------+----------------------+----------------------------------+
| | ID | select_type | テーブル | タイプ | 可能な_キー | キー | key_len | 参照 | 行 | 行 エクストラ |
+----+-------------+-------------+-------+-------- ---------------+-----------------------+---------+ ------+----------------------+----------------------------------+
| | 1 | プライマリ | <派生3> | すべて | ヌル | ヌル | ヌル | ヌル | 57 | 一時的な使用; ファイルソートの使用 |
| | 1 | プライマリ | <派生2> | すべて | ヌル | ヌル | ヌル | ヌル | 21937 | where を使用します。結合バッファーの使用 |
| | 3 | 派生 | ログコード | インデックス | IX_overview_code_desc | IX_overview_code_desc | 771 | ヌル | 80 | where を使用します。インデックスの使用 |
| | 2 | 派生 | log_entries | インデックス | IX_partner_code_date | IX_partner_code_date | 15 | ヌル | 22787 | where を使用します。インデックスの使用 |
+----+-------------+-------------+-------+-------- ---------------+-----------------------+---------+ ------+----------------------+----------------------------------+

更新 @Joe Stefanelli :

SELECT log_entries.date, log_codes.log_desc FROM log_entries INNER JOIN log_codes ON log_codes.log_code = log_entries.log_code WHERE log_entries.partner_id = 1 AND log_codes.category_overview = 1 ORDER BY date DESC;
+----+-------------+-------------+------+--------- ---+-----------------+---------------------+---- ----------------------+------+-------------------- --------------------------+
| | ID | select_type | テーブル | タイプ | 可能な_キー | キー | key_len | 参照 | 行 | 行 エクストラ |
+----+-------------+-------------+------+--------- ---+-----------------+---------------------+---- ----------------------+------+-------------------- --------------------------+
| | 1 | シンプル | ログコード | すべて | PRIMARY,IX_code_overview | ヌル | ヌル | ヌル | 80 | where を使用します。一時的な使用; ファイルソートの使用 |
| | 1 | シンプル | log_entries | 参照 | IX_code,IX_code_partner | IX_コード_パートナー | 7 | log_codes.log_code,const | 25 | where | の使用
+----+-------------+-------------+------+--------- ---+-----------------+---------------------+---- ----------------------+------+-------------------- --------------------------+
4

3 に答える 3

2

ここと同様の質問の問題のほとんどは、MySQL(および他のデータベース)がソートにインデックスを使用する方法を誤解していることに起因すると思います。答えは次のとおりです。MySQL はソートにインデックスを使用せず、インデックスの順序または逆方向にデータを読み取ることができるだけです。たまたま現在使用されているインデックスの順序でデータを並べ替えたい場合 - 幸運です。そうでない場合、結果は並べ替えられます (したがって、EXPLAIN のファイル並べ替え)。

つまり、結果全体の順序は、ほとんどの場合、どのテーブルが結合の最初であったかによって異なります。また、EXPLAIN を見ると、結合が「log_codes」テーブルから開始されていることがわかります (これははるかに小さいためです)。

基本的に必要なのは、「log_entries」の複合インデックス (partner_id、date)、「log_codes」のカバー複合インデックス (log_code、category_overview、log_desc) であり、結合順序を強制するために「INNER JOIN」を「STRAIGHT_JOIN」に変更します。 'date' DESC で並べ替えます (幸いなことに、このインデックスも対象となります)。

UPD1 : 申し訳ありませんが、最初のテーブルのインデックスを間違えました: である必要があります(partner_id, log_code, date)

しかし、別のテーブルの列でソートしようとすると、MySQL が log_codes テーブル (および 100x クエリ時間) で「一時的な使用」を選択する理由を理解するのにまだ苦労していますか?

MySQL は、データを取得する順序に同意する限り、データを直接出力するか、データを一時テーブルに入れ、並べ替えを適用してから出力することができます。結合で最初以外のテーブルのフィールドで並べ替える場合、MySQL はデータを並べ替える必要があり (インデックスの順序で出力するだけでなく)、データを並べ替えるには一時テーブルが必要です。

しかし、データセットにさらに入ると遅くなります (LIMIT 50000,25 で 6 秒)。なぜなのかご存知ですか?

行 50000,25 を出力するには、MySQL は最初の 50000 を取得してスキップする必要があります。インデックス内の列を見逃していたので、MySQL はインデックスをスキャンしただけでなく、各アイテムに対してlog_code値のディスク ルックアップを追加しました。すべてのデータをインデックスからフェッチできるため、カバリング インデックスを使用すると、はるかに高速になります。

UPD2 : インデックスを強制しようとします:

SELECT log_entries.date, log_codes.log_desc
FROM log_entries FORCE INDEX (IX_partner_code_date)
STRAIGHT_JOIN log_codes
  ON log_codes.log_code = log_entries.log_code
WHERE log_entries.partner_id = 1
  AND log_codes.category_overview = 1
ORDER BY log_entries.date DESC;
于 2012-04-19T19:35:41.983 に答える
1

あなたは2つのものが必要になるでしょう

クエリをリファクタリングする

SELECT log_entries.date, log_codes.log_desc FROM 
(SELECT log_code,date FROM log_entries WHERE partner_id = 1) log_entries
INNER JOIN
(SELECT log_code,log_desc FROM log_codes WHERE category_overview = 1) log_codes
USING (log_code); 

サブクエリをサポートし、テーブルへのアクセスを減らすためのインデックスを作成します

これらのインデックスを作成する前に、これらを実行してください

SELECT COUNT(1) rowcount,partner_id FROM log_entries GROUP BY partner_id;
SELECT COUNT(1) rowcount,category_overview FROM log_codes GROUP BY category_overview;

考えられるすべてのpartner_id値のカウントがlog_entriesテーブルの5%を超えていない場合は、このインデックスを作成します

ALTER TABLE log_entries ADD INDEX (partner_id,log_code,date);

考えられるすべてのcategory_overview値のカウントがlog_codesテーブルの5%を超えていない場合は、このインデックスを作成します

ALTER TABLE log_codes ADD INDEX (category_overview,log_code,log_desc);

試してみる !!!

LIMIT 0,25含まれているこのリファクタリングされたクエリを試してください

SELECT log_entries.date, log_codes.log_desc FROM 
(
    SELECT A.log_code FROM 
    (SELECT log_code FROM log_entries WHERE partner_id = 1) A INNER JOIN
    (SELECT log_code FROM log_codes WHERE category_overview = 1) B USING (log_code)
    LIMIT 0,25
) log_code_keys
INNER JOIN log_entries USING (log_code)
INNER JOIN log_code USING (log_code);
于 2012-04-18T21:37:18.423 に答える
0

IX_partner_codeおよびIX_overview_codeインデックスの列を逆にすることから始めます。これにより、JOIN 句と WHERE 句の両方をサポートするのにより適したものになるはずです。

...
KEY `IX_code_partner` (`log_code`,`partner_id`)
...
KEY `IX_code_overview` (`log_code`,`category_overview`),
...
于 2012-04-18T21:25:03.357 に答える