闲扯
很久以前,有一次我写了一个SQL:
select count(*) from test;
然后这个代码被我的其中一家公司的MySQL专家看到了,叫我过去说:
你难道不知道咱们不允许写count(*)吗?你不知道count(1)更快吗?
说完二话没说把我的SQL改了。
我自然是惴惴不安。
谁对谁错
先说结论,我是对的,count(*)不但不慢,count(1)也会和count(*)走一样的执行计划。
我们先看这个表:
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`xid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `xid` (`xid`)
) ENGINE=InnoDB;
现在看看count(*)的执行计划:
可以看到,这里出现了索引覆盖,也就是说优化器选择了xid这个索引进行了覆盖。
那么看看count(1)的执行计划:
结果是完全一样的。其实优化器现在是很智能的,不会因为你写了count(1)它就能出现什么黑科技,还是老老实实的一行一行在索引上统计,但是好处是不用二次回表检索了。
那么如果是count某个列呢?
我们首先给这个表里插入一条奇怪的数据:
insert into test(xid) values (null);
这个时候执行
select count(xid) from test;
结果也是很奇怪:
明显有问题,count(*)的结果是11。
看看执行计划:
可以看到还是索引覆盖的,而且真的扫描了11行。
MySQL在处理这种count(columnName)的时候会自动的把为null的列忽略掉,因此带来了看似不正确的结果。
《高性能MySQL》上关于NULL的论述中专门提到了,对于要索引的列,最好设置成not null。
这个现象也是该论调的一个有力证明。
写给杠精
可能有人会问,如果这个表上没有二级索引呢?岂不是count(1)更快?
其实不是这样的,我把表的索引去掉,这是现在的表结构:
这是两种SQL的执行计划:
还是完全一样。
其实想想,如果Oracle自己都发现了这个奇技淫巧,那么他们的工程师也会对这个进行优化的。
结论
直接count(*)就好,count(1)快这个论调就是个MySQL日常迷信。