从《数据库索引设计与优化》看mysql索引设计

很久之前写的一篇文章,主要是结合mysql45讲和《数据库索引设计与优化》讨论索引设计的,拿出来分享下。

选用什么引擎

对于INSERT_SELECT型数据库,如果没有事务的要求,更倾向于选择MyISAM。
因为InnoDB会维护更多的数据,包括以下几个方面:

  1. InnoDB的聚集索引结构在索引块中保存了数据块,因此在缓存时,要同时保存数据块和索引块;MyISAM的聚集索引则只保存索引块,叶子节点指向数据块,不需要缓存数据块的开销。
  2. MyISAM不需要写用来保证crash-safe能力的redo log;InnoDB需要在可重复读隔离级别时维护一致性视图(快照读),在读提交隔离级别时每个语句创建新的视图(当前读),也就是说MyISAM不需要维护undo log。
  3. 还有一个选择MyISAM潜在的好处是MyISAM中维护了count(*)的值,这个值在统计计算中经常被用到。而对于InnoDB来说,将会遍历一颗最小的索引数,逐行统计行数,显然比MyISAM慢很多。

下面两个图可以说明聚集索引和非聚集索引的结构。如果业务需要事务、外键、crash-safe能力,那就应当选择InnoDB。
image.pngimage.png

表的结构正确吗

我们都很了解表结构的三范式:

  1. 符合1NF的关系中的每个属性都不可再分
  2. 2NF在1NF的基础之上,消除了非主属性对于码的部分函数依赖

image.png
image.png

  1. 3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖

image.png
如果不符合这几点,就会出现数据冗余过大,插入异常,修改异常,删除异常等的问题,所以建议表的结构应该严格遵守这几点。
在这之外,我还想说一个一般大家不会注意到的问题。
首先引入两个概念,第一个概念是谓词,谓词指的是SQL语句中的搜索参数,或者说是条件表达式或者真值表达式。
第二个概念是过滤因子,过滤因子描述了谓词的选择性,即表中满足谓词条件的行数占总行数的比例。
对于两个谓词来说,它们的过滤因子一般不能简单相乘。两个谓词相关性越小,组合谓词的过滤因子越接近两个过滤因子的乘积。所以如果有非主属性对于码的部分函数依赖,组合谓词的过滤因子将得到最差的情况。

统计结果

优化order by

第一个需求是根据条件筛选得到所有的语句,然后order by通过src_id排序返回。是一个很简单的需求。
想要优化order by语句,首先要知道这个语句是如何执行的。

order by排序MySQL会根据使用的表结构的不同、参数的不同、内存大小的不同,选择不同的排序方法。

首先来看引擎表如果单行长度不大,就会采用全字段排序的方法。单行长度的限制是由数据库的max_length_for_sort_data参数限制的。如果单行小于这个参数,那就会采用全字段排序。全字段排序的过程如下图所示:

image.png
全字段排序的排序过程也不一定全在内存中完成,sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。
如果使用了磁盘临时文件,mysql将不得不使用归并排序,将需要排序的数据分成N份,每一份单独排序后存在这些临时文件中。然后把这N个有序文件再合并成一个有序的大文件。
此处多出来的开销是读写临时文件的IO时间,这个开销是相当大的。
如果单行长度太大,超过了max_length_for_sort_data的大小,就会使用row_id的排序方法:
image.png
可以看到,row_id会多出一次回表的操作,而且将物化结果集的过程提前了,一般认为物化结果集越晚进行越好,因为引擎会为了一致性做出很多牺牲并且必须要等待io。

另外提一点,sort_buffer是一个一维数组,性能消耗会比内存临时表更小。

然后是内部临时表。什么时候会用到内部临时表呢?

  1. 如果语句执行过程可以一边读数据,一边直接得到结果,是不需要额外内存的,否则就需要额外的内存,来保存中间结果;
  2. join_buffer 是无序数组,sort_buffer 是有序数组,临时表是二维表结构;如果执行逻辑需要用到二维表特性,就会优先考虑使用临时表。比如,union 需要用到唯一索引约束, group by 还需要用到另外一个字段来存累积计数。

内部临时表的存储方式由另一个参数:tmp_table_size设置,这个配置限制了临时表的大小,默认值是 16M。如果临时表大小超过了 tmp_table_size,那么内存临时表就会转成磁盘临时表。
对于内存临时表,排序仍然是rowid方式。
image.png
而对于磁盘临时表,同样会采用归并排序的方式。

