0
点赞
收藏
分享

微信扫一扫

【mysql深入剖析】

westfallon 2022-04-25 阅读 38
java

mysql

主从复制

概念

保证主服务器和从服务器的数据一致。当主服务器执行了删改查的操作后,从服务器将修改的操作同步到自己的服务器中(有一定的延迟),依次保证数据的一致性。

流程

1.Master开启事务执行操作,在提交事务完成数据更新之前,将提交事务的时间(数据更新的时间)记录到二进制日志文件中,每次事务可以称为“事件”,mysql会按照事务的提交时间,而不是每条sql语句的执行时间来记录二进制日志。在记录二进制日志完成后,Master会告知引擎可以提交事务完成数据更新操作了。

2.Slave有一个IO线程与Master保持普通的客户端链接,监控二进制日志文件的变更,将master的二进制日志中的变化的日志复制到自己服务器的中继日志中。

3.Slave的SQL线程执行更新过来的中继日志中的事件。

简单来说:

1.Master的binlog dump线程(特殊的二进制转储线程)将二进制日志中的事件发送给Slave的IO线程

2.Slave的IO线程接受到日志流之后,将事件写入Slave的relay log中

3.Slave的SQL线程读取relay log,将事件进行重放。

InnoDB & Myisam

InnoDBMyisam
支持事务
支持外键
支持行锁
支持MVCC
不支持全文索引

MVCC

在这里插入图片描述
Multi-Version Concurrency Control,多版本并发控制

保存数据行的历史版本,通过对数据行的多个版本进行管理来实现数据库的并发控制。

简单来说,我们平时看到的一条一条的记录,在数据库中保存的时候,可能不仅仅只有一条记录,而是有多个历史版本。

数据库事务有回滚的能力,既然能够回滚,那么就必须要在数据改变之前先把旧的数据记录下来,作为将来回滚的依据,那么这个记录就是 undo log。

当我们要添加一条记录的时候,就把添加的数据 id 记录到 undo log 中,将来回滚的时候就据此把数据删除;当我们要删除或者修改数据的时候,就把原数据记录到 undo log 中,将来据此恢复数据。查询操作因为不涉及回滚操作,所以就不需要记录到 undo log 中。

当开启一个事务的时候,会有一个事务id(transaction_id),该事务id是严格自增的。当事务开启的时候,会创建一个数组,数组中存储的是当前所有活跃的事务id,即还没有提交的事务的事务id。

此时,对于事务的操作是否可见,有五种情况:

  1. 如果这个值等于当前事务 id,说明这就是当前事务修改的,那么数据可见。
  2. 如果这个值小于数组中的最小值,说明当我们开启当前事务的时候,这行数据修改所涉及到的事务已经提交了,当前数据行是可见的。
  3. 如果这个值大于数组中的最大值,说明这行数据是我们在开启事务之后,还没有提交的时候,有另外一个会话也开启了事务,并且修改了这行数据,那么此时这行数据就是不可见的。
  4. 如果这个值的大小介于数组中最大值最小值之间(闭区间),且该值不在数组中,说明这也是一个已经提交的事务修改的数据,这是可见的。
  5. 如果这个值的大小介于数组中最大值最小值之间(闭区间),且该值在数组中(不等于当前事务 id),说明这是一个未提交的事务修改的数据,不可见。

针对4 、5 的情况,请看以下案例:

比如我们有 A、B、C、D 四个会话,首先 A、B、C 分别开启一个事务,事务 ID 是 3、4、5,然后 C 会话提交了事务,A、B 未提交。接下来 D 会话也开启了一个事务,事务 ID 是 6,那么当 D 会话开启事务的时候,数组中的值就是 [3,4,6]。现在假设有一行数据的 DB_TRX_ID 是 5(第四种情况),那么该行数据就是可见的(因为当前事务开启的时候它已经提交了);如果有一行数据的 DB_TRX_ID 是 4,那么该行就不可见(因为未提交)。
在这里插入图片描述

小结

MVCC 在一定程度上实现了读写并发,不过它只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下有效。

而 READ UNCOMMITTED 总是会读取最新的数据行,SERIALIZABLE 则会对所有读取的行都加锁,这两个都和 MVCC 不兼容

参考

https://www.pianshen.com/article/28291848525/

https://baijiahao.baidu.com/s?id=1719809398177412608&wfr=spider&for=pc

事务隔离级别

Read uncommitted读未提交

会出现脏读(事务B读取到了事务A未提交的数据)

Read committed读已提交

避免脏读,出现了不可重复读(事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变)

Repeatable read可重复读(mysql默认的隔离级别)

