什么是MVCC?
MVCC(Multiy Version Concurrent Control),即多版本并发控制,是一种乐观锁的实现。
MVCC使得读不会加锁,提高了数据库的并发处理能力。通过MVCC,MySQL可以实现【读已提交】和【可重复读】等隔离级别,保证了隔离性。
MVCC原理:
同一份数据临时保存多个版本的一种方式, 是通过undo log来实现的。
不同的事务可以读取到不同版本的数据,从而解决脏读和不可重复读的问题
即根据事务开始的时间不同,不同的事务对同一张表,同一时刻看到的数据可能是不一样的。但是对每个事务来说,在它的执行期间,不管它执行多长时间,它看到的数据都是一致的。
InnDB中的MVCC:
InnoDB中每个事务都有一个唯一的事务ID,即为transaction_id。它是在事务开始时向InnoDB申请,按照时间先后严格递增。
每行数据有多个版本,是依赖undo log 来实现的。
每次事务更新数据就会生成一个新的数据版本,并把transaction_id记为 row_trx_id,同时,旧的数据版本会保留在undo log中,而且新的版本会记录旧版本的回滚指针,通过它直接拿到上一个版本。
所以,InnoDB中的MVCC其实是通过在每行记录后面保留两个隐藏的列来实现的。一列是事务ID:trx_id ; 另一列是回滚指针:roll_pt 。
undo log :回滚日志
记录事务修改之前的数据的一个版本,可以用于回滚。同时提供MVCC下的读,也即是非锁定读。
根据操作的不同,undo log分为两种:insert undo log 和 update undo log。
1) insert undo log
insert 操作产生的undo log,因为insert 操作记录没有历史版本只对当前事务本身可见,对于其他事务此记录不可见。所以insert undo log可以在事务提交后直接删除而不需要进行purge操作。
purge:主要任务是将数据库中已经 mark del 的数据删除,另外也会批量回收 undo pages。
2)update undo log
update和delete删除产生的undo log都属于同一类型,update可以视为insert 新数据到原位置,delete旧数据,undo log 暂时保留旧数据。
多个事务都更新某条记录的时候,都会把旧的记录数据保存到undo log日志中。因此undo log日志中会存在同一条数据的多条记录的的情况。这也就是同一条记录在数据库中存在多个版本,也就是MVCC。
read-view(视图)
read-view是InnoDB在实现MVCC时用到的一致性视图,用于支持【读已提交】以及 【可重复读】隔离级别的实现。
read-view 不是真实存在的,只是一个概念,undo log才是它的体现。它主要是通过版本和undo log计算出来的,作用是决定事务能看到哪些数据。
问题:当一个新的事务开启后,一个比当前事务id小的事务进行了数据修改提交,那么,当前事务可以读取到比自己事务id小的事务的修改后的最新数据吗?
要回答这个问题,请继续往下看:
每个事务或语句有自己的一致性视图,普通查询语句是一致性读,一致性读会根据row trx_id和一致性视图确定数据版本的可见性。
数据版本的可见性规则:
read-view 中主要包含当前系统中还有哪些活跃的读写事务,在实现上InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在活跃(还未提交的事务) 。
规则如下:
①:事务创建时,已提交的事务对当前事务可见
②:事务创建时,未来开启的事务对当前事务不可见
③:事务创建时,对于早于当前创建的事务,如果在当前事务创建视图后提交,不可见;若在当前事务创建视图前提交,可见。
快照读和当前读
事务的更新操作,是先读后写,这个读是查询的数据行的最新数据。而在事务中的查询语句的读,是从历史版本中查询的,叫做快照读。
1、快照读
查询当前事务可见的历史版本中的数据
2、当前读
事务对记录更新操作,读取到的表中数据的最新值。然后执行更新操作,因为不可能去更新历史版本中的数据,可能已有其他事务先更新了。
select当前读
除了查询语句外,select语句如果加锁也是当前读。
加锁方式如:lock in mode 或 for undate
如:
select age from t where id=2 lock in mode.
select age from t where id=2 for update.
什么是幻读?
指的是当某个事务在读取某个范围内的记录时,会产生幻行,即两次读取的数据行不一致。
原因是,一个事务先读取了某个范围的数量,同时,另一个事务新增了这个范围的数据,再次读取发现两次读取的结果不一致。
MVCC使用快照读解决了部分幻读问题,但是在修改时还会存在幻读问题,幻读最终是通过间隙锁解决的。
什么是间隙锁?
间隙锁是可重复读级别下才会有的锁,结合MVCC和间隙锁可以解决幻读问题。
间隙锁(GAP LOCK):当我们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但并不存在的记录,叫做间隙(GAP),InnoDB也会对这个间隙加锁,这种锁机制叫做间隙锁。
如:user表中存在id为1,3,4,5,6的5条记录,那么这个SQL:
update user set score=’x‘ where id<6;
这条sql使用的是范围作为条件,InnoDB分为会为这条sql加排他锁。不仅对符合条件的记录加了锁,对不存在的记录,id为2的间隙也会加锁。这时候如果插入一条id为2的记录就会阻塞。这就是间隙锁。
//////////////////////////////////////////////////////////// 旧的笔记,仅供参考 START ////////////////////////////////////////////////////////////////
InndoDB拥有一个自增的全局事务ID,每开启一个新的事务,全局事务ID自增1,并且在事务中都会记录当前事务的唯一ID,也称为事务版本号。
InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的。一列保存了创建行 的事务的事务版本号,一列保存了删除行的事务的事务版本号
下面是可重复读隔离级别下,InnoDB实现MVCC的具体操作:
SELECT:InnoDB只查找创建版本号小于等于当前事务版本号 且 删除版本号为空,或者大于当前事务版本号 的记录。
这样可以确保事务读取的行,要么在事务开始前已经存在的,要么是事务自身新增或修改过的
INSERT:InnoDB为新插入的每一行保存当前事务版本号作为行版本号
DELETE:InnoDB为删除的每一行保存当前事务版本号作为删除版本号
UPDATE:删除原来的行插入当前事务版本号作为删除版本号,并插入一条新的记录,保存当前事务版本号作为行版本号
//////////////////////////////////////////////////////////// 旧的笔记,仅供参考 END ///////////////////////////////////////////////////////////////////
END.