MySQL索引优化三

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: MySQL索引优化三

这篇文章继续讨论剩下的内容,同样,请一定要先看MySQL索引优化一

分页查询

在平常,我们写的分页查询sql一般是这样

explain select * from employees order by name limit 10000,10;

这样的sql你会发现越翻到后面查询会越慢,这是因为这里看似是从表中查询10条记录,实际上是在表中查询了10010条记录,然后将10000条记录丢弃所得到的结果。

优化sql如下:

explain select * from employees t1 join (select id from employees order by `name` limit 10000, 10) t2 on t1.id = t2.id;

执行计划:

image-20230123092745913

优化思路:先使用覆盖索引方式查出10条数据,再使用这10条数据连接查询。

覆盖索引:查询的字段被索引完全覆盖,比如id在联合索引中

原理:结合MySQL数据结构, 主键索引(innodb引擎)会存储完整的记录,而二级索引只存储主键。MySQL一个结点默认为16KB。

故:二级索引一个叶子结点能够存放的记录会多的多,扫描二级索引比扫描主键索引的IO次数会少很多。

图示:

优化前sql查询时间

image-20230123092607529

set global query_cache_size=0;

set global query_cache_type=0;

优化后:

image-20230123093007842

Join查询

jion查询分为内连接,左连接,右连接;

关联时又有两种情况:使用索引字段关联,不使用索引字段关联。

我以案例举例说明,如以下两张表t1,t2, a字段有索引,b字段无索引

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;

t2表结构与t1完全相同

其中t1表具有1w条数据,t2表具有100条数据。

使用索引字段关联查询

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

执行计划:

image-20230122111216120

分析执行计划:

1、先全表扫描t2表(100条数据)

2、使用t2表的a字段关联查询t1表,使用索引idx_a

3、取出t1表中满足条件的行,和t2表的数据合并,返回结果给客户端

成本计算:

1、扫描t2表:100次

2、扫描t1表:100次,因为使用索引可以定位出的数据,这个过程的时间复杂度大概是O(1)

此处说的100次只是为了更好的计算和理解,实际可能就几次

翻译成代码可能是这样:

for x in range(100): # 循环100次
  print(x in t1) # 一次定位

所以总计扫描次数:100+100=200次

这里引出两个概念

小表驱动大表, 小表为驱动表,大表为被驱动表

  • inner join时,优化器一般会优先选择小表做驱动表, 排在前面的表并不一定就是驱动表。
  • left join时,左表是驱动表,右表是被驱动表
  • right join时,右表时驱动表,左表是被驱动表

嵌套循环连接 Nested-Loop Join(NLJ) 算法

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

使用索引字段关联查询的一般为NLJ算法

使用非索引字段查询

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

执行计划:

image-20230123094246851

Extra列:Using join buffer:使用join buffer(BNL算法)

分析执行计划:

1、先全表扫描t2表(100条数据),将数据加载到join buffer(内存)中

2、全表扫描t1表,逐一和join buffer中的数据比对

3、返回满足条件的行

成本计算:

1、扫描t2表:100次

2、扫描t1表:1w次

3、在内存中比对次数:100*10000=100w次

翻译成代码可能是这样:

for i in range(100): # 循环100次
  for j in range(10000) # 循环10000次

所以总计扫描次数为:100+10000=10100次,内存中数据比对次数为:100*1w=100w次

这个过程称为:基于块的嵌套循环连接Block Nested-Loop Join(BNL)算法

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

使用BNL算法join buffer不够时怎么办?

案例中t2表只有一百行数据,如果数据量很大时,比如t2表一共有1000行数据,join buffer一次只能放800行时怎么办?

此时会使用分段放的策略:先放入800行到join buffer,然后扫描t1表,比对完毕之后,将join buffer清空,放入剩余的200行,再次扫描t1表,再比对一次。

也就是说:此时会多扫描一次t1表,如果2次都放不下,就再多扫描一次,以此类推。

小结

join查询中一般有两种算法:

  • 嵌套循环连接(NLJ)算法:使用索引字段关联查询
  • 基于块的嵌套循环连接(BNL)算法:使用非索引字段关联查询

NLJ算法比BNL算法性能更高

关联查询的优化方式:

  • 对关联字段加索引:让MySQL尽量选择NLJ算法
  • 小表驱动大表:一般来说MySQL优化器会自己判断哪个是小表,如果使用left joinright join是要注意。
  • 如果不得已要使用BNL算法,那么在内存充足的情况下,可以调大一些join buffer,避免多次扫描被驱动表。

为什么非索引字段不使用NLJ算法?

NLJ算法性能这么好,为什么非索引字段关联时不使用这种算法呢?

这是因为NLJ算法采用的是磁盘扫描方式:先扫驱动表,取出一行数据,通过该数据的关联字段到被驱动表中查找,这个过程是使用索引查找的,非常快。

如果非索引字段采用这种方式,那么通过驱动表的数据的关联字段,到被驱动表中查找时,由于无法使用索引,此时走的是全表扫描。

比如驱动表有100条数据,那么就要全表扫描被驱动表100次,被驱动表有1w条数据,那么就是磁盘IO:100*1w=100w次,这个过程是非常慢的。

In&Exist

in和exist的优化只有一个原则:小表驱动大表

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

select * from A where id in (select id from B)

即in中的表为小表

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

select * from A where exists (select 1 from B where B.id = A.id)

即外层的表为小表

count查询

关于count这里就不详细说明了,因为各种用法效率都差不多。

字段有索引:count(*)≈count(1)>count(字段)≈count(主键 id)

字段无索引:count(*)≈count(1)>count(主键 id)>count(字段)

索引设计原则

