【MySQL】锁

93 阅读5分钟

1 全局锁

1.1 简介

全局锁就是对整个数据库实例加锁。加全局读锁的命令如下:

Flush tables with read lock

1.2 应用场景

全局锁的典型使用场景是,做全库逻辑备份

既然要全库只读,为什么不使用 set global readonly=true 的方式呢?确实 readonly 方式也可以让全库进入只读状态,但我还是会建议你用 FTWRL 方式,主要有两个原因:

  • 在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大,不建议你使用。
  • 在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

2 表锁

2.1 简介

lock tables … read/write

与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。

2.2 案例

如果在某个线程 A 中执行 lock tables t1 read, t2 write; 这个语句,则其他线程写 t1、读写 t2 的语句都会被阻塞。同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。连写 t1 都不允许,自然也不能访问其他表。

3 元数据锁

3.1 简介

MySQL 5.5版本中引入了 MDL,当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。

加锁:事务开始时,系统自动加的

解锁:事务提交的时候自动释放

3.2 案例

给小表加字段导致系统崩溃!

image.png

session C的写锁阻塞了后续的所有读锁,导致过多的读请求打崩服务!

4 行锁

MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 MyISAM 引擎就不支持行锁。这也是 MyISAM 被 InnoDB 替代的重要原因之一。

两阶段锁协议

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁的申请时机尽量往后放。减少锁时长。

5 间隙锁

5.1 简介

产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。顾名思义,间隙锁,锁的就是两个值之间的空隙。

注意

  • 间隙锁是开区间
  • 间隙锁是在可重复读隔离级别下才会生效的。所以,你如果把隔离级别设置为读提交的话,就没有间隙锁了

6 next-key lock

间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间。间隙锁和 next-key lock 的引入,帮我们解决了幻读的问题,但同时也带来了一些“困扰”。例如:死锁、减少并发

FAQ

1 为什么幻读会导致死锁?

image.png

你看到了,其实都不需要用到后面的 update 语句,就已经形成死锁了。我们按语句执行顺序来分析一下:

  1. session A 执行 select … for update 语句,由于 id=9 这一行并不存在,因此会加上间隙锁 (5,10);
  2. session B 执行 select … for update 语句,同样会加上间隙锁 (5,10),间隙锁之间不会冲突,因此这个语句可以执行成功;
  3. session B 试图插入一行 (9,9,9),被 session A 的间隙锁挡住了,只好进入等待;session A 试图插入一行 (9,9,9),被 session B 的间隙锁挡住了。
  4. 至此,两个 session 进入互相等待状态,形成死锁。当然,InnoDB 的死锁检测马上就发现了这对死锁关系,让 session A 的 insert 语句报错返回了。

2 为什么幻读只会在可重复读隔离级别下生效?

间隙锁是在可重复读隔离级别下才会生效,是因为在可重复读隔离级别下,MySQL会对查询的范围加上间隙锁,以防止其他事务在这个范围内插入新的数据。

在可重复读隔离级别下,MySQL会对读取的每一行数据都加上共享锁,以保证读取到的数据是一致的。同时,MySQL还会对查询的范围加上间隙锁,以防止其他事务在这个范围内插入新的数据。这样可以避免幻读的问题,即一个事务在读取数据时,发现有新的数据插入,导致读取的数据不一致。

在其他隔离级别下,间隙锁不会生效,因为MySQL不会对查询的范围加上间隙锁。例如,在读提交隔离级别下,MySQL只会对读取的每一行数据加上共享锁,而不会对查询的范围加上间隙锁。这样就可能会出现幻读的问题。

因此,间隙锁只在可重复读隔离级别下生效,是为了保证数据的一致性和避免幻读的问题。但是,间隙锁也会增加锁冲突的可能性,因此在使用时需要注意避免锁冲突的情况。