与其他锁一样,都是用来解决并发安全问题的。mysql 中的锁用来解决事务并发时数据行或表的安全问题
粒度角度分为
- 行级锁 ( Record Locks ):粒度小,开销大,可并发度高。锁单行
- 间隙锁 ( Gap Locks ) :粒度大,开销小,可并发度低。锁某个范围,防止其他事务在该范围中插入数据
- 临键锁 ( Next-Key Locks ) :就是行级锁和间隙锁一起使用。行锁锁单条记录,间隙锁锁记录间的间隙,实现全表或部份表锁的效果
种类角度分为(添加锁时,真正锁的是聚簇索引字段)
-
共享锁
- 为涉及到的 n 行数据加锁,加锁方式如下
select ... lock in share mode
- S 锁,也叫读锁,一种行级锁。事务获取数据上的共享锁后,其他事务只能获取该数据上的共享锁
- 核心在于共享,即读操作,不希望在事务读取数据后其他事务对读到的数据进行修改,但允许查询
-
排它锁
- 为涉及到的 n 行数据加锁,加锁方式如下
select ...for update; update; delete; insert;
- X 锁,也叫写锁,一种行级锁。事务获取数据上的排他锁后,其他事务无法获取该数据上的任何锁
处理方式角度分为
- 乐观锁:总认为在读数据到写数据这段时间内没有其他事务会去修改它,在写的时候再去检查数据是否被修改。一般通过添加 version 字段实现
- 悲观锁:总认为在读数据到写数据这段时间内会有其他事务去修改它,于是在读数据时就加锁,在事务提交前不允许其他事务修改数据
死锁的例子:事务 A B 并发,A B 都获取了数据的共享锁,此时 A B 都想要修改该数据,需要获取该数据的排它锁,出现锁冲突,A 等待 B 释放共享锁,B 等待 A 释放共享锁,进入死锁
select 操作加锁情况总结
-
普通 select 永远都不会加锁(Serializable 隔离级别除外)
-
RC / RU 隔离级别下只会在满足条件的记录上添加行锁,无论使用什么样的查询条件
-
RR 隔离级别
-
通过未添加索引的字段检索时总会采用临键锁锁全表
-
通过唯一约束字段索引进行检索时,会在满足条件的记录上添加行锁,满足条件的记录间添加间隙锁;如果查询结果为空,那就在查询条件所在的间隙添加间隙锁 。一个例子,如下
-- 一张表的 id, age 字段有 2,20 5,30 10,40 三个值, 通过如下 sql 查询(age 字段也建立了索引) select ... where id > 3 for update -- 会在 5 10 处添加行锁,(3,5)、(5,10)、(10,+无穷) 添加间隙锁 select ... where id = 3 for update -- 会在 (2,5) 添加间隙锁 select ... where age > 35 for update -- 会在 10 处添加行锁,(35,40)、(40,+无穷) 添加间隙锁
- 通过不唯一约束字段索引进行检索时与唯一约束字段只有一点不同:通过精确查询查询到记录时不光在记录上添加行锁,还会在两边添加间隙锁。一个例子,如下
-- 还是上面的表,不同的是 age 现在是不唯一索引 select ... where age = 30 for update -- 此时会在 5 处添加行锁,(20,30)、(30,40) 添加间隙锁
-
-
serializable 隔离级别:与 RR 大体相同,只是普通查询相当于 RR 的共享锁查询
通过锁的方式解决并发访问问题
- 脏读:通过共享锁读取数据。中心思想在于如果其他事务对数据做出修改,那么我就要等待排他锁释放后才可以读取到数据。虽然解决了脏读问题,但此时不能写读并发
- 不可重复读:也是通过共享锁读取数据。但中心思想在于我先通过共享锁读取数据,其他事务对数据作出修改时需要等待我释放共享锁。虽然解决了不可重复读问题,但此时不能读写并发
- 幻读:通过间隙锁封锁数据间的空隙,使其他事务无法插入