MySql InnoDB锁类型
从类型上来分类,InnoDB存储引擎实现了两种标准的锁
-
共享锁(S-Lock):允许事务读一行数据
-
排它锁(X-Lock):允许事务删除或者更新一行数据
如果一个事务获取了S锁,那么其他事务也可以立即获得S锁,但如果要对记录加X锁,必须等待该记录的所有锁(S或X)释放后才能加成功。如下表所示为S和X锁的兼容性
— | X | S |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
MySql支持多粒度的锁定操作,这就允许事务在表级和行级上的锁同时存在。为了支持在不同粒度上的加锁操作,InnoDB支持了额外的一种锁,意向锁(Intention Lock)。意向锁是将锁定的对象分为多个层级,意味着事务希望在更细粒度上进行加锁。
下边是一个网友介绍的意向锁的作用:
innodb的意向锁有什么作用? mysql官网上对于意向锁的解释中有这么一句话 “The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.” 意思是说加意向锁的目的是为了表明某个事务正在锁定一行或者将要锁定一行。 那么,意向锁的作用就是“表明”加锁的意图,可是为什么要表明这个 意图呢? 如果仅仅锁定一行仅仅需要加一个锁,那么就直接加锁就好了,这里要表明加锁意图的原因是因为要锁定一行不仅仅是要加一个锁,而是要做一系列操作吗? 作者:尹发条地精 我最近也在看这个,我说一下我的理解 ①在mysql中有表锁,LOCK TABLE my_tabl_name READ; 用读锁锁表,会阻塞其他事务修改表数据。LOCK TABLE my_table_name WRITe; 用写锁锁表,会阻塞其他事务读和写。 ②Innodb引擎又支持行锁,行锁分为共享锁,一个事务对一行的共享只读锁。排它锁,一个事务对一行的排他读写锁。 ③这两中类型的锁共存的问题考虑这个例子: 事务A锁住了表中的一行,让这一行只能读,不能写。之后,事务B申请整个表的写锁。如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。 数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。 数据库要怎么判断这个冲突呢? step1:判断表是否已被其他事务用表锁锁表 step2:判断表中的每一行是否已被行锁锁住。 注意step2,这样的判断方法效率实在不高,因为需要遍历整个表。 于是就有了意向锁。在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。在意向锁存在的情况下, 上面的判断可以改成 step1:不变 step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。 注意:申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。 总结:为了实现多粒度锁机制(白话:为了表锁和行锁都能用)
意向锁分类:
-
意向共享锁(IS):事务想要获取一张表中某几行的共享锁
-
意向排它锁(IX):事务想要获取一张表中的某几行的排它锁
意向锁的兼容性:
— | IS | IX | S | X |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
一致性非锁定读
一致性非锁定读是指InnoDB存储引擎通过多版本控制的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行DELETE或者UPDATE操作,这时读取操作并不会因此等待该行的X锁释放,而是读取一个快照数据。
当然在不同的事务隔离级别下,非一致锁定读读取的快照顺序也是不一样的。
-
READ COMMITTED:读取的快照数据永远是最新的一份快照数据
-
READ REPEATABLE:读取的快照是当前事务开始时的快照数据
一致性锁定读:
在数据库隔离级别为RR级别下,select操作使用了一致性非锁定读。在某些情况下,用户需要显示地对读操作进行加锁以保证数据的一致性逻辑。一般来说会使用以下两种方式:
-
select … for update
-
select … lock in share model
其中select … for update会给选择的行加一个X锁,select … lock in share model会加一个S锁。并且这两种用法必须在一个事务中。当事务提交后锁也就释放了。
锁的算法
InnoDB有3中锁的算法
-
Record Lock:在单行上加锁
-
Gap Lock:间隙锁,锁定一个范围,但不包含当前记录
-
Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并包含当前记录。
对于Next-Key Lock,其解决的是数据库的幻想问题,对于一个索引包含10,11,13,20的数据,那么Next-Key Lock可以锁定的区间为:
(-无穷,10],(10,11],(11, 13],(13, 20],(20, +无穷)
当查询的索引含有唯一属性时,InnoDB会对Next-Key Lock进行优化,将其降级为Record Key,只是锁住索引本身,并不会锁范围。
但对于非聚集索引,则会在非聚集索引上加Next-Key Lock
对于select * from z where b = 3,则会在(1, 3]上加锁,同时会在该记录后边加一个gap lock,即(3, 6),如果执行以下两个语句都会造成堵塞。
由此可见gap 锁是为了解决数据库的幻读现象。
对于唯一键的锁定,Next-Key Lock会降级为Record Lock的前提仅限于查询所有的唯一索引列。若唯一索引有多个列组成,而查询的是多个索引中的一个,那么查询仍然是range查询,而不是point查询,仍然使用Next-Key Lock进行锁定。
幻读问题
上图所示为幻读现象,A在同一个事务中读取的数据不一致,gap lock则解决了这个问题。在RR隔离级别下,gap锁会为(2, 无穷)加X锁,这样会话B插入4时就会阻塞。
锁存在的问题
脏读
在READ uncommitted隔离级别下,事务B可以读取A未提交的数据,如果A rollback,则B两次读取的数据将不一致。而在READ Committed隔离级别下,事务读取的是记录最新提交的快照,因此可以避免脏读现象
幻读
在READ Committed隔离级别下,当使用范围查询时会出现两次读取的数据不一致,因此造成幻读现象,在READ REPEATABLE级别下,由于Gap锁的作用可以避免幻读。