“ 大部分服务端系统都是数据密集型应用,主要的功能是基于数据库对各种业务数据进行增删查改。在互联网这种高并发场景,如何确保数据的准确性以及保证系统的吞吐量,事务的隔离性有很大一部分功劳” 本文主要探究MySQL数据库InnoD存储引擎的事务隔离级别及其背后实现原理,并且会回答以下问题:
01 — 事务是什么? 事务是数据库一个不可分的工作单元,可以将多个操作步骤表示为一个步骤,事务拥有ACID四大特性即 原子性(Atomicity):事务的操作是不可分的,即使有多个操作步骤,要么全部执行成功,要么全部执行失败。背后的实现原理是通过undo log做到的,回滚的时候基于undo log上的数据,如果执行了INSERT,那就再做一次DELETE,如果做了一次UPDATE,则会做一个相反的UPDATE。 隔离性(Isolation):并行执行的事务之间互相不影响,即A事务操作但未提交的数据,不会影响B事务的处理结果。背后的实现原理是通过MVCC以及数据库锁做到的,这部分也是本文介绍的重点。 持久性(Durability):事务一旦提交,其结果就是持久的,即使发生宕机,事务提交的数据也能恢复。背后的实现原理是通过redo log做到的,每次提交事务redo log都会刷到磁盘里。redo log作为重做记录了数据库的物理日志,上面记录了某个数据页做了什么修改,即使宕机也可以根据redo log恢复数据页的内容。 一致性(Consistency):事务执行前后会从一个正确一致状态到另一个正确一致状态,一致性是ACID里约束最弱的特性,因为这个特性更像一个结果。即做到了原子性、隔离性、持久性,自然就做到了一致性。 当然上述关于事务ACID的定义都是理论上的,实际情况可能并没有严格满足,不同事务隔离级别表现并不相同,所以下面会讨论事务的隔离级别 02 — 事务异常情况 脏读:一个事务读到了另一个事务未提交的数据
不可重复读:一个事务里多次查询一条数据,得到的结果不同
幻读:一个事务里多次查询一批数据,得到的结果不同,看到了新插入的行数据
03 — 基本概念 接下来会介绍隔离级别是如何解决这些异常情况的,先介绍几个基本概念 两阶段锁协议在 InnoDB 事务中,行锁是在用到的的时候才加上的,但并不是不再用了就立刻释放,而是要等到事务提交或回滚时才释放 锁的类型共享锁(shared lock):允许事务读一行数据 排他锁(exclusive lock):允许事务删除或更新一行数据
锁的算法行锁(record lock):针对单行记录的锁 间隙锁(gap lock):锁定一个范围但不包含记录本身,在可重复读级别下才存在 Next-Key Lock:间隙锁+行锁,锁定一个范围并且包含记录本身,每个next-key lock都是前开后闭区间,假设数据库里有3条记录0,5,10 那么就有(-∞,0]、(0,5]、(5,10]、(10,+∞]4个可能的next-key lock,在可重复读、串行化级别下才存在 快照读与当前读多版本并发控制(MVCC):一行数据有多个版本,根据事务Id获取一致性读视图,数据版相关内容维护在undo log里,在读已提交、可重复读级别下才存在,MVCC避免了锁的争抢,通过空间换时间来保证了数据读取的正确性 快照读:读取的是快照数据,可能并不是最新数据,如果MVCC生效就会走到MVCC背后相关的逻辑,最普通的查询语句就是快照读
当前读:读取到的是最新数据,且读的时候会进行加锁
读的时候会加一把共享锁
读的时候会加一把排他锁 实际情况在获取共享锁、排他锁的过程,还有意向共享锁,意向排他锁的获取,对本文想阐述的内容关系不大,不做赘述。 04 — 事务隔离级别 SQL标准定义了四个隔离级别
读未提交特点:读的时候未对数据加锁,修改数据的时候对数据添加行级共享锁 效果:因为修改数据时候添加的是共享锁,所以会被其他事务读到,产生 脏读的问题 读已提交特点: (快照读)读的时候会通过MVCC的一致性视图读到最新的、已提交事务 的数据版本 (当前读)根据sql语句的不同读数据的时候,会加一把共享行锁(lock in share mode)或者排他行锁(for update) 当读取数据不存在的时候,不会加锁。修改数据的时候会对数据行添加行 级排他锁,事务结束的时候才会释放,如果在执行语句的时候没有走到索 引,并且在修改的时候如果发现某行数据已经被锁定,这时会通过MVCC 读到最新的、已提交事务的数据版本再根据where条件来决定是否必须更 新改行,从而决定是否要等待锁的释放 效果: (快照读)因为通过MVCC读取的是已提交事务的数据版本,所以不会存在 脏读。通过MVCC读到的数据,没有进行加锁操作, 所以其他事务仍然可 以对读到的数据进行修改并提交,这时候再次读取就发生了不可重复读。 (当前读)通过显示加锁,防止了其他事务对其行数据的修改,从而避免了 不可重复读 可重复读特点: (快照读)读的时候会通过MVCC的一致性视图读到开启事务时刻、已提 交事务的数据版本 (当前读)根据sql语句的不同读数据的时候,会加一把共享锁(lock in share mode)或者排他锁(for update), 如果查询行不存在,next-key lock 则会退化成gap lock修改数据的时候会对数据行添加next-key lock不仅锁 住行数据本身,还锁住相邻的间隙,事务结束的时候才会释放 效果: (快照读)因为通过MVCC读取的是开启事务时刻,已提交事务的数据版 本,所以无论其他事务怎么修改,看到的数据都是雷打不动的固定的数据 版本,这时候再次读取就避免了不可重复读。因为在修改数据的时候使用 了next-key lock,所以即使进行插入操作也会被锁阻塞,这样也避免了幻 读情况的产生。在实际情况中,锁的算法比较灵活 ,根据sql语句的不 同,和命中索引的区别,即有next-key lock+gap lock锁住更大范围的情 况,也有在唯一索引场景 next-key lock退化成 record lock以及gap lock 的情况,这里后面可以单独拉一篇文章进行阐述,内容较多 串行化特点: 串行化和可重复读很相似, 区别是串行化级别会为每一个普通的
语句隐式转化为
给索引增加一个共享的next-key lcok,同时也不存在通过MVCC读取数据 的情况。针对修改,和可重复读一样会通过next-key lock进行索引数据行 的锁定 效果: 和不可重复读效果一样,解决了脏读、不可重复读、幻读,并且比不可重 复读的并发控制更加严格 05 — 如何选择? MySQL InnoDb引擎默认的事务隔离级别是可重复读,但是阿里内部设置 的数据库隔离级别是读已提交,这是为什么呢?谈谈我自己的看法
所以在没有对不可重复读、幻读有强烈诉求的情况下,读已提交是一个不错的选择 06 — 参考资料 InnoDB Locking and Transaction Model 《MySQL技术内幕 InnoDB存储引擎 第2版》 《数据库系统内幕》 本文转载自公众号: |
|
来自: 昵称34195792 > 《待分类》