按日期desc排序时,“使用临时”会降低查询速度
我有一个日志条目表,以及约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 ;
以下查询(匹配20k行的10k)在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表上使用? 删除索引实际上会使查询执行得更快,但仍然很慢(0.3秒)。
EXPLAIN输出不带ORDER BY的查询:
+----+-------------+-------------+------+----------------------------+------------------+---------+--------------------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------------+------+----------------------------+------------------+---------+--------------------------+------+-------------+ | 1 | SIMPLE | log_codes | ref | PRIMARY,IX_overview_code | IX_overview_code | 1 | const | 56 | | | 1 | SIMPLE | log_entries | ref | IX_code,IX_partner_code | IX_partner_code | 7 | const,log_codes.log_code | 25 | Using where | +----+-------------+-------------+------+----------------------------+------------------+---------+--------------------------+------+-------------+
并包括ORDER BY:
+----+-------------+-------------+------+----------------------------+------------------+---------+--------------------------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------------+------+----------------------------+------------------+---------+--------------------------+------+---------------------------------+ | 1 | SIMPLE | log_codes | ref | PRIMARY,IX_overview_code | IX_overview_code | 1 | const | 56 | Using temporary; Using filesort | | 1 | SIMPLE | log_entries | ref | IX_code,IX_partner_code | IX_partner_code | 7 | const,log_codes.log_code | 25 | Using 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 | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------------+------+-------------------------+------------------+---------+-------------------+------+---------------------------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 57 | Using temporary; Using filesort | | 1 | PRIMARY | log_entries | ref | IX_code,IX_partner_code | IX_partner_code | 7 | const,lc.log_code | 25 | Using where | | 2 | DERIVED | log_codes | ref | IX_overview_code | IX_overview_code | 1 | | 56 | | +----+-------------+-------------+------+-------------------------+------------------+---------+-------------------+------+---------------------------------+
UPDATE @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 | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------------+------+------------------+------------------+---------+------+-------+---------------------------------+ | 1 | PRIMARY | <derived3> | ALL | NULL | NULL | NULL | NULL | 57 | Using temporary; Using filesort | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 21937 | Using where; Using join buffer | | 3 | DERIVED | log_codes | ref | IX_overview_code | IX_overview_code | 1 | | 56 | | | 2 | DERIVED | log_entries | ALL | IX_partner_code | NULL | NULL | NULL | 22787 | Using 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 | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------------+-------+-----------------------+-----------------------+---------+------+-------+--------------------------------+ | 1 | PRIMARY | <derived3> | ALL | NULL | NULL | NULL | NULL | 57 | | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 21937 | Using where; Using join buffer | | 3 | DERIVED | log_codes | index | IX_overview_code_desc | IX_overview_code_desc | 771 | NULL | 80 | Using where; Using index | | 2 | DERIVED | log_entries | index | IX_partner_code_date | IX_partner_code_date | 15 | NULL | 22787 | Using where; Using index | +----+-------------+-------------+-------+-----------------------+-----------------------+---------+------+-------+--------------------------------+
使用索引,ORDER BY日期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 | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------------+-------+-----------------------+-----------------------+---------+------+-------+---------------------------------+ | 1 | PRIMARY | <derived3> | ALL | NULL | NULL | NULL | NULL | 57 | Using temporary; Using filesort | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 21937 | Using where; Using join buffer | | 3 | DERIVED | log_codes | index | IX_overview_code_desc | IX_overview_code_desc | 771 | NULL | 80 | Using where; Using index | | 2 | DERIVED | log_entries | index | IX_partner_code_date | IX_partner_code_date | 15 | NULL | 22787 | Using where; Using index | +----+-------------+-------------+-------+-----------------------+-----------------------+---------+------+-------+---------------------------------+
更新@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 | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------------+------+--------------------------+-----------------+---------+--------------------------+------+----------------------------------------------+ | 1 | SIMPLE | log_codes | ALL | PRIMARY,IX_code_overview | NULL | NULL | NULL | 80 | Using where; Using temporary; Using filesort | | 1 | SIMPLE | log_entries | ref | IX_code,IX_code_partner | IX_code_partner | 7 | log_codes.log_code,const | 25 | Using where | +----+-------------+-------------+------+--------------------------+-----------------+---------+--------------------------+------+----------------------------------------------+
我认为,这里和类似问题中的大多数问题都来自于误解MySQL(和其他数据库)如何使用索引进行排序。 答案是:MySQL不使用索引进行排序,它只能以索引顺序或相反方向读取数据。 如果您碰巧想按照当前使用的索引顺序对数据进行排序 - 那么您很幸运,否则结果将被排序(因此EXPLAIN中的filesort)
这是整个结果的顺序主要取决于哪个表是第一个连接。 如果你看看你的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秒)。 你知道为什么吗?
要输出行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;
你将需要两件事
对查询进行折衷
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);
我首先反转IX_partner_code
和IX_overview_code
索引中的列。 这应该使它们更适合同时支持JOIN和WHERE子句。
...
KEY `IX_code_partner` (`log_code`,`partner_id`)
...
KEY `IX_code_overview` (`log_code`,`category_overview`),
...
链接地址: http://www.djcxy.com/p/93601.html
上一篇: When ordering by date desc, "Using temporary" slows down query
下一篇: Hosting on Android: Backing up by cloning to external SD card?