MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?

青灯夜游
풀어 주다: 2021-12-20 17:33:13
앞으로
2439명이 탐색했습니다.

MySQL의 count(*)가 count(1)보다 정말 빠른가요? 다음 기사에서는 MySQL의 count(*)와 count(1)을 비교하여 성능 차이를 확인하는 데 도움이 되기를 바랍니다.

MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?

오늘 누군가가 MySQL에서 count(1)count(*)보다 빠르다고 말했습니다. 이것을 견딜 수 있나요? 당신은 그와 휴식을 취해야합니다. count(1)count(*) 快,这能忍?必须得和他掰扯掰扯。

声明:以下讨论基于 InnoDB 存储引擎,MyISAM 因为情况特殊我在文末会单独说一下。【相关推荐:mysql视频教程

先说结论:这两个性能差别不大。

1.实践

我准备了一张有 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;
로그인 후 복사

可以看到,有一个主键索引。

我们来用两种方式统计一下表中的记录数,如下:

MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?

可以看到,两条 SQL 的执行效率其实差不多,都是 0.14s。

再来看另外两个统计:

MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?

id 是主键,username 以及 address 则是普通字段。

可以看出,用 id 来统计,也有一丢丢优势。松哥这里因为测试数据样板比较小,所以效果不明显,小伙伴们可以加大测试数据量,那么这种差异会更加明显。

那么到底是什么原因造成的这种差异,接下来我们就来简单分析一下。

2. explain 分析

我们先用 explain 来看下这几个 SQL 不同的执行计划:

MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?

可以看到,前三个统计方式的执行计划是一样的,后面两个是一样的。

我这里和大家比较下 explain 中的不同项:

  • type:前三个的 type 值为 index,表示全索引扫描,就是把整个索引过一遍就行(注意是索引不是整个表);后两个的 type 值为 all,表示全表扫描,即不会使用索引。
  • key:这个表示 MySQL 决定采用哪个索引来优化对该表的访问,PRIMARY 表示利用主键索引,NULL 表示不用索引。
  • key_len:这个表示 MySQL 使用的键长度,因为我们的主键类型是 INT 且非空,所以值为 4。
  • Extra:这个中的 Using index 表示优化器只需要通过访问索引就可以获取到需要的数据(不需要回表)。

通过 explain 我们其实也能大概看出来前三种统计方式的执行效率是要高一些的(因为用到了索引),而后面两种的统计效率相对来说要低一些的(没用索引,需要全表扫描)。

仅有上面的分析还不够,我们再来从原理角度来分析一下。

3. 原理分析

3.1 主键索引与普通索引

在开始原理分析以前,我想先带领大家看一下 B+ 树,这对于我们理解接下来的内容有重要作用。

大家都知道,InnoDB 中索引的存储结构都是 B+ 树(至于什么是 B+ 树,和 B 树有什么区别,这个本文就不讨论了,这两个单独都能整出来一篇文章),主键索引和普通索引的存储又有所不同,如下图表示主键索引:

MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?

可以看到,在主键索引中,叶子结点保存了每一行的数据。

而在普通索引中,叶子结点保存的是主键值,当我们使用普通索引去搜索数据的时候,先在叶子结点中找到主键,再拿着主键去主键索引中查找数据,相当于做了两次查找,这也就是我们平常所说的回表操作。

3.2 原理分析

不知道小伙伴们有没有注意过,我们学习 MySQL 的时候,count 函数是归在聚合函数那一类的,就是 avg、sum 等,count 函数和这些归在一起,说明它也是一个聚合函数。

既然是聚合函数,那么就需要对返回的结果集进行一行行的判断,这里就涉及到一个问题,返回的结果是啥?我们分别来看:

对于 select count(1) from user;

면책조항: 다음 논의는 InnoDB 스토리지 엔진을 기반으로 합니다. MyISAM의 특수한 상황으로 인해 기사 마지막에 별도로 설명하겠습니다. [관련 권장 사항: mysql 비디오 튜토리얼]🎜🎜 결론부터 시작하겠습니다.둘 사이의 성능에는 큰 차이가 없습니다. 🎜

1. 연습

🎜100만 개의 데이터로 테이블을 준비했습니다. 테이블 구조는 다음과 같습니다. 🎜rrreee🎜보시다시피 , 기본 키 인덱스가 있습니다. 🎜🎜다음과 같이 두 가지 방법으로 테이블의 레코드 수를 계산해 보겠습니다. 🎜🎜🎜🎜두 SQL의 실행 효율성이 실제로는 둘 다 0.14초로 비슷하다는 것을 알 수 있습니다. 🎜🎜다른 두 통계를 살펴보겠습니다. 🎜🎜 MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?🎜🎜id는 기본 키이고 사용자 이름과 주소는 일반 필드입니다. 🎜🎜통계에 ID를 사용하면 몇 가지 장점이 있음을 알 수 있습니다. 송 형제 님, 테스트 데이터 샘플이 상대적으로 작기 때문에 효과가 명확하지 않습니다. 친구가 테스트 데이터의 양을 늘리면 차이가 더 분명해질 것입니다. 🎜🎜그럼 이 차이가 나는 이유는 무엇일까요? 다음에는 간단히 분석해 보겠습니다. 🎜

