SQL COUNT查询优化
查询记录表总量有两种方法
1 | SELECT COUNT(*) FROM table; |
这两种方法效果有所不同,COUNT(*)
返回的一定是表记录总量,COUNT(col_name)
返回的是该字段不为NULL值得记录总量。这两种查询可能一直也可能不一致。
在表记录较多的情况下,COUNT(*)
的执行时间会比较长。在生产环境这非常可怕。因为这一慢查询的执行,可能引起其他SQL
的效率低下,有可能引起链式反应像核爆炸那样耗尽服务器资源。
我们尽量要在生产环境避免这类语句执行。如果一定要,那么在满足一定条件下可以使用下面的小技巧来优化。
- 第一个条件是该表拥有自增长字段,并定义为主键。
- 第二个条件是该表不删除记录或只从头部删除数据,即保持表内记录的连续性。
满足以上条件,我们就可以使用下面的语句来替代COUNT(*)
1 | SELECT (MAX(id) - MIN(id) + 1) AS total FROM table; |
如果要使用WHERE
来缩减范围,只要不破坏数据的连续性的条件也可以使用这个方法优化。比如id
大于或者小鱼某个值,创建记录大于或者小于某个时间点等等。从优化原则来说,where可能会降低效率。但是如果where设定的合理,符合一定条件,也可以实现查询优化效果。如果条件是索引列,那么查询效率可能会较高。不过这是对于一般的SQL
查询来说,如果前提是“查询记录总数”,那就不一定了。
如果这个索引列具有跟自增长字段一致的顺序且连续,这个对于“查询记录总条数”是很好的,在缩小数据集范围的同时,还可以利用上文给出的小技巧,利用自增长字段高效得出结果。在利用这一条件时,需要注意以下几点:
- 不要对时间字段使用函数 Exp.
year(par_day)
- 正确使用时间段:尽量给出开始和结束时间,尽量避免单独使用大于或小于号
- 使用
BETWEEN
比使用大于小于号更好
当条件不具有连续性和顺序性时,如果能大量缩减数据集范围,也会有较高效率。但是就不能使用上文的小技巧了。
当使用SELECT COUNT(*) FROM table WHERE conditions
时,速度快的原因是数据经过过滤之后条数较少。当条件不具有连续性和顺序性,且过滤之后数据集依旧庞大时,效率也依然不会提高,这种情况下应该怎么办?
此时可以设计一些辅助统计的表来帮助我们达成提升效率的问题。虽然这些标的数据也是通过低效的查询得来的,但是这种方法可以避免低效SQL
影响生产系统。可以使用主从结构,专门使用一个从库来执行类似任务。另外,这种计算一般是一天一次或者一天数次,不会反复执行。
一般使用COUNT(*)
的很多情况是为了实现分页功能,如果记录是一个千万级以上的表,分页显示实际用户很少能浏览全部页数。基于这个原因,记录总数的计算是没有必要特别精准。换句话说使用一个数量级差不多的估算值也是可以的。因此,我们就可以通过explain来获得估算值。
1 | explain SELECT COUNT(*) AS aggregate FROM 'table' |
结果可以获得一个估计值(数量级相同,相差10%左右?)
在网上经常会看到一个观点,推荐使用COUNT(1)
来替代COUNT(*)
,认为可以提高性能。首先我们来了解一下COUNT()
函数的不同用法:
COUNT(*)
计算全部行总和
COUNT(1)
计算全部行总和,1代表一个固定值,不是第一个字段。其效果跟COUNT(*)`一样。
COUNT(col_name)
计算该列值非null行总和
经过测试我们发现这两种方法的速度相差无几。使用explain
可以看到,优化器对两种写法的分析结果是一致的。在优化器中,我们看到,它会使用主键索引。这对于提升性能有帮助。所以,在有计算COUNT()
的表内尽量创建索引,尽量使用自增长字段作为主键。不仅在这种情况有帮助,对于很多情况都有好处。
最后,如果要使用COUNT(col_name)
,那么你要非常清晰的理解你要的结果。