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 ;

以下查询(匹配 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 | 初级 | <派生2> | 全部 | 空 | 空 | 空 | 空 | 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 | 初级 | <派生3> | 全部 | 空 | 空 | 空 | 空 | 57 | 使用临时的;使用文件排序 |
| 1 | 初级 | <派生2> | 全部 | 空 | 空 | 空 | 空 | 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 | 初级 | <派生3> | 全部 | 空 | 空 | 空 | 空 | 57 | |
| 1 | 初级 | <派生2> | 全部 | 空 | 空 | 空 | 空 | 21937 | 使用哪里;使用连接缓冲区 |
| 3 | 派生 | 日志代码 | 索引 | IX_overview_code_desc | IX_overview_code_desc | 771 | 空 | 80 | 使用哪里;使用索引 |
| 2 | 派生 | 日志条目 | 索引 | IX_partner_code_date | IX_partner_code_date | 15 | 空 | 22787 | 使用哪里;使用索引 |
+----+-------------+-------------+--------+-------- ---------------+-----------+---------+ ------+--------+--------------------------------+

使用您的索引,按日期 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 | 初级 | <派生3> | 全部 | 空 | 空 | 空 | 空 | 57 | 使用临时的;使用文件排序 |
| 1 | 初级 | <派生2> | 全部 | 空 | 空 | 空 | 空 | 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 | 使用位置 |
+----+-------------+-------------+------+--------- -----------------+-----------------+---------+---- ----------------------+------+-------- --------------------------+
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'以强制加入顺序,并按“日期”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;
于 2012-04-19T19:35:41.983 回答
1

你将需要两件事

重构查询

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_codeIX_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 回答