Mysql innodb 面试要点记录(二)锁
1.innodb 的锁分类
- 列粒度锁:S 共享锁 X 排他锁
- 意向锁:IS 意向共享锁 IX 意向排他锁
1.1.意向锁:
- innodb 的锁支持多粒度锁定。为了实现多粒度锁,innodb 通过意向锁(IS共享意向锁、IX共享排他锁)的方式实现。
- 在细粒度上加锁,则需要先在粗粒度上加意向锁。比如,如果需要在记录行加锁,则先要在表上加意向锁,最后在行上加上 X/S 锁 。任何一个加锁操作导致的等待都需要等待粗粒度锁释放。
- 由于 innodb 支持的是行级锁,所以意向锁并不会阻塞除扫描全表外的任何请求。
1.2.锁的兼容列表
项目 | IS | IX | S | X |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
2.显式加锁方式:
- 共享锁:SELECT * LOCK IN SHARE MODE
- 排他锁:SELECT * FOR UPDATE
3.一致性非锁定读:
是指 innodb 通过多版本控制的方式来读取当前执行时间数据库中的数据。如果,读取的时候行正好在执行 DELETE 或 UPDATE 的排他操作,那读取操作不会等待,会直接去读取一个快照版本的行数据。
3.1 MVCC(多版本并发控制):
一行数据可能不止一个快照,实际上是通过 undo 段来完成的
3.2 事务隔离级别对一致性非锁定读的影响:
- READ COMMITED:此时,读取的内容是快照中最新的数据版本。
- REPEATABLE READ:此时,读取的内容是读取事务开始时候的数据版本。
- 详解:区别就是,当读取事务开始时候的数据版本,即是获取的读取操作事务开始时候的数据版本,在这个读取事务期间写入的其他内容都不会影响读取版本。READ COMMITED 则会读取被修改后的版本。所以,实际上READ COMMITED 这个事务级别在这种情况下会失去事务的隔离级别。
3.2 一致性锁定读:
则是通过在事务中显式的用 FOR UPDATE 和 LOCK IN SHARE MODE 加锁。当事务提交后锁才会释放。
4.自增长与锁:
- 自增长的锁是特殊处理的,并不是在事务完成后才释放。是在自增长值被 SQL 语句插入成功就会释放。
4.1影响:
- 有自增长值的表因为锁的存在并发插入性能比较差
- 大数据量的插入会影响插入性能,因为另一个事务中的插入会被阻塞。
5.锁的算法:
- Record Lock 单行记录的锁
- Gap Lock 锁定一个范围,但是不包含记录本身
- Next-Key Lock :Gap Lock + Record Lock 锁定一个范围,并且包含记录自身。
5.1 Next-Key Lock 细节:
- 在默认情况下,如果索引非唯一,类似于索引有键1,3,5,7。这样Next-key Lock则会包含这几个范围的锁(-∞,1],(1,3],(3,5],(5,7],(7,+∞)。
- 如果表在建立的时候没有设置索引,innodb 会默认使用主键来进行锁定。
5.2锁的降级:
当索引含有唯一属性时,Next-key Lock会自动降级为Record Lock 用来减少锁定的范围,加大并发的处理速度。但是此种情况只存在于查询【所有的唯一索引列】。如果,唯一索引由多个列组成,而查询是查找多个唯一索引列中的其中一个,那么这种查询由于联合索引的特性,查询是一个范围查询,而不是点查询,所以不会降级处理。
5.3如何规避幻读:
当某个表有2个索引,一个聚集唯一索引,一个辅助索引。有(1,1)(2,3),(3,5),(4,7)这4个数据行。执行SELECT * FROM T WHERE b=5 FOR UPDATE。此时聚集唯一索引因为锁降级的优化,会对a=3加上Record Lock,但是为了避免幻读,辅助索引会有2种锁,第一种是b=5的Record Lock,还有(3,5)(5,7) 2个范围的Grap lock。由于索引 B+tree 的属性,联合索引 a列又在b列之前,所以索引的节点值是
[举例,索引不一定真的是这种结构]
[] (3,5) []
/ \
[(1,1)(2,3)] [(3,5)(4,7)]
如果没有锁定b 列的(5,7)范围我再insert (4,5) 是可以插入进去的,因为a=3和 b=5都被 record lock 锁定了,但是按联合索引的特性是按前序列进行排序插入的,此时并不会触发到b=5的 record lock。这样就会导致幻读,连续执行SELECT * FROM T WHERE b=5 FOR UPDATE这个语句上一个事务的结果与下一次执行的这个语句得到的结果不会一样。
- oracle 数据库必须要在事务级别是 SERIALIZABLE 级别才能避免脏读,但是 innodb 通过这种范围锁的骚操作,直接在REPEATABLE READ以上的事务级别,通过使用Next-key lock的锁级别,避免了幻读
6.锁问题:
mysql innodb 的默认事务级别是 REPEATABLE READ
- 脏读:一个事务读取到了另外一个事务没有提交的数据(也称作脏数据)。破坏了数据库的隔离性
- 不可重复读(幻读): A事务里面先读取了部分数据,然后B事务对读取的这部分数据做了操作,然后A 事务又来读取这部分数据,则会出现 A 事务的两次对同一部分数据获取的结果不同。违反了数据库的事务一致性要求。
- 丢失更新:A 事务对 a 列进行了修改为 2 未提交时,B 事务更新 a 列为 3,未提交,此时 A B 两个事务提交。必将导致结果不是预期的。由于排他锁的介入,实际上数据层面不会发生这种操作,因为 A 事务在写的时候记录是被锁住的,其他事务是会被阻塞的。所以,这种情况会发生在应用数据库的更上层。如果需要在数据库层面解决这种问题,在读取记录的时候也需要加上 X 锁,在操作完成后再释放,此时可以避免不可重复读。
7.阻塞和死锁
略过,可以在其他层面理解。目前还没有深入了解的打算
8.锁升级:
由于 innodb 的锁根据页粒度进行加锁,并采取位图的方式,锁资源开销较小,不存在锁升级的操作。