1 全局锁
会阻塞业务写。
1.1 数据备份
对于支持可重复读的存储引擎,可以在备份前开启事务,备份使用这个事务readview
2 表级锁
2.1 表锁
lock table table_name read;
会话退出锁自动释放
2.2 元数据锁
不需要显式加,会自动加上:
-
增删改查时加元数据读锁
-
修改元数据时加元数据写锁
事务结束时自动释放
2.3 意向锁
判断表里的数据是否有记录被加锁了,有两种意向锁:
-
对表里的记录加共享锁之前,在表级别加一个意向共享锁
-
对表里的记录加独占锁之前,在表级别加一个意向独占锁
2.4 自增锁
存在于有自增列的表。
释放时机可配置,innodb_autoinc_lock_mode
-
0:语句执行完才释放
-
1:
-
普通insert:申请之后立马释放
-
insert ... select:还是等执行完才释放
-
-
2:申请之后立马释放
2.4.1 innodb_autoinc_lock_mode=2且binlog_format=statement导致主从库数据不一致的问题
-
a,b两个会话同时向表中插入数据
-
a会话申请了3个增值1、2、3,然后b会话申请1个4,再然后a会话申请1个5
-
binlog_format因为是statement会记录原始语句,但是记录的时候会先记录a会话的3+1条语句,再记录b会话的1条,因为申请自增值的时候没有等语句执行完,记录binlog的时候是执行完再记录。
-
当从库回放时,a会话的语句获得的自增值会是1、2、3、4,而不是1、2、3、5,主从就不一致了
3 行级锁
对于select,因为使用了mvcc,一般是不加锁的,但是有两种情况除外:
-
select ... lock in share mode
-
select ... for update
这两种情况只能在事务中,事务提交锁就释放了。
3.1 记录锁
记录锁有共享锁和互斥锁:
- 只有共享锁之间读兼容,其他都不兼容
3.2 间隙锁
只存在于可重复读级别,用于锁住一个区间,防止幻读。
也有共享和互斥的概念,但是没有互斥的作用,也就是说多个事务可以同时持有一个区间的间隙锁
3.3 临键锁
既能锁住记录,又能锁住区间,所以互斥锁之间有互斥
3.4 插入意向锁
实际是一种间隙锁,属于行级别锁。
一个事务想插入一条数据时,需要判断要插入的数据区间是否有间隙锁,有的话就给要插入的数据所在的点加上一个插入意向锁,并阻塞插入事务。
插入意向锁和包含要插入数据的间隙锁不能同时被拥有
4 加行级锁
加锁对象时索引,加锁单位是临键锁,前开后闭。
锁退化:使用记录锁或者间隙锁就能避免幻读,就会退化
4.1 唯一索引等值查询
4.1.1 记录存在,退化为记录锁
4.1.2 记录不存在,退化为间隙锁
4.2 唯一索引范围查询
对扫描到的每一个索引加临键锁,有些情况会退化成间隙锁或者记录锁,用锁分析命令分析就行,一个一个记不好记
4.3 非唯一索引等值查询
同时存在主键索引和非主键索引,会对两个索引都加锁,但是对于主键索引,只有在满足条件的时候才会加锁
分析行锁是否会导致阻塞,主要考虑二级索引树上是否有锁
4.4 非唯一索引的范围查询
不会退化为间隙锁或者记录锁
4.5 没有索引
会全表扫描,每一条记录都会加上临键锁,相当于锁住全表了。
所以对于select for update, update ,delete语句要检查是否走了索引。
对于update语句,可以通过设置sql_safe_updates来限制update只有在有索引的情况下才能执行
4.6 锁分析命令
select * from performance_schema.data_locks\G;
4.6.1 LOCK_TYPE:锁级别
4.6.2 LOCK_MODE:锁类型
-
X:临键锁
-
X,REC_NOT_GAP:记录锁
-
X,GAP:间隙锁
5 死锁
mysql中的死锁和普通死锁差不多,也是两个或者多个线程(事务)持有并等待其他线程(事务)持有的锁,形成交叉之后导致死锁
5.1 一个死锁例子
- 表中有主键索引值为1、2、3、6四条数据
以下步骤按顺序执行:
-
事务a更新值为4的记录
-
事务b更新值为5的记录
-
事务a插入值为4的记录
-
事务b插入值为5的记录
发生死锁,原因如下:
-
因为4不在表中,(3,6] 被加上间隙锁
-
因为5不在表中,(3,6] 被加上间隙锁,间隙锁之间是兼容的,所以可以同时被两个事务持有
-
事务a的插入意向锁被事务b的间隙锁阻塞
-
事务b的插入意向锁被事务a的间隙锁阻塞
5.2 避免死锁
-
设置事务锁超时时间innodb_lock_wait_timeout:超过这个时间事务回滚,事务持有的锁释放
-
主动死锁检测innodb_deadlock_detect:检测到死锁就回滚一个事务并释放锁
6 insert加行级锁
6.1 隐式锁
由聚簇索引记录的trx_id(一条记录上保存的)实现。
只有在可能冲突时才加锁,避免频繁加锁。
一些情况下转换成显式锁:
-
记录之间有间隙锁
-
唯一键冲突
6.1.1 记录之间有间隙锁
-
在事务a中使用二级索引获取条不存在的数据的锁,会导致间隙锁
-
然后在事务b中在间隙插入就会导致插入阻塞
事务a在(15,+∞]有临键锁:
事务b阻塞:
6.1.2 唯一键冲突
如果插入的记录和已有的主键冲突,会报错并且给冲突的记录加上S锁
如果唯一二级索引冲突,会在冲突的记录上加上S临键锁,这个临键锁是在发生冲突时才会有,没有冲突的时候是一个隐式锁。