MySQL优化学习手札(四) 单表访问方法

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: MySQL优化学习手札(四) 单表访问方法

概述

我们日常的查询,基本可以分为三类:

  1. 单表查询
  2. 子查询
  3. 连接查询

这三种可以组合,也可以分开,上面的顺序也是我们学习SQL的顺序,我们下面介绍其实现,也是按照上面这种顺序。看本篇之前建议先看这个本篇的前几篇:

  • MySQL优化学习手札(一)
  • MySQL优化学习手札(二)
  • MySQL优化学习手札(三)

当然如果你对MySQL的B+树索引比较熟悉也可以不看。

单表访问方法

我们写了一个单表查询的语句,MySQL是如何获取我们查询语句所对应的记录的呢:

SELECT * FROM Student WHERE ID = 1;

我们忽略语法解析、连接建立这些步骤,这些都搞定了,那么MySQL该如何定位记录呢?MySQL中执行查询的方式一共有以下两种:

  • 全表扫描(一条记录一条记录的去比较)
  • 使用索引进行查询,索引也有不同的类型,所以就算是使用索引进行查询,也分为几种不同的情况:
  • 针对主键或唯一二级索引的等值查询
  • 针对普通二级索引的等值查询
  • 针对索引列的范围查询
  • 直接扫描整个索引

在MySQL中执行查询语句的方式称之为访问方法或者访问类型。

通过主键列等值匹配来定位记录-const

SELECT * FROM Student WHERE ID = 1; ID是主键

Id是Student这张表的主键,这里让我们再回忆一下MySQL存储数据的基本结构:

  • InnoDB将数据划分为若干页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为16KB
  • InnoDB存储引擎会自动为主键(如果没有它会自动帮我们添加)建立聚簇索引,聚簇索引的叶子结点包含完整的用户记录。
  • 每个索引都对应一棵B+树,B+树分为好多层,最下边一层是叶子结点,其余的是内结点。所有的用户记录都存储在B+树的叶子结点。所有的目录项记录都存储在内结点
  • 我们也可以为自己感兴趣的列建立二级索引,二级索引的叶子结点包含的用户记录由索引列+主键组成,所以如果想通过二级索引来查找完整的用户记录的话,需要通过回表操作,也就是在通过二级索引找到主键值之后再到聚簇索引中查找完整的用户记录。
  • B+树的每层结点都是按照索引列值的从小到达的顺序排序而组成了双向链表,而每个页内的记录(不论是用户记录还是目录项记录)都是按照索引列的值从小到达的顺序而形成了一个单链表。如果是联合索引的话,则页面和记录先按照联合索引前边的列排序,如果该列的值相同,再按照联合索引后边的列进行排序。

也就是ID这一列是聚簇索引,同时按ID进行排序,所以这个相当快,可以先用定位到目录项地行为这一列位于哪个数据页,定位到之后,再用二分查找定位这条记录在哪个位置。现在让我们为Student再加上一列name,并为该列建立唯一索引,我们去执行如下查询:

select  * from student where name ='aa'

也同聚簇索引类似,但是name列由于是非聚簇索引列,叶子结点没有完整的记录,定位到name= ‘aa’这条记录后,还用用这条记录对应的主键去主键索引列去查完整的记录。即便有回表的代价,MySQL的开发人员仍然认为这种查询方式是非常快的,将这种访问方法定义为: const,也就是常数级别。但如果查询NULL值,情况就又有所不同:

select * from  student where name is null

因为唯一索引并不限制NULL值的数量,所以上面的查询语句可能会访问到多条记录。

ref

用唯一索引列去查找NULL值,会查询到多行,这种情况和用非唯一索引列去匹配记录类似,查询步骤和用唯一索引列去查询是一样的,定位这条记录在哪个数据页,然后到具体的页里面去匹配记录,由于我们是select * , 所以还要回表查询。如果匹配的记录比较少,回表的代价还是比较低的,MySQL就更倾向于使用索引+回表的方式来查找,采用二级索引进行等值查询记录的方式,MySQL将其定义为ref。

但对于某个包含多个索引列的二级索引列来说,只要最左边的连续索引列是与常数的等值比较就可能采用ref的访问方法。至于为什么是可能原因在于还是成本的衡量,如果你是select * , 这就要回表。如果查询的记录比较多,让MySQL觉得与其索引列+回表还不如直接扫描全表的话。

现在让我们再为Student再加: age, sex,同时为age、sex建一个普通的索引。如果我们查询的语句写成了下面这样:

