【数据库】MySQL索引,存储引擎

一、前提
  • 正确地创建合适的索引是数据库性能优化基础
  • 数据库索引是一种为了加速数据表中行记录检索的数据结构
  • 索引存储于磁盘当中
  • 数据量巨大时O(n)级别的查询实在是太慢了,所以有了索引
二、索引的数据结构延申致工作机制

索引中存储数据库的一个属性,每个属性的值都对应一个地址,数据库的信息是存储在磁盘中的,刚好索引的地址就是数据存储的磁盘地址,通过属性的匹配,找到对应的磁盘地址,从而快速查询到数据。
【数据库】MySQL索引,存储引擎_第1张图片

哈希索引特性总结(这里有一个文章讲的特别详细)
  • 等值匹配是非常高效的
  • 不支持范围查找
  • inoodb中有哈希索引,是自适应哈希索引,后面细说
二叉树不能够做索引

因为会存在一个性能最差的情况,就是极端的线性,这是一颗二叉树就是有点极端
【数据库】MySQL索引,存储引擎_第2张图片

平衡二叉树AVL

平衡二叉树的平衡就是通过左右旋做到的平衡,旋转因子可以是0,-1,1(如果没记错的话,//todo后续要查询一下)
这里我们假设要查询的值是15,首先内存中是10,发现比10大,通过p2指针查询到右节点20,发现比20小,于是通过p1节点查询到15,结果命中。数据存放的位置就是粉红色的数据区,p1,p2都是节点引用,可以理解为指针。
数据区一般由两种数据组成:
一是存储地址,通过地址指针,快速查询到数据;
二是在数据区存储本属性对应整行数据;
但是mysql为什么没用AVL做索引数据结构呢?
1、IO次数过多,性能损耗比较大,三层树也仅仅能存储7个数据,
2、IO的利用率过低,操作系统和磁盘的交互是以页为单位,一页存储4KB,但是每拿到一个节点,都要通过操作系统返回给内存,一个节点不足以填充完4KB,所以利用率低,磁盘io一次只能用到一次关键字。
【数据库】MySQL索引,存储引擎_第3张图片

B树(多路平衡查找树)

这里就是二三树,不再是左边右边两路查找,而是小于17的,17-35的,大于35的,变成三路查找了,这里不多说看图即可。
【数据库】MySQL索引,存储引擎_第4张图片
为了弥补上面的不足4KB大小的缺点,这里可以将第一个节点大小定死为4KB,假设一个关键字占用10byte,那就能存1600个关键字,也就是1601路;
节点中关键字的个数 = 子节点岔路个数 - 1
若第一层有1600个,第二层有1600^2个, 第三层有1600的三次方个,整棵树又矮又胖,
/
/
/

MySQL B+树

【数据库】MySQL索引,存储引擎_第5张图片
叶子节点首尾相连,最后一个还能连接到第一个,形成了天然有序的链表,根节点,中间节点都没有数据区,所有数据区都在叶子节点中。
特点:
1、采用左闭合式比较:1 <= x < 28; 28 <= x < 66;假设要查询的关键字是1,找到根节点中的1,通过p1指向左侧节点,找到1,再通过p1找到叶子节点中的关键字1,然后拿到1对应的数据区。
2、链式结构有利于数据查询,可以有范围查询了

B+树相对于B树的优势:
(1)B+树是B树的加强版,B树的特性B+树都有
(2)IO能力有提高,因为根节点和枝节点都没有数据区,不需要返回数据,极致的利用了4KB规则
(3)基于索引的表扫描性能
(4) 排序能力强
(5)查询稳定

三、Innodb的聚集索引

聚集索引:聚集索引是指数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同。好比按照姓氏排序的电话本,并且电话号紧随其后。聚集索引决定了表中数据的物理存储顺序,并且一个表中有且仅有一个聚集索引。
Innodb的聚集索引:基于B+树,既存储索引,也存储了行值,也就是上面数据区存储的第二种形式,数据存储于索引的叶子页,因此Innodb也能理解为基于索引的表。Innodb的索引有严格的主次之分。
回表:在辅助索引中通过zhang3关键字找到101,用101去主键索引中查询到101对应的数据,这个过程就叫做回表。回表不是一个好现象,影响性能,要尽量减少回表。
引发思考?为什么辅助索引中101的位置不存放地址呢?
因为数据是经常变动的,在地址对应到数据当中时,数据变动,地址也会改变,不可能做到每次主键索引中数据改变,都要通知给辅助索引去更改地址。
【数据库】MySQL索引,存储引擎_第6张图片

四、Myisam索引

索引存储再MYI文件中,所有叶子节点中存储的都是地址,地址指向的了MYD文件中对应的行,然后返回。MYD文件中是以id列的索引。index文件再索引当中,数据文件再data文件当中,形成了数据隔离。Myisam的索引方式也叫做“非聚集”和Innodb的索引方式对比。两个索引没有主次之分,查询后合并在一起,叫合并查询。
【数据库】MySQL索引,存储引擎_第7张图片【数据库】MySQL索引,存储引擎_第8张图片

五、索引的离散性

其实说的就是重复率,数据重复的多的列不建议创建索引,因为有可能导致执行计划在成本运算过程时,随机抽取出的4KB数据中重复率过高,会强制不走索引。
离散度计算公式:count(distinct col): count(col),当值达到15%时,不走索引

六、不走索引的情况
  • like’%XXX’时,因为根据离散度匹配和查询规则,从左往右一次进行,%的匹配度太高了,所以不走索引
  • todo 待补充
七、联合索引(可以减少回表)

1、联合索引是一个索引, 但是可以是两个字段放一起的查询
2、联合索引使用最左前置原则:最常用列 > 离散度高 > 最少空间占用率
3、单列索引是一种特殊的联合索引

八、覆盖索引(可以减少回表)

通过索引项的信息可直接返回所需的查询列,则该索引称之为SQL的覆盖索引。
通俗来讲:where xxx=?中的xxx对应的索引中包含select xxx的字段,并且select中不包含其他的索引中没有的字段,那么就是覆盖索引。
【数据库】MySQL索引,存储引擎_第9张图片

九、SQL常见优化规则

【数据库】MySQL索引,存储引擎_第10张图片

十、MySQL体系结构(C/S)

(其实没有很懂,我选择先了解)
【数据库】MySQL索引,存储引擎_第11张图片
【数据库】MySQL索引,存储引擎_第12张图片

十一、存储引擎

存储引擎的特性:
1、存储引擎用于修饰表
2、默认存储引擎是Innodb(5.5以后)
3、 存储引擎是插拔式的,可随时加载和卸载

csv存储引擎

特点:
1、不能定义索引,列定义必须为not null,不能设置自增列
2、CSV表的数据的存储格式用“,"隔开,可直接编辑文件进行数据的编排
3、数据安全性低
应用场景:
1、不适用大表或者数据的在线处理
2、数据的快速导出导入
3、表格直接转换成CSV

Archive存储引擎

特点:
1、压缩协议(ARZ文件格式)进行数据存储,磁盘占用少
2、只支持insert和select两种操作,不支持修改删除
3、只允许自增ID列建立索引,其实就是只有ID列可以
应用场景:
1、数据备份系统(如:日志系统,文档归档,一般都是增加,不涉及修删)
2、大量设备高频的数据采集(select)

Memory存储引擎(消耗的还是DB的存储)

特点:
1、数据都是存储在内存中,处理效率高,表达小限定默认16M
2、不支持大数据存储类型的字段,如blog,text,varchar(n) ->底层都是char(n)
3、支持Hash索引,等值查询效率高
4、数据的可靠性低,重启或系统崩溃数据丢失(不建议放热点数据)
应用场景:
1、热点数据快速加载(功能类似缓存中间件,但是一般不用,都用redis)
2、MySQL临时表存储(查询结果于内存中计算的数据),联表查询就会产生临时表,大多数都采用Memory存储引擎

Myisam存储引擎

MySQL5.5之前默认存储引擎Myisam
特点:
1、较快的数据插入和读取性能
2、数据存储具有较小的磁盘空间占用
3、支持表级别的锁,不支持事务
4、数据文件与索引文件分开存储,主键索引与辅助索引同级
5、针对数据统计有额外的常熟存储,故而count(*)的查询效率很高
应用场景:
1、只读应用或者以读为主的业务

Innodb存储引擎

MySQL5.5之后默认存储引擎Innodb(ACID四个特性)
特点:
1、支持事务
2、行级锁,利于并发
3、聚集索引
4、外键支持,保证数据的完整性
应用场景:
1、无脑选他就完了,不建议在一个数据库中使用多种存储引擎

十二、SQL的执行

DQL(数据查询语言,select)语句的执行过程
【数据库】MySQL索引,存储引擎_第13张图片
首先客户端发来SQL请求,先进入到SQL Interface当中,然后去缓存中查询,如果查询到了返回给客户端;若没有查询到,则走到解析器->优化器->执行器->去存储引擎层查询,若查询到返回给执行器,由执行器返回给SQL Interface->客户端。

客户端到SQL Interface的通讯阶段

通讯协议:TCP/IP,Unix Socket
通讯方式:长连接的半双工

解释一下半双工,全双工,单工。
(1)单工:单向传输,比如电视遥控器;
(2)全双工:同一时刻允许双向传输,比如打电话;
(3)半双工:同一时刻,只允许一个节点向另一个节点传输,比如对讲机;

若一个查询SQL语句发出去之后很久没有回应怎么办?
(1)若在linux操作系统:可以通过查询监听3306端口,查看处理情况
(2)若在sql层面:
一、可以通过show full processlist查看数据库所有连接信息,结合state状态看连接方在干嘛,可以去官方文档中查询。
二、可以通过show status like ‘Thread%’ 查看所有线程的情况,
通过上面的操作可以看到哪个线程有问题,利用 kill + 线程ID杀掉线程

SQL Interface查询缓存阶段

缓存查询是对比SQL语句的一致性,假设相同的SQL业务,多了一个空格都会被认为不存在缓存中,他不会去比较SQL的业务逻辑,单纯看SQL语句形式一致,和MyBatis中的一级缓存差不多。
【数据库】MySQL索引,存储引擎_第14张图片

SQL Interface到解析器阶段(缓存未命中)

通过Parser进行语法解析或词法解析,得到解析树。// todo详细了解语法解析和词法解析

优化执行阶段(解析器->优化器,Optimizer介入阶段)

in(1,23,5,23,52,13,1)用到了二分查找,性能好一些
其他具体如何优化有待整理
【数据库】MySQL索引,存储引擎_第15张图片
查看SQL执行计划:
-explain/desc + SQL 查看生成的最优执行计划
-set global optimizer_trace = ‘enable=on’ 打开记录SQL的执行计划开关

执行计划-type

type是sql执行获取数据的方式,也是评测SQL好坏最直观的参数。
取值有:
1、system
2、const 唯一性索引(主键和唯一索引 unique key)常量比较
3、eq_ref 唯一性索引扫描
4、ref 普通索引扫描
5、range 基于索引的范围查找
6、index full index scan
7、all full table scan
性能排序: system > const > eq_ref > range > index > all
执行计划的其他参数:
partitions:查询数据的分区信息
possible key:可能使用到的索引
key:真正使用到的索引,如果是NULL,则没有使用索引。
rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数,数值越小越好
filtered:指返回结果的行占需要读到的行(rows列的值)的百分比,数值越大越好,表示扫描数据的准确性高。
eg:rows为1000,filtered为50,则真正返回的数据估算为500行

执行器到存储引擎层

执行器中的执行引擎会按照执行计划的步骤,调用存储引擎中的API,获取数据。

数据返回

采用增量方式进行返回:

  • 开始生成第一条结果时,MySQL就开始往请求方逐步返回数据
  • MySQL服务器无需保存过多的数据,浪费内存
  • 用户体验好,若无需等待所有数据可将拿到了数据展示

DML Innodb和DDL Innodb没听懂,在第二个视频的两小时二十分左右

十三、Innodb 的架构设计

【数据库】MySQL索引,存储引擎_第16张图片

Buffer Pool的设计

【数据库】MySQL索引,存储引擎_第17张图片
Buffer Pool是用来缓存表记录数据和索引数据的

1、缓冲池污染问题:因为不小心的操作导致带入一个表中大量数据,但是这个表后期还不会使用,于是数据就留在了表中,也存在于Buffer Pool中,所以造成数据污染
2、LRU内存淘汰算法,这个操作系统学过,看相应笔记就行了
那么如何解决上面两个问题呢?
buffer Pool有两个部分,一个是 new Sublist 另一个是Old Sublist,中间是Midpoint,把整个容器一分为二,当有新的SQL执行完毕会产生相对应的结果集,结果集会被插入到old的头部,当后面有SQL激活这个结果集时,结果集将被移动到new的头部,其实一直都还是从上到下移动的,只不过插入位置不是最上方,这样其实在优化LRU算法。

Innodb的特性总结

【数据库】MySQL索引,存储引擎_第18张图片

On-Disk Structures(系统表空间,这里的文件不能删除)

-InnoDB data Dictionary:存储执行计划,索引;
-Doublewrite Buffer:固定2MB大小,用于刷脏的时候数据备份;
-Change Buffer: 一个在内存中一个在磁盘中,只要是落库到磁盘;
-Undo Logs:日志记录的地方,不必主动配置,默认写在系统表空间;
每一张表都有独立的表空间进行存储。

Innodb的事务支持

一、原子性:最小的工作单元,要么一起提交成功,要么全部失败回滚
二、一致性:事务中操作的数据及状态改变时一致的,即写入资料的结果必须完全符合预设的规则,不会因为出现意外等原因导致数据的不一致
三、隔离性:并发访问场景下数据的可见性设定带来不同的问题
四、持久性:事务所做的修改会永久保存,不会因为系统意外导致数据的丢失

(1) 原子性是如何保证的?答:XA的2阶段的提交来保证事务的最终提交
【数据库】MySQL索引,存储引擎_第19张图片
首先mySql服务器去存储引擎中的Log Buffer中的prepare中记录状态,然后通知mysql服务层记录Binlog日志,然后日志满了后,回到mysql服务器,告诉Innodb存储引擎去commit提交,所以XA的两阶段提交保证了原子性的事务最终提交。
(2)原子性的回滚是如何保证的呢?
在事务异常中断,或者主动rollback过程中,我们可以基于Undo Log进行数据的回滚保证事务原子性。
Undo Log是默认开在共享表空间中的物理文件的buffer,它主要是逻辑日志,用来记录事务过程中每条数据的变化情况,在Innodb存储引擎中用来实现并发控制(MVCC)。
(3)Undo Log是如何实现多版本并发控制的呢?
事务未提交之前,Undo保存了未提交之前的版本数据,Undo中的数据可以作为数据旧版本快照供其他事务进行快照读。

(4)持久性是如何保证的?(Redo Log)
刷脏:将 Innodb BufferPool中未提交到磁盘中的变更数据,存放到磁盘中。
Redo Log:是物理日志,记录数据页的物理修改,并不记录行的情况。
数据库IO的最小单位是16kb的页,操作系统IO的最小单位是4kb的页,如果在刷脏过程中出现系统宕机,导致脏页数据部分写入成功,部分写入失败,就出现页断裂,也叫部分写失败
如果是还没有刷脏就发生系统意外宕机,并没有出现页断裂的情况,通过Redo Log就可以进行数据的恢复。如果刷脏过程中出现意外宕机,出现页断裂:MySQL用Double Write技术中的在共享表空间中定义的DoubleWrite Buffer拿到脏页数据进行恢复,再应用Redo Log进行数据的持久化。
(5)双写机制
DoubleWrite Buffer是开在共享表空间的物理文件的buffer,大小是2MB。再刷脏开始之时,先进行脏页的备份操作,将脏页数据写入Double Write Buffer,它是连续的空间,所以是顺序写入到共享表空间,然后进行刷脏操作。如果刷脏过程中宕机造成页断裂,通过DoubleWrite Buffer进行恢复。
【数据库】MySQL索引,存储引擎_第20张图片
再SQL当中,一条语句就是默认的一个事务,即使是多个语句一起执行,那也是多个事务。

隔离性

一、四种数据读取问题
(1)脏读(读取到未提交的数据)
A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读。
(2)不可重复读(前后多次读取,数据内容不一致)
事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。多是针对update操作。
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。
(3)幻读(前后多次读取,数据总量不一致)
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。多是针对insert和delete操作。
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
二、事务隔离级别
(1)Read uncommitted 读未提交,这种会产生脏读
(2)Read committed 读提交,避免脏读,但会产生不可重复读
(3)Repeatable read 重复读,可能会出现幻读
(4)Serializable 序列化(串行化),前三种问题都可避免
【数据库】MySQL索引,存储引擎_第21张图片
三、Innodb隔离性问题的解决
(1)LBCC的当前读,利用行锁机制,事务开始操作数据之前,对其枷锁,阻止其他事务对数据进行修改,要求数据有一定的时效性。
(2)MVCC的快照读,利用undo的多版本信息,事务开始操作数据之前,将数据在当下时间点进行一份数据快照的备份,利用这个快照提供给其他事务进行一致性读取。并发访问读写操作数据库时,对正在事务内处理的数据做多版本的管理,避免写操作的堵塞,写操作拿到的数据一定是当前读,从而引发读操作的并发阻塞问题。

  • DML语句都是当前读的范围,如update, delete, insert, select等等,sql读取的数据是最新版本
  • 普通的select是快照读,数据是历史版本,如select*
Innodb中的锁(LBCC解决数据隔离问题)

Innodb中的锁都是行锁,有共享锁和排他锁,行锁的具体实现是临键锁,间隙锁,记录锁
(1)共享锁:读锁,多个事务对于数据可以共享访问,不能操作修改
使用方式:select * from table where id=1 lock in share mode – 加锁
commit/rollback – 释锁
(2)排他锁:写锁,互斥锁/独占锁,事务获取了写锁,其他事务就不能在获取锁,只有该获取了排他锁的事务可以对数据进行读取和修改
使用方式:delete/update/insert – 加锁
select * from table where… for update – 加锁
commit/rollback – 释锁
(3)行锁是如何实现的?
①Innodb的行锁是通过给索引上的索引项加锁来实现的。即索引对应的值
②Innodb按照辅助索引进行数据操作时,辅助索引和主键索引都将锁定指定的索引项。假设锁定num为1的值,那么id作为主键索引也会同num=1的对应id锁住
③通过索引进行数据检索,Innodb才使用行级锁,否则使用表锁。也就是用了索引才行锁。
(4)行锁通过临键锁,间隙锁,记录锁来实现
-当范围查询时使用临键锁
-当查询数据不存在时,使用间隙锁
-当查询数据是等值匹配且数据命中的时候,使用记录锁
记录锁:锁住具体索引的索引项
【数据库】MySQL索引,存储引擎_第22张图片
间隙锁:锁住数据不存在的区间(区间:左开右开)【数据库】MySQL索引,存储引擎_第23张图片
临键锁:范围查询且命中,锁住命中记录空间+下一个区间 (区间:左开右闭)
【数据库】MySQL索引,存储引擎_第24张图片

MVCC

【数据库】MySQL索引,存储引擎_第25张图片
事务的操作都会获取Innodb提供的事务操作ID(一个自增的版本序号),整个事务内这个版本序号有且唯一。
快照读-MVCC数据查询规则
1、查找数据行版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始之前已经存在的,要么是在事务自身插入或者修改过的。
2、查找删除版本号要么为NULL,要么大于当前事务版本号的记录,确保取出来的行记录再事务开启之前没有被删除。

MVCC-数据删除
【数据库】MySQL索引,存储引擎_第26张图片
MVCC-数据查询
【数据库】MySQL索引,存储引擎_第27张图片
MVCC-数据修改
这里会先进行数据的拷贝,然后再修改。
【数据库】MySQL索引,存储引擎_第28张图片

十四、MySQL的性能优化

(1)可以从一下几个方面进行优化
- 硬件&操作系统优化
- 系统架构优化
- MySQL服务的配置优化
- SQL优化
(2)硬件&操作系统优化(DBA和运维的工作)
- 更合适的CPU:OLTP(CPU密集型)/OLAP(I/O密集型)
- 更多的RAM
- 更快的硬盘存储
- 优化网络,操作系统
(3)系统架构优化
- 缓存设计
- 读写分离,主从复制
- 分库分表,mycat代理中间件
【数据库】MySQL索引,存储引擎_第29张图片【数据库】MySQL索引,存储引擎_第30张图片
(4)MySQL配置优化
- 配置的作用域
- 配置是否热加载(是否可以不用重启mysql就可以改变的配置)
(5)SQL常见优化规则
【数据库】MySQL索引,存储引擎_第31张图片

你可能感兴趣的:(笔记)