MySQL索引优化二

分页查询优化

很多时候我们的业务系统实现分页功能可能会用如下sql实现

select * from employees limit 10000,10;

表示从表employees中取出从10001行开始的10条记录.看似只查询了10条记录,实际这条sql是先读取10010条记录,然后抛弃前10000条记录,然后读到后面10条想要的数据,因此查询一张大表比较靠后的数据,执行效率是非常低的.

常见的分页查询优化

  1. 根据自增且连续的主键排序的分页查询

    首先来看一个根据自增且连续主键排序的分页查询的例子:

    select * from employees limit 90000,5
    

    在这里插入图片描述

    该SQL表示查询从第90001开始的5条记录,没添加单独order by,表示通过 主键排序.再看表employees,因为主键是自增并且连续的,所以可以改写成按照主键去查询第90001开始的5条记录,如下:

    select * from employees where id > 90000 limit 5
    

    在这里插入图片描述

    查询结果是一致的.再对比下执行计划

    explain select * from employees limit 90000,5;
    explain select * from employees where id > 90000 limit 5
    

    在这里插入图片描述
    在这里插入图片描述

    显然改写后的sql走了索引,而且扫描行数大大减少,执行效率更高.

    但是,这条改写的sql再很多场景并不实用,因为表中可能某些记录被删后,主键空缺,导致结果不一致.

    先删除一条前面的记录,然后再测试原sql和改写后的sql:
    在这里插入图片描述

    在这里插入图片描述

    两条sql的结果并不一样,因此,如果主键不连续,不能使用上面描述的优化方法.

    另外如果原sql是order by 非主键字段,按照上面说的方法会导致两条sql的结果不一致.所以这种改写得满足一下两个条件:

    • 主键是连续且自增
    • 结果是按照主键排序的
  2. 根据非主键字段排序的分页查询

    select * from employees order by name limit 90000,5;
    

    在这里插入图片描述

    explain select * from employees order by name limit 90000,5;
    

    在这里插入图片描述

    发现key字段对应的值为null,并没有使用name字段的索引.扫描整个索引并查找到没有索引的行,需要遍历多个B+树,成本比扫描全表的成本更高,所以优化器放弃使用索引.

    知道不走索引的原因后.可以做出针对性的优化.关键是 让排序时返回的字段尽可能的少,所以可以让排序和分页操作先查出主键,然后根据主键查到对应的记录,sql改写如下

    select e.* from employees e inner join(select id from employees order by name limit 90000,5)ed on e.id = ed.id;
    

    在这里插入图片描述

    需要的结果与原sql一致,执行时间减少了一半以上,再对比优化前后sql的执行计划:

    explain select e.* from employees e inner join(select id from employees order by name limit 90000,5)ed on e.id = ed.id;
    

    在这里插入图片描述

    原sql使用的是filesort排序,而优化后的sql使用的是索引排序.

    Join关联查询优化

    drop procedure if exists insert_t1; 
    delimiter ;;
    CREATE TABLE `t1` (`id` int(11) NOT NULL AUTO_INCREMENT,`a` int(11) DEFAULT NULL,`b` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_a` (`a`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;create table t2 like t1;-- 往t1表插入1万行记录
    create procedure insert_t1()        
    begindeclare i int;                    set i=1;                          while(i<=10000)do                 insert into t1(a,b) values(i,i);set i=i+1;                       end while;
    end;;
    delimiter ;
    call insert_t1();-- 往t2表插入100行记录
    drop procedure if exists insert_t2; 
    delimiter ;;
    create procedure insert_t2()        
    begindeclare i int;                    set i=1;                          while(i<=100)do                 insert into t2(a,b) values(i,i);set i=i+1;                       end while;
    end;;
    delimiter ;
    call insert_t2();
    

mysql的表关联常见有两种算法

  • Nested-Loop Join算法
  • Block Nested-Loop Join算法
  1. 嵌套循环连接 Nested-Loop Join(NLJ)算法

    一次一行循环的从第一张表(驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动表)里取出满足条件的行,然后取出两张表结果合集.

    explain select * from t1 inner join t2 on t1.a = t2.a
    

    在这里插入图片描述

    从执行计划中可以看到这些信息:

    • 驱动表是t2,被驱动表是t1.先执行的就是驱动表(执行计划结果的id如果一样,则按从上到下的顺序执行sql);优化器一般都会优先选择 小表做驱动表,用where条件过滤完驱动表,然后再跟被驱动表做关联查询.所以使用inner join 时,排在前面的并不一定就是驱动表
    • 当使用了left join时,左表是驱动表,右表是被驱动表,当使用right join时,右表是驱动表,左表是被驱动表,当使用join时,mysql会选择数据量较小的表作为驱动表,大表作为被驱动表
    • 使用了NLJ算法.一般join语句中,如果执行计划Extra字段中未出现 Using join buffer则表示使用的join算法是NLJ

    上面sql的大致流程如下:

    1. 从表t2中读取一行数据(如果t2的查询有where条件,先用条件过滤完,再从过滤结果取一行数据)
    2. 从第1步的数据中,取出关联字段a,到表t1中查找
    3. 取出表t1满足条件的行,跟t2中获取到的结果合并,作为结果返回给客户端
    4. 重复上面3步

    整个过程会读取t2表的所有数据(扫描 100行),然后遍历这每行数据中字段a的值,根据t2表中的a的值索引扫描t1表中对应的行(扫描100次t1的索引,1次扫描可以认为最终只扫描t1表一行完整数据,也就是总共t1表也扫描了100行).因此整个过程扫描了200行.

    如果被驱动表关联字段没有索引,使用NLJ算法性能会比较低,mysql会选择Block Nested-Loop Join算法

  2. 基于块的嵌套循环连接Block Nested-Loop Join(BNL)算法

    驱动表的数据读入到join_buffer中,然后扫描 被驱动表,把 被驱动表每一行取出来跟join_buffer中数据做对比.

    explain select * from t1 inner join t2 on t1.b = t2.b
    

    在这里插入图片描述

    Extra字段中的Using join buffer(Block Nested Loop)说明该关联查询使用的是BNL算法.

    上面sql的大致流程如下:

    1. 把t2的所有数据放入到join_buffer
    2. 把表t1中每一行取出来,跟join_buffer中的数据做比对
    3. 返回满足join条件的数据

    整个过程对表t1和t2都做了一次全表扫描,因此扫描的总行数为10000(t1)+100(t2)=10010.并且jion_buffer里的数据是无序的,因此对表中的每一行,都要做100次判断,所以内存中判断的次数是100*10000=100W次.

    示例表t2才100行,如果t2是一个大表,join_buffer放不下怎么处理?

    join_buffer的大小是join_buffer_size设定的,默认值是256k.如果放不下所有数据的话,策略很简单,就是 分段放.

    例如t2表有100行数据,join_buffer一次只能放80行,那么执行过程就是先往join_buffer里放80行记录,然后从t1表里取数据跟join_buffer中数据对比得到部分结果,然后清空join_buffer,再放入t2剩余的20行.再次从t1表里取数据跟join_buffer中数据对比.所以就多扫了一次t1表.

    被驱动表的关联字段没索引为什么使用BNL而不是NLJ?

    如果上面第二条sql使用NLJ,那么扫描行数为100*10000=100W次,这个是磁盘扫描,

    很显然,用BNL磁盘扫描次数少很多,相比于磁盘扫描,BNL的内存计算会快很多

    因此MySQL对于被驱动表的关联字段没索引的关联查询使用的是BNL.如果有索引的情况下一般选择NLJ.有索引的情况下NLJ比BNL快.

    对于关联sql的优化

    • 关联字段加索引:让mysql做join操作时尽量选择NLJ算法,驱动表因为需要全部查询出来,索引过滤条件也尽量走索引,避免全表扫描,总之,能走索引的尽量都走索引
    • 小表驱动大表:写多表连接sql时如果 明确知道那张表是小表可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间

    **straight_join解释:**straight_join功能同join类似,但能让左边表来驱动右边的表,能改表优化器对于联表查询的执行顺序.

    -- 指定t2作为驱动表
    select * from t2 straight_join t1 on t2.a = t1.a
    
    • straight_join只适用于inner join,并不适用于left join,right join(因为left,right已经指定了表的执行顺序)
    • 尽可能让优化器去判断,因为大部分情况下优化器是比开发人员判断的更准确.使用straight_join一定要慎重,因为部分情况下手动指定的不一定会有优化器选择的靠谱

    对于小表的明确定义

    再决定那个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量,数据量小的那个表,就是"小表",应该作为驱动表.

in和exists优化

原则:小表驱动大表,即小的数据集驱动大的数据集

in: 当B表的数据集小于A表的数据集时,in优于exists

select * from A where id in (select id from B)
-- 等价于
for(select id from B){select * from A where A.id = B.id 
}

exists: 当A表的数据集小于B表的数据集时,exists优于in

将主查询A的数据,放到子查询B中做条件验证,根据验证结果(true/false)来决定主查询的数据是否保留

select * from A where exists(select 1 from B where B.id = A.id)
-- 等价于
for(select * from A){select * from B where B.id=A.id
}
-- A表与B表的ID字段应建立索引
  1. exists(subquery)只返回true/false,因此子查询中的select *也可以select 1替换,官方说法是实际执行时会忽略select清单,因此没有区别
  2. exists子查询的实际过程中可能经过了优化,而不是我们理解的逐条比对
  3. exists子查询往往也可以用join来代替.那种最优需要具体问题具体分析

count(*)查询优化

explain select count(1) from employees;
explain select count(id) from employees;
explain select count(name) from employees;
explain select count(*) from employees;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四个sql的执行计划一样.说明这四个sql执行效率应该差不多.

字段有索引:count(*)≈count(1)>count(字段)>count(id),字段有索引,count(字段)走二级索引,二级索引存储数据比主键索引少,所以count(字段)>count(id)

字段无索引:count(*)≈count(1)>count(id)>count(字段),字段无索引,count(字段)走不了索引,count(id)还可以走主键索引,所以count(id)>count(字段)

count(1)跟count(字段)执行过程类似,不过count(1)不需要取出字段统计,就用常量1做统计,count(字段)还需要取出字段,所以理论上count(1)比count(字段)会快一点.

count(*)时例外,mysql并不会把全部字段取出来,而是专门做了优化,不取值按行累加,效率很高,所以不需要用count(字段)或count(1)来代替count( *).

为什么对于count(id),mysql最终选择辅助索引而不是主键聚簇索引?

二级索引相对于主键索引存储数据更少,检索性能更高.mysql内部做了优化

常见优化方法

  1. 查询mysql自己维护的总行数

    对于MyISAM存储引擎的表做不带where条件的count查询性能是很高的,因为MyISAM存储引擎的表会被mysql存储再磁盘上,查询不需要计算

    -- 示例表
    CREATE TABLE `test_myisam` (`id` int(11) NOT NULL AUTO_INCREMENT,`a` int(11) DEFAULT NULL,`b` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_a` (`a`)
    ) ENGINE=MyISAM AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8;
    

    在这里插入图片描述

    对于 InnoDB存储引擎的表mysql不会存储表的总记录行数(因为涉及MVCC机制),查询count需要实时计算

  2. show table status

    如果只需要知道表总行数的估计值可以用以下sql查询,性能很高

    show table status like 'employees'
    

    在这里插入图片描述

  3. 将总数维护到Redis里面

    插入或删除表数据时同时维护redis里的表总行数key的计数值(用incr/decr),但这种方式可能不准,很难保证表操作和redis操作的事务一致性

  4. 增加数据库计数表

    插入或删除表数据行的时候同时维护计数表,让它们在同一个事务里操作

