【问题标题】:When ordering by date desc, "Using temporary" slows down query按日期顺序排序时,“使用临时”会减慢查询速度
【发布时间】:2012-04-30 09:13:17
【问题描述】:

我有一个日志条目表,以及大约 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 的查询输出:

+----+-------------+-------------+------+--------- --------------------+------------------+----------+- --------------+------+-------------+
|编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
+----+-------------+-------------+------+--------- --------------------+------------------+----------+- --------------+------+-------------+
| 1 |简单 |日志代码 |参考 | PRIMARY,IX_overview_code | IX_overview_code | 1 |常量 | 56 | |
| 1 |简单 |日志条目 |参考 | IX_code,IX_partner_code | IX_partner_code | 7 |常量,log_codes.log_code | 25 |使用位置 |
+----+-------------+-------------+------+--------- --------------------+------------------+----------+- --------------+------+-------------+

并包括 ORDER BY:

+----+-------------+-------------+------+--------- --------------------+------------------+----------+- ----------------------+------+------ ----------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+-------------+------+--------- --------------------+------------------+----------+- ----------------------+------+------ ----------------+ | 1 |简单 |日志代码 |参考 | PRIMARY,IX_overview_code | IX_overview_code | 1 |常量 | 56 |使用临时的;使用文件排序 | | 1 |简单 |日志条目 |参考 | IX_code,IX_partner_code | IX_partner_code | 7 |常量,log_codes.log_code | 25 |使用位置 | +----+-------------+-------------+------+--------- --------------------+------------------+----------+- ----------------------+------+------ ----------------+

关于如何让这个查询执行得更快的任何提示?我不明白为什么需要“使用临时”,因为应该在获取和排序适当的日志条目之前选择日志代码?

更新@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; +----+-------------+-------------+------+--------- --+------------------+----------+---- ---------------+------+--------------- ------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+-------------+------+--------- --+------------------+----------+---- ---------------+------+--------------- ------+ | 1 |初级 | |全部 |空 |空 |空 |空 | 57 |使用临时的;使用文件排序 | | 1 |初级 |日志条目 |参考 | IX_code,IX_partner_code | IX_partner_code | 7 |常量,lc.log_code | 25 |使用位置 | | 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; +----+-------------+-------------+------+--------- ------+------+---------+------+---- ---+------------------------------------------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+-------------+------+--------- ------+------+---------+------+---- ---+------------------------------------------------+ | 1 |初级 | |全部 |空 |空 |空 |空 | 57 |使用临时的;使用文件排序 | | 1 |初级 | |全部 |空 |空 |空 |空 | 21937 |使用哪里;使用连接缓冲区 | | 3 |派生 |日志代码 |参考 | IX_overview_code | IX_overview_code | 1 | | 56 | | | 2 |派生 |日志条目 |全部 | IX_partner_code |空 |空 |空 | 22787 |使用位置 | +----+-------------+-------------+------+--------- ------+------+---------+------+---- ---+------------------------------------------------+

有了你的索引,没有排序:

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); +----+-------------+-------------+--------+-------- ---------------+-----------+---------+ ------+--------+--------------------------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+-------------+--------+-------- ---------------+-----------+---------+ ------+--------+--------------------------------+ | 1 |初级 | |全部 |空 |空 |空 |空 | 57 | | | 1 |初级 | |全部 |空 |空 |空 |空 | 21937 |使用哪里;使用连接缓冲区 | | 3 |派生 |日志代码 |索引 | IX_overview_code_desc | IX_overview_code_desc | 771 |空 | 80 |使用哪里;使用索引 | | 2 |派生 |日志条目 |索引 | IX_partner_code_date | IX_partner_code_date | 15 |空 | 22787 |使用哪里;使用索引 | +----+-------------+-------------+--------+-------- ---------------+-----------+---------+ ------+--------+--------------------------------+

使用您的索引,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; +----+-------------+-------------+--------+-------- ---------------+-----------+---------+ ------+--------+----------------------------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+-------------+--------+-------- ---------------+-----------+---------+ ------+--------+----------------------------------+ | 1 |初级 | |全部 |空 |空 |空 |空 | 57 |使用临时的;使用文件排序 | | 1 |初级 | |全部 |空 |空 |空 |空 | 21937 |使用哪里;使用连接缓冲区 | | 3 |派生 |日志代码 |索引 | IX_overview_code_desc | IX_overview_code_desc | 771 |空 | 80 |使用哪里;使用索引 | | 2 |派生 |日志条目 |索引 | IX_partner_code_date | IX_partner_code_date | 15 |空 | 22787 |使用哪里;使用索引 | +----+-------------+-------------+--------+-------- ---------------+-----------+---------+ ------+--------+----------------------------------+

更新@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; +----+-------------+-------------+------+--------- -----------------+-----------------+---------+---- ----------------------+------+-------- --------------------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+-------------+------+--------- -----------------+-----------------+---------+---- ----------------------+------+-------- --------------------------+ | 1 |简单 |日志代码 |全部 | PRIMARY,IX_code_overview |空 |空 |空 | 80 |使用哪里;使用临时的;使用文件排序 | | 1 |简单 |日志条目 |参考 | IX_code,IX_code_partner | IX_code_partner | 7 | log_codes.log_code,const | 25 |使用位置 | +----+-------------+-------------+------+--------- -----------------+-----------------+---------+---- ----------------------+------+-------- --------------------------+

