本文共 1821 字,大约阅读时间需要 6 分钟。
MVCC即多版本并发控制,是InnoDB自带的一种机制,其为表格添加了三个列,如下
DB_ROW_ID | DB_ROLL_PTR | DB_TRX_ID | col1 |
---|---|---|---|
隐藏的行id,当没有合适的列可以创建索引时,使用此列创建索引 | 回滚指针,指向Undo Log中的上一版本数据的地址 | 记录当前事务的id |
其中DB_TRX_ID和DB_ROLL_PTR是关键,下面举例说明,
当一个事务A开启后,在RR隔离级别下,只在第一次调用select时会获得一个表格的Read View,其包括四个属性
那么在开启事务A后,执行
SELECT
会根据Read View对数据进行过滤,其可以读取到的数据有如下部分
除上面提到的,都不可以select到,如trx_ids中除了当前事务号插入的数据,因为这些事务仍然活跃且未提交
写
包括update、insert、delete,都会将对应数据行的DB_TRX_ID赋值为当前事务版本号
MVCC机制下,select读取数据是不需要加锁的,其读取的是快照中的数据,包含事务开启前的数据以及当前事务添加的数据
也即insert、update、delete这三种方式,其会对访问数据加锁
见下面例子
事务A | 事务B |
---|---|
begin; | |
begin; | |
select * from test; 🎈 | |
insert into test(id,name) values(3,'kk'); | |
select * from test;与🎈处查询结果一致 | |
commit; | |
insert into test(id,name) values(3,'kk'); 报错!因为此数值已经出现在表格中,但是由于查询时遵循MVCC的机制,因而不能查询出未提交事务的结果;如果在事务Bcommit之前发生,则会被阻塞 | |
select * from test;//可以查询到事务B插入的值 |
从上可见,由于insert操作属于当前读,事务A在Bcommit前进行insert时,会被阻塞,但是select不会被阻塞
那么,在事务A中进行insert这个当前读操作就发生了幻读,因为这和select读取的结果不一致
从MVCC的机制可知,其不能解决幻读,InnoDB引入了Next-Key Lock解决了此问题(关键在于上锁)
锁定一个记录上的索引,而不是记录本身
锁定索引之间的间隙
假设select * from test where id between 10 and 20 for update;
那么id从10到20的数据就会被锁住,尽管10-20之间可能没有数据
首先会对指定的行的索引上锁,且会对索引之前所在的gap上锁
假设在事务A中执行select * from test where id=3 for update;且假设当前表格中有id为1,5,10
那么,有如下情况,如果id为
主键(UNIQUE)
不会触发gap lock
普通索引(UNUNIQUE)
会锁住id为(1,5]的区间
不是索引列
那么会对(-∞,+∞)上锁
MySQL下的这个隔离级别解决幻读的方式很悲观,其会对事务中每条sql涉及到的资源进行上锁,这样就可以阻塞其他事务对数据的操作,从而使得事务可以串行化的执行
可见,这种方式的效率是很低的
一般情况下,在RR隔离级别时,可以通过使用Next-Key Locks的方式对敏感数据上锁,即可保证一定区域内幻读的解决
触发Next-Key Locks有select for update和当前读操作
转载地址:http://kasyz.baihongyu.com/