面试官灵魂一问:SELECT COUNT(*) 会造成全表扫描吗?

news2024/12/27 1:15:35

SELECT COUNT(*) 会造成全表扫描吗?

  • 前言
  • SQL 选用索引的执行成本如何计算
  • 实例说明
  • 总结

前言

SELECT COUNT(*)会不会导致全表扫描引起慢查询呢?

SELECT COUNT(*) FROM SomeTable

网上有一种说法,针对无 where_clause 的 COUNT(*),MySQL 是有优化的,优化器会选择成本最小的辅助索引查询计数,其实反而性能最高,这种说法对不对呢

针对这个疑问,我首先去生产上找了一个千万级别的表使用 EXPLAIN 来查询了一下执行计划

EXPLAIN SELECT COUNT(*) FROM SomeTable

结果如下

图片

如图所示: 发现确实此条语句在此例中用到的并不是主键索引,而是辅助索引,实际上在此例中我试验了,不管是 COUNT(1),还是 COUNT(),MySQL 都会用成本最小 的辅助索引查询方式来计数,也就是使用 COUNT() 由于 MySQL 的优化已经保证了它的查询性能是最好的!随带提一句,COUNT()是 SQL92 定义的标准统计行数的语法,并且效率高,所以请直接使用COUNT()查询表的行数!

所以这种说法确实是对的。但有个前提,在 MySQL 5.6 之后的版本中才有这种优化。

那么这个成本最小该怎么定义呢,有时候在 WHERE 中指定了多个条件,为啥最终 MySQL 执行的时候却选择了另一个索引,甚至不选索引?

本文将会给你答案,本文将会从以下两方面来分析

  • SQL 选用索引的执行成本如何计算
  • 实例说明

SQL 选用索引的执行成本如何计算

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

  • IO 成本: 即从磁盘把数据加载到内存的成本,默认情况下,读取数据页的 IO 成本是 1,MySQL 是以页的形式读取数据的,即当用到某个数据时,并不会只读取这个数据,而会把这个数据相邻的数据也一起读到内存中,这就是有名的程序局部性原理,所以 MySQL 每次会读取一整页,一页的成本就是 1。所以 IO 的成本主要和页的大小有关

  • CPU 成本:将数据读入内存后,还要检测数据是否满足条件和排序等 CPU 操作的成本,显然它与行数有关,默认情况下,检测记录的成本是 0.2。

实例说明

为了根据以上两个成本来算出使用索引的最终成本,我们先准备一个表(以下操作基于 MySQL 5.7.18)

