MySQL中的多版本并发控制(MVCC)是现代数据库引擎实现中常用的处理读写冲突的方法。MVCC作为MySQL的一个高级应用特性,旨在改善数据库低吞吐量的高并发场景下的性能。1、MVCC产生的背景是什么?事务的4种隔离级别和对应的3种异常:脏读:一个事务读取到另一个事务还没有提交的数据;不可重复读:在同一个事务中,同一个数据被读取两次,内容不同;MagicRead:在同一个事务中,用同一个操作读取两次,得到的记录条数不同。在MySQL中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,但是不能解决幻读的问题。如果要解决幻读问题,就需要采用序列化的方式,即将隔离级别提高到最高,但这会大大降低数据库的事务并发能力。MVCC使用乐观锁来解决不可重复读和幻读的问题。它在大多数情况下可以替代行级锁,减少系统开销。MySQL并发事务会造成更新丢失的问题。解决方案是锁,主要分为两类:乐观锁:顾名思义,它的实现假设了一个更好的情况。每次取数据都假设别人不会修改,所以不会加锁,但是更新的时候会判断这段时间别人有没有更新过数据,可以使用version来实现编号机制和CAS算法。悲观锁:顾名思义,悲观锁总是假设一个不好的情况。每次取数据,你都认为别人会修改它,所以你每次取数据都会加锁,这样别人要取这个数据就会阻塞,直到拿到锁(共享资源只被一个线程使用)一次,其他线程阻塞,资源使用后转移给其他线程)。2、什么是MVCC,它解决了哪些问题?MVCC通过对数据行的多版本管理来实现数据库的并发控制。简单来说,它的思想就是保存数据的历史版本。我们可以通过比较版本号来判断数据是否显示(具体规则后面会介绍),在读取数据的时候不用加锁就可以保证事务的隔离效果。通过MVCC,我们可以解决以下问题:(1)读写之间的阻塞问题,通过MVCC,可以防止读写互相阻塞,即读不阻塞写,写不阻塞读,从而提高并发事务处理能力。(2)死锁概率降低。这是因为MVCC采用了乐观锁的方式,读数据时不需要加锁,写操作只锁必要的行。(3)解决读一致性问题。一致性阅读也称为快照阅读。当我们查询数据库某个时间点的快照时,只能看到该时间点之前事务提交更新的结果,看不到该时间点之后事务提交的更新结果。.解释一下可能比较难理解的几个词:Snapshotread:读取快照数据,简单的没有加锁的SELECT就是快照读(只是一个普通的读操作)。Currentread:Currentread是读取最新的数据,不是数据的历史版本。锁定SELECT,或者增删改查数据,都会执行当前读取(包括锁定读取和DML操作)。3.应用实例分析为了让大家更好的理解MVCC,我们用一个实例场景来说明。假设有一个账户金额表user_balance,包含三个字段,分别是用户名、余额balance和银行卡卡号。表数据如下:用户A和用户B之间转账,此时数据库管理员想查询user_balance表中的总金额,两种场景存在并发??。没有MVCC会出现什么问题。Case1:用户A因为需要加行锁,需要等待很长时间才能给B转账,如下图。Case2:当我们在读的时候使用行锁,可能会出现死锁,如下图所示。比如我们读到A有1000元,此时B开始给A转钱。4、InnoDB是如何实现MVCC的?查询一条记录时,执行过程如下:首先获取交易本身的版本号,即交易ID;获取阅读视图;查询得到的数据,然后在ReadView中与交易版本号进行比较;如果不满足ReadView规则,需要从UndoLog中获取历史快照;最后返回符合规则的数据。相关概念1.交易版本号自增交易ID,用于标记交易执行的先后顺序。2.ReadView在MVCC机制中,多个事务更新同一条记录会产生多个历史快照,存储在UndoLog中。如果一个事务要查询这条行记录,需要读取哪个版本的行记录?这时候就需要ReadView,它可以帮助我们解决行可见性问题。读取视图在当前事务启动时保存所有活动(尚未提交)事务的列表。换个角度,可以理解为ReadView保存了一个本次交易不应该看到的其他交易ID的列表。ReadView中的几个重要属性:up_limit_id,活跃交易中最小的交易ID;trx_ids,系统当前活跃的交易ID集合;low_limit_id,活跃交易中最大的交易ID;creator_trx_id,创建此读取视图ID的事务。3、行记录的隐藏列。InnoDB的叶子节点段存放数据页,行记录保存在数据页中。这些行记录中有一些重要的隐藏字段:DB_ROW_ID:6字节,记录操作数据的事务ID;DB_TRX_ID:6-byte,当创建的表没有合适的索引作为聚簇索引时,会使用隐藏ID创建聚簇索引;DB_ROLL_PTR:7字节,回滚指针,指向上一版本数据在undolog中的位置4.聚簇索引聚簇索引是指数据库表行中数据的物理顺序与逻辑(索引)键值的顺序。一张表只能有一个聚簇索引,因为一张表的物理顺序只有一种情况,所以只能有一个对应的聚簇索引。5.UndoLogInnoDB将行记录快照保存在UndoLog中,可以在回滚段中找到。主要用于记录数据修改前的日志。在修改表信息之前,数据会被复制到UndoLog中。当事务回滚时,可以通过UndoLog中的日志恢复数据。回滚段中的回滚指针之间的关系如下图所示:5.InnoDB如何解决幻读?1.在readcommitted的情况下,即使采用MVCC方式也会出现幻读。我们同时开启事务A和事务B,先在事务A中进行一定条件范围的查询,读的时候使用排它锁。在事务B中添加一条满足条件范围的数据并提交,然后我们在事务A中再次查询条件范围内的数据,会发现结果集中多了一条满足条件的数据,所以出现幻读。幻读的原因是InnoDB只在读取提交时使用记录锁定。InnoDB有三种类型的行锁:记录锁:为单行记录加锁。间隙锁定:可以锁定范围(索引之间的间隙),但不能锁定记录本身。使用间隙锁可以防止幻读。Next-Keylock:锁定一个range,同时锁定record本身,相当于gaplock+recordlock,可以解决幻读问题。2、在可重复读的情况下,InnoDB可以通过Next-Keylock+MVCC解决幻读问题。当尝试插入球员AlexLen(身高2.16米)时,事务B会超时,无法插入数据。这是因为Next-Key锁会锁定height>2.08的范围,不能插入符合这个范围的数据。然后事务A重新进行条件范围的查询,就不会出现幻读了。6.总结MVCC的核心是UndoLog+ReadView。“MV”是通过UndoLog保存数据的历史版本,实现多版本管理;“CC”是通过ReadView来实现管理,通过ReadView的原理来决定数据是否显示。同时,对于不同的隔离级别,ReadView的生成策略不同,实现了不同的隔离级别。
