数据库监控与调优【十四】—— COUNT语句优化

news2024/10/7 16:17:11

COUNT语句优化

有关COUNT的几个实验与结论

准备工作

create table user_test_count
(
    id       int primary key not null auto_increment,
    name     varchar(45),
    age      int,
    email    varchar(60),
    birthday date
) engine 'innodb';

insert into user_test_count (id, name, age, email, birthday)
values (1, '张三', 20, 'zhangsan@imooc.com', '2000-01-01');
insert into user_test_count (id, name, age, email, birthday)
values (2, '李四', 30, 'lisi@imooc.com', '1990-01-01');
insert into user_test_count (id, name, age, email, birthday)
values (3, '王五', 40, null, null);
insert into user_test_count (id, name, age, email, birthday)
values (4, '柯柯', 18, null, null);

count(*)

只有一个主键索引
EXPLAIN SELECT count( * ) FROM user_test_count;

在这里插入图片描述

可以发现这条sql语句使用了主键索引

给email字段添加普通索引
ALTER TABLE user_test_count ADD INDEX user_test_count_email_index ( email );

再次执行上面得count查询语句

在这里插入图片描述

通过结果,可以发现使用了user_test_count_email_index这个普通索引

故得出结论:

1.count(*)当没有非主键索引时,会使用主键索引

2.count(*)如果存在非主键索引,会使用非主键索引

再次给birthday字段添加普通索引
ALTER TABLE user_test_count ADD INDEX user_test_count_birthday_index ( birthday );

再次执行上面得count查询语句

在这里插入图片描述

通过结果,可以发现使用了user_test_count_birthday_index这个普通索引

通过观察两个普通索引的长度可以得出结论:

3.count(*)如果存在多个非主键索引,会使用一个最小的非主键索引

为什么会出现上述情况

该表存储引擎是innodb,而innodb的非主键索引,叶子节点存储的是索引+主键;innodb的主键索引,叶子节点存储的是主键+表数据

而mysql是以页为单位的,一个页的默认大小是1024K。

于是,在一个页里面,非主键索引可以存储更多的条目。

对于一张表,假设存在1000000条数据,使用非主键索引扫描的页数可能只有100个,而使用主键索引可能需要500个

于是,在count查询语句,使用非主键索引的性能比使用主键索引的性能要好,如果存在多个非主键索引,会使用一个最小的非主键索引,也是为了在一个页里面存储更多的数据,从而节省扫描的次数

count(字段)

count的字段存在索引
EXPLAIN SELECT count( email ) FROM user_test_count;

在这里插入图片描述

通过结果,可以发现使用了user_test_count_email_index这个普通索引

count的字段不存在索引
EXPLAIN SELECT count( age ) FROM user_test_count;

在这里插入图片描述

通过结果,可以发现使用了全表扫描

对比count(*)和count(email)的结果
-- 结果为4
SELECT count( * ) FROM user_test_count;
-- 结果为2
SELECT count( email ) FROM user_test_count;

故得出结论:

1.count(字段)如果这个字段上面存在索引,走这个字段的索引

2.count(字段)如果这个字段上面不存在索引,走全表扫描

3.count(字段)只会对该字段统计,会排除掉该字段值为null的行,count(*)不会排除

count(1)

当存在三个索引,主键索引、普通索引user_test_count_email_index(长度为243字节)和普通索引user_test_count_birthday_index(长度为4字节),执行下面的sql

EXPLAIN SELECT count( 1 ) FROM user_test_count;

在这里插入图片描述

可以看到count(1)也是走了长度较小的索引user_test_count_birthday_index,和count(*)一样

详情见官方文档:https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html

具体结论:

1.count(*)和count(1)没有区别

2.MyISAM引擎中,如果count(*)没有where条件(形如 select count(*) from 表名)查询会非常快。因为mysql会为此存储引擎存储精确的行数,并且可以非常快速地访问

3.从MySQL 8.0.13开始,InnoDB引擎中,如果count(*)没有where条件(形如 select count(*) from 表名),查询也会被优化,性能有所提升

总结

  • count(*)和count(1)一样
    • 官方文档:https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html
  • count(*)会选择最小的非主键索引,如果不存在任何非主键索引,则会使用主键
  • count(*)不会排除为null的行,而count(字段)会排除
  • 对于不带查询条件的count(*)语句,MyISAM引擎以及InnoDB引擎(MySQL >= 8.0.13),都作了优化
  • 如果没有特殊需求,尽量使用count(*)

COUNT语句具体优化

