SQL 查询优化——真的没有啥可改进的了吗?

Posted

技术标签:

【中文标题】SQL 查询优化——真的没有啥可改进的了吗?【英文标题】:SQL query optimization - really nothing more to improve?SQL 查询优化——真的没有什么可改进的了吗? 【发布时间】:2015-05-25 05:05:17 【问题描述】:

我有以下问题。我是从 mysql 慢查询日志中挑选出来的:

SELECT AVG(item.duration) AS dur 
FROM `item` 
INNER JOIN item_step ON item_step.item_id = item.id 
WHERE
item_step.number = '2' AND 
(IS_OK(item_step.result) OR item_step.result2 IN ("R1", "R2")) AND 
item.time >= '2015-03-01 07:00:00' AND 
item.time < '2015-05-01 07:00:00';

像往常一样,我尝试使用解释来检查它:

+----+-------------+-----------+------+----------------------------+---------+---------+------------------+--------+----------+-------------+
| id | select_type | table     | type | possible_keys              | key     | key_len | ref              | rows   | filtered | Extra       |
+----+-------------+-----------+------+----------------------------+---------+---------+------------------+--------+----------+-------------+
|  1 | SIMPLE      | item      | ALL  | PRIMARY,time               | NULL    | NULL    | NULL             | 790464 |    38.74 | Using where |
|  1 | SIMPLE      | item_step | ref  | number,item_id,result2_idx | item_id | 4       | debug_db.item.id |      1 |   100.00 | Using where |
+----+-------------+-----------+------+----------------------------+---------+---------+------------------+--------+----------+-------------+

idtime 上向表item 添加索引没有给出任何结果。 实际上time 列有一个索引,表使用外键连接并有一个索引..

我不知道在这里做什么。难道真的不可能优化这个查询来避免使用 join_type = ALL 吗?

【问题讨论】:

您对项目(编号)有索引吗?我会尝试在 item(number, time,Id,duration) 上添加索引。 @Tim3880 号码属于 item_step,是的,它有一个索引。我现在将尝试对 item(time, id, duration) 的索引... 我的错,请忽略它。 只要有人尝试我的想法,就已经感觉更好了。 @BogdanBurim 有可能通过使用covering index 使其更快 - 在您的情况下,可能是 item_step 上的 (number, item_id, result, result2) 和 (id, time, duration) on item - 允许查询根本不从表中读取,因为所有需要的数据已经按排序顺序存储在某个索引中。但它在数据大小和插入/更新时的性能方面是有代价的,所以应该只在 (number) 上添加索引不够时使用。 【参考方案1】:

由于您似乎已经拥有从 item_step.item_iditem.item_id 的 FK,因此您唯一可以改进的选择是专注于用于过滤记录的部分。

稍微重新格式化您的查询:

SELECT AVG(item.duration) AS dur 
  FROM `item` 
  INNER JOIN item_step 
     ON item_step.item_id = item.id 
    AND item_step.number = '2' 
    AND (IS_OK(item_step.result) OR item_step.result2 IN ("R1", "R2"))
  WHERE item.time >= '2015-03-01 07:00:00'
    AND item.time < '2015-05-01 07:00:00';

首先要注意的是IS_OK(item_step.result)。我不知道这个函数的背后是什么,但我很确定它会阻止优化器有效地使用这个字段的任何索引。如果公式可以直接写在查询中,我建议这样做。 (例如IN (1, 4, 9),或IN (SELECT OK FROM result_values)等...)

按照字段名称,我将假设我们首先要将item_id 列表减少到最低限度,然后使用该减少的列表来处理item_step 表。为此,您首先需要在time 字段上建立索引。我假设 item_id 字段自动包含在索引中,因为它是 PK 字段,但我不是 MySQL 专家,它也可能取决于您的存储引擎。无论如何,在 MSSQL 中它就是这样工作的,YMMV。

接下来要做的第二件事是将item_ids 列表转到item_step 表并减少那里的记录数。为此,您需要item_id, number, result2, result 上的复合索引。如果您设法将 IS_OK() 函数“内联”写入查询中,您可能想尝试交换最后两个字段...您需要测试的东西。

从我在这里和那里读到的内容来看,MySQL 不像 MSSQL 那样支持索引上的 INCLUDE 之类的东西。一种解决方法是在item 上的time, duration 上创建一个“覆盖”索引。这样,一切都可以直接从索引中完成,但在向item 表添加数据时需要更多的磁盘空间和 CPU 要求。

简而言之:

item 上添加索引time, durationitem_step 上添加索引item_id, number, result2, result 看看是否可以内联 IS_OK() 函数。

【讨论】:

从问题中的解释可以看出,时间索引已检查但仍未使用-因此该条件很可能没有足够的选择性。您的 (time, duration) 在 InnoDB 中等同于 (time, duration, id) (如果 id 是主键),因此它“禁止”更改连接顺序,因为它不能用于获取实际 item_step 的正确项目.但似乎大多数条件都在 item_step 上,所以(从我们可以从没有表统计的问题中看到)“反向”连接顺序对我来说似乎更好。 内联 IS_OK 做出了 2.2 倍的积极变化,其他事情已经完成(索引),有些根本没有改变情况(重新格式化 where 条件以加入规则)。谢谢! 重新格式化并没有真正带来太多好处。我猜大多数(现代)RDBMS 并不真正关心谓词的顺序,但是通过将事物分组到它们的逻辑位置,它使我们人类更容易理解正在发生的事情。太糟糕了,时间字段的选择性不够。希望目前的情况“足够快”。祝你好运! @user1786423:我无法从谷歌得到明确的答案,所以感谢您确认它是如何工作的! @deroby 是的,它取决于引擎,但 InnoDB 使用主键线索(总是,你不能通过其他索引进行集群),所以主键我总是附加到每个索引。如果没有主要的,则使用第一个唯一的,或者如果没有唯一的,则生成内部“类似自动增量”的集群键。

以上是关于SQL 查询优化——真的没有啥可改进的了吗?的主要内容,如果未能解决你的问题,请参考以下文章

sql优化:通过子查询或自己的查询计算所有行/其他改进

TiDB 查询优化及调优系列调优案例实践

使用多个连接和条件优化 SQL 查询

sql优化

TiDB 查询优化及调优系列TiDB 查询计划简介

如何对Oracle sql 进行性能优化的调整