select * from student where age = '18' and sex >  '女'

这种访问方法在MySQL中的查询级别就不是ref,原因在于对sex这一列使用的是范围查询。

ref_or_null

根据普通索引进行匹配,但同时查找该索引列为null的值,像下面这样:

select * from  student where name = 'aa' and  name is  null

这种查询级别在MySQL中我们称之为ref_or_null.

range-范围查询

我们日常的开发中范围查询也是高频出现,像下面这样:

select * from student where age in ('96','18') OR (age >= 28 and key <= 79);

对于MySQL来说执行这个查询有两种选择,省事速度慢自然是全表扫描,当然也可以使用二级索引+回表的方式。上面的条件是一个搜索范围,在MySQL中将这种查询级别定义为range。

index

select sex,age from student where sex = '男'

age和sex组成的联合索引中,age在前,sex和age上都有索引,那对于MySQL来说就有两种选择,一种是用sex上的索引+回表查出age。另一种是遍历sex和age对应的联合索引,这种效率事实上更高,因为非主键索引只存储了主键列和索引列,数据页会更小,也不需要回表,代价更小。这种查询级别在MySQL中被定义为index。

all

如其名,扫描全表。

该如何回表

前面我们唠叨了回表这个词,事实上行对于不同的查询场景也有不同的回表策略。

  • 情况一, 查询用到了两个索引列,该如何回表:
select  *  from student where age > '50'  and name = '张三'

age 和 name 都是索引列,  该使用哪个索引呢,MySQL优化器一般会根据Student的统计数据来判断到底使用哪个条件对应的二级索引中查询扫描的行数会更少。然后将从二级所以呢中查询的结果经过回表得到完整的用户记录,再根据另一个条件过滤记录。一般情况来说,等值查找比范围匹配需要扫描的范围更少,这里假设查询优化器使用name列进行查找。所以对于上面的语句,查询步骤如下:

根据name = ‘张三’去name对应的索引上去查找对应二级索引的记录。

然后回表二级索引对应的主键去聚簇索引中找到对应的完整的用户记录,再根据age > ‘50’进行过滤。

  • 情况三:   有的搜索条件无法使用索引

我们再为Student 添加一个card的字段,然后执行下面的查询:

select * from student where age > 30 and card = '001'

对于这种情况,card上面没有索引,MySQL会倾向于使用age上的索引然后回表查出记录,再用card =  ‘001’进行过滤。

那如果是or呢?

select * from student where age > 30 or card = '001'

这种情况就没有办法用到age上的索引,因为age索引上就只有索引列和主键,MySQL就可能会进行扫描。

索引合并

MySQL在一般情况下执行一个查询是最多只会用到单个二级所以呢,有一般就会有特殊情况,在一些特殊情况可能在一个查询中使用到多个二级索引,使用到多个二级索引的这种情况,在MySQL中被称为:index merge.

Intersection合并

下面的讨论中暂时移除age身上的单独索引列,仅保留age、sex的联合索引。

Intersection 意为交集,简单的理解就是一个查询可以使用多个二级索引,将多个二级索引中查询到的结果取交集:

select * from  Student  where age = '18'and sex = '男' and name = '张三'

age 和 name 上都有索引,那么执行上面的语句,就有两种执行方案供MySQL所选择(不要提全表):

  • 选择一个条件去对应的索引列上去查找记录,然后回表,用另一个条件过滤。
  • age索引列去查找记录,name索引列查找记录。由于所以存储的有主键列,这两个结果集求交集。

那哪种访问方式成本比较低呢,一般情况下MySQL更倾向于选择读取多个二级索引的方式,因为读取二级索引是顺序I/O, 回表是随机I/O.

所以如果只读取一个二级索引时需要回表的记录数特别多,而读取多个二级索引之后取交集的记录数非常少,这种情况回表的损耗可能就要比访问多个索引更高。

下面两个查询就不能使用Intersection索引合并:

select * from Student where  age = '18' and sex = '男' and name > 'zhangsan'

我们分析一下为什么这种情况为什么就不能使用索引合并求交集,原因在于时间复杂度的问题,age是范围匹配(age 和 sex建立联合索引,),扫描到的主键列未必有序,有序集合求交集的时间复杂度O(n),无序集合求交集的时间复杂度为O(n^2).

下面的查询是一样的原因,不能用到索引合并:

select * from Student where  age = '18'  and name =  'zhangsan'

联合索引age相同,按照sex进行排序,但是仅根据age这一列得到的记录主键可能还是无序的,所以也无法用到索引合并。