【问题讨论】:

  • 请尝试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并回复
  • @Eugen Rieck 谢谢。我已将您的查询的 EXPLAIN 输出添加到问题中。查询性能大致相同(在 LIMIT 0,25 上为 0.6 秒)。

标签: mysql indexing query-optimization


【解决方案1】:

我认为,这里和类似问题中的大多数问题都来自对 MySQL(和其他数据库)如何使用索引进行排序的误解。答案是:MySQL 不使用索引进行排序,它只是可以按照索引的顺序或相反的方向读取数据。如果您碰巧希望按照当前使用的索引的顺序对数据进行排序 - 您很幸运,否则结果将被排序(因此 EXPLAIN 中的文件排序)

整个结果的顺序主要取决于连接中的第一个表。如果您查看您的 EXPLAIN,您会看到连接从“log_codes”表开始(因为它要小得多)。

基本上,您需要的是'log_entries'上的复合索引(partner_id,date),'log_codes'的覆盖复合索引(log_code,category_overview,log_desc),将'INNER JOIN'更改为'STRAIGHT_JOIN'以强制加入顺序,并按“日期”DESC 排序(幸运的是,这个索引也会覆盖)。

UPD1:对不起,我打错了第一个表的索引:应该是(partner_id, log_code, date)

但是当我尝试对另一个表中的列进行排序时,我仍然难以理解为什么 MySQL 选择在 log_codes 表上“使用临时”(以及 100 倍的查询时间)?

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;

【讨论】:

  • 有价值的信息。但是当我尝试对另一个表中的列进行排序时,我仍然很难理解为什么 MySQL 选择在 log_codes 表上“使用临时”(以及 100 倍的查询时间)?测试了 STRAIGHT_JOIN,它似乎提供了非常好的性能(LIMIT 0,25 为 0.0058 秒)!但随着我深入数据集,它会变慢(LIMIT 50000,25 为 6 秒)。你知道为什么吗?不过,这不是一个大问题,我可以权衡最旧日志条目的性能以快速加载最新元素。索引 (log_code, partner_id) 的表现与 (partner_id, date) 相当。
  • 感谢@newtower,这非常有用。我添加了一个 (partner_id, log_code, date) 索引。使用了索引,但仍然很慢。 key_len 是 2,所以我猜日期部分没有使用?顺便说一句,在将使用此查询的最大数据集中,当前索引的基数为 (7,254,176904)。我听说理想情况下高基数字段应该在索引的开头,你有任何 cmets 吗?
  • @elaxsj, date 部分用于选择的值,应该有using index 在解释。顺便说一句,我假设您通过date DESC 订购。关于基数,你是对的,但这主要影响条件的应用顺序。在这种情况下,您首先需要通过partner_id 应用条件,然后检查其余行是否在另一个表中满足第二个条件。
  • 我通过date DESC 订购,并拥有IX_partner_code_date(partner_id, log_code, date) 索引。解释输出:SIMPLE / log_entries / ref / IX_partner_code_logentry,IX_partner_code_date / IX_partner_code_date / 2 / const / 167457 / Using where; Using filesort。还尝试通过logentry_id DESC 订购,使用IX_partner_code_logentry(partner_id, log_code, logentry_id),结果相同。奇怪的?希望我能够解决这个问题,并且新查询将完美运行:)
  • 啊。现在我懂了。我在 log_entries 中还有一些其他字段,我没有考虑到简化,我知道索引需要涵盖所有这些才能按索引排序。那么没有覆盖索引可能会更好。但是,我注意到查询在除 PRIMARY (logentry_id) 之外的 log_entries 上没有任何索引时执行得最好。这是否意味着我应该只使用 PRIMARY 索引?数字如下...
【解决方案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);

【讨论】:

  • 谢谢!我没有添加索引,因为大约 40% 的日志代码有 category_overview,而目前 90% 的日志条目有合作伙伴 1(将添加更多合作伙伴,并且会平局)。试一下查询,它仍然很慢,我在最后一个派生查询上看到“使用临时”。我已将查询的 EXPLAIN 输出添加到我的问题中。
  • 只是为了实验,请无论如何创建索引并运行它。
  • 已添加索引,按日期顺序订购时仍“使用临时”/缓慢。我已经用 EXPLAIN 输出更新了我的问题。
  • 最新查询返回 60950 行,而应该只有 9743 匹配行。我的 SQL 知识有限,所以我看不出查询出了什么问题。另请记住,我需要按日期顺序订购。非常感谢您的努力!
【解决方案3】:

我会先反转IX_partner_codeIX_overview_code 索引中的列。这应该使它们更适合同时支持 JOIN 和 WHERE 子句。

...
KEY `IX_code_partner` (`log_code`,`partner_id`)
...
KEY `IX_code_overview` (`log_code`,`category_overview`),
...

【讨论】:

  • 谢谢。我试图更改索引。 “使用临时”仍然存在,并且似乎没有使用代码/概述索引。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-21
  • 2015-09-12
  • 1970-01-01
  • 2022-11-11
相关资源
最近更新 更多