数据库理论中根据互斥性将锁分为两种:
共享锁(S锁、读锁):事务获得元组的共享锁后,其它事务也只能获得该元组的共享锁,而不能获得排它锁;获得共享锁的事务可以对元组进行读操作
排它锁(X锁,写锁):事务获得元组的排它锁后,其它事务既不能获得该元组的共享锁,也不能获得排它锁;获得排它锁的事务可以对元组进行写操作
此外,MySQL在此基础上做了扩展,增加了两种意向锁:意向共享锁(IS锁),意向排它锁(IX锁)。其中,“意向”表明并不是真的已经获取到了锁,而是未来极有可能会主动获取锁
S锁和X锁是行锁,而IS锁和IX锁是表锁
不同事务间四种锁的兼容/互斥矩阵如下:
IS IX S X
IS √ √ √ ×
IX √ √ × ×
S √ × √ ×
X × × × ×
除了通过GET_LOCK和RELEASE_LOCK这对MySQL内置函数手动获取/释放锁之后,下列SQL语句还自带了锁:
select——不加锁
select lock in share mode——意向共享锁
select for update——意向排它锁
insert——排它锁
update——排它锁
delete——排它锁
可以看出的是,实际上MySQL并没有直接提供共享锁的语句。而需要注意的是,IS锁语句select lock in share mode也需要谨慎使用。这是因为如果两个事务同时对一个元组执行了select lock in share mode后,就分别获取了IS锁,这是兼容的;但如果其中一个事务再执行写操作(insert/update/delete),尝试获取X锁时,会由于互斥而被挂起,形成死锁
MySQL的InnoDB引擎默认支持行锁,来尽量缩小锁定元组的粒度
行锁分为三级,粒度从小到大依次是:
记录锁(Record Lock):单行
间隔锁(Gap Lock):一个开区间内的多行
防插入锁(Next-Key Lock):一个前开后闭区间内的多行,实际上是记录锁和间隔锁的结合
防插入锁的区间是根据索引来确定的。对于没有索引的列,则根据该列的聚簇索引(简单而言,聚簇索引是根据数据在物理空间中的分布疏密情况计算得出的聚集点)决定。例如:
某普通索引列当前值有:1, 5, 10, 20,那么此时它的防插入锁区间分别是:
(-∞, 1], (1, 5], (5, 10], (10, 20], (20, +∞)
下列SQL语句自带的行锁级别为:
insert——记录锁
update——防插入锁
delete——防插入锁
此外,若查询的列包含唯一索引或主键,则行锁将被自动降级到记录锁
MySQL的InnoDB引擎也同样支持MyISAM的表锁,即下列一组锁表语句:
LOCK TABLE(S) {tableName} {lockType} [, {tableName} {lockType}, ...]
UNLOCK TABLES
锁类型lockType的取值有:
READ:持有该锁的事务只能读不能写,允许其它事务获取READ锁,其它事务也可以在不显示获取READ锁是情况下读表
READ LOCAL:对于InnoDB而言与READ相同,不推荐使用
WRITE:持有该锁的事务能读和写表,不允许其它事务操作该表,其它尝试获取该表WRITE锁的事务将被挂起直到锁释放
LOW PRIORITY WRITE:LOW PRIORITY修饰的锁表请求将在竞争WRITE锁时拥有较低的优先级
由于表锁对查询的影响较大,因此MySQL对表锁的释放有着严格的限定:
1. 只提供了UNLOCK TABLES命令,一旦显式调用释放表锁,则释放全部表锁
2. LOCK TABLES命令将首先隐式释放事务当时持有的全部表锁,再进行锁表;这就要求应在一条LOCK TABLES语句中带上全部需要锁定的表,而不能逐条进行锁定
3. 事务开启时(start transaction),一条UNLOCK TABLES语句将被隐式调用,确保当前没有表锁