MySQL 锁分类有哪些?一文带你详解!!

首先让我们创建一张用户表,并插入一些数据:

CREATE TABLE userinfo (
  user_id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  password VARCHAR(50) NOT NULL,
  email VARCHAR(100),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO userinfo (username, password, email) VALUES ('张三', 'password123', 'zhangsan@example.com');
INSERT INTO userinfo (username, password, email) VALUES ('李四', 'password456', 'lisi@example.com');
INSERT INTO userinfo (username, password, email) VALUES ('王五', 'password789', 'wangwu@example.com');

全局锁

插入全局锁的命令

flush tables with read lock

执行后,整个数据库就处于只读状态了,这时其他线程执行以下操作,都会被阻塞:

  • 对数据的增删改操作,比如 insert、delete、update等语句;
  • 对表结构的更改操作,比如 alter table、drop table 等语句。

比如此时我们想插入一条语句,但显示被读锁阻塞住了
在这里插入图片描述
执行释放锁之后的命令就可以插入成功了

unlock tables;

在这里插入图片描述

全局锁的应用场景

全局锁的应用场景主要应用于 全库逻辑备份

举个例子,如果不加读写锁的话

在备份用户表和商品表的时候,有一个用户购买了一件商品

就会导致备份的数据 是扣减前的用户余额 和 扣减后的商品余额 —— 用户余额没变,货却没了!

在这里插入图片描述

全局锁的缺点

加上全局锁后,数据库就变成了 只读状态

那么如果数据库有很多数据,备份就会花费很多时间,而且备份期间只能读数据而不能更新数据,就会导致业务的停滞

那有什么办法可以改进呢?

可以利用 MVCC 机制,在可重复读隔离级别下,开启事务后,会先创建一个 Read View,然后整个事务执行期间都会使用这个 Read View,同时也依然可以对数据进行更新操作。

这就是事务四大特性的 隔离性,这样备份期间备份的数据一直都是在开启事务时的数据。

注意,这需要能够适用 可重复读隔离级别 下的存储引擎,比如 Innodb。

但是 MyISAM 这种不支持事务的引擎,在备份数据库时就要使用全局锁的方法了。

表级锁

表锁

表锁有两种:

//表级别的共享锁,也就是读锁;
lock tables t_student read;

 //表级别的独占锁,也就是写锁;
lock tables t_stuent write;

这里的锁有点乱,我们可以做四次实验试试看

  1. 给表设置读锁,然后读数据,发现是正常没问题的
    在这里插入图片描述
  2. 然后我们进行写数据,发现被阻塞住了
    在这里插入图片描述

如图,这里我们给用户表设置了读锁,然后想写入一条数据,发现被阻塞住了

在这里插入图片描述

说明这里的本线程的表级别的读锁,会限制住本线程的写操作

  1. 我们解开表级锁,并设置读锁,然后再进行读操作

在这里插入图片描述
发现这里,阻塞住了,陷入了死锁状态,注意右下角的查询时间,这里是一直在查询没停止的(我只是截了个图),说明本线程的写锁也会限制住本线程的读操作

  1. 我们在写锁下进行写操作,正常没问题
    在这里插入图片描述

所以,可以看出来,表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。

所以,表锁的颗粒度是很大的,会影响到并发性能,尽量要避免使用,Innodb 牛逼的地方在于实现了颗粒度更细的行级锁。

元数据(MDL)锁

我们不需要显示的使用 MDL,因为当我们对数据库表进行操作时,会自动给这个表加上 MDL:

  • 当我们对一张表进行 CRUD 操作时,会对表加一个 元数据读锁;
  • 当我们对一张表的结构进行操作的时候,会对表加一个 元数据写锁

MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。

当有线程在执行 select 语句( 加 MDL 读锁)的期间,如果有其他线程要更改该表的结构( 申请 MDL 写锁),那么将会被阻塞,直到执行完 select 语句( 释放 MDL 读锁)。

反之,当有线程对表结构进行变更( 加 MDL 写锁)的期间,如果有其他线程执行了 CRUD 操作( 申请 MDL 读锁),那么就会被阻塞,直到表结构变更完成( 释放 MDL 写锁)。

MDL 锁的问题

MDL 是在事务提交之后才会释放的,也就是说,事务执行期间,MDL 是一直持有的

那如果数据库中有一个长事务,也就是开启之后一直没提交的事务,那当我们对表结构做变更操作的时候,可能就会出现一些问题:

  1. 线程A 开启了事务
  2. 线程A 执行了一条查询 select 语句,这时会加 MDL读锁
  3. 线程B 执行了一条查询 select 语句,这时不会阻塞,因为读读并不冲突
  4. 线程C 试图修改表字段,但此时事务A 并没有提交,所以 MDL 读锁就还占用着,此时线程C 就无法申请到 MDL 写锁,而导致阻塞
  5. 之后的线程 D、E、F 想要执行查询语句时,就会被阻塞住,然后数据库的线程就会爆满
    在这里插入图片描述

为什么写锁阻塞住,读锁也会被阻塞住?

因为 申请 MDL 锁的操作会形成一个队列,队列中 写锁获取的优先级高于读锁,一旦出现MDL 写锁等待,就会阻塞住该表的所有 CRUD 操作

在这里插入图片描述
所以,为了能安全的对表结构进行变更,在变更之前,我们要先看看数据库中的长事务,是否有事务已经对表加上了 MDL读锁,如果可以就考虑先 kill 掉他,然后再做变更。

意向锁

意向锁有两种:意向共享锁、意向排他锁:

  • 意向共享锁(Intention Shared Lock,IS):当一个事务请求获取一个行级锁或表级锁时,InnoDB会自动获取相应的表的意向锁。

这个锁表明事务打算以共享方式(读取)访问数据。其他事务在查询数据时,可以通过检查意向锁来快速确定是否有任何行级锁冲突,而无需检查表中每一行的锁状态。

  • 意向排他锁(Intention Exclusive Lock,IX):与意向共享锁类似,意向排他锁表明事务打算以排他方式(写入)访问数据。

当事务持有意向排他锁时,其他事务不能获取意向共享锁,从而防止其他读取操作的干扰。

也就是说,当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。

而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。

不过,select 也是可以对记录加共享锁和独占锁的,具体方式如下:

//先在表上加上意向共享锁,然后对读取的记录加共享锁 
select ... lock in share mode;  

//先表上加上意向独占锁,然后对读取的记录加独占锁 
select ... for update;

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(lock tables … read)和独占表锁(lock tables … write)发生冲突。

表锁和行锁是满足读读共享、读写互斥、写写互斥的。

如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。

那么有了「意向锁」,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。

所以,意向锁的目的是为了快速判断表里是否有记录被加锁。

AUTO-INC 锁

auto-inc 锁(AUTO-INCREMENT lock),是InnoDB中用于处理自动增长(AUTO_INCREMENT)字段的一种特殊的锁定机制。当向表中插入数据时,如果该表包含有AUTO_INCREMENT列,并且你需要为这个列指定一个值,那么会涉及到auto-inc锁。

auto-inc锁的主要目的是确保在同一事务中插入的记录能够获得连续的AUTO_INCREMENT值。在并发插入的场景中,这可以避免插入操作之间的干扰,确保插入的顺序性。

行级锁

InnoDB 是支持行级锁的,而 MyISAM 是不支持的

记录锁(Record Lock)

记录锁是行级锁的一种,它针对数据表中的单个记录进行锁定,有 S 锁 和 X 锁之分,也就是共享锁和独占锁之分。

一个事务对一条记录加了 S 锁,其他事务可以加 S 锁,但不能加 X 锁

一个事务对一条记录加了 X 锁,其他事务 S 锁和 X 锁都不能加

记录锁通常是通过索引项来实现的,即数据库会锁定索引项来实现对行的锁定。

在这里插入图片描述

间隙锁(Gap Lock)

Gap Lock是锁定数据表中两个记录之间的空隙(即间隙锁)。

间隙锁只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的问题。

它用于防止其他事务插入新的记录,从而保护一个范围行的完整性。

Gap Lock不会锁定实际的行记录,而是锁定一个范围,阻止其他事务在这个范围内插入新行。

而且,间隙锁之间是不冲突的,可以相互兼容,只不过无法往间隙范围内插入数据

在这里插入图片描述

临键锁(Next-Key Lock)

Next-Key Lock是 记录锁 和 间隙锁 的组合。

它技能保护锁住的该条记录,又能阻止其他事务将其新纪录插入到被保护记录前面的间隙中

在这里插入图片描述

因为临键锁是包含 记录锁 + 间隙锁的,而间隙锁之间是可以相互兼容的,但记录锁就需要注意考虑 S 型 和 X 型的关系了

插入意向锁

插入意向锁是与上面的间隙锁对应生效的

如果一条记录的位置已经被其他事务加了间隙锁(当然,也包含临键锁中的间隙锁),那么,这个插入操作就会被阻塞,在此期间会生成一个 插入意向锁,表明有事务想要在某个区间插入新纪录,但是现在处于等待状态

比如事务 A 在一个区间内加了间隙锁,然后事务 B 想要在该区间插入一条语句,这时候发现有个间隙锁,于是就会生成一个 插入意向锁,然后将锁呢设置为等待状态。

MySQL 加锁的时候,会先生成锁结构,然后设置锁状态,锁状态为正常状态,就表示事务获取到了锁,如果为等待状态,就意味着没有获取到锁

参考链接:

小林coding MySQL有哪些锁?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值