MySQL 锁简述
# 一、锁的分类
# 1,全局锁
# 1.1,命令
- 加锁:flush tables with read lock;
- 解锁:unlock tables;
# 1.2,应用场景
- 对整个数据库加读写锁,一般用于业务备份;
- 在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样;
# 1.3,缺点
- 如果数据库里有很多数据,备份就会花费很多的时间;
- 备份期间,业务只能读数据,而不能更新数据,会造成业务停滞;
# 1.4,解决方法
- 如果数据库的引擎支持的事务支持可重复读的隔离级别,那么在备份数据库之前先开启事务,这样备份期间备份的数据一直是在开启事务时的数据;
# 2,表级锁
# 2.1,表锁
- 命令:
- 表级别的共享锁,也就是读锁:lock tables t_student read;
- 表级别的独占锁,也就是写锁:lock tables t_student write;
- 释放当前会话的所有表锁:unlock tables;
- 会对整个表加读写锁;
- 尽量避免在使用 InnoDB 引擎的表使用表锁,因为表锁的颗粒度太大,会影响并发性能;
# 2.2,元数据锁(MDL)
- 不需要显示的使用 MDL,因为对数据库表进行操作时,会自动给这个表加上 MDL;
- 对一张表进行 CRUD 操作时,加的是 MDL 读锁;
- 对一张表做结构变更操作的时候,加的是 MDL 写锁
- 事务执行期间,MDL 是一直持有的,事务结束时才会释放锁;
- 申请 MDL 锁的操作会形成一个队列,队列中写锁获取优先级高于读锁,一旦出现 MDL 写锁等待,会阻塞后续该表的所有CRUD操作;
# 2.3,意向锁
- 在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」;
- 在使用 InnoDB 引擎的表里对某些记录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」;
- 意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(lock tables ... read)和独占表锁(lock tables ... write)发生冲突;
- 表锁和行锁是满足读读共享、读写互斥、写写互斥的;
- 意向锁的目的是为了快速判断表里是否有记录被加锁,如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,效率会很慢;
- 有了「意向锁」,在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,就不用去遍历表里的记录;
- 普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的;
# 2.4,AUTO-INC锁
- 为声明为 AUTO_INCREMENT 属性的字段实现自动递增;
- 是特殊的表锁机制,锁不是再一个事务提交后才释放,而是在执行完插入语句后就会立即释放;
- AUTO-INC 锁对大量数据进行插入的时候,会影响插入性能,因为另一个事务中的插入会被阻塞;
- 提供了一种轻量级的锁,在该字段自增后,就释放掉锁;
# 3,行级锁
# 3.0,锁定读
- 对读取的记录加共享锁:select ... lock in share mode;
- 对读取的记录加独占锁:select ... for update;
- update 和 delete 操作都会加行级锁,且锁的类型都是独占锁(X型锁);
# 3.1,记录锁
- Record Lock,仅仅把一条记录锁上;
- 记录锁是有 S 锁和 X 锁之分的;
- 仅有 S 型与 S 锁兼容,S 型与 X 锁不兼容,X 型与 X 锁不兼容;
- 读已提交隔离级别下,仅有该行级锁;
# 3.2,间隙锁
- Gap Lock,锁定一个范围,但是不包含记录本身,开区间;
- 只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象;
- 间隙锁的意义只在于阻止区间被插入,因此是可以共存的;
- 两个事务可以同时持有包含共同间隙的间隙锁;
- 共同间隙可以是完全一样,也可以是子集;
# 3.3,组合锁
- Next-Key Lock,记录锁和间隙锁的组合,锁定一个范围,并且锁定记录本身,前开后闭;
- 是记录加锁的基本单位,加锁的位置一般是索引;
- 组合锁是包含间隙锁+记录锁的,如果一个事务获取了 X 型的组合锁,那么另外一个事务在获取相同范围的 X 型的组合锁时,是会被阻塞的,源于记录锁的互斥逻辑;
# 3.4 插入意向锁
- 它是一种特殊的间隙锁,属于行级别锁,该锁只用于并发插入操作;
- MySQL 加锁时,是先生成锁结构,然后设置锁的状态,如果锁状态是等待状态,并不是意味着事务成功获取到了锁,只有当锁状态为正常状态时,才代表事务成功获取到了锁;
- 在同一个范围内,插入意向锁与间隙锁是冲突的;
- 当其它事务持有该间隙的间隙锁时,插入操作就会发生阻塞,需要等待其它事务释放间隙锁之后,才能获取到插入意向锁;
- 在此期间会先生成一个插入意向锁,但处于等待状态「可能因等待造成死锁」,表明有事务想在某个区间插入新记录;
- 间隙锁锁住的是一个区间,*「插入意向锁」*锁住的是一个点;
# 二、加锁过程
# 1,唯一索引进行等值查询
- 当条件值存在时,组合锁会退化成「记录锁」;
- 当条件值不存在时,组合锁会退化成「间隙锁」;
# 2,唯一索引范围查询
- 「大于等于」范围查询,当条件值存在且唯一时,该条件值记录的组合锁会退化成「记录锁」,其他记录仍然为组合锁;
- 「大于等于」范围查询,当条件值不存在时,所有记录都为组合锁,最后一个不符合条件的记录和第一个符合条件的记录也会形成组合锁,同时其他事务无法在这个区间插入数据(X型锁互斥);
- 「小于或者小于等于」范围查询,当条件值记录不在表中,终止范围查询的记录的组合锁会退化成间隙锁,其他记录仍然为组合锁;
- 「小于或者小于等于」范围查询,当条件值记录在表中,所有记录仍然为组合锁;
- 「小于」范围查询,当条件值记录在表中,终止范围查询的记录的组合锁会退化成间隙锁,其他记录仍然为组合锁;
- 因为组合锁是前开后闭,对小于或小于等于的范围查询,可能满足前开但不满足后闭,所以可能会有记录的退化为间隙锁的情况;
- 对于大于等于的范围查询,可能满足后闭但不满足前开,所以可能会有记录退化为记录锁的情况;
# 3,非唯一索引等值查询
- 同时存在主键索引和非唯一索引,非唯一索引加锁时,对满足查询条件的记录会对它们的主键索引也加锁;
- 二级索引树是按照二级索引值顺序存放的,在相同的二级索引值情况下,再按主键的顺序存放;
- 插入数据时,要同时考虑二级索引和主键索引,才能确定插入位置,并且需要插入位置的下一条没有间隙锁;
- 当查询的记录「存在」时,扫描到第一个不符合条件的记录即停止,对该记录的非唯一索引加的组合锁会退化为间隙锁,对存在的记录的非唯一索引添加组合锁,并对其主键索引添加记录锁;
- 当查询的记录「不存在」时,扫描到第一条不符合条件的记录即停止,对该记录的非唯一索引加的组合锁会退化为间隙锁;
- 插入数据时,要同时考虑二级索引和主键索引,才能确定插入位置,并且需要插入位置的下一条没有间隙锁;
- 当有一个事务持有二级索引的间隙锁 (m, n) 时,对于边界m、n记录的数据插入,需要主键值小于m的主键值,或大于n的主键值,才能插入成功,否则会因为间隙锁而失败;
# 4,非唯一索引范围查询
- 无论查询记录存不存在,组合锁不会退化为间隙锁和记录锁,同时满足条件的主键索引仍然添加记录锁;
- 由于非唯一索引不具有唯一性,且记录锁无法防止插入,只能防止删除或者修改,对于「大于等于」的范围查询,条件记录存在时,如果退化为记录锁,就会导致其他事务插入一条相同二级索引的记录,前后两次查询的结果集就不相同,出现了幻读现象;
# 三、防止全表扫描
# 1,发生条件
- 执行 update、delete、select ... for update 等具有加锁性质的语句,没有使用索引列作为查询条件,或者查询语句没有走索引查询,导致扫描是全表扫描,那么每一条记录的索引上都会加 next-key 锁,就相当于锁住的全表,这时如果其他事务对该表进行增、删、改操作的时候,都会被阻塞;
- 即使加了索引作为查询条件,也需要看优化器最终选择的是索引扫描,还是全表扫描;
- 全表扫描,锁是在遍历索引的时候加上的,并不是针对输出的结果加锁;
# 2,避免方法
- 当 sql_safe_updates 设置为 1 时:
- update 语句必须满足如下条件之一才能执行成功:
- 使用 where,并且 where 条件中必须有索引列;
- 使用 limit;
- 同时使用 where 和 limit,此时 where 条件中可以没有索引列;
- delete 语句必须满足以下条件能执行成功:
- 同时使用 where 和 limit,此时 where 条件中可以没有索引列;
- update 语句必须满足如下条件之一才能执行成功:
- 如果 where 条件带上了索引列,但是优化器最终扫描选择的是全表,可以使用 force index([index_name]) 可以告诉优化器使用哪个索引;
# 四、避免死锁
# 1,死锁的四个必要条件
- 互斥、占有且等待、不可强占用、循环等待;
# 2,死锁避免方式
- 设置事务等待锁的超时时间;
- 事务超时后回滚,锁释放,另一事务就可以继续执行;
- InnoDB中,参数innodb_lock_wait_timeout用来设置超时时间的,默认值时50秒;
- 开启主动死锁检测:
- 主动死锁检测在发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行;
- 参数innodb_deadlock_detect设为 on,表示开启这个逻辑,默认就开启;
# 3,唯一键冲突
- 插入一个与「已有的记录的主键或者唯一二级索引列值相同」的记录时会失败,然后对这条记录会加上 S 型的锁;
- 主键索引重复:
- 当隔离级别为读已提交时,插入新记录的事务会给已存在的主键值重复的聚簇索引记录添加 S 型记录锁;
- 当隔离级别是可重复读(默认隔离级别),插入新记录的事务会给已存在的主键值重复的聚簇索引记录添加 S 型记录锁。
- 唯一二级索引列重复:
- 不论是哪个隔离级别,插入新记录的事务都会给已存在的二级索引列值重复的二级索引记录添加 S 型 next-key 锁;
编辑 (opens new window)
上次更新: 2024/08/12, 17:02:28