1、MySQL采用MVCC方式处理读并发,采用LBCC处理写并发,为什么要用这种方式呢?
-
只要涉及到并发,那么一定离不开锁。咱们在了解MVCC之前,先看看MySQL有哪些锁。锁的知识大家看看这位大牛的总结吧(偷个懒)
-
知道了有哪些锁,那么我们就使用InnoDB引擎,看看并发场景下MySQL是怎么使用锁的。(book表主键索引为bookid)
-
事务A未提交,由于是update操作,而且bookid是主键,所以会给 bookid=16 这行记录(聚簇索引)上加X锁。奇怪的事情发生了,事务B竟然可以读到 bookid=16 这行记录,不是加了X锁吗?怎么没有被阻塞呢?
-
要解答上面的问题,MVCC就要闪亮登场了。先给大家介绍一下,MVCC的几个重要组成元素: undo log、read view、数据表的三个隐式字段(db_trx_id、roll_pointer、del_flag);
-
不着急,我们一个一个来,我们新插入一条数据瞧瞧数据表的隐式字段
-- 首次在数据库插入这条记录,在数据库中会以下表形式保存,有3个隐式字段
-- 事务1
begin;
insert into t_user values(1,'王小虎',20);
commit;
--事务2处理逻辑与id=1这条记录无关
--事务3
begin;
update t_user set name='王小' where id=1;
commit;
步骤 | id | name | age | db_trx_id(最近修改的事务id) | roll_pointer(回滚指针) | del_flag(删除标记) |
---|---|---|---|---|---|---|
1 | 1 | 王小虎 | 20 | 1 | null | null |
- | - | - | - | - | ↑ | - |
2 | 1 | 王小 | 20 | 3 | 0x198452(是上一步操作-步骤1中这条记录在undo log的地址值) | null |
- 然后再来说说read view
1、快照读-触发时机:
select
2、当前读-触发时机:
select ... for update、
select ... lock in share mode、
insert、delete、update
3、read view就是快照读的时候产生的读视图,它包含下面3个元素:
trx_list:就是select执行的瞬间,对数据库中当前还未提交的事务做了一个快照,它保存的是此刻所有还未提交事务id的集合;
low_limit_id:下一次产生read view时的事务id;
up_limit_id:trx_list中,事务id的最小值;
- undo log是回退日志。
- 知道了上面这些知识,我们现在就开始正式说说这三者是如何协同实现的MVCC(多版本并发控制)
- 先在数据库插入一条记录
-- t_user(id,name,age),其中id是主键
insert into t_user values(1,'王小虎',20);
然后分别开启3个事务
步骤 | 事务5 | 事务6 | 事务7 |
---|---|---|---|
一 | 开启事务 | 开启事务 | - |
二 | update t_user set name=‘王小’ where id=1; | select * from t_user where id<10; | 开启事务 |
三 | - | - | insert into t_user values(2,‘秋红叶’,25); |
四 | 提交事务 | - | 提交事务 |
五 | - | select * from t_user where id<10; | - |
一、MVCC流程(只分析事务6,隔离级别为RR):
1、 步骤二触发快照读,开启read view,此时read view 中各参数为 trx_list={5,6,7},low_limit_id=8;up_limit_id=5;
2、然后根据如下逻辑进行判断:id=1这条记录最近是被事务3处理过,所以db_trx_id=3
// 数据库保存的事务早于当前活跃的事务,当前事务能直接访问此记录
if (db_trx_id < up_limit_id) {
return true;
}
// 当前活跃的事务还未提交,但是外面有事务已经提交并保存到了数据库,当前活跃的事务无法访问这条记录
if (db_trx_id >= low_limit_id) {
// 去undo log日志链重新递归查询
return false;
}
// 开启read view时,当时活跃的事务之间是不能相互访问的
if (trx_list.contains(db_trx_id)) {
// 去undo log日志链重新递归查询
return false;
} else {
return true;
}
3、由上述逻辑可知,db_trx_id(3)< up_limit_id(5),所以事务6的步骤二能查询到记录。
4、事务6到步骤五时,read view与步骤二一样,所以它无论如何都不会读到事务7的记录,不会发生幻读。
5、如果是RC级别,此时的read view元素内容为:
trx_list={6},up_limit_id=6,low_limit_id=8;
我们分析一下此时它的查询逻辑:
db_trx_id(7)> up_limit_id(6)
→ db_trx_id(7)< low_limit_id(8)
→ trx_list{6}不包含db_trx_id(8),
所以能访问到id=2这条记录,从而发生了幻读!
二、理解当前读与快照读
1、快照读,相当于抓拍,你加不加锁与我无关,提高了读写并发
2、当前读是LBCC,基于锁的并发控制
2、LBCC大家移步看这位大神的介绍吧
3、事务的4大特性
- Atomicity 原子性
- Consistency 一致性
- Isolation 隔离性
- Durability 持久性
4、事务的4种隔离级别
- Read Uncommitted 读未提交
- Read Committed 读已提交
- Repeatable Read 可重复读(InnoDB存储引擎默认的级别)
- Serializable 串行化
5、不同事务级别带来的并发问题
- 脏读:事务A读到了事务B还没提交的记录;
- 不可重复读(update时会出现):事务A读取 id=1 的记录时,事务B修改并提交该条记录后,事务A再次读取,会与第一次的结果不一致;
- 普通幻读(insert、delete时会出现):事务A读取 id between 1 and 10 的 10 条记录时,事务B删除一条 id=6 的记录并提交后,事务A再次读取,只能读取到 9 条记录;
- 特殊幻读:事务A读取 id between 1 and 10 的 9 条记录时(数据库当前只有1-5,7-10这9条数据),事务B插入一条 id=6 的记录并提交后,事务A也插入一条 id=6 的记录,会报错主键冲突;