Mysql中的表锁和行锁
Mysql为了解决事物并发执行导致的一些列为题,引入了锁,在InnoDB
存储引擎中,锁分为表锁
和行锁
两类。
1. 共享锁和独占锁(S和X)
1.1 共享锁(S锁)
共享锁也叫S锁
,S锁与S锁是兼容关系,不会被阻塞,S锁与X锁是不兼容的,会被阻塞。
加S锁读取数据:
SELECT ... LOCK IN SHARED MODE;
当在读取到的记录上加S锁,此时允许其他事物继续获取这些记录的S锁,但是不能获取X锁,会被阻塞,直到记录的S锁被释放为止。
1.2 独占锁(X锁)
独占锁也叫X锁
,X锁和X锁是不兼容的,会被阻塞;X锁与S锁也是不兼容的,会被阻塞。
加S锁读取数据:
SELECT ... FOR UPDATE;
会为读取到的记录加X锁,此时不允许其他事务获取这些数据的S锁和X锁。如果其他事物想获取这些记录的S锁或X锁,会被阻塞,直到记录的X锁释放为止。
其实对于写操作来锁,基本是上都是加的X锁,例如UPDATE
和DELETE
:
DELETE
在进行数据删除时,会先获取对应记录的X锁,然后执行删除操作,
UPDATE
在进行数据修改时,在没有修改记录的主键的情况下,也是会先定位到对应的记录,然后获取记录的X锁,然后执行数据更新操作(这里的更新操作可能是将记录放到删除链上,再插入一条新记录;也可能是对修改列的数据进行修改。当被修改的列占用的存储空间发生变化时,前者的情况会发生);如果修改了主键,就会执行DELETE和INSERT操作。
INSERT
进行数据插入时,一般是不会创建任何锁结构的,如果发生事物并发执行冲突时,会创建隐式锁进行处理。
2. 表锁
大概有以下几种:
- 表级S锁和X锁,分别表示表级别的共享锁和独占锁
- 表级意向锁,分为IS意向共享锁和IX意向独占锁
- 自增锁,还可以再分为两种锁
- AUTO-INC锁,在insert语句执行完成后会释放锁
- 轻量级锁,在生成值后会释放锁
2.1 表级S锁和X锁
一般是用不到表级S锁和X锁的,当然我们也可以手动获取表级S锁和X锁。
这里的READ
对应的就是S锁,WRITE
对应的是X锁。
LOCK TABLES 表名 READ;
LOCK TABLES 表名 WRITE;
2.2 意向锁
意向锁主要是用来表示一个加锁的状态的,目的是当其他事物加表锁的时候,可以快速知道当前表中有没有记录被加锁了。比如向表中的一条记录加S锁时,会先在表级别加一个IS锁,然后对记录加S锁。当下一个事务过来加表锁的时候,就可以通过判断表是否意向锁来确定表中是否有记录被加锁,从而避免了每次在加表锁的时候还要去遍历每条记录看是否加锁了。
2.3 自增锁
被AUTO_INCREMENT
修饰的列执行插入操作时,会使用自增锁,生成对应的列的值,然后将数据插入到表中。自增锁主要分为以下两种:
AUTO-INC锁
,在执行插入语句时,会加一个表级别的锁,然后为对应的列生成值,最后插入数据并释放自增锁。一般在无法预计插入数据条数时,会使用该方式进行自增列的值生成。轻量级锁
,在执行插入语句时,会获取自增锁,生成对应的列的值,然后释放锁,最后插入数据。一般在确定插入数据条数时,会使用该方法进行自增列值的生成。
3. 行级锁
行级锁是用来给表中的记录进行加锁的,与表级锁相比,锁的粒度更细,能提供更好的并发性能,但是会占用更多的资源。
3.1 记录锁(LOCK_REC_NOT_GAP)
记录锁锁定的是一条记录,有S锁和X锁之分,如果是给一条记录加了X记录锁,那么其他事物将无法获取该记录的X记录锁和S记录锁;如果给一条记录加了S记录锁,那么其他事物只能对该记录加S记录锁,不能加X记录锁。
锁定id为5的这条记录加LOCK_REC_NOT_GAP
^
|
|
_______________
| 1 | 3 | 5 | 8 |
|---------------|
| c | d | a | b |
---------------
3.2 间隙锁(LOCK_GAP)
间隙锁锁定的是两个相邻记录中间的间隙,目的是锁定相邻记录的间隙,方式数据插入,Mysql中正是使用了这种方式进行加锁防止幻影行的插入。如下图所示,给ID为5的数据加间隙锁,会锁定ID为5的记录前面的间隙。间隙锁也分S锁和X锁,但是间隙锁的S锁和X锁的效果是一样的。
LOCK_GAP
只是为了防止幻影行的插入,所以加了间隙锁,并不影响其他事物为该记录加记录锁和间隙锁。
锁定id为5的这条记录加LOCK_GAP
^
|
|
_______________
| 1 | 3 | 5 | 8 |
|---------------|
| c | d | a | b |
---------------
3.3 Next-Key锁(LOCK_ORDINARY)
Next-Key锁可以看作是记录锁和间隙锁的合体,Next-Key锁会锁定记录前面的间隙和该记录
锁定id为5的这条记录加LOCK_ORDINARY
^ ^
| |
| |
_______________
| 1 | 3 | 5 | 8 |
|---------------|
| c | d | a | b |
---------------
3.3 意向插入锁(LOCK_INSERT_INTENTION)
当记录插入时,要入位置被其他事物加了间隙锁(Next-Key锁也包含间隙锁),插入操作的事物会生成一个插入意向锁,然后进入等待状态等待间隙锁释放。
3.4 隐式锁
当一个事物进行记录插入操作,本身插入操作是不会创建任何锁结构的,但是此时遇到了另外一个事物在需要对该记录进行加锁(X锁或者S锁),此时就可能会出现脏读/写的情况,因为插入操作本身没有进行任何加锁,如果此时插入操作的事物没有提交,第二个事务如果锁定了该记录,进行了读或者写操作,就会出现脏读/写。
如果想避免脏读/写,正确的操作应该是先判断插入操作的事务是否提交了,如果提交了第二个事务就可能正常读/写该记录,如果没有提交,第二个事务就需要进入等待状态,此时第二个事务就会为插入操作的事物创建一个X锁,为当前事物本身(第二个事务)也创建一个锁结构,然后进入等待状态,此时第一个事物还在执行,第二个事务进入了阻塞状态,两个事物都对应一个锁结构。
4. 锁结构
锁所在的事物信息
指向事物信息的指针索引信息
记录行级锁加锁的索引表/行锁信息
表锁信息
行锁信息
Space ID
记录所在的表空间Page Number
记录所在的页号n_bits
表示使用了多少个尾部的bit位
type_mode
记录的是锁相关的信息还有等待状态lock_mode
锁模式,占用低4位LOCK_IS
共享意向锁LOCK_IX
独占意向锁LOCK_S
共享锁LOCK_X
独占锁LOCK_AUTO_INC
AUTO-INC锁
lock_type
锁类型LOCK_TABLE
表锁LOCK_REC
记录锁
rec_lock_type
行锁的具体类型LOCK_ORDINARY
next-key锁LOCK_GAP
间隙锁LOCK_REC_NOT_GAP
记录锁LOCK_INSERT_INTENTION
意向锁其他信息
LOCK_WAIT
等待状态
其他信息
一堆bit位
如果是行锁,每一个bit位对应一条记录,0表示对应的记录没有加锁,1表示对应记录加锁了
_________________
| 锁所在的事物信息 |
|----------------|
| 索引信息 |
|----------------|
| 表/行锁信息 |
|----------------|
| type mod |
|--------------—-|
| 其他信息 |
|----------------|
| 一些bit位 |
----------------