InnoDB存储引擎中的锁
锁是数据库系统区别与文件系统的一个关键特性。数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。但不同数据库之间或不同存储引擎之间,对于锁的实现方法各不相同,本文主要介绍MySQL InnoDB存储引擎中的锁。
锁种类
根据对数据库的操作,可分为以下两种
- 排他锁(又称写锁,X锁)
- 共享锁(又称读锁,S锁)
顾名思义,读操作时,获取共享锁,共享锁之间是兼容的;而写操作时,获取排他锁,并与其他任何锁都不兼容。
举个例子 ,当事务A中对数据对象a加上排他锁,则只允许事务A对对象a进行读写操作,其他事务想要对a操作只能等待事务A释放锁;而当对a加上共享锁时,则多个事务都可以同时读,但如果此时想对数据对象a进行写操作,只能等待其他共享锁释放。
锁粒度
InnoDB中根据锁粒度,主要分为以下两种
- 行级锁
- 表级锁
此外,InnoDB存储引擎支持多粒度锁定,允许事务中存在行级锁和表级锁。不过,当事务A对表中记录上写锁,而事务B想对表上表级锁时,事务B需要要遍历表中的所有行,看看是否有被锁定,这样效率很低。所以,为了更有效的支持多粒度锁定,InnoDB存储引擎引入了意向锁,在事务想要给表中的某几行加锁时,需要先给表上加上对应类型的锁。根据操作类型,也分为两种:
- 意向排他锁(又称IX锁)
- 意向共享锁(又称IS锁)
不过由于InnoDB存储引擎支持行级锁,因此意向锁并不会阻塞除全表扫以外的任何请求。主要是为了表示是否有请求锁定表中的某几行
一致性读
InnoDB中,通过多版本控制的方式来读取当前时间,数据库中的数据,称之为一致性非锁定读。在读取的行正在执行DELETE或UPDATE操作,这时执行select ... from table
操作并不会去等待行上的X锁释放,相反地,InnoDB会去读取行的一个快照。
在不同的隔离级别下,读取的方式不同,并不是每个隔离级别下都采用非锁定的一致性读,就算都是一致性非锁定读也有不同的表现。
而显式的对读取操作进行加锁以保证数据逻辑的一致性,则为一致性锁定读,其中根据不同操作,加不同类型的锁。
select ... for update
:对读取的行记录加排他锁select ... lock in share mode
:对读取的行记录加共享锁
需要注意的是,使用一致性锁定读,前提是在一个事务中,务必加上
begin
,start transaction
或set autocommit=1
锁算法
InnoDB存储引擎中有3种行级锁算法,分别是
- Record Lock: 记录锁,锁定一条记录
- Gap Lock:间隙锁,锁定一个范围,但不包括记录本身
- Next-Key Lock:Gap Lock + Record Lock,锁定一个范围并锁定记录本身
在默认隔离级别可重复读
下,采用了Next-Key Locking
的机制来避免幻读。
假设有这么一张users表:
1 | CREATE TABLE users( |
表中有这么几条记录
1 | +------|-------------|--------------|-------+ |
当我们执行
1 | select * from users where age = 20 for update; |
InnoDB会为(10,20]的记录加上Next-Key Lock,而为(20,30)记录加上Gap Lock。
然而当我们执行
1 | select * from users where id = 1 for update; |
此时,InnoDB只会锁住id=1这条记录,这是因为在查询列是唯一索引的情况下,Next-Key Lock 会降级为Record Lock,从而提高并发。不过如果唯一索引是由多个列组成,而查询仅是多个列中的一个,那么查询会是range类型,最终还是会使用Next-Key Lock 进行锁定。
参考:
《高性能MySQL》