数据结构与索引-- mySql索引诡异事件

什么时候使用B+树索引

  • 并不是所有查询条件下出现的列都需要添加索引。对于什么时候添加索引,我们通过经验判断,访问表中很少一部分行时候,使用B+树索引才有意义。
  • 对于性别字段,地区字段,类型字段,他们取值范围很少,即选择性低。如下sql
select * from moment where status = 1;
  • 对于性别,状态,可取值范围局限性非常大。对于上述SQL得到的结果可能是该表50% 的数据(假设2中状态),这时候,添加B+树索引完全没有必要。相反,如果某个字段取值范围不固定,几乎没有重复,即高选择性,此时使用B+树索引是最合适的,例如nickName 昵称字段,基本上一个应用中都不给你重复出现。
  • 如上,当访问选择性高的字段并从表中取出很少一部分行时候,对这个字段添加B+树索引是非常有必要的,但是如果出现了访问字段是高选择性的,但是取出的行数据占表中大部分数据时候,MySql数据库可能就不会使用B+树索引了,我们看如下一个案例。
show index from New_UserBaseInfo;

在这里插入图片描述

  • 表New_userBaseInfo大约有450W条数据。encodePhone字段上有一个索引,这时候,我们查找 ‘89F5F342F1ABE260F4F3D728174CF379’ 这个加密手机号时候,得到如下执行计划:
explain select * from New_UserBaseInfo  where encodePhone = '89F5F342F1ABE260F4F3D728174CF379' 

在这里插入图片描述

  • 可以看到 使用了idx_encodePhone这个索引,也符合我们前面提到的套选择性,选取表中很少行的原色,但是入座执行下面的这条语句:
explain select * from New_UserBaseInfo  where encodePhone > '89F5F342F1ABE260F4F3D728174CF379' 

在这里插入图片描述

  • 可以看到possible_keys依然是idx_encodePhone,但是实际上优化器使用的索引key是null, 而且type是ALL,说他他匹配到了对应的索引,但是他并没有使用索引去查下,还是全表查询。way???
  • 因为这不符合我们之前说的规则,虽然encodePhone 这个字段的值是高选择性,但是我们取出的行数据中占了表中的一大部分数据。可以看到rows显示的是44W+ 数据,包括99%的数据了。因此查询优化器并没有使用索引
  • 也许会有疑问,查找加密手机号大于 89F5F342F1ABE260F4F3D728174CF379 的字段,这种情况几乎不太可能出现。的确如此,但是我们考虑New_UserBaseInfo上 的lastRegisterTime 字段(注册时间),改字段日期类型,字段上有一个idx_lastRegisterTime 非唯一索引,看如下两条查询语句:
explain select * from New_UserBaseInfo where lastRegisterTime < '2019-06-24';

在这里插入图片描述

explain select * from New_UserBaseInfo where lastRegisterTime < '2019-06-25';

在这里插入图片描述

  • 查找用户注册时间小于某个时间的sql语句。前后两条SQL只相差1天时间,2条SQL语句的执行计划竟然不一样。在第二条SQL执行的时候,虽然同样使用的idx_lastRegisterTime索引,但是优化器却没有使用这个索引,而是对全表扫描。

  • MySQL数据库的查询优化器会通过explain的rows字段预估查询可能的到的行,如果大于某个值,则B+树会选择全表扫描。这个值大概是在20% 左右数据总量的时候会触发。即当我们取出的数据占比超过全数据量的20% 的时候,优化器不会使用索引,而是全表扫描。

  • 但是rows中预估的数据并不是绝对准确的,可以看大优化器判断日期小于2019-06-24 的数据是:757912,但是实际值:462377

  • 实际值少了大概38%,这可能对查询优化器的选择产生一定的影响,如果对比强制使用索引和使用优化器选择的全表扫描来查询注册日期小于2019-06-25的数据,最终发现如下:

 select * from New_UserBaseInfo force index(idx_lastRegisterTime)where lastRegisterTime < '2019-06-25';select * from New_UserBaseInfo where lastRegisterTime < '2019-06-25';
  • 查询时间分别是1.45s,5.8s,第一句SQL强制使用idx_lastRegisterTime 索引,所用的时间是4.15s,根据优化器选择的全表扫方式,执行第二SQL确5.8s,因此优化器的选择并不完全是正确的,有时候需要自己去判断。