**对于使用了limit方式的磁盘表,如果limit行数的大小不超过sort_buffer_size的大小,那么MySQL会将排序方法特化为优先队列排序也就是堆排。**这样做的好处是不需要产生临时文件。

我们可以从以上这些讨论,看出order by是一个昂贵的操作,无论是耗费的cpu时间、内存还是反复回表,我们都要尽量避免这几个操作。可以通过以下几种方式优化:

  1. 建立联合索引。如果order by的数据是在索引树上有序的那么就不需要sort_buffer,也不需要排序了。这时候,explain分析select语句就能够发现Extra字段中没有Using filesort也没有Tempory了。
  2. 覆盖索引。在索引中覆盖所有字段,那么就避免了回表的操作。这时候,explain分析select语句就能发现Extra字段中有using index,说明覆盖了索引。
  3. 在现有系统中增加索引往往也是非常昂贵的操作,所以可以更改数据库参数,避免产生磁盘文件。使用更大的sort_buffer_size和tmp_table_size参数避免磁盘文件进行转储。

优化group by

group by的执行流程如下图所示:
image.png
排序仍然是使用上面rowid的方式进行,那么这个过程我们可以如何优化呢?

  1. 使用order by null避免排序。如果你的业务不需要使用有序的结果,那么避免group by默认的排序无疑能节省巨大的开销。
  2. 使用SQL_BIG_RESULT字段禁止SQL优化器使用内存临时表。为了防止出现“先放到内存临时表,插入一部分数据后,发现内存临时表不够用了再转成磁盘临时表”的情况,我们可以使用SQL_BIG_RESULT告诉优化器,直接使用磁盘临时表,避免转换开销。
  3. 索引和数据库参数的更改仍然是有效的。

如何选好索引

一切之前…

在开始之前,我们需要了解几个概念,学习这些概念对于索引的设计是至关重要的。
索引片
索引被扫描的部分。索引片的宽度会影响对表的同步读的数量。
三星索引

  • 如果必须扫描的索引片的宽度是最小的,那么得到一颗星。
  • 如果索引行的顺序与查询语句的需求一致,避免了排序操作,那么得到一颗星。
  • 如果索引行包含查询语句中所有的列,避免回表,那么得到一颗星。

困难谓词
无法参与定义索引片的谓词(不可索引谓词),即该谓词无法成为匹配谓词
不可索引谓词,比如<>
可索引谓词,比如C1=5,in条也是可索引谓词,而or条件就是不可索引谓词
最小的索引片宽度
对于如何得到最小的扫描索引片宽度,可以使用以下的步骤:
取出对优化器来说不过分复杂的等值谓词(非困难谓词)列作为前导列(任意顺序都可,因为对于任意顺序在B+树上的查找时间都将是相同的),将选择性最好的范围谓词作为索引的下一列。
注意:最小的索引片宽度并不一定是最好的。
半宽索引
一个包含WHERE子句中所有列的索引,使用半宽索引将使得访问路径在必要时才访问表。
宽索引
至少满足第三条的索引称为宽索引
最左匹配原则
最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配
最佳索引
为一个select设计出的最好的索引,可能是二星索引也可能是三星索引。
范围谓词和三星索引
对于order by列的索引,仅当其放在范围谓词列之前时才能够满足第二颗星。否则会因为最左匹配原则,使这个索引失效,从而失去避免排序的作用。因为顺序的限制,这时候可能就无法得到最窄的扫描索引片了,因此这种情况下最佳的索引将会是一个二星索引。

评估语句执行时间

我们现在已经可以通过一些工具,比如explain得到的rows,慢查询日志中的总扫描行数来查看访问表的次数。但是对于语句执行时间的评估或者说概念上的理解仍然是很重要的,它能够帮助我们更好的定位问题,并且是一个非常低成本的工具。
这里介绍一种比较直观的估算方法:QUBE(快速上限估算法)。

在这里插入图片描述

LRT指的是本地响应时间,TR指的是随机访问的数量,TS指的是顺序访问的数量,F指的是有效FETCH的数量。

下面依次介绍这几个概念:
访问:DBMS读取一个索引行或者表行的成本称为一次访问
随机访问:一次对于表行的查找访问
顺序访问:读取物理上连续的下一行
Fetch:游标操作中用于提取数据的SQL调用,相当于物化表行的成本

举几个简单的例子:(针对InnoDB引擎)

SELECT CNO,LNAME,FNAME
FROM CUST
WHERE CNO = :CNO