主键列可以是范围匹配的查询:

select  * from Student where id > 1 and name = '张三'

上面不是说了范围匹配无法用到索引合并吗?但是索引存储了索引列和主键,我们甚至可以认为这个只用到了name这一列,然后再回表。如个只用name列再回表的代价大于主键列和name列使用索引列合并的代价,那么MySQL就可能倾向于使用id列和name进行索引合并。

Union合并

有求交集,就有求并集,这是一对双生子。MySQL在某些特定去年高考下才可能会使用到Union索引合并:

  • 情况一: 二级索引列是等值匹配的情况,对于联合索引来说,在联合索引中的每个列都必须等值匹配,不能出现只匹配部分列的情况。
select * from student where name = 'a' or (age = '18' and sex = '男')

下面两个查询就不能进行Union索引

select * from student where name > 'a' or (age = '18' and sex = '男')
select * from student where name = 'a' or age = '18'

原因还是和排序有关,两个集合进行排序。

  • 情况二: 主键列可以是范围匹配
  • 情况三:使用Intersection 索引合并的搜索条件

可以理解为两个交集的并。

Sort-Union合并

Union索引合并的使用条件有点苛刻,必须保证各个二级索引列再进行等值匹配的条件下才可能被用到。

select * from Student  where name > 'a' and age < '25'

上面这个SQL语句就无法用到Union排序,原因在于从name和age这两个列查到的主键值不是排好序的,但如果排序的代价不高呢,MySQL在代价不高的情况下会如下执行:

  • 先根据条件name > ‘a’ 取值,然后根据主键进行排序
  • 再根据age < ‘25’ 取值,然后根据主键进行排序。

拍好序剩下的操作就和Union索引合并方式就一样的。

索引合并的注意事项

我们先为card属性添加一个普通索引:

select * from student where name = '张三' and card = '001'

这个查询之所以可能用到索引合并的原因在于,name 和 card 是两个索引,两个列要是一个索引就不用MySQL来合并了。这样就不用读两棵B+树了。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
24天前
|
SQL 关系型数据库 MySQL
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
MySQL慢查询优化、索引优化,是必知必备,大厂面试高频,本文深入详解,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
|
28天前
|
SQL 关系型数据库 MySQL
MySQL慢查询优化、索引优化、以及表等优化详解
本文详细介绍了MySQL优化方案,包括索引优化、SQL慢查询优化和数据库表优化,帮助提升数据库性能。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
MySQL慢查询优化、索引优化、以及表等优化详解
|
17天前
|
关系型数据库 MySQL Java
MySQL索引优化与Java应用实践
【11月更文挑战第25天】在大数据量和高并发的业务场景下,MySQL数据库的索引优化是提升查询性能的关键。本文将深入探讨MySQL索引的多种类型、优化策略及其在Java应用中的实践,通过历史背景、业务场景、底层原理的介绍,并结合Java示例代码,帮助Java架构师更好地理解并应用这些技术。
22 2
|
1月前
|
缓存 监控 关系型数据库
如何优化MySQL查询速度?
如何优化MySQL查询速度?【10月更文挑战第31天】
71 3
|
1月前
|
缓存 关系型数据库 MySQL
如何优化 MySQL 数据库的性能?
【10月更文挑战第28天】
75 1
|
1月前
|
关系型数据库 MySQL
Mysql 中日期比较大小的方法有哪些?
在 MySQL 中,可以通过多种方法比较日期的大小,包括使用比较运算符、NOW() 函数、DATEDIFF 函数和 DATE 函数。这些方法可以帮助你筛选出特定日期范围内的记录,确保日期格式一致以避免错误。
|
1月前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
196 1
|
7天前
|
SQL 关系型数据库 MySQL
MySQL导入.sql文件后数据库乱码问题
本文分析了导入.sql文件后数据库备注出现乱码的原因,包括字符集不匹配、备注内容编码问题及MySQL版本或配置问题,并提供了详细的解决步骤,如检查和统一字符集设置、修改客户端连接方式、检查MySQL配置等,确保导入过程顺利。
|
27天前
|
SQL 关系型数据库 MySQL
12 PHP配置数据库MySQL
路老师分享了PHP操作MySQL数据库的方法,包括安装并连接MySQL服务器、选择数据库、执行SQL语句(如插入、更新、删除和查询),以及将结果集返回到数组。通过具体示例代码,详细介绍了每一步的操作流程,帮助读者快速入门PHP与MySQL的交互。
34 1
|
29天前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
39 4