避免了不可重复读和脏读,会出现幻读(在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写))

Serializable序列化

事务只能一个接着一个地执行,但不能并发执行
在这里插入图片描述
在这里插入图片描述

注意

幻读vs 不可重复读

事务A 按照一定条件进行数据读取, 期间事务B 插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B 新插入的数据 称为幻读。(读到了其他事务新插入的数据,这种现象为幻读)
如果事务A 按一定条件搜索, 期间事务B 删除了符合条件的某一条数据,导致事务A 再次读取时数据少了一条。这种情况归为 不可重复读

隔离级别查询sql

查询事务的隔离级别:select @@tx_isolation;

设置事务的隔离级别:set transaction isolation level

事务的特性

A原子性(Atomic) 对数据的修改要么全部执行,要么全部不执行。
C一致性(Consistent) 在事务执行前后,数据状态保持一致性。
I隔离性(Isolated) 一个事务的处理不能影响另一个事务的处理。
D持续性(Durable) 事务处理结束,其效果在数据库中持久化。

事务导致的问题

脏读(dirty read):一个事务读取了另一个事务尚未提交的数据,
不可重复读(non-repeatable read) :一个事务的操作导致另一个事务前后两次读取到不同的数据
幻读(phantom read) :一个事务的操作导致另一个事务前后两次查询的结果数据量不同。

事务的传播行为

在这里插入图片描述
代码使用方式

@Transactional(rollbackFor = Exception.class,propagation = xxx)

索引

高效查找数据的排好序的数据结构

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。

优点

提高数据检索的效率,降低IO成本

通过索引对数据进行排序,降低了数据排序的成本,降低了CPU消耗,并且提高了查询效率

缺点

提高了查询效率,但是降低了更新的效率,因为更新的时候,不仅仅要更新数据库数据,还要更新对应索引文件中的信息。

mysql索引(innodb存储引擎)

B+树

索引类型

唯一索引:列值必须唯一,可以有空值,但是该空值只能有一个

主键索引:列值必须唯一且不可以有空值

全文索引:只有char、warchar、text类型字段可以使用全文索引

空间索引

联合索引:多个字段组成一个索引,支持最左前缀匹配原则

普通索引:没有任何限制
举例

1.我们创建了一张user表,有主键id,但是并没有让其自动递增。
在这里插入图片描述
2.我们往表中添加了三条数据,主键id 分别为1,2,3,但是插入的时候是乱序插入的,当查询的时候,查询出来的数据是有序的
在这里插入图片描述
3.为什么是有序的呢?
原因:在插入的时候,mysql就将数据进行了排序。

innodb存储引擎

存储引擎主要用来存储数据,读取数据,会与磁盘打交道。

存储引擎往磁盘中写入和读取数据时,每次会写入/读取“一页”的数据,“一页”的数据默认大小为16KB。

读取数据时最少读取一页的数据,从磁盘中读取出来数据放到内存中,当innodb第二次读取时,如果发现内存中已经存在要读取的数据,便不会再读取磁盘了。

写入数据的时候,往磁盘中要写入“一页”的数据16kb。接下来,操作系统开始往磁盘中持久化数据,但是操作系统本身也有一个“页”的概念,且大小为4kb,因此16kb的数据会分多次才能持久化到磁盘中,这个多次就可能存在失败的情况。mysql对此进行了处理。

innodb“一页”

在这里插入图片描述
页是一个逻辑单位,表示存储的最小的基本单位。

上图中的用户数据区域就是一页中的数据

在往表中insert数据的时候,innodb便会将该数据持久化到磁盘中,往用户数据区域存储,但是如果区域中已经有了数据,便需要对数据进行排序存储(就是在逻辑上要排序,物理上的存储的话是随机的),因此在插入的时候因为有了排序这一操作,插入的性能受到了影响,但是相对应的,读取的性能便提高了。

举例

1.引入索引概念

如下图,我们在往mysql中insert数据的时候,从第二条数据开始便需要进行逻辑上的排序。因此当我们开始查询数据时,比如我们执行sql:select * from 表 where id = 3,因为此时数据库中的主键只有1,4,8,因此直接在id为4以前的数据中查询即可,大大加快了查询的性能。但是如果只是在用户数据区域中查询,此时的查询时链表式查询,需要一条一条查询去,如果我们要查询id为3000的数据,就得一个一个查查到3000,为了解决该问题,便有了页目录这个概念,也就是索引的概念。
在这里插入图片描述

2.引入二分查找概念

如下图

