🥁 锁
2022年6月20日
- db
🥁 锁
1. 锁的拥有者
1) 读锁 | 共享锁
select ... lock in share mode | for share
[MySQL 8.0 新增]- 可以保证自己读到的是最新数据,只有没有其他事务竞争时,才可以修改数据
2) 写锁 | 排它锁
select ... for update
- 若是查询的是索引,那么会加行锁
- 若是查询的是普通字段,那么会加表锁
- 可以保证自己读到的是最新数据,并且该数据只允许自己修改数据,其他事务想要读取和修改,则被阻塞
- 只有拥有该锁的事务可以读取和修改,其他事务都不可以读取和修改,并且同一时间只能有一个事务加写锁
2. 锁的粒度
1) 表级锁
- 共享锁 & 排它锁
- 表共享锁
lock tables chap read
unlock tables
释放表锁
- 表排它锁
lock tables chap update
unlock tables
释放表锁
- 表共享锁
- 意向锁
- 兼容性:
- 意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突
- 只会阻塞 表级读锁或表级写锁
- 意向锁也 不会和行锁冲突,行锁只会和行锁冲突
- 意向共享锁
- 事务有意向对某些行加共享锁
- 意向排它锁
- 事务有意向对某些行加排它锁
- 作用:
- 解决 表级锁 & 行级锁 共存的问题
若没有意向锁,则我们添加 表锁 时,需要遍历表中所有行来判断有没有行锁,效率低
- 若是存在意向锁,则我们添加 表锁 时,可以直接一次判断表中是否有数据行被锁定
- 举例:
- 事务 A 申请 写行锁 之前,数据库会自动给 事务 A 申请表的 意向排他锁
- 当 事务 B 申请表的互斥锁时,就会失败
- 兼容性:
2) 页锁
3) 行锁
- 在
RC
隔离级别不会加任何锁
// id 为主键,只需要在 id == 49 这个主键索引上加 写锁
update user set age = 10 where id = 49;
// name 为普通索引,首先在 name == 'Tom' 这个索引上加 写锁;然后回表,在 id == 49 这个主键索引上加 写锁
update user set age = 10 where name = 'Tom';
① 记录锁 [Record Lock]
- 锁定一个记录上的索引,而不是记录本身
- 如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用
- 当 SQL 语句无法使用索引时,会进行全表扫描,这个时候 MySQL 会给整张表的所有数据行加记录锁,
- 再由 MySQL Server 层进行过滤
- 但是,在 MySQL Server 层进行过滤的时候,如果发现不满足 WHERE 条件,会释放对应记录的锁。
- 这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。
- 因此,更新操作必须要根据索引进行操作,没有索引时,不仅会消耗大量的锁资源,增加数据库的开销,还会极大的降低了数据库的并发性能
② 间隙锁 [Gap Lock]
- 锁定索引之间的间隙,但是不包含索引本身
- 为开区间
- 间隙锁可以防止其他事务在这个范围内插入或修改记录,保证两次读取这个范围内的记录不会变,可以 防止幻读
- 间隙锁和间隙锁之间是互不冲突的,间隙锁唯一的作用就是 防止其他事务的 insert
③ 临键锁 [Next-Key Lock]
- 不仅锁定一个记录上的索引,也锁定索引之间的间隙
- 为 左开右闭 ( left , right ] 区间
④ 插入意向锁
- 插入意向锁 是 间隙锁 的一种,专门针对
insert
操作, - 官方意思是说多个事务在同一个索引同一个范围区间插入记录时候,如果插入位置不冲突,不会彼此阻塞
id = 30
和id = 49
之间如果有两个事务要同时分别插入id = 32
和id = 33
是没问题的,- 虽然两个事务都会在
id = 30
和id = 50
之间加上插入意向锁,但是不会冲突
- 若是在
RC
隔离级别,此时有个 update 事务还未提交,然后有个 insert 操作,此时 insert 操作不阻塞,不用等待 update 事务提交 - 若是在
RR
隔离级别,此时有个 update 事务还未提交,然后有个 insert 操作,此时 insert 操作阻塞,需要等待 update 事务提交- 从而保证了 RR 隔离级别下,通过 临键锁 实现了 串行化,保证在整个事务执行过程中,无论何时查到的数据记录条数不会改变
- 插入意向锁 只会和 间隙锁或 Next-key 锁 冲突,
- 间隙锁唯一的作用就是防止其他事务插入记录造成幻读,正是由于在执行
INSERT
语句时需要加插入意向锁,而插入意向锁和间隙锁冲突,从而阻止了插入操作的执行
- 间隙锁唯一的作用就是防止其他事务插入记录造成幻读,正是由于在执行
- 如果一个事务加了 插入意向锁,不影响其他事务加任何锁;但若是一个事务加了 临键锁 | 间隙锁,那么当前事务加 插入意向锁 时,会阻塞
- 这里有个顺序问题
临键锁具体使用 [MySQL 8.0.26]
原始数据:
id [主键] | b [普通索引] | a [无索引] |
---|---|---|
0 | 0 | 0 |
4 | 4 | 4 |
8 | 8 | 8 |
16 | 16 | 16 |
32 | 32 | 32 |
- 唯一索引等值查询
- 唯一索引范围查询
- 非唯一索引等值查询
- 当记录存在时,需要加 左临键锁 & 右间隙锁
- 作用:
- 之所以要把记录前后的间隙都锁住,仍然是为了防止幻读,
- 因为是非唯一索引,所以 id = 49 可能会有多条记录,为了防止再插入一条 id = 49 的记录
- 非唯一索引范围查询
3. 锁的态度
1) 悲观锁
- 优点:
- 保证数据安全
- 缺点:
- 影响性能
- 适用场景:
- 写多读少的并发环境
2) 乐观锁
- 优点:
- 避免加锁的开销,提高响应性能
- 缺点:
- 写竞争激烈情况下,多次 CAS 重试,CPU 损耗大
- 适用场景:
- 读多写少的并发环境
- 如何实现:
- 开发者自行实现
- 可通过 添加
版本 | 时间戳
实现
4. 锁的方式
1) 显式锁
2) 隐式锁
5. 其他
1) 全局锁
2) 死锁
- 排查流程
- 查看死锁日志
show engine innodb status
- 找出死锁 sql
- 分析 sql 死锁情况
- 模拟 死锁 案发
- 分析 死锁 日志
- 分析 死锁 结果
- 查看死锁日志