以上操作只支持无条件的count统计

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

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

相关文章

Pillow教程07:调整图片的亮度+对比度+色彩+锐度

---------------Pillow教程集合--------------- Python项目18&#xff1a;使用Pillow模块&#xff0c;随机生成4位数的图片验证码 Python教程93&#xff1a;初识Pillow模块&#xff08;创建Image对象查看属性图片的保存与缩放&#xff09; Pillow教程02&#xff1a;图片的裁…

JVM本地方法

本地方法接口 NAtive Method就是一个java调用非java代码的接口 本地方法栈&#xff08;Native Method Statck&#xff09; Java虚拟机栈用于管理Java方法的调用&#xff0c;而本地方法栈用于管理本地方法的调用。 本地方法栈&#xff0c;也是线程私有的。 允许被实现成固定或…

【机器学习之---数学】统计学基础概念

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 统计学基础 1. 频率派 频率学派&#xff08;传统学派&#xff09;认为样本信息来自总体&#xff0c;通过对样本信息的研究可以合理地推断和估计总体信息…

Transformer 模型中增加一个 Token 对计算量的影响

Transformer 模型中增加一个 Token 对计算量的影响 Transformer 模型中增加一个 Token 对计算量的影响1. Transformer 模型简介2. Token 对计算量的影响3. 增加一个 Token 的计算量估算4. 应对策略5. 结论 Transformer 模型中增加一个 Token 对计算量的影响 Transformer 模型作…