准备工作

准备两个版本的MySQL,分别是8.0.18和5.6

都执行以下sql创建表,并尽可能多的添加数据,这里添加了2844047条数据

CREATE TABLE `salaries`  (
  `emp_no` int(0) NOT NULL,
  `salary` int(0) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`) USING BTREE,
  INDEX `salaries_from_date_to_date_index`(`from_date`, `to_date`) USING BTREE,
  CONSTRAINT `salaries_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

MySQL 8.0.18版本

执行结果

SELECT count( * ) FROM salaries;	-- 执行时间120ms
SHOW CREATE TABLE salaries;	-- innodb
SELECT version( );	-- 版本8.0.18 > 8.0.13,可以针对无条件的count(*)语句去优化

MySQL 5.6版本

执行结果

SELECT count( * ) FROM salaries;	-- 执行时间841ms
SHOW CREATE TABLE salaries;	-- innodb
SELECT version( );	-- 版本5.6

任意版本查看执行计划

EXPLAIN SELECT count( * ) FROM salaries;

在这里插入图片描述

通过结果可以发现走了index全索引扫描,且走salaries_from_date_to_date_index索引,长度为6字节

所以,可以降低索引长度进行优化

方案一

创建一个更小的非主键索引,因为效果不明显就不演示了

方案二

把数据库表的存储引擎换成MyISAM,因为MyISAM引擎针对没有条件的count(*)语句会直接返回。

这种做法在实际项目中用得很少,一般不会修改数据库表的存储引擎。而且即使修改了数据库表的存储引擎,也只能提升不带查询条件的count(*)语句

方案三

新增一个汇总表,table[table_name, count] => 如:salaries, 2844047

表数据的新增和删除会通过触发器自动的修改汇总表的值

好处:结果比较准确,且比较灵活,可以根据需求设计汇总表。例如:需求不是简单的无条件查询count,而是有条件查询,比如某个薪资范围的用户数,就可以将汇总表设计成[salary范围, count]

坏处:增加了维护的成本

方案四

sql_calc_found_rows

实际项目中的count语句往往是和分页查询一同使用,如下

SELECT * FROM salaries LIMIT 0, 10;
SELECT COUNT(*) FROM salaries;

使用sql_calc_found_rows,如下

SELECT sql_calc_found_rows * FROM salaries LIMIT 0, 10;

在做完以上查询之后,mysql会自动地执行count

再在执行完上述语句之后,执行一条sql,获取count

SELECT FOUND_ROWS() AS salary_count;

好处:效率很高

坏处:MySQL 8.0.17已经废弃这种用法, 未来会被删除

注意:测试这种方式,需要在mysql终端执行,idea无法正常返回结果

在mybatis中使用sql_calc_found_rows参考文章:https://blog.csdn.net/myth_g/article/details/89672722

方案五

缓存

SELECT count( * ) FROM salaries;的结果存入缓存,后续用定时任务实时更新缓存的数据

好处:性能比较高,结果比较准确,有误差但是比较小,除非在缓存更新期间,新增或者删除了大量数据

坏处:引入了额外的组件,增加了架构的复杂度

方案六

information_schema.tables

SELECT * FROM information_schema.`TABLES` WHERE table_schema = 'employees' AND table_name = 'salaries';

返回结果TABLE_ROWS列即为想要的结果

好处:不操作salaries表,不论salaries表有多少条数据,都可以迅速返回结果

坏处:估算值,并不是准确值,所以需要业务对count的结果准确性要求不高

方案七

SHOW TABLE STATUS WHERE NAME = 'salaries';

返回结果Rows列即为想要的结果

好处:不操作salaries表,不论salaries表有多少条数据,都可以迅速返回结果

坏处:估算值,并不是准确值,所以需要业务对count的结果准确性要求不高

方案八

EXPLAIN SELECT * FROM salaries;

返回结果rows列即为想要的结果

好处:不操作salaries表,不论salaries表有多少条数据,都可以迅速返回结果

坏处:估算值,并不是准确值,所以需要业务对count的结果准确性要求不高

count语句优化实战

存在一条count语句

SELECT count( * ) FROM salaries WHERE emp_no > 10010;	-- 执行时间799ms,较慢

使用explain查看执行计划

EXPLAIN SELECT count( * ) FROM salaries WHERE emp_no > 10010;

在这里插入图片描述

通过执行结果,可以看到type是range,表示范围查询,使用了主键,预计扫描1419213行,行数很多,导致性能低下

现在用的MySQL版本是8.0.18,这个版本的MySQL不带条件的count(*)查询是很快的

通过以下sql查询数据库最小的emp_no

SELECT MIN(emp_no) FROM salaries;

得到结果是10001,非常接近10010,故可以优化sql语句如下

SELECT count( * ) - ( SELECT count( * ) FROM salaries WHERE emp_no <= 10010 ) FROM salaries;	-- 执行时间444ms,优化效果明显

使用explain查看执行计划

EXPLAIN SELECT count( * ) - ( SELECT count( * ) FROM salaries WHERE emp_no <= 10010 ) FROM salaries;

在这里插入图片描述

通过执行结果,可以看到首先执行了type=index的查询,扫描了2838426行,本质上就是SELECT count( * ) FROM salaries;查询,但是由于MySQL版本是8.0.18,这个版本的MySQL不带条件的count(*)查询是很快的,故这个查询花费时间较少。接着,再执行SELECT count( * ) FROM salaries WHERE emp_no <= 10010;扫描的是主键,扫描了112行,花费的时间比之前小很多。

如果MySQL版本低于8.0.13,该如何优化?

可以采用方案三(汇总表记录salaries表总数,再查询emp_no <= 10010的数量,通过总数-查询的数量即可)或者方案五

从这个实战例子,我们可以得出以下启发

1.尽量减少sql扫描的行

2.根据MySQL不同版本的特点进行调优,活学活用调优方案

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

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

相关文章

算法设计与分析之回溯法

文章目录 1. 回溯法简介1.1 DFS的基本思想1.2 回溯法的基本思想1.3 回溯法和DFS的区别1.4 剪枝 2. 01背包问题&#xff1a;子集树2.1 问题介绍2.2 解决思路2.3 算法实现2.4 如何优化 3. 旅行商问题TSP&#xff1a;排序树3.1 问题介绍3.2 解决思路3.3 算法框架3.4 算法实现 4. 总…

项目一点点记录

kafka发布通知 kafka是消息队列&#xff0c;kafka采用发布订阅模式进行消息的生产与消费。在项目中&#xff0c;我们采用spring来整合kafka&#xff0c; 通过定义事件event来封装 点赞、关注、评论三类事件&#xff0c;event实体中有 事件主题topic&#xff0c;当前用户id&…

怎么给PDF添加图片水印?其实很简单,看这篇就会了!

许多人都意识到版权问题的重要性&#xff0c;尽管在日常生活中我们可能很少遇到&#xff0c;但在办公和学习中却经常涉及到此类问题。例如&#xff0c;我们辛辛苦苦制作的PDF文件&#xff0c;如何确保不被他人盗用呢?这就涉及到如何为PDF添加图片水印的问题&#xff0c;相当于…

无向图G的广度优先搜索和深度优先搜索以及完整程序

图的遍历算法有两种&#xff1a;广度优先搜索和深度优先搜索 一.广度优先搜索类似于层次遍历&#xff0c;需要借助辅助队列 空间复杂度为O(|V|);空间复杂度由辅助队列大小决定 时间复杂度为O(|V||E|) 为避免同一顶点被多次访问&#xff0c;设计visited[]来标记顶点 二.深度…

MyBatis 从初识到掌握

目录 今日良言&#xff1a;与其抱怨于黑暗&#xff0c;不如提灯向前行 一、初识MyBatis 1.MyBatis定义 2.为什么要学习MyBatis 3.MyBatis的创建 二、MyBatis的相关操作 1.增删改查操作 2.动态SQL使用 今日良言&#xff1a;与其抱怨于黑暗&#xff0c;不如提灯向前行 一…

UE4/5 通过Control rig的FullBody【蜘蛛模型,不用basic ik】

目录 根设置 FullBody IK 额外骨设置 ​编辑 晃动效果 根设置 第一步你需要准备一个蜘蛛模型&#xff0c;不论是官方示例或者是epic上购买的模型 然后我用的是epic上面购买的一个眼球蜘蛛&#xff1a; 第一步&#xff0c;我们从根创建一个空项【这个记得脱离父子级到root之…

SQLServer 2016 R2数据库新建、附加、分离、备份、还原、复制等基本操作

一、打开Microsoft SQL Server Management Studio 在桌面上找到图标&#xff0c;双击运行 打开Microsoft SQL Server Management Studio 17 输入服务器名称&#xff0c;选择SQL Server 身份验证&#xff0c;sa和sa密码&#xff0c;可以勾选记住密码&#xff0c;以便以后的登录…

分享基于安卓项目的单元测试总结

前言&#xff1a; 负责公司的单元测试体系的搭建&#xff0c;大约有一两个月的时间了&#xff0c;从最初的框架的调研&#xff0c;到中期全员的培训&#xff0c;以及后期对几十个项目单元测试的引入和推进&#xff0c;也算是对安卓的单元测试有了一些初步的收获以及一些新的认…

【雕爷学编程】Arduino动手做(131)---跑马灯矩阵键盘模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

线性代数基础--矩阵

矩阵 矩阵是由排列在矩形阵列中的数字或其他数学对象组成的表格结构。它由行和列组成&#xff0c;并且在数学和应用领域中广泛使用。 基本概念 元素&#xff1a;矩阵中的每个数字称为元素。元素可以是实数、复数或其他数学对象。 维度&#xff1a;矩阵的维度表示矩阵的行数和…

vtk创建点

使用vtk库创建三维空间中的点 引言开发环境示例一项目结构实现代码 运行效果示例二项目结构实现代码 运行效果总结 引言 本文仅适合初学者。 本文不提供vtk动态库的生成&#xff0c;以及在QtCreator中的引进vtk时的配置。 本文先由示例一开始&#xff0c;然后再在示例一的基础…

aws使用外部 ID对其他账号授权

点击前往授权,进入控制台 https://signin.aws.amazon.com/signin?redirect_urihttps%3A%2F%2Fconsole.aws.amazon.com%2Fconsole%2Fhome%3FhashArgs%3D%2523%26isauthcode%3Dtrue%26state%3DhashArgsFromTB_eu-north-1_f2d9c316b93c0026&client_idarn%3Aaws%3Asignin%3A%…

Glassdoor美国公司员工及面试者评价数据

一、数据简介 除了股东、债权人、政府等外部利益相关者外&#xff0c;员工的利益更应该得到公司的恰当保护&#xff0c;因为员工才是公司创造价值的真正主体。提高企业在产品市场的竞争力&#xff0c;首先就是要提高员工对企业的满意度&#xff0c;只有员工的满意度更高、幸福感…

7个技巧,助你同时轻松管理和跟踪多个项目

仅仅想到要兼顾这么多重要的职责&#xff0c;就会让许多专业的项目经理感到焦虑。当涉及多个项目的多种项目管理工具的处理&#xff0c;即使对于了解项目管理的项目经理来说&#xff0c;也是一项艰巨的任务&#xff0c;而对于在这个领域没有经过适当培训的人来说&#xff0c;这…

强化学习从基础到进阶--案例与实践[7.1]:深度确定性策略梯度DDPG算法、双延迟深度确定性策略梯度TD3算法详解项目实战

【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧&#xff08;调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍&#xff1a;【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧…

计算机网络—数据链路层

文章目录 数据链路层服务差错编码多路访问协议信道划分随机访问MAC协议 数据链路层服务 该层中的帧数据结构&#xff1a; 帧头部会因为不同的局域网协议而不同&#xff0c;因此会在另一篇博文中继续介绍不同的帧数据报&#xff0c;不在本博文介绍。&#xff08;不过除了PPP协…

Docker学习笔记11

Docker容器镜像&#xff1a; 1&#xff09;docker client 向docker daemon发起创建容器的请求&#xff1b; 2&#xff09;docker daemon查找本地有客户端需要的镜像&#xff1b; 3&#xff09;如无&#xff0c;docker daemon则到容器的镜像仓库中下载客户端需要的镜像&#…

线性代数基础--向量

目录 向量的概念 基本概念 抽象概念 向量的意义 几何意义 物理意义 欧式空间 特点和性质 行向量与列向量 行向量 列向量 两者的关系 向量的基本运算与范数 向量的基本运算 向量的加法 数乘运算&#xff08;实数与向量相乘&#xff09; 转置 向量的范数 向量…

echart 设置柱状图y轴最大刻度

start 最近接到需求希望柱状图 y轴最大高度可以略高一些&#xff1b;柱状图的数据能展示在柱状图的上方 记录一下相关配置项 解决方案 官方文档说明 https://echarts.apache.org/zh/option.html#xAxis.max 效果 代码 {key: business,title: {text: 业务领域分类,textSt…

DAY32:回溯算法(七)全排列+全排列Ⅱ(排列问题)

文章目录 46.全排列思路树形图used数组的作用 伪代码完整版时间复杂度总结 47.全排列Ⅱ思路树形图 完整版时间复杂度总结 46.全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xf…