上一章, MySQL(一): select和update的执行流程 我们MySQL设置这么一套复杂的机制:为了确保服务器故障时,保证有恢复数据的能力,同时能保证数据一致性,同时也要保证服务器在正常情况下,尽可能减少IO磁盘的消耗。

举例子:如果有一个SQL请求,就直接访问磁盘,对磁盘进行读写操作,请求一旦暴增,扛不住这么大的并发,确保每个更新都先更新缓存,再同步磁盘,可以保证在异常情况下,数据的一致性得以保证。

大纲内容

  • 事务隔离级别
  • MVCC多版本并发控制

读书才能够系统的学习,听别人偶尔的两句断章取义的结论,远远不能够支撑设计一套系统。:

预前知识

MySQL的事务原理:事务就是要保证一组数据库操作,要么全部成功,要么全部失败,同生共死的关系。在MySQL中,事务支持是在引擎层做的,只有InnoDB引擎存在事务,在默认情况下,当执行第一个DML语句时,事务才开启了,并不是因为begin/start执行才开始。

好处:主要考虑到要在最大程度上减少并发冲突,减少锁持有的时间

关于begin/start命令,如果想要使用begin/start命令执行就开启事务
可以使用start transaction with consistent snapshot命令,这个命令一执行,事务就开启了。
InnoDB中每个事务都有一个唯一id, trx_id,是递增的,如果使用默认命令,则执行第一个DML语句时,申请trx_id,如果使用start transaction with consistent snapshot,开始就申请trx_id。


事务隔离级别

数据库一般会并发操作多个事务,涉及到多个事务对同一条数据的增删改查,在不同的事务隔离级别下,会分别造成脏写,脏读,不可重复读,幻读
这些问题的产生:数据库的多事务并发导致的

脏写

在多并发场景下,最新的事务对同一条数据修改的值,会覆盖掉其他事务所修改的值。


脏读读未提交会造成脏读

在多并发场景下,事务A修改id=1的值,将值改为peter,未提交事务,事务B开启事务,在查询id=1的值时,能查到事务A所修改的最新值。
假设事务A回滚了,事务B还拿到了最新的数据,造成了数据不一致性问题。

解决办法:使用读已提交级别。

1:事务A查询id=1的值,假设id=1默认值为Bob。

2:事务B读取id=1的值,并且修改了Peter,此时未提交事务,

3:事务A再查询id=1的值,值还是Bob,

4:事务B提交事务,事务A再查询id=1的值,读到了最新值。

解决脏读,用读已提交级别会带来不可重复读问题


不可重复读:读已提交会造成不可成复读

在多并发场景下,事务A修改id=1的值,将值修改为peter,提交事务,事务B开启事务,查询到id=1的值,能读到peter的值,其他事务对id=1的值修改并提交,事务B每次都能获取到最新值,违背了隔离性原则。注重点:数据内容的变化

解决办法:使用不可重复读

1:事务A开启事务,读取id=1的值,age为500,

2:事务B修改id=1的值,并且修改age=400,并提交事务。

3:事务A再读取id=1的值,age还是为500,保证了每个事务读到的数据都是第一次读的值。

4:事务A进行更新语句,update user set age=age-100 where id = 1,得到的结果是300,而不是400,原因是在RR级别下,除了普通查询外,任何操作都是当前读,读的都是最新的数据。

解决不可重读,用可重复读来解决不可重复读问题。

注意:当事务B新增了一条最新数据id=2,此时事务A查询select时发现只有一条id=1的数据,但可以对id=2进行更新,而事务A内,更新完后可以查看到两条记录,造成了幻读。


幻读:可重复读会造成幻读

在多并发场景下,事务A读了到事务B新增的一条数据。

注意重点:数据行数的变化
使用串行化可解决幻读

MVCC多版本并发控制

RR级别下,当事务开启时,执行第一个select,会生成read-review,在当前事务内,每次查询都是基于当前read-review。read-review由所有未提交事务Id的数组+最大事务id组成

RC级别下,当事务开启时,在当前事务内,每执行一次select,都会生成一个新的read-review。
begin命令并不是一个事务的起点,只有第一次执行DML语句时,事务才开启


假设在RR可重复读级别下,数据库允许多个事务同时执行,如何保证每个事务的隔离性呢?
使用MVCC来确保事务的相互隔离,MVCC是利用了undolog日志版本链和read-review机制来一一对比,对比规则如下⬇️
如果版本链中的trx_id<min_id,说明当前事务可见
如果版本链中的trx_id>max_id,说明当前事务不可见
如果版本链中的min_id<=trx_id<=max_id,存在两种判断。
-----若版本链中的trx_id位于数组中,则说明这个版本是由未提交的事务生成,固不可见。
-----若版本链中的trx_id不在数组中,说明这个版本是由已提交的事务生成,固可见。

所有未提交事务id视图数组和已创建的最大事务id组成
trx_id是事务id,数组是未提交事务的数组,max_id是最大事务Id,min_id是数组中最小的id,
每个事务开启时,查询select语句执行,生成read-Review。
read-review由未提交的事务Id组成的数组+当前最大事务Id
undolog版本链由主键id, 事务id, 修改的值, 指向上一个链路的指针组成。