【无标题】C高级325

练习1&#xff1a;输入一个数&#xff0c;实现倒叙123-》321 练习2&#xff1a;输入一个&#xff0c;判断是否是素数 练习3&#xff1a;输入一个文件名&#xff0c; 判断是否在家目录下存在, 如果是一个目录&#xff0c;则直接输出是目录下的sh文件的个数 如果存在则判断是否是…

ELF 1技术贴|应用层更改引脚复用的方法

在嵌入式系统设计中&#xff0c;引脚复用功能通常是通过设备树(Device Tree)预先配置设定的。出厂的设备树中UART2_TX_DATA和UART2_RX_DATA两个引脚被复用成了UART2功能&#xff0c;如果想要在不更换系统镜像的情况下&#xff0c;将这两个引脚的功能转换为GPIO&#xff0c;并作…

深入探讨iOS开发:从创建第一个iOS程序到纯代码实现全面解析

iOS开发作为移动应用开发的重要领域之一&#xff0c;对于开发人员具有重要意义。本文将深入探讨iOS开发的各个方面&#xff0c;从创建第一个iOS程序到纯代码实现iOS开发&#xff0c;带领读者全面了解iOS应用程序的开发流程和技术要点。 &#x1f4f1; 第一个iOS程序 在创建第…

基于springboot+vue+Mysql的超市进销存系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