2. 분석 설명

🎜 먼저 explain을 사용하여 다양한 SQL 실행 계획을 살펴보겠습니다. 🎜🎜MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?🎜🎜 처음 세 가지 통계 방법의 실행 계획이 동일함을 알 수 있으며, 마지막 두 개는 동일합니다. 🎜🎜여기서 설명의 다양한 항목을 비교해 보겠습니다. 🎜
  • type: 처음 세 가지의 유형 값은 index입니다. 이는 전체 인덱스 스캔을 의미합니다. 즉, 전체 인덱스를 살펴봅니다. (인덱스는 전체 테이블이 아닙니다). 후자의 두 유형 값은 모두입니다. 이는 전체 테이블 스캔을 의미합니다. 즉, 인덱스는 테이블이 아닙니다. 사용된.
  • 키: 이는 MySQL이 테이블에 대한 액세스를 최적화하는 데 사용할 인덱스를 결정한다는 의미입니다. PRIMARY는 기본 키 인덱스를 사용한다는 의미이고, NULL은 인덱스가 사용되지 않음을 의미합니다.
  • key_len: 이는 MySQL에서 사용하는 키 길이를 나타냅니다. 기본 키 유형이 INT이고 null이 아니기 때문에 값은 4입니다.
  • 추가: 여기서 인덱스를 사용하면 최적화 프로그램이 필요한 데이터를 얻기 위해 인덱스에 액세스하기만 하면 됩니다(테이블을 반환할 필요가 없음).
🎜설명을 통해 실제로 처음 세 가지 통계 방법의 실행 효율성이 (인덱스 사용으로 인해) 더 높은 반면, 후자 두 가지의 통계 효율성은 더 높다는 것을 대략적으로 알 수 있습니다. 상대적으로 낮다고 합니다(인덱스를 사용하지 않고 전체 테이블 스캔이 필요함). 🎜🎜위의 분석만으로는 충분하지 않습니다. 🎜

3. 원리 분석

🎜3.1 기본 키 인덱스와 일반 인덱스🎜🎜원리 분석을 시작하기 전에 다음 내용을 이해하는 데 중요한 B+ 트리를 살펴보시기 바랍니다. 🎜🎜InnoDB의 인덱스 저장 구조가 B+ 트리라는 것은 누구나 알고 있습니다(B+ 트리가 무엇인지, B+ 트리와 B-트리의 차이점에 대해서는 이 기사에서는 이에 대해 논의하지 않습니다. 둘 다 가능합니다). 기사에 나와 있음) 기본 키 인덱스와 일반 인덱스의 저장 방식이 다릅니다. 다음 그림은 기본 키 인덱스를 보여줍니다. 🎜🎜🎜🎜기본키 인덱스를 보면 리프 노드가 각 행의 데이터를 저장하는 것을 볼 수 있습니다. 🎜🎜일반 인덱스에서는 리프 노드에 기본 키 값이 저장됩니다. 일반 인덱스를 사용하여 데이터를 검색할 때 먼저 리프 노드에서 기본 키를 찾은 다음 기본 키를 사용하여 데이터를 검색합니다. 기본 키 인덱스입니다. Yu는 일반적으로 테이블 반환 작업이라고 부르는 두 가지 검색을 수행했습니다. 🎜🎜3.2 원리 분석🎜🎜친구들이 MySQL을 배울 때 카운트가 function was 집계함수에 속하는 것은 avg, sum 등이 있습니다. count 함수도 이들과 함께 그룹화되어 집계함수임을 나타냅니다. 🎜🎜집계 함수이기 때문에 반환된 결과를 한 줄씩 판단해야 합니다. 여기에는 반환된 결과가 무엇입니까?라는 질문이 포함됩니다. 별도로 살펴보겠습니다. 🎜🎜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; 的执行计划:

MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?

可以看到,此时使用的索引就是 username 索引了,和我们前面的分析结果是一致的。

从上面的描述中我们就可以看出,第一个查询性能最高,第二个次之(因为需要读取 id 并返回),第三个最差(因为需要全表扫描),第四个的查询性能则接近第一个。

4. MyISAM 呢?

可能有小伙伴知道,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;의 실행 계획을 살펴보겠습니다.

MySQL에서 count(*)를 분석하는 것이 count(1)보다 정말 빠른가요?

인덱스는 사용자 이름 인덱스로, 이전 분석 결과와 일치합니다.

위 설명을 보면 첫 번째 쿼리의 성능이 가장 높고, 두 번째 쿼리가 두 번째(ID를 읽고 반환해야 하기 때문에), 세 번째 쿼리가 가장 나쁘다는 것을 알 수 있습니다(테이블 전체를 스캔해야 하기 때문에). , 네 번째 두 번째 쿼리 성능은 첫 번째 쿼리 성능에 가깝습니다.

4. MyISAM은 어떻습니까?

MyISAM 엔진의 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:juejin.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