目录
- 前置知识
- 实例分析加锁情况
todo 结合参考文章0和参考文章1重新梳理下, ACID, explain分析
- 深入篇可以总结下https://www.jianshu.com/p/945d391f9ef0
- redo log undo log
- orderby limit为什么有时候返回值不同,因为非稳定性算法比如快排排序,orderby的列不唯一,加个唯一列比如id就好
- mvcc在select insert(事务2) update select不能解决幻读RR级别下,可以找下资料总结
前置知识
MVCC多版本并发控制
- MVCC(Multi-Version Concurrency Control): 读不加锁,读写不冲突。同一份数据临时保存多个版本的一种方式,进而实现并发控制。 MVCC分两类
- 快照读: 读取的是记录中的可见版本(可能是历史版本, 可以简单理解为每条记录多一个version字段,每次更新都会操作这个version。),不用加锁。
- 当前读(特殊的select操作、insert、delete和update):读取的是记录中最新版本,并且当前读返回的记录都会加上锁,这样保证了了其他事务不会再并发修改这条记录。
聚集索引
- 完整的记录,储存在主键索引中,通过主键索引,就可以获取记录中所有的列。非聚集索引就需要回表查数据。
隔离级别
- Read Uncommitted:可以读取未提交记录。存在脏读,不可重复读,幻读现象。此隔离级别一般不会使用。
- Read Committed(RC): 解决了脏读,存在不可重复读,幻读现象
- Repeatable Read(RR): 解决了不可重复读,存在幻读现象。可重复读主要是针对其他事务的update与delete不会对本事务产生影响,而其他事务的insert操作对本事务的查询产生影响则称为幻读。
- Serializable:从MVCC并发控制退化为基于锁的并发控制。
InnoDB中完整行锁
- 记录锁(Record Lock):记录锁锁定索引中的一条记录
- 间隙锁(Gap Lock):间隙锁要么锁住索引记录中间的值,要么锁住第一个索引记录前面的值或最后一个索引记录后面的值。
- Next-Key Lock:Next-Key锁时索引记录上的记录锁和在记录之前的间隙锁的组合。
实例分析加锁情况
/**
* table T (id int, name varchar(20));执行的是delete语句,下面展示了RR避免可重复读的方式
* 如果是insert语句,则不同事务有可能出现幻读可以通过for update形式避免,for update在
* insert形式无数据情况下会加gap锁, 实例分析参考参考文章2.InnoDB Gap锁
**/
delete from T where id = 10;
id为主键 + RC/RR
- id是主键时,执行该语句时,只需要将主键id = 10的记录加上写锁
id唯一索引 + RC/RR
- 由于id是唯一索引,因此delete语句会选择走id列的索引进行where条件过滤,在找到id = 10的记录后,首先会将唯一索引上的id = 10的记录加上X锁,同时,会根据读取到的name列,回到主键索引(聚簇索引),然后将聚簇索引上的name 对应的主键索引项加写锁。唯一索引只有一条记录相对应,所以查询只会返回一条记录。
id不唯一索引+RC
- 若id列上有非唯一索引,那么对应的所有满足SQL查询条件的记录,都会加上锁。同时,这些记录在主键索引上也会加上锁。
id无索引+RC
- 若id列上没有索引,MySQL会走聚簇索引进行全表扫描过滤。由于是在MySQl Server层面进行的。因此每条记录无论是否满足条件,都会加上X锁,但是,为了效率考虑,MySQL在这方面进行了改进,在扫描过程中,若记录不满足过滤条件,会进行解锁操作。
id不唯一索引+RR
-
在RR隔离级别下,id列上有非唯一索引,对于上述的SQL语句;首先,通过id索引定位到第一条满足条件的记录,给记录加上X锁,并且给其Gap加上Gap锁,然后在主键聚簇索引上满足相同条件的记录加上X锁,然后返回;之后读取下一条记录重复进行。直至第一条出现不满足条件的记录,此时,不需要给记录加上X锁,但是需要给其Gap加上Gap锁吗,最后返回结果。
id无索引+RR
- 在Repeatable Read隔离级别下,如果进行全表扫描的当前读,那么会锁上表上的所有记录,并且所有的Gap加上Gap锁。
- 当然在MySQL中,可以触发 semi-consistent read来缓解锁开销与并发影响,但是semi-consistent read本身也会带来其他的问题,不建议使用。