MySQL의 count(*)가 count(1)보다 정말 빠른가요? 다음 기사에서는 MySQL의 count(*)와 count(1)을 비교하여 성능 차이를 확인하는 데 도움이 되기를 바랍니다.
오늘 누군가가 MySQL에서 count(1)
가 count(*)
보다 빠르다고 말했습니다. 이것을 견딜 수 있나요? 당신은 그와 휴식을 취해야합니다. count(1)
比 count(*)
快,这能忍?必须得和他掰扯掰扯。
声明:以下讨论基于 InnoDB 存储引擎,MyISAM 因为情况特殊我在文末会单独说一下。【相关推荐:mysql视频教程】
先说结论:这两个性能差别不大。
我准备了一张有 100W 条数据的表,表结构如下:
CREATE TABLE `user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `address` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
可以看到,有一个主键索引。
我们来用两种方式统计一下表中的记录数,如下:
可以看到,两条 SQL 的执行效率其实差不多,都是 0.14s。
再来看另外两个统计:
id 是主键,username 以及 address 则是普通字段。
可以看出,用 id 来统计,也有一丢丢优势。松哥这里因为测试数据样板比较小,所以效果不明显,小伙伴们可以加大测试数据量,那么这种差异会更加明显。
那么到底是什么原因造成的这种差异,接下来我们就来简单分析一下。
我们先用 explain 来看下这几个 SQL 不同的执行计划:
可以看到,前三个统计方式的执行计划是一样的,后面两个是一样的。
我这里和大家比较下 explain 中的不同项:
通过 explain 我们其实也能大概看出来前三种统计方式的执行效率是要高一些的(因为用到了索引),而后面两种的统计效率相对来说要低一些的(没用索引,需要全表扫描)。
仅有上面的分析还不够,我们再来从原理角度来分析一下。
3.1 主键索引与普通索引
在开始原理分析以前,我想先带领大家看一下 B+ 树,这对于我们理解接下来的内容有重要作用。
大家都知道,InnoDB 中索引的存储结构都是 B+ 树(至于什么是 B+ 树,和 B 树有什么区别,这个本文就不讨论了,这两个单独都能整出来一篇文章),主键索引和普通索引的存储又有所不同,如下图表示主键索引:
可以看到,在主键索引中,叶子结点保存了每一行的数据。
而在普通索引中,叶子结点保存的是主键值,当我们使用普通索引去搜索数据的时候,先在叶子结点中找到主键,再拿着主键去主键索引中查找数据,相当于做了两次查找,这也就是我们平常所说的回表操作。
3.2 原理分析
不知道小伙伴们有没有注意过,我们学习 MySQL 的时候,count 函数是归在聚合函数那一类的,就是 avg、sum 等,count 函数和这些归在一起,说明它也是一个聚合函数。
既然是聚合函数,那么就需要对返回的结果集进行一行行的判断,这里就涉及到一个问题,返回的结果是啥?我们分别来看:
对于 select count(1) from user;
select count(1) from user;
쿼리의 경우 InnoDB 엔진은 통과할 최소 인덱스 트리(기본 키 인덱스일 필요는 없음)를 찾지만 not 데이터를 읽겠지만 리프 노드를 읽으면 1을 반환하고 최종적으로 결과가 누적됩니다. 🎜select count(id) from user;
쿼리의 경우 InnoDB 엔진은 전체 기본 키 인덱스를 순회한 다음 ID를 읽고 반환하지만 ID가 기본 키이기 때문에 B+ 트리의 리프 노드에 있으므로 이 프로세스에는 무작위 IO가 포함되지 않으며(데이터 페이지에서 데이터를 가져오기 위해 테이블로 돌아가는 등의 작업이 필요하지 않음) 성능도 괜찮습니다. select count(id) from user;
这个查询来说,InnoDB 引擎会遍历整个主键索引,然后读取 id 并返回,不过因为 id 是主键,就在 B+ 树的叶子节点上,所以这个过程不会涉及到随机 IO(并不需要回表等操作去数据页拿数据),性能也是 OK 的。
对于 select count(username) from user;
这个查询来说,InnoDB 引擎会遍历整张表做全表扫描,读取每一行的 username 字段并返回,如果 username 在定义时候设置了 not null,那么直接统计 username 的个数;如果 username 在定义的时候没有设置 not null,那么就先判断一下 username 是否为空,然后再统计。
最后再来说说 select count(*) from user;
,这个 SQL 的特殊之处在于它被 MySQL 优化过,当 MySQL 看到 count(*)
就知道你是想统计总记录数,就会去找到一个最小的索引树去遍历,然后统计记录数。
因为主键索引(聚集索引)的叶子节点是数据,而普通索引的叶子节点则是主键值,所以普通索引的索引树要小一些。然而在上文的案例中,我们只有主键索引,所以最终使用的就是主键索引。
现在,如果我修改上面的表,为 username 字段也添加索引,然后我们再来看 explain select count(*) from user;
的执行计划:
可以看到,此时使用的索引就是 username 索引了,和我们前面的分析结果是一致的。
从上面的描述中我们就可以看出,第一个查询性能最高,第二个次之(因为需要读取 id 并返回),第三个最差(因为需要全表扫描),第四个的查询性能则接近第一个。
可能有小伙伴知道,MyISAM 引擎中的 select count(*) from user;
select count(username) from user;
쿼리의 경우 InnoDB 엔진은 전체 테이블 스캔을 수행하기 위해 전체 테이블을 탐색하고 각 행의 사용자 이름 필드를 읽고 사용자 이름이 다음과 같은 경우 이를 반환합니다. null이 아닌 경우 정의된 경우 사용자 이름 수가 직접 계산됩니다. 정의 시 사용자 이름이 null로 설정되지 않은 경우 먼저 사용자 이름이 비어 있는지 확인한 다음 계산합니다. 마지막으로 select count(*) from user;
에 대해 이야기해 보겠습니다. 이 SQL의 특별한 점은 MySQL이 count(*)
를 볼 때 최적화되었다는 것입니다. > 총 레코드 수를 계산하려면 탐색할 최소 인덱스 트리를 찾은 다음 레코드 수를 계산한다는 점만 알아두십시오.
기본 키 인덱스(클러스터형 인덱스)의 리프 노드는 데이터이고 일반 인덱스의 리프 노드는 기본 키 값이므로 일반 인덱스의 인덱스 트리는 더 작습니다. 그러나 위의 경우에는 기본키 인덱스만 있으므로 최종적으로는 기본키 인덱스가 사용됩니다.이제 위 표를 수정하고 사용자 이름 필드에 인덱스를 추가하면
explain select count(*) from user;
의 실행 계획을 살펴보겠습니다.
인덱스는 사용자 이름 인덱스로, 이전 분석 결과와 일치합니다. 위 설명을 보면 첫 번째 쿼리의 성능이 가장 높고, 두 번째 쿼리가 두 번째(ID를 읽고 반환해야 하기 때문에), 세 번째 쿼리가 가장 나쁘다는 것을 알 수 있습니다(테이블 전체를 스캔해야 하기 때문에). , 네 번째 두 번째 쿼리 성능은 첫 번째 쿼리 성능에 가깝습니다.
select count(*) from user;
작업이 매우 빠르다는 것을 아는 친구도 있을 것입니다. 이는 MyISAM이 테이블에 행 수를 직접 저장하기 때문입니다. 디스크에 있으므로 필요할 때 직접 읽을 수 있으므로 속도가 매우 빠릅니다. MyISAM 엔진이 이렇게 하는 이유는 주로 트랜잭션을 지원하지 않기 때문입니다. 따라서 통계는 실제로 레코드 행만 추가하면 매우 쉽습니다. 🎜🎜하지만 우리가 일반적으로 사용하는 InnoDB는 이것을 할 수 없습니다! 왜? InnoDB는 트랜잭션을 지원하기 때문입니다! InnoDB는 트랜잭션을 지원하기 위해 MVCC 다중 버전 동시성 제어를 도입하므로 데이터를 읽을 때 더티 읽기, 팬텀 읽기, 반복 불가능한 읽기 등의 문제가 발생할 수 있습니다. 🎜🎜🎜자세한 내용은 https:/를 참조하세요. /www.bilibili.com/video/BV14L4y1B7mB🎜🎜🎜따라서 InnoDB는 각 데이터 행을 꺼내서 해당 데이터 행이 현재 세션에 표시되는지 여부를 확인해야 합니다. 표시되는 경우 데이터 행이 계산됩니다. , 그렇지 않으면 계산되지 않습니다. 🎜🎜물론, MySQL의 MVCC는 사실 매우 거창한 주제입니다. 송 형제님이 나중에 시간이 나면 MVCC에 대해 자세히 소개해 드리겠습니다. 🎜🎜그럼 친구 여러분, 이제 이해가 되셨나요? 질문이 있으시면 토론을 위해 메시지를 남겨주세요. 🎜🎜더 많은 프로그래밍 관련 지식을 보려면 🎜프로그래밍 비디오🎜를 방문하세요! ! 🎜위 내용은 MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!