MySQL 中的锁:
- 按照性质划分:共享锁、排它锁;
- 按照锁粒度划分:全局锁、表级锁、行级锁;
- 按照思想划分:乐观锁、悲观锁。
共享锁和排它锁
MySQL 中的锁按照性质分可分为共享锁和排他锁。
-
共享锁:所有事务只能读不能写(包括加锁的客户端)。
select ... lock in share mode; -
排他锁:当前加锁的客户端可读可写,其他客户端不可读不可写。
select ... for update;
共享锁只能兼容共享锁,不兼容排它锁。排它锁不兼容共享锁和排它锁。
MySQL 中的锁按照加锁范围又可以分为三类:
-
全局锁:锁定数据库中的所有表;
-
表级锁:锁住整张表;
-
行级锁:锁住对应的行数据。
全局锁
对整个数据库实例加锁,加锁后整个数据库实例就处于只读状态,后续的数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句都将被阻塞。
使用场景:全库逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
表级锁
每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。
表级锁主要分为:
-
表锁;
-
元数据锁;
-
意向锁。
表锁
对于表锁,分为两类:
- 表共享锁(read lock):所有的客户端都只能读(当前加锁的客户端也只能读,不能写),不能写;
- 表排他锁(write lock):对当前加锁的客户端,可读可写,对于其他的客户端,不可读也不可写。
// 表共享锁(读锁)
lock tables xxx read
// 表排他锁(写锁)
lock tables xxx write
// 释放当前会话的所有表锁
unlock tables
元数据锁
元数据锁(Meta Data Lock),MDL 加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。
主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。
在 MySQL 5.5 中引入了 MDL,
-
当对一张表进行增删改查的时候,加 MDL 读锁(共享) ;
-
当对表结构进行变更操作的时候,加 MDL 写锁(排他) 。
MDL 在事务提交后释放。
意向锁
为了避免 DML 在执行时,加的行锁与表锁的冲突,在 InnoDB 中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查,快速判断表中是否有记录被加锁。
一个客户端对某一行加上了行锁,那么系统也会对其加上一个意向锁,当别的客户端来想要对其加上表锁时,便会检查意向锁是否兼容,若是不兼容,便会阻塞直到意向锁释放。
意向锁分为两类:
-
意向共享锁(IS) :表示事务打算在资源上设置共享锁(读锁)。这通常用于表示事务计划读取资源,并不希望在读取时有其他事务设置排它锁。
-
意向排他锁(lX) :表示事务打算在资源上设置排它锁(写锁)。这表示事务计划修改资源,并不希望有其他事务同时设置共享或排它锁。
意向锁会在触发意向锁的事务提交或者回滚后释放。
行级锁
每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。
行级锁是通过对索引上的索引项加锁来实现的,而不是对记录加锁。
InnoDB 默认是行级锁。数据库在更新时,会根据 where 条件中是否包含索引考虑加锁范围,如果有索引,那么就使用索引添加行级锁,如果没有索引,那么就会添加表级锁。
行级锁主要分为:
-
记录锁;
-
间隙锁;
-
临键锁。
记录锁(Record Lock)
记录锁是针对索引记录的锁,锁定的总是索引记录。防止其他事务对此进行update和delete。在 RC、RR 隔离级别下都支持。
-- id 列为主键列或唯一索引列
SELECT * FROM table WHERE id = 1 FOR UPDATE;
上述 sql 语句中,id 为 1 的记录行会被锁住。
注意:id 列必须为唯一索引列或主键列,否则上述语句加的锁就会变成临键锁。
同时查询语句必须为精准匹配(=) ,不能为 >、<、like等,否则也会退化成临键锁
间隙锁(Gap Lock)
锁定一个范围,但不包括记录本身,确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在 RR 隔离级别下都支持。
间隙锁是通过索引来实现的。这意味着间隙锁只能作用于索引,而不能直接作用于非索引列。
临键锁(Next-key Lock)
记录锁和间隙锁的组合,锁定一个左开右闭的范围,同时还锁定记录本身。在 RR 隔离级别下支持。
通过临键锁可以解决 RR 隔离级别下当前读产生幻读的问题(RR 隔离级别下快照读产生幻读问题由 MVCC 解决)。
临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。
乐观锁和悲观锁
乐观锁
基于 CAS 机制,在更新之前,先查询一下库存表中当前库存数(quantity),然后在做 update 的时候,以库存数作为一个修改条件。当提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。
注意:乐观锁并非无锁,在 update 的时候会加锁,数据库在更新时,会根据 where 条件中是否包含索引考虑加锁范围,如果有索引,那么就使用索引添加行级锁,如果没有索引,那么就会添加表级锁。
悲观锁
MySQL 中的悲观锁主要依靠数据库提供的锁机制实现,首先要关闭 MySQL 数据库的自动提交属性,然后通过 select ... for update进行加锁。
过程如下:
- 开启事务。
- 使用**
select ... for update尝试为数据加上排它锁**。 - 如果加锁失败,说明该记录正在被修改,当前查询需要等待或者抛异常。
- 如果加锁成功,就对记录做修改。
- 提交事务。