IT评测·应用市场-qidao123.com技术社区

标题: SELECT COUNT(*) 会造成全表扫描?回去等通知吧 [打印本页]

作者: 莱莱    时间: 2023-4-12 23:23
标题: SELECT COUNT(*) 会造成全表扫描?回去等通知吧
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~
Github地址
如果访问不了Github,可以访问gitee地址。
gitee地址
前言

SELECT COUNT(*)会不会导致全表扫描引起慢查询呢?
  1. SELECT COUNT(*) FROM SomeTable
复制代码
网上有一种说法,针对无 where_clause 的 COUNT(*),MySQL 是有优化的,优化器会选择成本最小的辅助索引查询计数,其实反而性能最高,这种说法对不对呢
针对这个疑问,我首先去生产上找了一个千万级别的表使用  EXPLAIN 来查询了一下执行计划
  1. EXPLAIN SELECT COUNT(*) FROM SomeTable
复制代码
结果如下

如图所示: 发现确实此条语句在此例中用到的并不是主键索引,而是辅助索引,实际上在此例中我试验了,不管是 COUNT(1),还是 COUNT(),MySQL 都会用成本最小的辅助索引查询方式来计数,也就是使用 COUNT() 由于 MySQL 的优化已经保证了它的查询性能是最好的!随带提一句,COUNT()是 SQL92 定义的标准统计行数的语法,并且效率高,所以请直接使用COUNT()查询表的行数!
所以这种说法确实是对的。但有个前提,在 MySQL 5.6 之后的版本中才有这种优化。
那么这个成本最小该怎么定义呢,有时候在 WHERE 中指定了多个条件,为啥最终 MySQL 执行的时候却选择了另一个索引,甚至不选索引?
本文将会给你答案,本文将会从以下两方面来分析
SQL 选用索引的执行成本如何计算

就如前文所述,在有多个索引的情况下, 在查询数据前,MySQL 会选择成本最小原则来选择使用对应的索引,这里的成本主要包含两个方面。
最全面的Java面试网站
实例说明

为了根据以上两个成本来算出使用索引的最终成本,我们先准备一个表(以下操作基于 MySQL 5.7.18)
  1. CREATE TABLE `person` (
  2.   `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3.   `name` varchar(255) NOT NULL,
  4.   `score` int(11) NOT NULL,
  5.   `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  6.   PRIMARY KEY (`id`),
  7.   KEY `name_score` (`name`(191),`score`),
  8.   KEY `create_time` (`create_time`)
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
复制代码
这个表除了主键索引之外,还有另外两个索引, name_score 及 create_time。然后我们在此表中插入 10 w 行数据,只要写一个存储过程调用即可,如下:
  1. CREATE PROCEDURE insert_person()
  2. begin
  3.     declare c_id integer default 1;
  4.     while c_id<=100000 do
  5.     insert into person values(c_id, concat('name',c_id), c_id+100, date_sub(NOW(), interval c_id second));
  6.     set c_id=c_id+1;
  7.     end while;
  8. end
复制代码

用了全表扫描!理论上应该用 name_score 或者 create_time 索引才对,从 WHERE 的查询条件来看确实都能命中索引,那是否是使用 SELECT * 造成的回表代价太大所致呢,我们改成覆盖索引的形式试一下
  1. EXPLAIN SELECT COUNT(*) FROM person
复制代码
结果 MySQL 依然选择了全表扫描!这就比较有意思了,理论上采用了覆盖索引的方式进行查找性能肯定是比全表扫描更好的,为啥 MySQL 选择了全表扫描呢,既然它认为全表扫描比使用覆盖索引的形式性能更好,那我们分别用这两者执行来比较下查询时间吧
  1. SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'
复制代码
从实际执行的效果看使用覆盖索引查询比使用全表扫描执行的时间快了一倍!说明 MySQL 在查询前做的成本估算不准!我们先来看看 MySQL 做全表扫描的成本有多少。
前面我们说了成本主要 IO 成本和 CPU 成本有关,对于全表扫描来说也就是分别和聚簇索引占用的页面数和表中的记录数。执行以下命令
  1. SELECT create_time FROM person WHERE NAME >'name84059' AND create_time > '2020-05-23 14:39:18'
复制代码

可以发现
也就是说全表扫描的成本是 20052.8 + 353 =  20406。
这个结果对不对呢,我们可以用一个工具验证一下。在 MySQL 5.6 及之后的版本中,我们可以用 optimizer trace 功能来查看优化器生成计划的整个过程 ,它列出了选择每个索引的执行计划成本以及最终的选择结果,我们可以依赖这些信息来进一步优化我们的 SQL。
optimizer_trace 功能使用如下
  1. -- 全表扫描执行时间: 4.0 ms
  2. SELECT create_time FROM person WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'
  3. -- 使用覆盖索引执行时间: 2.0 ms
  4. SELECT create_time FROM person force index(create_time) WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'
复制代码
执行之后我们主要观察使用 name_score,create_time 索引及全表扫描的成本。
先来看下使用 name_score 索引执行的的预估执行成本:
[code]{    "index": "name_score",    "ranges": [      "name84059




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4