并发问题,
丢失修改,不可重复读,幻读,脏读
- 不可重复读
有个事务T,T读了两次数据,在两次中间有人update/delete了数据,导致T两次读的结果不一致 - 幻读
(在高性能mysql里把这个归为不可重复读)同上读了两次,中间有人insert了一条数据,导致两次结果不一致 - 脏读
即读到其他事务未提交的数据,读到其他事务回滚的数据 - 丢失修改
两个事务先后读改再提交,导致最终结果错误(?)
事务隔离级别有四种,RU,RC,RR和SI
三段锁协议
一段锁:在改前加X锁,T完释放,可解决丢失修改(不可以啊?)
二段锁:读前S锁,读完释放,解决脏读
三段锁,读前S锁,T完释放,解决不可重复读
以上是理论,实际InnoDB的隔离级别的实现与这些大不相同,很多文章也提到了,ANSI规定的四种隔离级别会有漏洞,所以oracle与mysql实际的事务隔离级别的实现与它规定的隔离级别不同
mysql的实现
RC
在RC和RR下都用到了MVCC.
MySQL的读已提交实际是语句级别快照,即快照读的时候读的总是最新的快照.读的策略与可重复读类似。
RC写锁的使用方式。这里不需要GAP LOCK,只使用记录锁(所以会幻读)。并且事务只持有被UPDATE/DELETE记录的写锁(可重复读需要保留全部写锁直到事务结束,而读已提交只保留真正更改的)。
RR
RR下也用MVCC,和RC的区别是它读的是事务开始时的快照.
RR下默认读不加锁而是用快照读,只有写才加锁,读写互不阻塞,但会有Write Skew异常(TODO)。
RC和RR差别是:RC隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView
在RR下一般下select默认是快照读,除非用select for update 和select in share lock
- 其中select in share lock是用行锁,
- select for update是用间隙锁,即next lock算法,
- 快照读,能保证read和update操作能并行,即用MVCC存储每个事务的多个版本,故名字为多版本控制
如果是快照读,可重复读级别下不存在幻读;如果先用快照读又用select...from...for update(locking read)读就存在幻读;如果两次查询都是select...from...for update不会幻读。
快照读,实际上读的是undo log的快照.(undo log具体结构TODO)
next-key lock
next-key锁=行锁+间隙锁, 来解决幻读,是谓词锁predict lock的改进(?)
还有用previous lock(TODO)
next-key lock会在select for update触发,锁定[)左闭右开的旁边两个值范围,左闭右开是因为自增操作(不是很理解?)
record lock会锁索引,没有索引则锁隐式的主键
对于唯一索引,InnoDB会把next-key lock降级为Record lock!!
即:若是没有索引的条件下,就获取所有行,都加上行锁,然后Mysql会再次过滤符合条件的的行并释放锁(这什么鬼,unique怎么会没索引),只有符合条件的行才会继续持有锁。这样性能也会消耗很大,
例子:
RR下,对于select a from xx where a<4 and a>3 for update的时候,假如数据有1,2,3,4,5,6,就会触发next key lock会把[3,4)和[2.3)锁上,注意锁的对象是对应索引而不是表,而这里左闭右开的原因网上不统一,好像在哪看到过说是为了方便一个auto_increment自增操作(好像是深入InnoDB那本书里?)
对于select * for update会上表锁
两段锁
两阶段锁协议的定义:
在 InnoDB 事务中,行锁是在需要的时候才加上的(即手动调用select in share lock),但并不是不需要了就立刻释放,而是要等到事务结束时才释放。
既然有了两阶段锁协议的存在,那么如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放,这样可以在一定程度上减少死锁出现的概率(这是什么鬼)。
MVCC
在多版本存储上,MySQL采用从新到旧(Newest To Oldest)的版本链。
B+Tree叶结点上,始终存储的是最新的数据(可能是还未提交的数据)。
而旧版本数据,通过UNDO记录(做DELTA)存储在回滚段(Rollback Segment)里。
每一条记录都会维护一个ROW HEADER元信息,存储有创建这条记录的事务ID,一个指向UNDO记录的指针。通过最新记录和UNDO信息,可以还原出旧版本的记录。
用白话文说,每一行数据会有多种版本,而旧版本信息是存在undo log里的,要用的时候通过对应的undo log来推导出来
提一下乐观锁,这里innoDB的MVCC和乐观锁的关系可以看一下参考资料4,是个好文
可串行化(Serializable)
在可串行化级别上,MySQL执行S2PL并发控制协议, 一阶段申请,一阶段释放。读写都要加锁。
参考资料
- 知乎的MVCC
- CNblog的next key lock
- undo_log
- MVCC和乐观锁
- https://blog.youkuaiyun.com/qq_25827845/article/details/91345678说了两段锁
- https://zhuanlan.zhihu.com/p/117452178这些是redo log和其他内容,其他内容没看
TODO
next lock算法细则next key lock会锁定多大的范围呢间隙锁和next lock区别,next lock=行锁+间隙锁吧next key lock普通的select都会锁定这个范围吗,RR级别 select不用for update 会用next-key lock吗write skew产生的幻读,知乎的解释ReadView是什么?MVCC和快照的实现,里面也有undo和readview关系数据库面经- 各种冲突画时间图来巩固下
- snapshot(readview)和可见性的判断
- [数据库并发](https://zhuanlan.zhihu.com/p/139697896)
- https://www.cnblogs.com/flythinking/p/8514133.html这里的各种隔离界别的实验抄下来
- https://zhuanlan.zhihu.com/p/75673270锁机制,多种情况猜测用什么锁
- 这里的MVCC讲的比较详细https://blog.youkuaiyun.com/qq_25827845/article/details/91049389
- undo log的格式,readview是什么,readview的可见性又是啥,https://juejin.im/post/5dfc846051882512327a63b6这里面有讲readview格式