主键id为1,2的数据被分到了页目录为1的部分,4和8被分到了页目录为4的部分,这样当我们要查询主键id为3的数据的时候,只需要到页目录中查找,最后只需要在页目录为1的部分中找即可,大大加快了查询的性能。

当我们要查询id为30的数据时,我们只需要判断30在页目录的4和50之间,因此进入4这个部分,然后对里面的数据进行依次查找。
在这里插入图片描述
如下图,一页数据存储满之后会开辟新的一页,页多了之后页会形成链表,此时查找的时候依旧要一页一页遍历查找。因此需要开辟一个新的空间来管理页目录。
在这里插入图片描述

3.引入B+Tree结构

如下图为管理页目录的空间

当我们要查找id为3000的数据时,直接就能判断到该数据在第四页,因此便直接去第四页查找数据即可。

即原本需要从第一页查找数据,直到查到第四页才能找到数据,需要进行四次磁盘io

现在只需要进行两次磁盘io,第一次在页目录管理中找到在哪一页,第二次直接去该页查找数据。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如上图,该图为innodb的索引B+Tree的结构,红色的为非叶子节点(冗余了主键id),黄色为真正存储数据的叶子节点。

myisam&innodb索引

myisam

底层索引结构中叶子节点上存储的是磁盘物理位置
在这里插入图片描述

innodb

底层索引结构叶子节点中存储的是完整数据
在这里插入图片描述

4.全表扫描&索引扫描

如果查询时是通过叶子节点从第一页开始依次查询,则是全表扫描;

如果查询时是通过非叶子节点从上向下查询到具体某叶子节点上,则是索引扫描。

5.explain

判断是否走了索引,可以使用explain进行查询

explain sql;
在这里插入图片描述

6.页满了怎么处理

一页数据最多只能存放16kb的数据,假设我们一条数据是4kb,那么最多能放4条数据(主键id分别是1,2,4,8)。接下来再次insert了一条主键id为7的数据,由于当前页已满,因此需要再开辟一页进行存储,但是由于7小于8,因此要调整数据的排序,需要将8的数据放到新开辟的页上,将7放在第一页。而这种情况的insert,对于磁盘而言,需要开辟新的一页,需要调整数据的排序,会影响性能,因此一般建议大家主键id用mysql的自增去自增,而不要自己insert,可以避免insert的时候磁盘重新排序问题。

7.为什么建议innodb表必须建主键索引,并且推荐使用整型的自增主键?

innodb表的主键索引就是聚簇索引,且一张表中只会有一个聚簇索引,它会将表内的数据有序排列成B+Tree的结构。如果不建主键的话,mysql会默认建立一个隐形的主键索引,用来将表数据进行排序维护,但是如果能够不让mysql自己做的事情就尽量不让mysql去做。

1.如果主键建立的不是整型的自增主键,而是建立的是uuid类型:建立了索引之后,如果使用索引去查询数据的话,都是要对索引进行多次比对之后找到对应的索引的,如果使用的是uuid类型,它进行比对是足位比对的,如果uuid长度为20位,比对的时候,很可能前19位都是相同的,只有最后一位是不同的,那这样比对的效率就是比较低的,相比整型的比较而言,整型的比较是要更快的。

2.uuid长度上占用空间比整型占用的空间大。

3.如果是整型索引,如下,我现在要再插入一条主键为3的索引,变化是这样的
在这里插入图片描述
如果是uuid索引,要在叶子节点中间再插入的话,
在这里插入图片描述
再插入一条是不
在这里插入图片描述
再进行插入是呢,此时树的高度增高了,也就是说它往叶子节点中插入的时候,如果已经满了两个数据了,再往里面插入的时候,会导致该节点再次二叉,使得高度增加。
在这里插入图片描述

8.mysql建索引的时候,为什么不用hash结构而是使用b+tree?

如果使用hash建立索引,有时候效率是比B+Tree要更高的,虽然会出现hash冲突的情况,但是mysql对冲突也有很多的优化。

不使用hash的原因是它不支持范围查找。
在这里插入图片描述

主键索引

根据主键生成的B+Tree,就叫主键索引。非叶子结点中冗余的字段便是主键。
在这里插入图片描述
查询a>4的时候,是先根据索引树查询到a=4的索引,然后叶子节点上后面的数据都是大于4的数据。

联合索引

用字段b c d建立联合索引,非叶子节点中的冗余字段值便是bcd的值,叶子节点存储的是对应的主键id值。

联合索引查询流程:

1.当我们要查询bcd值为111的数据时,通过联合索引的B+Tree找到叶子节点中的主键id为1

