Mysql系列(四)彻底理解MVCC+行锁+表锁+间隙锁
文章目录一. 什么是MVCC二.什么是行锁、表锁、间隙锁三. MVCC与各种锁的关系四. MVCC的实现原理4.1 多版本4.2 undo log4.2 readview一. 什么是MVCCMVCCMulti-Version Concurrency Control即多版本并发控制。不使用锁主要是用来提高数据库的并发性能算是一种概念不同的数据库有不同的实现方式本文主要介绍mysql的innodb引擎中的实现方式。在mysql的innodb中前面我们有篇文章《Mysql系列二Mysql事务四大隔离级别详解演示》分析了4种隔离级别以及每种隔离级别下导致的问题脏读、不可重复读、幻读。其中不可重复读、幻读就是使用MVCC来解决的。我们知道mysql事务具备ACID四个特性其中的就是MVCC来解决的整体如下原子性 (Atomicity) —— Undo Log原理在执行修改操作前InnoDB 会先将旧数据记录到 Undo Log回滚日志中。作用如果事务执行中途失败或主动回滚MySQL 会根据 Undo Log 将数据恢复到修改前的状态确保事务“要么全做要么全不做”。2. 持久性 (Durability) —— Redo Log原理事务提交时MySQL 会先将修改记录写入 Redo Log重做日志再异步刷入磁盘数据文件。作用Redo Log 是物理日志写入速度极快。即使数据库突然宕机重启后也能通过 Redo Log 恢复已提交但未落盘的数据确保数据不丢失。3. 隔离性 (Isolation) —— MVCC LocksMVCC (多版本并发控制)通过 Undo Log 和 Read View 实现。它让读操作不加锁每个事务能看到数据的一个特定“快照”解决了读写冲突。锁机制行锁 (Row Lock)只锁定被修改的行提高并发度。间隙锁 (Gap Lock)防止其他事务在范围内插入新数据解决“幻读”问题。4. 一致性 (Consistency) —— 最终结果原理一致性不是由某个单一技术实现的而是 A、I、D 共同作用的结果。作用通过原子性保证数据完整通过隔离性保证并发正确通过持久性保证数据安全最终让数据库从一个一致状态平滑过渡到另一个一致状态。二.什么是行锁、表锁、间隙锁首先锁的存在目的是为了在并发场景下保持数据的安全、一致。并发场景有读-读 此并发场景不需要进行并发控制也就是不需要加锁。读-写 此并发场景需要并发控制不然就会出现脏读幻读不可重复读的问题。写-写 此并发场景需要并发控制不然就会出现更新丢失的问题。进行并发控制常规手段就是加锁不管是咋java业务代码中还是mysql数据库本身都有实现自己的锁其中mysql的锁有以下几种行锁锁住表中的一行比如 update user set name‘张三’ where id1会锁住id1的那一行数据其他事务再想更新就只能等前一个事务释放锁。表锁锁住整个表比如update user set name‘张三’由于没有加where条件此更新sql会对整个表进行更新也就是会锁住整个表。间隙锁比如事务A执行update user set name‘张三’ where id 1 and id4; 假如表中只有id1、2 两条数据A事务还没提交那么此时事务B再次插入一条id3的数据理论上是允许的但是实际上是B只能等A提交因为事务A执行的是id1and id4范围涵盖了id3的也即是把id3的这个间隙也给锁了叫做间隙锁。除了这3种锁还有乐观锁、悲观锁、记录锁、自增锁、意向锁三. MVCC与各种锁的关系既然可以使用行锁、表锁、间隙锁来保证数据操作的安全性那么还要MVCC的出现是为何呢实际是因为在性能方面还有优化的空间。虽然使用锁可以保证数据安全但是毕竟加了锁就意味着并发性能的降低因此能不使用锁就尽量不使用锁。在某些场景下MVCC可以在比使用锁更快。在读-读、读-写、写-写这3种并发场景中读-写 可以不使用锁而是使用MVCC来实现数据的并发操作以及安全一致性。因此mysql是同时使用了MVCC行锁、表锁、间隙锁来保证了数据安全又尽可能大的实现了性能最优化。四. MVCC的实现原理4.1 多版本那么不使用锁MVCC是如何更高效的解决读-写这种并发场景下的数据安全呢我们知道事务在执行失败时会将数据回滚为上个版本而MVCC叫做多版本并发控制核心概念就在版本上也就是说数据库存储了多个版本的数据。多个版本整体上分为两类最新版本、历史版本。这也牵扯出来另外两个概念快照读读取的是数据库种历史版本的数据常规的select * from user 属于是快照读当前读读取的是数据库种最新版本的数据当前读的操作有select * from user in share mode(共享锁),select * from user for updateupdate, insert ,delete(排他锁)那么多个版本mysql是如何存储的呢innodb存储引擎中我们存在表中的数据除了我们设置的业务字段另外还有3个默认字段如下图末尾3个DB_TRX_ID: 事务id存的是创建事务的id或者最后一次更新事务的idDB_ROW_ID: 主键id建表时如果没设置主键则此字段会成为主键发挥作用DB_ROLL_PTR: 回滚指针指向上一个版本的数据如果没有上个版本则为null。其中DB_ROLL_PTR结合undo log实现。4.2 undo logundo log称为回滚日志是InnoDB MVCC事务特性的重要组成部分存在形式就是一种日志文件。undo log的数据结构非常复杂可以简单理解为链表链表头部存储最新版本尾部存储最早版本。通过遍历链表就可以找到对应版本的数据。大多数对数据的变更操作包括INSERT/DELETE/UPDATE其中INSERT操作使用insert_undo因为新插入就只有一个版本因此产生的Undo日志可以在事务提交后直接删除而对于UPDATE/DELETE则需要维护多版本信息在InnoDB里UPDATE和DELETE操作产生的Undo日志被归成一类即update_undomvcc使用的undolog就是update_undo。需要注意的是上面图中最新record的trxid是3创建这个节点的事务不是当前事务3而是3之后的事务比如事务4也就是说事务4在修改数据之前会把当前最新历史数据保存为undolog中的最新record就是上图中的trxid3的这个这个很关键会影响后面mvcc的查找逻辑只有undolog还不足以实现mvcc因为既然undolog维护了那么多版本遍历的时候应该去找哪一个版本呢这里必然牵扯到一种规则这种规则在不同的数据库隔离级别下是不一样的。innodb具体实现的时候使用了一种叫做readview的定西。4.2 readviewreadview称为读视图是事务在进行快照读的时候产生的算是一种数据结构。readview中包含3个参数trx_list :系统活跃的事务在执行还没有提交事务id列表up_limit_id trx_list列表中最小的事务id也叫min_trx_idlow_limit_id ReadView生成时刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的最大值1也叫max_trx_id比如当前事务id已经分配到了4那么low_limit_id的值就是5每个事务在进行自己的快照读时都会产生自己事务对应的readview。Demo例如下图中有4个事务同时在执行事务id分别为1234其中123还没有提交4已经提交那么当事务2在进行快照读时产生一个readview其trx_list存的就是活跃的123如下图此时最小的事务id是1最大的事务id是5 尚未分配的下一个id当前最新的事务id是4 事务4的id。可见性算法上图中右侧黄色部分就是本次查询到底该取哪个版本数据的规则也叫可见性算法查找过程注意首先查找当前活跃的修改此行数据的那个事务id本demo如果没提交就是4因为已经提交了所以没有活跃的修改的了下一步就是遍历undolog的最新数据到最老数据逐个判断每条log的事务id当作DB_TRX_ID本demo链头是4因为事务4刚已经提交然后使用上图中黄色可见性算法比对最终确定当前遍历的数据是不是目标数据。上面例子中最新的事务id是4因为4不满足uplimitid(因为事务4是后更新的只有当事务4后面开始的查询才会满足第一个条件比如事务4提交后又开了一个查询事务5)这里有个需要主要的点为什么是uplimitid而不是uplimitid因为等于的场景一定是当前事务id还在活跃着呢没提交肯定是不可见的。既然不满足步骤1那么继续步骤2同样不满足lowlimitid大于等于的场景表示当前查询是在当前undolog之后产生的肯定不可见当前undolog本的demo是在事务4提交之前产生的理论上有可见的可能性步骤3在判断当前undolog的事务id是不是在快照中活跃的id中如果在说明快照在undolog提交之前产生的肯定不可见如果不在像本demo则可见因为事务2在创建读视图时事务4已经提交了事务4的id不在事务2readview的活跃id中。读视图的产生时机在不同的隔离级别下readview产生的时机是不同的RC 每次进行快照读的时候都会产生新的readviewRR 只有在第一次进行快照读的时候才会产生readview之后的读操作都会使用第一次生成的readview。如果此事务中间发生了update等当前读操作也会生成新的readview。当前事务会通过指针与当前readview建立联系如果是RC隔离级别每次创建新的readview都会废弃旧的指针重新指向最新的readview
上一篇/下一篇内容由系统自动关联
返回资讯列表 →