mysql锁

38 阅读5分钟

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四条数据

以下步骤按顺序执行:

  1. 事务a更新值为4的记录

  2. 事务b更新值为5的记录

  3. 事务a插入值为4的记录

  4. 事务b插入值为5的记录

发生死锁,原因如下:

  1. 因为4不在表中,(3,6] 被加上间隙锁

  2. 因为5不在表中,(3,6] 被加上间隙锁,间隙锁之间是兼容的,所以可以同时被两个事务持有

  3. 事务a的插入意向锁被事务b的间隙锁阻塞

  4. 事务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临键锁,这个临键锁是在发生冲突时才会有,没有冲突的时候是一个隐式锁。