其中CNO字段加了主键索引。
我们假设符合要求的结果只有一个。
那么LRT = 210ms+10.1ms=20ms
包括对于主键索引的一次随机访问和对于表行(缓存数据块)的随机访问加上一个字段的Fetch成本。

SELECT CNO,LNAME,FNAME
FROM CUST
WHERE ZIP = :ZIP AND LNAME = :LNAME
ORDER BY FNAME

其中ZIP、LNAME、FNAME字段加了组合索引。CNO字段加了主键索引。
我们假设符合要求的结果共有1000个。
因为FNAME字段在索引中,所以就避免了排序的开销,但因为没有ZIP字段,所以仍然需要回表。
首先访问索引行(找到第一个符合要求的数据)需要一次随机访问;然后顺序访问1000次索引行(直到找到最后一个不符合要求的数据为止);之后访问主键索引需要一次随机访问,和999次顺序访问主键索引。之后提取1000个数据。
所以LRT=210ms+19990.01ms+1000*0.1ms=140ms。

如果CNO不是主键呢?也就是说CNO字段上没有任何索引的时候会发生什么?

1000次对于主键索引的顺序访问将会变成1000次对于字段的随机访问,
LRT=10s+10ms+100ms 这个时候将CNO加到索引中将会是一个好主意。

增加一个索引的代价

  1. 每次增加一个索引,插入操作花费的时间增加10ms
  2. RAID 5下磁盘平均负载的贡献
    在这里插入图片描述

磁盘平均负载 = 随机插入涉及的索引数量*插入频率/磁盘数量
现在我们可以粗略的知道阿里云DMS实例中的SQL分析倍数怎么来的了!就是性能的提高-增加索引的代价

如何制定最合适的索引

根据我们前面提到的范围谓词和三星索引的原则,我们有两种方案可以选择:
第一种方案:

  • 选出对于优化器来说不过分复杂的等值谓词列,将这些列作为索引的前导列
  • 将具有最好选择性的范围谓词作为索引的下一个列
  • 以正确的顺序添加order by列
  • 以任意顺序将SELECT语句中其它的列添加到索引中

第二种方案:

  • 选出对于优化器来说不过分复杂的等值谓词列,将这些列作为索引的前导列
  • 以正确的顺序添加ORDER BY列
  • 以任意顺序将SELECT中其它列添加到索引中

最后再举一个例子:
对于SELECT语句:

SELECT cno,fname FROM CUST WHERE city = :city AND lname IN ( lname1,lname2 ) 
ORDER BY fname

我们如何设计它的索引呢?
我们假设过滤因子最大大小为0.1,数据集为1 000 000
按照两种方案分别设计的索引包括:
A:(city,lname,fname,cno)
B:(city,fname,lname,cno)
A的方式因为最左匹配所以根据city和lname取出后需要进行排序
但是A的索引片厚度只有B的十分之一,也就是A:1 000 0000.10.1 B:1 000 0000.1
LRT(A)=1
10ms+10 0000.01ms+10 0000.1ms
LRT(B)=110ms+100 0000.01ms+10 000*0.1ms
A大约为1s,B大约为2s
排序成本在1s的差别中是微不足道的,所以我们在这里不考虑它。

推荐的方式

  1. 选择正确的表结构
  2. 制定更好的索引
  3. 使用explain、慢查询日志、云服务工具(性能洞察和分析)
  4. …(比如高性能MySQL、数据库索引设计与优化、MySQL实战45讲等)

名词速查