顺序读,随机读与预读取

  • 之前介绍的规则中,索引使用原则,高选择,取出表中少部分数据。但是为什么只能是少部分数据?这就和InnoDB的顺序读和随机读取有关系
    • 顺序读:是指定顺序的读取磁盘上的快(Block);随机读(Random Read)是指访问的快不是连续的,需要磁盘的磁头不断移动。当前传统机械磁盘的瓶颈之一就是随机读取的速度较低。
    • 在网上找的资料:同时对比RAID(磁盘阵列)开启write back 和write Through的性能差异。测试磁盘是由4块15000转的硬盘组成的RADI 10.测试文件大小2GB,块大小64KB。
    • Write-through:CPU向cache写入数据时,同时向memory(后端存储)也写一份,使cache和memory的数据保持一致
    • Write-back:cpu更新cache时,只是把更新的cache区标记一下,并不同步更新memory(后端存储)。只是在cache区要被新进入的数据取代时,才更新memory(后端存储)。
write backwrite Through
顺序读193.7665.333
随机读82.11716.218
  • 可以看到,不管是否开启RAID卡的Write Back功能,磁盘的随机读性能都远远小于顺序读的性能。而上表中也说明了Write Back相对于Write Through 的性能提升。
  • 在数据库中,顺序读是指根据索引的叶节点数据就能顺序的读取所需要的行数据。这个顺序只是逻辑上的顺序读取,在物理磁盘上,行对应的数据可能还是随机分布在磁盘上的不同地址。但是相对来说,物理磁盘上的数据还是比较顺序的,因为B+树的构建是根据区来管理的,区是64个连续的页。如根据主键进行读取,或者通过辅助索引的叶节点就能读取到数据。
  • 随机读,一般指访问辅助索引叶节点不能完全得到的结果,需要根据辅助索引叶节点中的主键去找时机行数据。一般说来,辅助索引和主键所在的数据段是不同的,因此访问是随机的方式
  • 之前的sql lastRegisterTime < ‘2019-06-25’ 这条就是典型的随机读取。而正是因为读取的方式是随机的,并且随机读的性能会远低于顺序读取,因此优化器才会选择全表扫描的方式,而不是走 idx_lastRegisterTime 这个索引。
预读取
  • InnoDB存储引擎为了提高读性能,引入了预读取技术。预读取是通过一次IO请求将多个页面预读取缓冲池中,并且估计预读取的多个页马上会被访问。传统的IO请求每次只读取一个页,在传统机械硬盘较低的IOPS下。预读取技术可以大大提高读取性能。

  • InnoDB有两个预读取的方法,随机预读取(Random read ahead)和线性预读取(linear read ahead)

    • 随机预读取:指定一个区(64个连续的页)中的13个页面也在缓冲区中,并且在LRU列表的前端(即页是被频繁访问),则InnoDB存储引擎会将这个区中神域的所有页预读到缓冲区。
    • 线性预读取基于缓冲池中的页的访问模式,而不是数量。如果一个区中的24个页都被顺序访问了,则InnoDB存储引擎会读取下一个区的所有页。
    • LRU页解析:Innodb为了加快对磁盘中数据的操作,在操作磁盘上的数据时,会先把数据存放到一块名为Buffer Pool的内存缓冲池中,但是内存的大小远小于磁盘的大小,因此需要一种机制来淘汰非热点数据,保证内存中存在的数据是较为频繁访问的数据。LRU是这种管理场景下最常用的算法,类似Redis中的LRU淘汰算法:
      • 新数据插入到链表头部;
      • 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
      • 当链表满的时候,将链表尾部的数据丢弃。
  • InnoDB1.0.4 开始,缩进访问的预读取被取消了,而线性预读取还是保留了,并且加入了innodb_read_ahead_threshold参数,改参数标识一个区中的多少个页面被顺序访问时候,InnoDB存储引擎才开启预读取,即预读下一个区中所有页。默认值是56,当一个区中56个页都被访问过,则预读下一个区的所有项。

show VARIABLES like 'innodb_read_ahead_threshold'

在这里插入图片描述

  • 固态硬盘的情况,固态硬盘没有读写磁头,读取不需要旋转,因此随机读取性能得到质的提高。因为固态硬盘现在并没有全面普及,所InnoDB存储引擎中没有见到对固态硬盘相关的一些优化。

辅助索引的优化

  • 辅助索引的叶子节点包含主键,但是辅助索引的叶子节点不包含完整的行信息,因此,InnoDB存储引擎总是会先从辅助索引的叶节点判断是否能得到所需要的数据。用如下案例解释:
drop table if EXISTS tcreate table t(a int not null, b varchar(20), PRIMARY key(a), key(b));insert into t select 1, 'k';insert into t select 2, 'do';insert into t select 3, 'dr';insert into t select 4, 'an';
select * from t;
  • 如上我们插入的数据,我们执行如下查询语句:

  • 顺序如下:
    在这里插入图片描述

  • 加入我们插入的数据如下:


insert into t select 1, 'a';insert into t select 2, 'b';insert into t select 3, 'c';insert into t select 4, 'd';
select * from t;
  • 查询结果如下:
    在这里插入图片描述

  • 我们可以看到,他的排序规则是按照b的顺序排列的,并不是根据主键a的顺序排列,这也就是我们上面提到的,因为辅助索引中包含了主键的值,因此访问b列上的辅助索引就能得到a的值,这样就可以得到表中所有数据的值。 通常情况,一个辅助索引页中,能存放的数据比主键索引页上存放的数据多,因此优化器选择了辅助索引 ,如果我们解释这句查询语句得到如下结果:

在这里插入图片描述

  • 可以看到,优化器最终选择b索引,如果想得到对列a的排序结果,还需要对他进行Order By 操作,这样优化器才会走主键,避免在查询b列后又发生对a的排序操作。如下图:
    在这里插入图片描述

  • 或者可以强制使用主键索引
    在这里插入图片描述

联合索引

  • 联合索引是指对表上的多个列做索引。之前说的情况,都对表上的某个列进行索引。联合索引类似:
alter table t add key idx_a_b(a,b);
  • 什么时候该使用联合索引?在这个问题之前我们应该弄清楚联合索引内部的结构,本质上,联合索引还是 B+树,不同的是联合索引的键值数量不是1个,二手大于等于2个。我们用简单的两个key的情况说明问题,如上a,b两个key,我们用如下图表示:

在这里插入图片描述

  • 如上图中看到多个key情况的B+树,和我们之前讨论的单个键值没有什么区别,键值都是排序的,通过叶子节点可以逻辑上顺序的读出所有数据,就上面的例子来说:(1,1)(1,2)(2,1)(2,4)(3,1)(3,2)数据按照(a,b)的顺序存放。
  • 例如对于查询 select * from table where a= xxx and b = xxx,这种情况显然可以用(a,b)联合索引。对应单个a的查询 select * from table where a= xxx也可以使用(a,b)联合索引。但是对于select * from table where b= xxx单个b的查询不可以用这个B+树索引。可以看到叶子节点上b的值 1,2,1,4,1,2,显然不是按排序的,因此对于b列的查询使用不到(a,b)的联合索引。
  • 联合索引的第二个好处:可以对第二个键值进行排序,例如,很多情况,我们都只查询某个用户订单信息,并按照时间排序,取出最近一段时间的购买记录,这个时候使用联合索引可以避免多一次的排序操作。因为索引本身的叶子节点已经排序了。如下测试案例:

create table buy_log(userid int unsigned not null, buy_date date);insert into buy_log values
(1,'2021-01-19'),
(2,'2021-01-19'),
(3,'2021-01-19'),
(1,'2021-02-19'),
(3,'2021-02-19'),
(1,'2021-03-19'),
(1,'2021-04-19');ALTER table buy_log add key(userid);
alter table buy_log add key(userid, buy_date);
  • 如上建立两个索引,都包含userid字段,对userid进行查询,看优化器的选择,如下:
    在这里插入图片描述

  • 如上,possible_keys中有两个索引,分别是单个userid和userid,buy_date的联合索引。优化选择的是userid,因为改叶节点包含单个键值,因此一个页能存放的记录更多,接着,看一下的查询,我们假定要取出userid= 1的最近三次购买记录,并分析使用单个索引和符合索引区别:
    在这里插入图片描述

  • 同样的都可以用两个索引,但是这次优化器选择了符合索引,因为这个联合索引中buy_date已经排序好了,如果我们强制使用userid的单个索引,会有如下结果:
    在这里插入图片描述

  • 如上extra信息中,看到Using filesort,filesort指排序,但是不是文件中完成,我们可以对比执行:

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

在这里插入图片描述

  • 如上看到增加了排序操作,但是如果使用userid, buy_date的联合索引userid_2,就不会有这一次额外的操作,如下:

在这里插入图片描述

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

上一篇:数据结构与索引-- B+树索引
下一篇:mysql技术分享-- 视图是什么

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

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

相关文章

[Java基础]抽象类和接口的区别

抽象类和接口的区别&#xff1a;

async,await执行流看不懂?看完这篇以后再也不会了

昨天有朋友在公众号发消息说看不懂await&#xff0c;async执行流&#xff0c;其实看不懂太正常了&#xff0c;因为你没经过社会的毒打&#xff0c;没吃过牢饭就不知道自由有多重要&#xff0c;没生过病就不知道健康有多重要&#xff0c;没用过ContinueWith就不知道await,async有…