关于索引部分到这里就差不多了,总结一下索引设计原则

  1. 先写代码,再根据情况建索引
    一般来说,都是都没代码写完之后,才能明确哪些字段会用到索引,但我也发现大部人写完代码就不管了。所以如果在设计时可以初步知道哪些字段可以建立索引,那么可以在设计表时就建好索引,写完代码再做调整
  2. 尽量让联合索引覆盖大部分业务
    一个表不要建立太多的索引,因为MySQL维护索引也是需要耗费性能的,所以尽量让一到三个联合索引就覆盖业务里面的sql查询条件
  3. 不要在小基数的字段上建索引
    如果在小基数的字段上建立索引是没有意义的,如性别,一张1千万数据的表,对半分的话500w男,500w女,筛选不出什么。
  4. 字符串长度过长的索引可以取部分前缀建立索引
    字段过长的话也会导致索引占用的磁盘空间比较大,如varcahr(255), 这个时候可以取部分前缀建立索引,如前20个字符。但要注意的是,这样会导致排序失效,因为只取了前20个字符串,索引只能保证大范围的有序。

也可以在后期根据一定的计算规则计算最佳索引长度:distinct(left(字段,长度))/count约等于1

  1. 后期可以根据慢sql日志继续优化索引
    随意业务的迭代,查询条件也会发生改变,此时可以根据慢sql持续优化索引
  2. 可以建唯一索引,尽量建唯一索引
  3. where条件和order by冲突时时,优先取where的条件建索引
    因为筛选出数据后,一般数据量比较少,排序的成本不大,所以优先让数据更快的筛选出来。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
21天前
|
存储 自然语言处理 关系型数据库
MySQL高级篇——索引的创建与设计原则
索引的分类与使用、MySQL8.0索引新特性、适合创建索引的情况、不适合创建索引的情况
MySQL高级篇——索引的创建与设计原则
|
21天前
|
存储 SQL 关系型数据库
MySQL高级篇——索引失效的11种情况
索引优化思路、要尽量满足全值匹配、最佳左前缀法则、主键插入顺序尽量自增、计算、函数导致索引失效、类型转换(手动或自动)导致索引失效、范围条件右边的列索引失效、不等于符号导致索引失效、is not null、not like无法使用索引、左模糊查询导致索引失效、“OR”前后存在非索引列,导致索引失效、不同字符集导致索引失败,建议utf8mb4
MySQL高级篇——索引失效的11种情况
|
1月前
|
存储 关系型数据库 MySQL
MySQL基础:索引
MySQL中的索引是一种数据结构,能大幅提升数据库查询效率和减少I/O成本,类似于书的目录帮助快速定位内容。其优势包括提高检索效率和降低排序成本,但会占用空间并影响更新表的效率。鉴于查询远多于更新,索引仍被推荐使用。索引分为多种类型,如B+树和哈希索引,其中B+树因其较低的高度和稳定的查询开销成为常用选择。创建和删除索引需谨慎,以免影响性能。
42 4
MySQL基础:索引
|
21天前
|
存储 SQL 关系型数据库
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
MySQL调优主要分为三个步骤:监控报警、排查慢SQL、MySQL调优。 排查慢SQL:开启慢查询日志 、找出最慢的几条SQL、分析查询计划 。 MySQL调优: 基础优化:缓存优化、硬件优化、参数优化、定期清理垃圾、使用合适的存储引擎、读写分离、分库分表; 表设计优化:数据类型优化、冷热数据分表等。 索引优化:考虑索引失效的11个场景、遵循索引设计原则、连接查询优化、排序优化、深分页查询优化、覆盖索引、索引下推、用普通索引等。 SQL优化。
168 15
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
|
21天前
|
SQL 缓存 关系型数据库
MySQL高级篇——关联查询和子查询优化
左外连接:优先右表创建索引,连接字段类型要一致、内连接:驱动表由数据量和索引决定、 join语句原理、子查询优化:拆开查询或优化成连接查询
MySQL高级篇——关联查询和子查询优化
|
21天前
|
存储 缓存 关系型数据库
MySQL高级篇——存储引擎和索引
MyISAM:不支持外键和事务,表锁不适合高并发,只缓存索引,内存要求低,查询快MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务、行级锁、外键,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。5.5之前默认的存储引擎优势是访问的速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用针对数据统计有额外的常数存储。故而 count(*) 的查询效率很高表名.frm 存储表结构;表名.MYD 存储数据 (MYData);
MySQL高级篇——存储引擎和索引
|
21天前
|
算法 关系型数据库 MySQL
MySQL高级篇——排序、分组、分页优化
排序优化建议、案例验证、范围查询时索引字段选择、filesort调优、双路排序和单路排序、分组优化、带排序的深分页优化
MySQL高级篇——排序、分组、分页优化
|
21天前
|
存储 关系型数据库 MySQL
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
覆盖索引、前缀索引、索引下推、SQL优化、EXISTS 和 IN 的区分、建议COUNT(*)或COUNT(1)、建议SELECT(字段)而不是SELECT(*)、LIMIT 1 对优化的影响、多使用COMMIT、主键设计、自增主键的缺点、淘宝订单号的主键设计、MySQL 8.0改造UUID为有序
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
|
5天前
|
存储 关系型数据库 MySQL
MySQL索引失效及避免策略:优化查询性能的关键
MySQL索引失效及避免策略:优化查询性能的关键
22 3
|
10天前
|
关系型数据库 MySQL 数据库
MySQL删除全局唯一索引unique
这篇文章介绍了如何在MySQL数据库中删除全局唯一的索引(unique index),包括查看索引、删除索引的方法和确认删除后的状态。
32 9
下一篇
无影云桌面