MySQL 事务实现原理

是什么是事务

事务是数据库中一些列操作的集合,这个集合是按顺序逐个执行的。在mysql中,保证数据具备ACID特性,这种特性使得事务使用起来非常安全和方便。比如银行转账操作,使用事务就可以保证转账结果的正确,不同转账之间的隔离,转账过程中发生错误的回滚,以及机器崩溃的现场恢复。可以在99.99%的情况下保证我们数据安全。

事务具备ACID特性,分别是原子性、持久性、隔离性、一致性。
1、原子性指事务中的所有操作要么全部成功要么全部失败,不能成功一半。
2、持久性指事务执行中机器崩溃后,重启机器,能重新执行失败的事务,保证事务不会因为某种情况被抛弃。
3、隔离性是针对事务之间的并发提出的概念,mysql做出了不同的隔离级别,隔离级别越高,事务并发性能越低,一般默认采用可重复读级别。
4、一致性基于前三点特性所产生,即事务过程中无论发生什么,最终数据都是准确无误的。

bin、redo、undo日志

每次update、insert、delete类型的SQL执行时都会先写三条日志。

bin log记录执行的每条SQL,用来做主从复制;
redo log记录修改内容和修改页的位置,用来维护持久性;
undo log记录相反类型的SQL,用来做rollback和实现mvcc。

事务数据读写直接操作的都是缓存,由操作系统定时刷新到磁盘。log以追加写的方式写入缓存,一般mysql默认设置log写入缓存立即刷新到磁盘,保证log数据不丢失。

隔离级别

脏读:事务A还未提交修改,事务B读取到的未提交数据是脏数据。(丢失更新:事务A修改记录完成,但还未提交;事务B也修改了该记录并提交,但是事务B由于其他原因回滚,导致事务A的修改失效。)
不可重复读:事务A获得查询结果,在事务A提交前,事务B将某条数据修改并提交,事务A再次查询,两次查询结果不同。
幻读:和不可重复读相同的场景,但幻读专指数据的删除或插入,而不是修改。

读取已提交(解决1);可重复读(解决1、2);串行化(全部解决)。

MVCC实现原理

mvcc是乐观锁的一种实现方式,目的是解决读写互斥问题,增大并发程度。

场景举例:每次更新数据都为其记录一个递增的版本。线程A当第一次读取数据时,版本号为1;此时该数据被线程B修改,那么版本号变为2;线程A再次读取数据,发现版本号有变,此时可以根据不同的隔离级别来决定是否展示变更后的数据。

mysql中的mvcc通过隐藏列DB_TRX_ID、DB_ROLL_PTR和ReadView实现。DB_TRX_ID是该行记录当前的事务id,DB_ROLL_PTR指向该行在undo log中的位置,ReadView是一个记录当前活跃事务id的链表。

当一行记录被修改时,undo文件中会存储它的相反操作,并且存有修改之前的版本号。每次修改会在undo文件中形成链式存储,链表头部是最近的版本,尾部是最老版本。

当事务在执行select操作之前,会先构造ReadView,并通过ReadView判断当前记录是否是可见的。ReadView中按序存储(事务id排序)着所有活跃事务id,如果当前记录的事务id小于ReadView中的最小值,意味着该记录已经被提交,则该记录可见。如果大于ReadView中最大值,那么该记录应当在之后的事务中提交,因此属于不可见。如果处于最大最小值之前,则需要通过二分查找判断,事务id是否存在于ReadView中,如果存在,说明该记录的事务活跃,但无法判定事务是否已经提交,因此需要读取DB_ROLL_PTR,获取上一个版本的事务id,看看上个版本是否是可见的,通过层层递归,直到某一个之前的版本满足可见的要求。

可重复读实现原理

快照读。
场景1:在事务A两次查询中间,事务B修改数据的场景下,第一次查询的结果会作为快照保存,之后再次查询就都是快照内容。

这是由于快照读本质是mvcc实现的,因为事务A本身对数据操作,会导致事务A维护的版本号变更,再次查询,事务A查询的就是最新版本号数据。

mvcc在mysql中,通过隐藏字段DB_TRX_ID、DB_ROLL_PTR和undo log实现。DB_TRX_ID记录该行记录目前的版本号,DB_ROLL_PTR记录该行之前版本在undo log中的位置。那么在上述场景1下,事务A通过DB_ROLL_PTR,可以直接获取被事务B修改之前的数据。

场景2:如果在事务B插入数据1之后,事务A也修改了数据1,那么事务A再次查询就会看到本不存在的数据1。可见不可重复读无法解决幻读。

若想解决幻读,需要使用mvcc+NextKey Locks(间隙锁+行锁)。

我们都知道InnoDB支持行锁,并且行锁是锁住索引。而间隙锁用来锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为Repeatable Read或以上级别而已的,间隙锁和行锁一起组成了Next-Key Lock。当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁,再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙插入记录。这样就有效的防止了幻读的发生。
默认情况下,InnoDB工作在Repeatable Read的隔离级别下,并且以Next-Key Lock的方式对索引行进行加锁。当查询的索引具有唯一性(主键、唯一索引)时,Innodb存储引擎会对Next-Key Lock进行优化,将其降为行锁,仅仅锁住索引本身,而不是范围(除非锁定不存在的值)。若是普通索引,则会使用Next-Key Lock将记录和间隙一起锁定。

posted @ 2022-01-15 17:45  moon_orange  阅读(1282)  评论(0编辑  收藏  举报