如何分析EFCore引发的内存泄漏

调查实体框架核心中的内存泄漏不要让内存泄漏成为洪水术语“内存泄漏”和“ .NET应用程序”不是经常一起使用。但是&#xff0c;我们最近在一个.NET Core Web应用程序中出现了一系列内存不足异常。事实证明&#xff0c;此问题是由Entity Framework Core中的行为更改引起的&…

数据结构与算法-- 二叉树中和为某一值的路径

二叉树中和为某一值的路径 题目&#xff1a;输入一颗二叉树和一个整数&#xff0c;打印出二叉树中节点值的和为给定值的所有路径。从树的根节点开始往下一只到叶子节点所经过的节点形成一条路径。我们用二叉树节点的定义沿用之前文章中 二叉查找树实现原理定义。如下&#xff…

微服务统计,分析,图表,监控, 分布式追踪一体化的 HttpReports 在 .Net Core 的应用...

前言介绍HttpReports 是针对.Net Core 开发的轻量级APM系统&#xff0c;基于MIT开源协议, 使用HttpReports可以快速搭建.Net Core环境下统计,分析,图表,监控&#xff0c;分布式追踪一体化的站点&#xff0c; 适应.Net Core WebAPI,MVC&#xff0c;Web项目, 通过引用Nuget构建Da…

WPF 创建自定义面板

前面两个章节分别介绍了两个自定义控件:自定义的ColorPicker和FlipPanel控件。接下来介绍派生自定义面板以及构建自定义绘图控件。创建自定义面板是一种特殊但较常见的自定义控件开发子集。前面以及介绍过有关面板方面的知识&#xff0c;了解到面板驻留一个或多个子元素&#x…

vue.js中mock本地json数据

vue.js中mock本地json数据 新版本的vue项目中已经将dev-server.js&#xff0c;dev-client.js两个js文件合并到了webpack.dev.conf.js文件中&#xff0c;以下分别是新旧版本的build目录结构&#xff1a; 新版本&#xff1a; 旧版本&#xff1a; 本次验证mock&#xff1a;运…

互联网40岁失业是一个无法打破的魔咒吗?

最近刚刚过完生日&#xff0c;又大了一岁&#xff0c;距离40岁又进了一步。年纪大了&#xff0c;就要多复盘。最近几天思考的比较多&#xff0c;因为身边失业的朋友开始多了起来。我又有点陷入担忧、焦虑的心态了。好在我一直是个有阿Q精神的中年油腻男&#xff0c;很快安抚好自…

数据结构与算法--复杂链表的复制

复杂链表的复制 题目&#xff1a;实现一个函数complexListNode 复制一个复杂链表。在链表中&#xff0c;每个节点除了有一个next指针指向下一个节点&#xff0c;还有另外一个before节点&#xff0c;before节点指向链表中任意一个节点&#xff0c;或者null节点。链表节点定义使…

如何实时主动监控你的网站接口是否挂掉并及时报警

“ 阅读本文大概需要 10 分钟。 ”最近我在公司负责的业务已经正式投入上线了&#xff0c;既然是线上环境&#xff0c;那么就需要保证其可用性。我负责的业务其中就包括一个 Web Service&#xff0c;我需要保证 Service 的每个接口都是可用的&#xff0c;如果某个时间流量大了或…

数据结构与算法--二叉查找树转顺序排列双向链表

二叉查找树转顺序排列双向链表 题目&#xff1a;输入一颗二叉查找树&#xff0c;将二叉查找树转成一个排序的双向链表&#xff0c;要求不能创建任何新节点&#xff0c;只调整树节点中指针的指向。例如下图所示&#xff1a; 本次二叉查找树节点定义使用之前文章 数据结构与算法…

5种避免C#.NET中因事件造成内存泄漏的技术

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。 5种避免C&#xff03;.NET中事件造成的内存泄漏的技术C&#xff03;&#xff08;通常是.NET&#xff09;中的事件注册是内存泄漏的最常见原因。至少从我的经验来看。实际上&#xff0c;我从事件中看到了太多的内存泄漏&a…

数据结构与算法--字符串的排列组合问题

字符串的全排列 题目&#xff1a;输入一个字符串&#xff0c;打印出改字符串中所有字符的所有排列。例如输入字符串abc&#xff0c;那么打印出由a&#xff0c;b&#xff0c;c字符组成的所有字符串&#xff1a;abc&#xff0c;acb&#xff0c;bac&#xff0c;bca&#xff0c;cab…