MySQL SELECT count(*) 性能优化

1
SELECT count(*) as count FROM mytable WHERE is_del= 0 AND is_use = 1;

上面这个简单的语句却被记录在MySQL的慢日志。

这个InnoDB表有70多万的数据量,有text字段,is_del和is_use是组合索引。

目前除了使用SHOW TABLE STATUS WHERE NAME = ‘mytable’的近似行数,还没有找到更明确的解决办法。

下面是一些记录:

MyISAM SELECT COUNT(*)

对于MyISAM表,SELECT检索,在没有检索其他的列,同时没有WHERE子句时,COUNT(*)是被优化的,返回结果非常快,例如:

1
mysql> SELECT COUNT(*) FROM student;

这种优化仅适用于MyISAM表,因为对于存储引擎,有一个精确的行数被存储,并且可以被快速访问。如果第一列被定义为NOT NULL,COUNT(1)有同样的优化。·

注:COUNT(col)统计行数中不为NULL的列,它可能速度会慢,个人感觉并不是因为他不是索引,而是因为他包含NULL行。

InnoDB SELECT COUNT(*)

InnoDB没有保存表中行的内部计数,因为并发事务可能在同一时间看到不同的行数(具体可查看隔离级别的内容),因此,SELECT COUNT(*)只计数当前事务可见的行。

在MySQL5.7.18之前,InnoDB通过扫描聚簇索引执行SELECT COUNT()。在MySQL5.7.18,InnoDB执行SELECT COUNT()通过遍历一个小的二级索引,如果存在的话。

如果索引没有完全在缓存池中,执行SELECT COUNT(*)会花费一些时间。为了更快的计数,你可以创建一个计数表,让你的应用在插入和删除的时候更新它。然而,这种方式在成千上万的并发事务启动更新同一个计数表的情况下伸缩性不好。如果近似的值足够了,SHOW TABLE STATUS WHERE NAME = 'mytable'可以被利用。

InnoDB处理SELECT COUNT(*)和SELECT COUNT(1)一样,没有性能差异。

因为count()通常用来统计计数,所以要扫描许多行,基本没有什么精确的优化方法。但是对于MyISAM引擎带有WHERE子句的查询还是有优化余地的,例如t1表里面有5000行数据,要统计id大于5的列,下面两种作比较:

SELECT COUNT(*) FROM t1 WHERE id > 5;

SELECT (SELECT COUNT(*) FROM t1) - COUNT(*) FROM t1 WHERE id<= 5;

第一个语句扫描4995列,而第二个只扫描5列,因为优化阶段会将子句解析为常数,差异还是很明显的。

(完)