MySQL-数据库锁及InnoDB的锁
数据库锁
基本概念
加锁的目的
数据库的锁是为了解决事务的隔离性问题,为了让事务之间相互不影响,每个事务进行操作的时候都会对数据加上一把特有的锁,防止其他事务同时操作数据。
锁是基于什么实现的
在Innodb中,锁是作用在索引上面的,当我们的SQL命中索引时,那么锁住的就是命中条件内的索引节点(行锁),如果没有命中索引的话,那我们锁的就是整个索引树(表锁)
锁的分类
- 基于锁的属性分类:共享锁、排他锁。
- 基于锁的粒度分类:行级锁(innodb)、表级锁(innodb、myisam)、页级锁(innodb引擎)、记录锁、间隙锁、临键锁、自增锁。
- 基于锁的状态分类:意向共享锁、意向排它锁。
- 基于加锁的态度分类:悲观锁、乐观锁。
InnoDB的锁
隐式锁定和显式锁定
- 隐式锁定
InnoDB在事务执行过程中,使用两阶段锁协议(不主动进行显示锁定的情况)- 随时都可以执行锁定,InnoDB会根据隔离级别在需要的时候自动加锁;
- 锁只有在事务执行commit或者rollback的时候才会释放,并且所有的锁都是在同一时刻被释放。
- 显式锁定
InnoDB也支持通过特定的语句进行显示锁定(存储引擎层)1
2select ... lock in share mode //共享锁
select ... for update //排他锁
锁类型
1. 基本锁
在一个高并发系统中,会出现多会话同时访问同一资源的情况,此时即产生了竞争。
为了保证数据的一致性,必须要用锁机制来控制资源的并发访问。
InnoDB采用的行锁的设计(MyISAM只支持表锁),行锁带来更高的并发性,但管理复杂度也要比表锁更高。
InnoDB共实现了2种标准的行级锁:
- 共享锁(S Lock),允许持有锁的事务读取数据。
- 排它锁(X Lock),允许持有锁的事务修改和删除数据。
2. 意向锁 Intention locks
InnoDB存储引擎支持多粒度的锁定,即允许行级锁和表级锁同时存在。
- 意向共享锁(IS lock),表示事务想要获得表中某几行的共享锁。
- 意向排它锁(IX lock),表示事务想要获得表中某几行的排它锁。
意向锁是一种表级锁,表示事务稍后希望在更细的粒度上(行级别)加锁。
- select … lock in share mode; 会对表施加IS锁
- select … for update; 会对表施加IX锁
X | IX | S | IS | |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
例如,如果请求在记录r上加X锁,则需要先在表级别上加意向排他锁(IX),如果此时表上存在其他锁,则意向锁需要等待表级别锁的释放,待表级意向锁(IX)获得成功后,才可以对行级别加X锁。
意向锁只会阻塞表级别的请求(如全表扫描、lock tables … write),除此之外不会阻塞任何操作。
3. 行锁/记录锁 Record locks
InnoDB行锁是通过给索引上的索引项加锁来实现的,而不是给表的行记录加锁实现的,这就意味着只有通过索引条件检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁。
由于InnoDB的行锁实现是针对索引字段添加的锁,不是针对行记录加的锁,因此虽然访问的是InnoDB引擎下表的不同行,但如果使用相同的索引字段作为过滤条件,依然会发生锁冲突,只能串行进行,不能并发进行。
即使SQL中使用了索引,但是经过MySQL的优化器后,如果认为全表扫描比使用索引效率高,此时会放弃使用索引,因此也不会使用行锁,而是使用表锁。
例如:select col from t where col=1 for update;
即对col为1的记录添加记录锁,阻止其他事务对此记录的操作。
4. 间隙锁 Gap locks
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。
例如:select col from t where col between 1 and 10 for update;
会锁住1~10之间的间隙,而不管这段间隙内是否存在记录,因此间隙锁可能只锁住了一段空气。此时如果想插入为col为5的记录会被阻塞,即使5的记录不存在。
间隙锁的唯一目的即是阻止其他的事务往间隙中插入记录,因此不同的事务可以对同样的间隙重复加锁,没有共享和排他类型之分。
InnoDB自动使用间隙锁的条件
可重复读 Repeatable Read
级别下才会有间隙锁。必须在RR级别下。- 检索条件必须有索引。(没有索引的话,mysql会全表扫描,那样会锁定整张表所有的记录,包括不存在的记录,此时其他事务不能修改不能删除不能添加)
1 | # 打开间隙锁设置 |
5. 临键锁 Next-Key locks
行锁与间隙锁组合起来用就叫做Next-Key Lock。
InnoDB默认加锁方式是next-key
锁。
next-key lock
会对记录本身和记录之前的区间加锁。
A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.
如果一个会话占有了索引记录R的共享/排他锁,其他会话不能立刻在R之前的区间插入新的索引记录。
If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.
例如:假设表中存在索引记录1,10。则next-key lock可能锁住的范围是:(-∞,1]、(1,10]、(10,+∞),对于最后一个区间,next-key lock也会锁住最大记录之后的间隙。
Next-key lock
只在MySQL InnoDB
的*repeatable read
隔离级别下使用,主要是用来解决幻读(phantom read)
的问题。
当对唯一键值进行锁定时,查询的索引含有唯一属性,next-key lock将会降级为record lock,即仅锁住唯一记录。
而如果唯一键由多个列组成,而查询仅使用其中一列,则其实是range查询,InnoDB会依然使用next-key lock进行锁定。
6. 插入意向锁 Insert intention locks
插入意向锁是间隙锁的一种,其由insert语句在插入记录前获取,代表将在间隙中插入记录的意向。
多个事务可以对同一个间隙重复加insert intention lock,只要插入的记录值不同,事务就不会冲突。
例如表中已存在记录1和10,两个事务分别想插入5和6。两个事务都会对1和10记录之间的间隙(2,9)加insert intention lock,但由于插入的记录值不同,因此后续对要插入的记录获取X锁的时候并不会冲突。
7. 自增锁 auto-inc locks
自增锁是一种特殊类型的表锁,只要在事务对auto_increment类型的列插入数据时,才会施加auto-inc lock,此时其他想插入的事务都需要等待该锁的释放,而持有该锁的事务可以获得连续的primary key值。