优化 SQL 查询以避免全表扫描

Posted

技术标签:

【中文标题】优化 SQL 查询以避免全表扫描【英文标题】:Optimizing a SQL query to avoid full table scan 【发布时间】:2011-05-19 02:00:55 【问题描述】:

考虑以下查询:

SELECT * FROM Transactions
WHERE day(Stamp - interval 3 hour) = 1;

Transactions 表中的Stamp 列是一个TIMESTAMP,上面有一个索引。 如何更改此查询以避免全表扫描? (即在 day() 函数之外使用 Stamp

谢谢!

【问题讨论】:

我不是要“功能索引”——它们不存在。相反,我想以与将“SELECT * FROM table WHERE sqrt(column) = 2”转换为“SELECT * FROM table WHERE column = 4”相同的方式转换此查询 【参考方案1】:

我会这样做:

添加一些额外的字段:YEAR、MONTH、DAY 甚至 HOUR、MINUTE,具体取决于您期望的流量。 然后构建一个触发器来填充额外的字段,可能会提前减去 3 小时的间隔。 最后在额外的字段上建立一些索引。

【讨论】:

mysql 不支持函数索引 - Massimog 的解决方案是指定每个可能的日期范围的唯一替代方案 - 但是由于您的查询可能会提取 1/30 的行,因此使用索引查找将并不比使用全表扫描快很多。 您在性能方面的考虑可能是对的 - 从来没有真正考虑过这一点。对转换我的查询更感兴趣。 看起来我想要的不太可能,从性能的角度来看,您的建议看起来是最好的。【参考方案2】:

如果目标只是避免全表扫描,并且您有一个用于事务的主键(比如命名为 PK),请考虑添加覆盖索引

ALTER TABLE Transactions ADD INDEX cover_1 (PK, Stamp)

然后

SELECT * FROM Transactions WHERE PK IN (SELECT PK FROM Transactions
WHERE day(Stamp - interval 3 hour) = 1
 )

此查询不应使用全表扫描(但是如果表中的行数很少或出于任何其他统计原因,优化器可能会决定使用全表扫描:))

更好的方法可能是使用临时表而不是子查询。

【讨论】:

【参考方案3】:

你可以经常重写函数,这样你就有了看起来像WHERE Stamp=XXXX 的东西,而 XXXX 是一些表达式。您可以为每个月创建一系列 BETWEEN 语句 WHERE Stamp BETWEEN timestamp('2010-01-01 00:00:00') AND timestamp ('2010-01-01 23:59:59') OR Stamp BETWEEN ...,但我不确定在这种情况下这是否会使用索引。正如@petr 所建议的那样,我将建立一个列是一个月中的某一天。

【讨论】:

【参考方案4】:

在运行主查询之前单独计算所需的 Stamp 值,即

第 1 步 - 计算所需的 Stamp 值

第 2 步 - 运行查询,其中 Stamp >(计算值)

因为第 2 步中没有计算,所以您应该可以使用您的索引。

【讨论】:

【参考方案5】:

如果我理解正确,您基本上想返回邮票落在每个月第一天的所有行(减去 3 小时)?如果(这是一个很大的如果),你有一个固定的窗口,比如最近的 6 个月,你可以列举 6 个范围测试。但是,我仍然不确定索引访问是否会更快。

select *
  from transactions
 where stamp between timestamp '2010-06-01 03:00:00' and timestamp '2010-06-02 02:59:59'
    or stamp between timestamp '2010-07-01 03:00:00' and timestamp '2010-07-02 02:59:59'
    or stamp between timestamp '2010-08-01 03:00:00' and timestamp '2010-08-02 02:59:59'
    or stamp between timestamp '2010-09-01 03:00:00' and timestamp '2010-09-02 02:59:59'
    or stamp between timestamp '2010-10-01 03:00:00' and timestamp '2010-10-02 02:59:59'
    or stamp between timestamp '2010-11-01 03:00:00' and timestamp '2010-11-02 02:59:59'
    or stamp between timestamp '2010-12-01 03:00:00' and timestamp '2010-12-02 02:59:59';

注意!我不确定时间戳的毫秒部分是如何工作的。您可能需要相应地填充它。

【讨论】:

【参考方案6】:

稍微修改 petr 的答案以避免 IN 子句,并使其适用于 MyISAM 或 InnoDB。

对于 MyISAM

ALTER TABLE Transactions ADD INDEX cover_1 (PK, Stamp)

或者,对于 InnoDB,PK 隐式包含在每个索引中,

ALTER TABLE Transactions ADD INDEX Stamp (Stamp)

然后

SELECT * 
FROM Transactions LEFT JOIN
  (
  SELECT PK 
  FROM Transactions 
  WHERE DAYOFMONTH(Stamp - interval 3 hour) = 1
  ) a ON Transactions.PK=a.PK

子查询将只执行索引,而外部查询只会从 a.PK 所经过的表中提取行。

【讨论】:

以上是关于优化 SQL 查询以避免全表扫描的主要内容,如果未能解决你的问题,请参考以下文章

避免全表扫描的sql优化

大数据课堂0008会引起全表扫描的几种SQL 以及sql优化

Oracle SQL优化必要的全表扫描思路分析

SQL查询语句优化方法

MySQL---sql语句优化

MySQL---sql语句优化