2.根据主键id的值为1到主键索引B+Tree中查找其叶子节点中的数据(该过程叫做回表)

在这里插入图片描述

回表

(主键索引中的叶子节点存储的是完整数据,联合索引的叶子节点中存储的是主键id)
在这里插入图片描述

最左匹配原则

联合索引遵循最左匹配原则,如果abc三个字段建立了联合索引,但是查询条件没有a,只有bc或者只有c或者只有b,是走不了联合索引的,因为该联合索引的索引结构是先根据a 排序,在a相同的情况下根据b排序,然后b相同的情况下根据c排序,如果没有a的查询条件,只看索引中的b和c,是乱序的。因此走不了联合索引。

聚集索引(聚簇索引)

叶子节点上存储的是完整数据

主键索引就是聚集索引

非聚集索引

叶子节点上存储的不是数据(例如myisam的索引,存储的是索引在磁盘上的物理位置)

buffer Pool(innodb内存区)

在这里插入图片描述
在这里插入图片描述
1.innodb启动的时候,会先开启一块128M的内存空间(Buffer Pool)

2.执行一个查询语句时,先从磁盘中查询到对应页

3.将该页数据复制到Buffer Pool中,在Buffer Pool中数据结构是数组,页数据会从头往后放在数组中

4.当执行修改等操作时,先修改了磁盘中的值,然后修改Buffer Pool中的值

5.Buffer Pool也会通过其他原因其他操作再次持久化到磁盘中,此时该下标的数组便空了

6.因此在Buffer Pool中的数组中的数据是散乱的(如下图),Buffer Pool中的空闲区域不是连续的

7.当有其他磁盘中的页数据要往Buffer Pool中存储的时候,要放在哪个下标位置呢?
在这里插入图片描述
8.在innodb中有一个free链表,专门用来管理Buffer Pool中的空闲区域

9.从数据结构角度来开,free链表中的头节点为基节点,会记录基础信息,如除了自己之外的头节点是哪个,当前链表的尾节点是什么、当前链表的节点总数。

从开发的角度来看,free的第二个节点才是头节点。

10.节点中会有一个控制块的变量,用来存储Buffer Pool中的一个空闲区域的指针。

11.Buffer Pool的大小为128M,一页区域为16KB,我们可以计算出来Buffer Pool最多可以有多少页数据,因此free链表的长度也有最大长度。

12.当从磁盘中要将A页复制到Buffer Pool中时,会通过free链表找到头节点,根据头节点中的控制块的指针找到对应在Buffer Pool中的空闲区域,然后将页数据放在那里。

13.此时该头节点会被从free链表中移除(链表的删除效率是很高的)

14.当在Buffer Pool中有新的空闲区域存在的时候,会在free链表尾部添加控制块节点,然后存储对应的指针信息。
在这里插入图片描述

update时,Buffer Pool的操作

1.当执行update的时候,在Buffer Pool中将该数据查询出来,然后修改,该数据被视为“脏页”

2.innodb中有一个flush链表,其控制块的指针是指向“脏页”

3.innodb后台有一个线程会定时读取flush链表数据,更新磁盘对应页
在这里插入图片描述

buffer Pool满了如何操作

1.当buffer pool128M都放满之后,mysql之后的查询后的页怎么处理呢?

2.有一个lru链表,遵循最近最少原则将最近查询次数最少的数据淘汰掉
在这里插入图片描述
3.lru链表中氛围热数据区域(八分之五),冷数据区域(八分之三)

4.如果没有冷热数据区域的区分,那么情况会是这样:

当有一页数据被查询后,会淘汰链表最后一条数据,然后将当前查询的数据插入到链表的头部,这种情况下,链表距离头部越近的节点,越是热点数据

但是当进行全表扫描的时候,这种情况下,全表扫描扫描到的n页数据不停的淘汰链表尾部数据,然后再插入到链表头部,如果之前链表中有热点数据,此时通过一次全表扫描,所有热点数据均被淘汰。

为了解决这个情况,lru进行了优化,区分了热数据区域和冷数据区域。

5.当进行查询的时候,判断该数据在lru链表中上一次查询时间和本次查询时间间隔,如果间隔时间小于1s,则mysql认为是在多次全表扫描,视为冷数据,放在冷数据区域;如果间隔时间大于1s,则mysql认为是在正常业务查询,视为热数据,放在热数据区域,即将热数据区域的尾节点删除,当前查询数据放在头节点。

mysql线程如何更新flush链表中的脏页去更新磁盘数据?在该期间mysql挂掉导致无法更新成功如何解决?

步骤

1.执行一条update语句,修改Buffer Pool 里的页数据,该页变为脏页

