SQL 查询优化 - 真的没有其他需要改进的地方了吗?

SQL query optimization - really nothing more to improve?

我有以下查询。我从 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 上向 table item 添加索引没有任何结果。 实际上 time 列有一个索引,tables 使用外键连接并有一个索引..

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

由于您似乎已经拥有从 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 table。为此,您首先需要在 time 字段上建立索引。我假设 item_id 字段自动包含在索引中,因为它是 PK 字段,但我不是 MySQL 专家,它也可能取决于您的存储引擎。无论如何,在 MSSQL 中它就是这样工作的,YMMV。

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

根据我在这里和那里阅读的内容,MySQL 不支持像 MSSQL 一样的索引上的 INCLUDE 之类的东西。一种解决方法是在 item 上的 time, duration 上创建一个 'covering' 索引。这样,一切都可以直接从索引完成,代价是在将数据添加到 item table 时需要更多的磁盘 space 和 CPU 要求。

简而言之:

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