CREATE TABLE `person` (    `id` bigint(20) NOT NULL AUTO_INCREMENT,    `name` varchar(255) NOT NULL,    `score` int(11) NOT NULL,    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,    PRIMARY KEY (`id`),    KEY `name_score` (`name`(191),`score`),    KEY `create_time` (`create_time`)  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这个表除了主键索引之外,还有另外两个索引, name_score 及 create_time。然后我们在此表中插入 10 w 行数据,只要写一个存储过程调用即可,如下:

CREATE PROCEDURE insert_person()  begin      declare c_id integer default 1;      while c_id<=100000 do      insert into person values(c_id, concat('name',c_id), c_id+100, date_sub(NOW(), interval c_id second));      set c_id=c_id+1;      end while;  end

插入之后我们现在使用 EXPLAIN 来计算下统计总行数到底使用的是哪个索引

EXPLAIN SELECT COUNT(*) FROM person

图片

从结果上看它选择了 create_time 辅助索引,显然 MySQL 认为使用此索引进行查询成本最小,这也是符合我们的预期,使用辅助索引来查询确实是性能最高的!

我们再来看以下 SQL 会使用哪个索引

SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'   

用了全表扫描!理论上应该用 name_score 或者 create_time 索引才对,从 WHERE 的查询条件来看确实都能命中索引,那是否是使用 SELECT * 造成的回表代价太大所致呢,我们改成覆盖索引的形式试一下

SELECT create_time FROM person WHERE NAME >'name84059' AND create_time > '2020-05-23 14:39:18'   

结果 MySQL 依然选择了全表扫描!这就比较有意思了,
理论上采用了覆盖索引的方式进行查找性能肯定是比全表扫描更好的,
为啥 MySQL 选择了全表扫描呢,既然它认为全表扫描比使用覆盖索引的
形式性能更好,那我们分别用这两者执行来比较下查询时间吧
-- 全表扫描执行时间: 4.0 ms  SELECT create_time FROM person WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'     -- 使用覆盖索引执行时间: 2.0 ms  SELECT create_time FROM person force index(create_time) WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'   
从实际执行的效果看使用覆盖索引查询比使用全表扫描执行的时间快了一倍!说明 MySQL 在查询前做的成本估算不准!我们先来看看 MySQL 做全表扫描的成本有多少。

前面我们说了成本主要 IO 成本和 CPU 成本有关,对于全表扫描来说也就是分别和聚簇索引占用的页面数和表中的记录数。执行以下命令

SHOW TABLE STATUS LIKE 'person'  

图片

可以发现

1.行数是 100264,我们不是插入了 10 w 行的数据了吗,怎么算出的数据反而多了,其实这里的计算是估算 ,也有可能这里的行数统计出来比 10 w 少了,估算方式有兴趣大家去网上查找,这里不是本文重点,就不展开了。得知行数,那我们知道 CPU 成本是 100264 * 0.2 = 20052.8。

2.数据长度是 5783552,InnoDB 每个页面的大小是 16 KB,可以算出页面数量是 353。

也就是说全表扫描的成本是 20052.8 + 353 = 20406。

这个结果对不对呢,我们可以用一个工具验证一下。在 MySQL 5.6 及之后的版本中,我们可以用 optimizer trace 功能来查看优化器生成计划的整个过程 ,它列出了选择每个索引的执行计划成本以及最终的选择结果,我们可以依赖这些信息来进一步优化我们的 SQL。

optimizer_trace 功能使用如下

SET optimizer_trace="enabled=on";  SELECT create_time FROM person WHERE NAME >'name84059' AND create_time > '2020-05-23 14:39:18';  SELECT * FROM information_schema.OPTIMIZER_TRACE;  SET optimizer_trace="enabled=off";

执行之后我们主要观察使用 name_score,create_time 索引及全表扫描的成本。

先来看下使用 name_score 索引执行的的预估执行成本:

{      "index": "name_score",      "ranges": [        "name84059 <= name"      ],      "index_dives_for_eq_ranges": true,      "rows": 25372,      "cost": 30447  }

可以看到执行成本为 30447,高于我们之前算出来的全表扫描成本:20406。所以没选择此索引执行

注意:这里的 30447 是查询二级索引的 IO 成本和 CPU 成本之和,再加上回表查询聚簇索引的 IO 成本和 CPU 成本之和。

再来看下使用 create_time 索引执行的的预估执行成本:

{      "index": "create_time",      "ranges": [        "0x5ec8c516 < create_time"      ],      "index_dives_for_eq_ranges": true,      "rows": 50132,      "cost": 60159,      "cause": "cost"  }

可以看到成本是 60159,远大于全表扫描成本 20406,自然也没选择此索引。

再来看计算出的全表扫描成本:

{      "considered_execution_plans": [        {          "plan_prefix": [          ],          "table": "`person`",          "best_access_path": {            "considered_access_paths": [              {                "rows_to_scan": 100264,                "access_type": "scan",                "resulting_rows": 100264,                "cost": 20406,                "chosen": true              }            ]          },          "condition_filtering_pct": 100,          "rows_for_plan": 100264,          "cost_for_plan": 20406,          "chosen": true        }      ]  }

注意看 cost:20406,与我们之前算出来的完全一样!这个值在以上三者算出的执行成本中最小,所以最终 MySQL 选择了用全表扫描的方式来执行此 SQL。

实际上 optimizer trace 详细列出了覆盖索引,回表的成本统计情况,有兴趣的可以去研究一下。

从以上分析可以看出, MySQL 选择的执行计划未必是最佳的,原因有挺多,就比如上文说的行数统计信息不准,再比如 MySQL 认为的最优跟我们认为不一样,我们可以认为执行时间短的是最优的,但 MySQL 认为的成本小未必意味着执行时间短。

总结

本文通过一个例子深入剖析了 MySQL 的执行计划是如何选择的,以及为什么它的选择未必是我们认为的最优的,这也提醒我们,在生产中如果有多个索引的情况,使用 WHERE 进行过滤未必会选中你认为的索引,我们可以提前使用 EXPLAIN, optimizer trace 来优化我们的查询语句。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/455010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云计算下的企业数据备份与恢复实践

在当前的数字化时代&#xff0c;数据已经成为企业的核心资产之一。对于企业而言&#xff0c;数据的安全性和可用性至关重要。数据的丢失和破坏会给企业带来巨大的损失&#xff0c;因此&#xff0c;备份和恢复数据已经成为企业数据管理的重要环节。随着云计算的普及和发展&#…

深度学习模型参数量与训练数据量的平衡对泛化性能的影响

一、引言 深度学习模型在计算机视觉、自然语言处理等领域取得了显著的成果。为了获得泛化性能良好的模型&#xff0c;研究者需要在模型复杂度和训练数据量之间找到合适的平衡。本文将探讨这两者之间的关系以及如何在实际应用中实现最佳效果。 二、模型复杂度与训练数据量的关…

Windows云服务器配置多用户登录

Windows Server 2012操作系统的云服务器为例介绍实现多用户登录的操作步骤 安装桌面会话主机和远程桌面授权 登录Windows云服务器。在操作系统界面&#xff0c; 打开“服务器管理器”&#xff0c;单击“添加角色和功能”。 图1 添加角色和功能 保持默认参数&#xff0c;单击“…

Mars3d引用单个示例文件WeiVectorTileLayer.js报错的解决办法

参考文档修改文件后&#xff0c;发现依然报错&#xff1a; Mars3d单独引用示例文件教程_3d文件示例_绵绵-火星科技的博客-CSDN博客 具体报错截图&#xff1a; 看报错是&#xff1a; CesiumVectorTile.js里的错误&#xff0c;进去看看错误行&#xff0c;是不是缺cesium // if …

数组题目总结 -- 差分数组

零. 差分数组工具类 1. 思路和代码 diff 存在的意义就是想要通过构建 diff 数组来实现对原数组&#xff08;nums&#xff09;频繁的加减操作。差分数组的构建代码&#xff1a; int[] res new int[diff.length]; // 根据差分数组构造结果数组 res[0] diff[0]; for (int i …

Qt QWidget 抗锯齿圆角窗口的一个实现方案(支持子控件)

QWidget抗锯齿圆角窗口的一个实现方案 由于 QWidget::setMask 接口设置圆角不支持抗锯齿&#xff0c;所以通常会使用透明窗口加圆角背景&#xff0c;但圆角背景不能满足对子控件的裁剪&#xff0c;子控件与圆角区域重叠的部分还是能显示出来。当然对于大多数窗口&#xff0c;留…

Linux-初学者系列——篇幅7_文本编辑和处理命令

文本编辑和处理命令-目录 一、系统基本编辑命令安装vim软件工具包语法格式&#xff1a; 1、vim编辑命令模式01 普通模式02 编辑模式03 命令模式 2、编辑文件技巧01 批量删除多行指定信息02 批量增加多列指定信息03 编辑常见问题错误1&#xff1a;没有指定编辑信息错误2&#xf…

Kubernetes Service、Ingress、Ingress Controller

Kubernetes 网络模型 Kubernetes 对网络设施的基本要求 Pod 能够与所有其它节点上的 Pod 相互通信&#xff0c; 且不需要网络地址转译&#xff08;NAT&#xff09; 节点上的代理&#xff08;比如&#xff1a;系统守护进程、kubelet&#xff09;可以和节点上的所有 Pod 相互通…

基于Html+Css的图片展示25

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

SLAM论文速递【SLAM—— RDS-SLAM:基于语义分割方法的实时动态SLAM—4.24(1)

论文信息 题目&#xff1a; RDS-SLAM:Real-Time Dynamic SLAM Using Semantic Segmentation Methods RDS-SLAM:基于语义分割方法的实时动态SLAM论文地址&#xff1a; https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber9318990发表期刊&#xff1a; IEEE Access ( Volum…

如何发起一次完整的HTTP的请求流程

目录 &#x1f4a1; 预备知识 &#x1f50a; 浏览器端发起 HTTP 请求流程 1、构建请求 2、查找缓存 3、准备IP地址和端口 4、等待TCP队列 5、建立TCP连接 6、发送HTTP请求 &#x1f50a; 服务器端处理 HTTP 请求流程 1、返回请求 2、断开连接 3、重定向 HTTP 是一种…

【hello Linux】文件时间

目录 1. 简单介绍文件的三个时间&#xff1a; 2. 查看文件时间的命令&#xff1a; 3. makefile的时间编译原理&#xff1a; Linux&#x1f337; 1. 简单介绍文件的三个时间&#xff1a; 在Linux中&#xff0c;记录着文件的三方面时间&#xff1a; 1. Access&#xff1a;记…

适合小白的docker实战演示——docker基础入门命令

一、docker简单背景介绍 docker是dotCloud公司开源的一个基于LXC&#xff08;LXC为Linux Container的简写。Linux Container 容器是一种内核虚拟化技术&#xff0c;可以提供轻量级的虚拟化&#xff0c;以便隔离进程和资源&#xff09;的高级容器引擎&#xff0c;源码托管在Gith…

JAVA——线程池

目录 一、线程池的概念 二、Java标准库中的线程池 三、ThreadPoolExecutor 类的参数 四、线程池的拒绝策略 五、模拟实现线程池 一、线程池的概念 线程池顾名思义就是集中存储线程的地方——联想一下水池。 线程池是一种多线程处理形式&#xff0c;处理过程中将任务添加到…

PostMan笔记(五)数据监控与压力测试

1. 数据监控 1.1 说明 Postman提供了一种方便的方式来监控API请求和响应数据&#xff0c;以便在测试和开发过程中查看和分析API的性能和功能。数据监控允许您在实时和历史记录中查看API请求和响应数据&#xff0c;并使用图表和统计信息对其进行可视化分析。 1.2 怎么使用数据…

KuiperInfer深度学习推理框架-源码阅读和二次开发(2):算子开发流程(以sigmoid为例)

前言&#xff1a;KuiperInfer是一个从零实现一个高性能的深度学习推理库&#xff0c;中文教程已经非常完善了。本系列博客主要是自己学习的一点笔记和二次开发的教程&#xff0c;欢迎更多的AI推理爱好者一起来玩。这篇写一下算子开发流程&#xff0c;以sigmoid算子为例&#xf…

音视频技术开发周刊 | 290

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 TCSVT 2022 | 基于环路多帧预测的深度视频压缩 本文基于端到端深度视频压缩框架&#xff0c;提出了一种环路多帧预测模块&#xff08;in-loop frame prediction module&a…

UV坐标应用范例——计算屏幕坐标作为UV

迷幻角色背景 大家好&#xff0c;我是阿赵。 之前介绍过了经典的Shader写法&#xff0c;物体顶点坐标在顶点程序转换到裁剪空间&#xff0c;然后在片段程序里面通过模型的UV进行贴图采样&#xff0c;然后把颜色显示在模型上面。 之前也介绍过经典的顶点程序应用&#xff0c;树木…

26.Spring-AOP(切面编程)

目录 一、Spring-AOP。 &#xff08;1&#xff09;AOP的简介。 &#xff08;2&#xff09;AOP的底层实现-动态代理。 &#xff08;2.1&#xff09;JDK的动态代理。 &#xff08;2.2&#xff09;cglib的动态代理。 &#xff08;3&#xff09;AOP的相关概念。 &#xff0…

【Linux】5、使用 Linux 快捷按键小技巧

目录 一、CTRL C二、CTRL D三、history 命令四、CTRL R五、光标移动快捷方式六、清屏 一、CTRL C &#x1f941; ① 可用于强制停止某些程序的运行 &#x1f941; ② 若命令输入错误&#xff0c;可用它退出当前命令 二、CTRL D &#x1f941; ① 退出登录的账户 &#…