2.该update语句会生成一个redo log日志

3.redo log持久化到磁盘中(事务提交时)

4.持久化的方式是顺序持久化:原本mysql启动的时候在磁盘中已经生成了两个空的logfile文件,redo log只需要将内容按顺序往logfile中追加写入即可。磁盘在读取logfile的时候是顺序读取,性能也是比较快的。

4.修改磁盘页数据成功

5.即使在线程读取logfile之前mysql就挂掉了,但是只要再次启动起来,便可以重新读取logfile内容,继续修改脏页数据。

6.注意,redo log 就是logfile
在这里插入图片描述
mysql启动后会生成两个logfile文件,每个文件是48M。

redo log为什么会有两个文件?

redo log是innodb的redo log。在mysql启动的时候便会生成两个文件, logfile0和logfile1
在这里插入图片描述
1.当执行update语句后,会向磁盘的redo log写入,先往logfile0写,满了之后往logfile1写

2.当logfile1也满了之后,继续往logfile0写入,但是如果写入的话会将文件中以前写入的数据冲掉,因此这里有一个checkpoint(检查点)

3.检查点:将logfile0中的页和buffer Pool中的页进行一一对应,能在Buffer Pool中对应到的那些页数据,持久化到磁盘中,然后将新的redo log再往logfile0中写入。

4.但是出现这种情况的时候,说明mysql已经很慢了。

5.那么解决mysql很慢这个问题的优化方案是:将logfile调大

6.但是logfile如果很大的话,重启mysql的时候会启动很慢。因为logfile的内容很多。

redo log什么时候持久化到磁盘中?

1.事务提交的时候才会将redo log持久化到磁盘中

2.在事务提交之前,redo log会被存储到 log Buffer中(存储redo log 的内存区域)

innodb可以对持久化进行配置,如下图
在这里插入图片描述
三种策略的优劣

配置值
0速度快因为需要后台线程持久化内存中的redo log,因此mysql不能挂。否则会丢数据
1不会丢数据速度最慢
2速度快mysql可以挂,但是操作系统不能挂。因为redo log是被写到了操作系统的缓冲区了

在这里插入图片描述

binlog & redo log & undo log

binlogmysql的概念;存储的sql语句;用于mysql主从复制
redologinnodb的概念;存储的是某一页的某位置的数据的修改(记录的是物理位置);速度更快
undo log回滚时会用到;执行一条update语句,将Buffer Pool中的页数据修改后,会将修改之前的该数据存储在undo log中。当最终不是进行事务提交而是进行回滚的时候,会到undo log中找到该条数据,再次执行update,update回在undo log中记录的值。

在这里插入图片描述

Double Write Buffer

是磁盘的一个结构
在这里插入图片描述
Buffer Pool的一页是16KB,最终该数据需要持久化到磁盘中,但是对于磁盘来说,磁盘的一页大小为4KB,也就是说Buffer Pool的一页数据需要进行4次io,但是如果在进行过程中,突然操作系统挂了,那么再次启动起来之后,需要继续将这一页数据持久化到磁盘中,但是我们很难去判断磁盘中本页数据究竟哪些修改过了,哪些没有修改过,因此出现了以下解决办法:

1.Buffer Pool先将数据写到双写缓冲区,如果在此期间停电了,此时磁盘内的数据还没有被修改,重启程序后,还可以读取redo log的内容去修改磁盘

2.如果成功都写入了双写缓冲区,在从双写缓冲区写入磁盘期间停电了,此时磁盘内的数据一部分被修改,一部分没有被修改,但是双写缓冲区就已经是磁盘的一部分,也就是说Buffer Pool内存的一页完整的数据已经被写入到磁盘的双写缓冲区中了,因此当重启程序后,就正常将双写缓冲区中的数据修改到磁盘中就好了,此时redo log也没啥用了。

3.但是这样的解决办法相当于原本从内存到磁盘的一页数据持久化只需要4次io,但是现在变成了两次磁盘操作,变成了8次io,性能下降。

4.对此也有解决办法:

如果磁盘支持原子性操作,一页数据要么全部修改成功,要么全部修改失败,则不需要用到双写缓冲区了。

Change Buffer

用来优化写缓存

change Buffer是Buffer Pool的一个结构,占Buffer Pool的25%。
在这里插入图片描述

mysql相关命令

在这里插入图片描述

面试问题

  1. 主键索引和聚集索引(聚簇索引)的区别
    https://baijiahao.baidu.com/s?id=1728100432281515489&wfr=spider&for=pc
举报

相关推荐

0 条评论