工地污水处理一体化成套设备如何选型

工地污水处理一体化成套设备的选型是确保工地污水处理效果的关键。在选择合适的设备前&#xff0c;我们需要考虑几个重要因素。 首先&#xff0c;我们需要评估工地的实际污水处理需求。包括污水产生量、水质特征、处理要求等。通过了解工地的情况&#xff0c;我们能够确定适合处…

《探索移动开发的未来之路》

移动开发作为当今科技领域中最为炙手可热的领域之一&#xff0c;正以惊人的速度不断迭代和发展。从技术进展到应用案例&#xff0c;再到面临的挑战与机遇以及未来的趋势&#xff0c;移动开发都呈现出了令人瞩目的发展前景。本文将围绕移动开发的技术进展、行业应用案例、面临的…

服务运营 | 印第安纳大学翟成成:改变生活的水井选址

编者按&#xff1a; 作者于2023年4月在“Production and Operations Management”上发表的“Improving drinking water access and equity in rural Sub-Saharan Africa”探讨了欠发达地区水资源供应中的可达性和公平性问题。作者于2020年1月去往非洲埃塞俄比亚提格雷地区进行…

2.9 Python缩进规则(包含快捷键)

Python缩进规则&#xff08;包含快捷键&#xff09; 和其它程序设计语言&#xff08;如 Java、C 语言&#xff09;采用大括号“{}”分隔代码块不同&#xff0c;Python采用代码缩进和冒号&#xff08; : &#xff09;来区分代码块之间的层次。 在 Python 中&#xff0c;对于类…

【Java面试题】计算机网络

文章目录 1.计算机网络基础1.1网络分层模型/OSI七层模型是什么&#xff1f;1.2TCP/IP四层模型是什么&#xff1f;每一层的作用&#xff1f;1.2.1TCP四层模型&#xff1f;1.2.2为什么网络要分层&#xff1f; 1.2常见网络协议1.2.1应用层常见的协议1.2.2网络层常见的协议 2.HTTP2…

基数排序详解

基数排序详解 一、基数排序的基本概念二、基数排序的特点二、基数排序的工作过程三、基数排序的伪代码四、基数排序的C语言代码示例五、基数排序的稳定性六、基数排序的优化与变体七、基数排序的应用场景八、结论 在计算机科学中&#xff0c;排序算法是一种非常基础和重要的算法…

Salesforce Flow直播预报

各位使用Salesforce的亲们已经发现Process Builder/Workflow距离最后退役的时间是越来越近了&#xff0c;强大的并且以后会更强大的Flow即将全面一统江湖&#xff0c;想快速上手Flow或者想了解Salesforce产品的Flow原理的宝们可以预约起来啦&#xff01;

【二叉树】Leetcode 102. 二叉树的层序遍历【中等】

二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09; 示例1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]] 解题思路…

如何使用ArkTS中的canvas实现签名板功能

一、屏幕旋转 实现签名板的第一个功能就是旋转屏幕。旋转屏幕在各种框架中都有不一样的方式&#xff0c;比如&#xff1a; 在H5端&#xff0c;我们一般是使用CSS中的transform属性中的rotate()方法来强制将网页横屏&#xff0c;然后实现一系列功能在嵌套第三方APP中&#xff…

spring 的理解

spring 的理解 spring 是一个基础的框架&#xff0c;同时提高了一个Bean 的容器&#xff0c;用来装载Bean对象spring会帮我们创建Bean 对象并维护Bean对象 的生命周期。在spring 框架上&#xff0c;还有springCloud,spring Boot 的技术框架&#xff0c;都是以Spring为基石的sp…

Covalent Network(CQT)的以太坊时光机:在 Rollup 时代确保长期数据可用性

以太坊正在经历一场向 “Rollup 时代” 的转型之旅&#xff0c;这一转型由以太坊改进提案 EIP-4844 推动。这标志着区块链技术的一个关键转折&#xff0c;采用了一种被称为“数据块&#xff08;blobs&#xff09;”的新型数据结构。为了与以太坊的扩容努力保持一致&#xff0c;…

基于Arduino IDE 野火ESP8266模块 一键配网 的开发

一、配网介绍 ESP8266 一键配网&#xff08;也称为 SmartConfig 或 FastConfig&#xff09;是一种允许用户通过智能手机上的应用程序快速配置 ESP8266 Wi-Fi 模块的方法&#xff0c;而无需手动输入 SSID 和密码。为了实现这一功能&#xff0c;则需要一个支持 SmartConfig 的智能…