设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K(这个“完全”不要漏了),那么我们称 K 为候选码,简称为。在实际中我们通常可以理解为:假如当 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码。一张表中可以有超过一个码。(实际应用中为了方便,通常选择其中的一个码作为主码
主属
包含在任意一个码中的属性称为主属性。
非主属性
不包含在任何一个码中的属性称为非主属性。
完全函数依赖
在一张表中,若 X → Y,且对于 X 的任何一个真子集(假如属性组 X 包含超过一个属性的话),X ’ → Y 不成立,那么我们称 Y 对于 X 完全函数依赖,记作 X F→ Y。
部分函数依赖
假如 Y 函数依赖于 X,但同时 Y 并不完全函数依赖于 X,那么我们就称 Y 部分函数依赖于 X,记作 X P→ Y
传递函数依赖
假如 Z 函数依赖于 Y,且 Y 函数依赖于 X (严格来说还有一个X 不包含于Y,且 Y 不函数依赖于Z的前提条件),那么我们就称 Z 传递函数依赖于 X ,记作 X T→ Z
索引片
索引被扫描的部分。索引片的宽度会影响对表的同步读的数量。
三星索引

  • 如果必须扫描的索引片的宽度是最小的,那么得到一颗星。
  • 如果索引行的顺序与查询语句的需求一致,避免了排序操作,那么得到一颗星。
  • 如果索引行包含查询语句中所有的列,避免回表,那么得到一颗星。

困难谓词
无法参与定义索引片的谓词,即该谓词无法成为匹配谓词。
最小的索引片宽度
对于如何得到最小的扫描索引片宽度,可以使用以下的步骤:
取出对优化器来说不过分复杂的等值谓词(非困难谓词)列作为前导列(任意顺序都可,因为对于任意顺序在B+树上的查找时间都将是相同的),将选择性最好的范围谓词作为索引的下一列。
注意:最小的索引片宽度并不一定是最好的。
半宽索引
一个包含WHERE子句中所有列的索引,使用半宽索引将使得访问路径在必要时才访问表。
宽索引
至少满足第三条的索引称为宽索引
最左匹配原则
最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配
最佳索引
为一个select设计出的最好的索引,可能是二星索引也可能是三星索引。
范围谓词和三星索引
对于order by列的索引,仅当其放在范围谓词列之前时才能够满足第二颗星。否则会因为最左匹配原则,使这个索引失效,从而失去避免排序的作用。因为顺序的限制,这时候可能就无法得到最窄的扫描索引片了,因此这种情况下最佳的索引将会是一个二星索引。

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

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

相关文章

一个古老的终端显示控制命令-tput命令|帮你解决中英文混合时终端列对齐输出问题

终端显示控制对于写一个 终端脚本 是非常有用的&#xff0c;在没有图形界面时Unix和类Unix系统用户都是在Terminal控制终端下工作的。虽然Linux操作系统的图形界面出现淡化了终端显示控制&#xff0c;但终端控制功能依旧是每个命令行用户不可或缺的一部分。只是我们不知道在使用…

【LeetCode:129. 求根节点到叶节点数字之和 | 二叉树 + 递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

记录汇川:水塔指令解释-ST

可以通过帮助查看指令手册 PLC的IO地址映射-两种方法 第一种&#xff1a; 新建一个全局变量表&#xff0c;按照如图所示建立IO地址 第二种&#xff1a; 直接如图所示位置定义名字 注意&#xff1a;IW和QB这两个前面一个有蓝色M一个没有。 蓝色的M表示模块发生变化的时候地址不会…

【S32K 进阶之旅】 NXP S32K3 以太网 RMII 接口调试(1)

前言 大联大世平集团推出了一款基于 NXP 车规级 MCU S32K344 的开发板——花名“Cavalry”&#xff0c;它使用 BGA257 封装的 32 位 ArmCortex-M7 S32K344 作为主控芯片&#xff0c;在69.6*130mm 的小体积开发板上搭载了 SBC 电源管理芯片、CAN 收发器、LIN 收发器、FLASH 存储…

低噪声,高增益的音频信号处理电路芯片选型分析

随着智能手机、汽车音频、AI智能音箱&#xff0c;智能家居、家庭影院、平板电脑、笔记本电脑等智能设备的普及&#xff1b;数字音频功放芯片的应用也越来越广泛&#xff1b;同时对音频信号处理的芯片的性能要求越来越高&#xff1b;以下几款就是常用热门音频信号处理电路芯片分…

NVM NodeJs版本管理 通关宝典

NVM NodeJs版本管理 通关宝典&#x1f3f9; 文章目录 NVM NodeJs版本管理 通关宝典&#x1f3f9;一、NVM是什么二、开始使用NVM三、NVM 命令速查四、手动安装特定Node版本(Windows)&#x1f644;4.1 NVM for windows 运行机制4.2 手动安装流程 五、切换 NVM 下载镜像源六、常见…

21. Mysql 事件或定时任务,解放双手,轻松实现自动化

文章目录 概念常见操作事件调度器操作查看事件创建事件删除事件启动与关闭事件 精选示例构造实时数据定时统计数据 总结参考资料 概念 Mysql 事件是一种在特定时间点自动执行的数据库操作&#xff0c;也可以称呼为定时任务&#xff0c;它可以自动执行更新数据、插入数据、删除…

x-cmd pkg | doggo - 现代化的 DNS 客户端

目录 简介首次用户快速实验指南功能特点类似工具与竞品进一步探索 简介 doggo 是一个由 Karan Sharma 于 2020 年使用 Go 语言开发的 DNS 客户端。它类似于 dig 命令&#xff0c;但旨在以现代化、简洁和可读的格式输出 DNS 查询结果。 首次用户快速实验指南 使用 x doggo 即可…

1.4 day4 IO进程线程

使用两个子进程进行文件拷贝&#xff0c;父进程进行资源回收 #include <myhead.h> int main(int argc, const char *argv[]) {//创建一个文件描述符并以只读的方式打开int fd-1;if((fdopen("./test.bmp",O_RDONLY))-1){perror("open error");return…

IDAPython详细版(二)

六&#xff1a;操作数 可以使用idc.get_openrand_typed(ea,n)得到操作数的类型。ea是地址&#xff0c;n是索引 这里有8种不同类型的操作数类 0_void 如果一个指令木有任何操作数它将返回0 0_reg 如果一个操作数是一个普通的寄存器将返回此类型。这个值在内部表示为1. o_mem …

java: 写入数据到HBase

一、添加依赖 <dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-client</artifactId><version>2.6.0</version></dependency><dependency><groupId>org.apache.hbase</groupId><art…

Prometheus-Alertmanage钉钉实现告警

获取钉钉的webhook地址 1、注册企业钉钉 a、注册企业钉钉 浏览器打开钉钉注册页面 填入手机号码&#xff0c;填入获取到的验证码&#xff0c;点注册 填入企业资料并注册 注册成功后&#xff0c;扫描二维码下载钉钉&#xff0c;如下图&#xff1a; b、添加机器人 管理后台 因…

Modbus RTU Learn

一、传输方式 采用主从应答方式进行 帧格式 功能码对应寄存器 演示01功能码 返回数据 0F 转换成二进制 02功能码 0F 转换成二进制 03功能码 读两个寄存器 04功能码 返回数据&#xff0c;读两个寄存器 05功能码 06功能码 10功能码 响应报文

在centos上安装WordPress 及创建配置文件无反应的解决方案

一、安装Apache服务 1.直接在命令行中输入以下命令即可,顺便安装编译组件&#xff1a; yum install -y httpd yum install -y httpd-devel2.启动Apache服务和设置Apache自启&#xff1a; 1 systemctl start httpd 2 systemctl enable httpd 3.添加一个测试页面&#xff0c;…

windows安装kafka以及kafka管理工具推荐

windows安装 1.下载地址 下载地址 下载最新版本的.tgz文件解压 2.修改配置 修改config目录下的zookeeper.properties中的dataDir属性 server.properties文件中的log.dir属性 3.启动zookeeper 进入到bin\windows\下的用cmd输入zookeeper-server-start.bat ..\..\config\zo…

5款实用的Win10软件,提高工作效率

​ 大家好&#xff0c;今天我来给大家推荐5款实用的Win10软件&#xff0c;它们可以帮助你提高工作效率&#xff0c;优化系统性能。 1.视频剪辑——DaVinciResolve ​ DaVinciResolve是一款集视频剪辑、调色、特效和音频后期制作于一体的专业软件。它拥有强大的图像处理技术&a…

Minitab 18安装包下载及安装教程

Minitab 18下载链接&#xff1a;https://docs.qq.com/doc/DUnRSaml6UHRpWFdn 1.选中下载好的安装包&#xff0c;鼠标右键解压到”Minitab 18“文件夹 2.选中”f4-mini181-setup.exe“&#xff0c;鼠标右击选择“以管理员身份运行” 3.点击“确定” 4.点击“下一步” 5.勾选我接…

android 分享文件

1.在AndroidManifest.xml 中配置 FileProvider <providerandroid:name"android.support.v4.content.FileProvider"android:authorities"com.example.caliv.ffyy.fileProvider"android:exported"false"android:grantUriPermissions"true…

宝宝的听力发育进程

小宝宝听力发育进程&#xff1a; 在母亲怀孕中晚期&#xff0c;小宝宝就有了听觉&#xff1a;6个月胎儿的听力已经和成年人相当了。 ◆ 出生0-7天的小宝宝会随声音变化产生不同反应&#xff0c;当在宝宝耳边拍巴掌或摇摇铃&#xff0c;宝宝可能会有惊跳反应&#xff0c;或吓哭…

查询json数组

步骤一&#xff1a;创建表格 首先&#xff0c;我们需要创建一个表格来存储包含JSON对象数组的数据。可以使用以下代码创建一个名为 my_table 的表格&#xff1a; CREATE TABLE my_table (id INT PRIMARY KEY AUTO_INCREMENT,json_data JSON ); 上述代码创建了一个包含两个列的…