最近整理学习了下mysql锁的一些问题

102 阅读4分钟

1.锁的互斥性

行锁和表锁的互斥性有一定的差异,因为表锁是基于mysql自身的,而行锁是基于存储引擎的

行锁互斥性

行锁按互斥性分为共享锁和排它锁。

不同的事务可以重复获取某行数据的共享锁。但是一旦事务获取了某行数据的排它锁,其他事务就不能获取该数据的共享锁或者排它锁(因为排它锁排斥其他任何锁)。

读数据的时候默认不加锁,也可以手动加共享锁(select ****for share)或者排它锁(select ****for update)

update语句默认需要隐式的获取排它锁后才能执行

也正是因为这点,才解决了脏写的问题。

脏写:

  1. 两个事务对同一行数据做更新操作,A事务修改字段name,B事务修改字段age;
  2. A事务执行成功,B事务执行失败回滚,不光回滚了自己所做的对age字段的修改,也回滚了A事务已经成功执行并提交的对name字段的修改。

而因为update语句默认就必须加排他性的行锁,因此不可能出现多个事务同时修改同一行数据的情况,也就避免了脏写

当一行数据的排它锁被事务获取,其他的事务的读操作若没有手动加锁,则也可以成功读取到数据,反之则会阻塞直到排它锁被释放。(这一点很重要,也是行锁和表锁的的互斥性有差异的地方)

表锁互斥性

表锁分按互斥性分为读锁和写锁。当表被事务加上读锁,其他事务可以读但不能写;当表被加上写锁,则其他事务不能做任何操作(即使其他事务想不带锁的进行读写也会被阻塞,这一点和行锁不同)

2.加锁对象

行锁

主要分为记录锁(锁具体的某行数据)、间隙锁(锁左开右开区间)、临键锁(记录锁和间隙锁的结合),后两种锁只在可重复读级别以上才存在,为了避免幻读

表锁

主要分为以下几种:

  • 元数据锁(锁表结构)

  • 自增锁(用于事务并发插入数据时,有序获取自增id),自增锁有以下几种模式:

    1. 传统模式:事务获取到锁才能执行插入操作,相当于多事务串行
    2. 连续模式:对于插入数据条数已知的insert语句,先抢一个轻量级的锁,分配一段连续的自增id即可释放锁并执行插入,提高了并发度
    3. 交错模式:假设两个事务插入的数据条数不能直接从sql语句得知。采取给A事务分配1、3、5、7、9,给B事务分片2、4、6、8、10的方式,交错分配自增id
  • 意向锁:加表锁时,要判断该表是否已经有数据被加了行锁,否则可能跟要加的表锁产生冲突。因此加行锁时,会给表加上一个意向锁,表示该表里有数据被加行锁了

  • 普通表锁:锁住整张表的数据

3.隐式加锁时机

读未提交读已提交可重复读串行化不手动开启事务并开启事务自动提交
读操作无锁无锁无锁共享锁无锁
写操作排它锁排它锁排它锁排它锁排它锁

上图是不同隔离级别下,隐式加锁的区别。除了串行化级别以外,其他级别都是读操作不加锁,写操作加排它锁

4.自动解锁时机

对于行锁,不管处于哪个隔离级别,都是等事务提交或回滚时才会释放锁

对于表锁,情况如下:

image-20241230151030267

6.查询锁信息

查询锁信息sql

 -- 查询已经存在的锁
 SELECT * FROM performance_schema.data_locks;
 -- 查询等待锁的事务信息
 SELECT * FROM performance_schema.data_lock_waits;

image-20241230214645410

截取“SELECT * FROM performance_schema.data_locks;”的部分查询列,结果如上。表示加了一个行级排它锁(rec_not_gap表示不是间隙锁,只是普通记录锁),以及一个表级意向排他锁(IX的I是intention的意思)

7.死锁检测

mysql默认有死锁检测机制,若发生死锁则会自动回滚某个事务释放锁资源