前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL锁概述

MySQL锁概述

作者头像
凯哥的Java技术活
发布2022-07-08 14:13:01
3680
发布2022-07-08 14:13:01
举报

✎ 编 者 按

本文介绍比较重要的一些锁,基于这些锁,对于理解MySQL的其他特性是大有帮助。部分锁例如AUT0-INC Locks有兴趣请自己发掘。

MySQL锁分类

  • S: Shared Locks,共享锁
  • X: Exclusive Locks,排他锁(独占锁)
  • Intention Locks,意向锁
  • Record Locks,记录锁
  • Gap Locks,间隙锁
  • Next-Key Locks,记录锁和这条记录之前的间隙锁
  • Insert intention Locks,插入意向锁

测试环境

MySQL 8.0版本

创建数据库:

代码语言:javascript
复制
CREATE DATABASE my_test

创建一个只有id字段的表t

代码语言:javascript
复制
CREATE TABLE t (id INT, PAIMARY KEY(id));

统一使用事务自动提交;

代码语言:javascript
复制
show variables like 'autocommit';

确认结果:

统一使用可重复读的隔离级别:

代码语言:javascript
复制
show variables like '%isolation%';

如果不一致,请修改,这也是MySQL默认的自动提交和隔离级别设置。

锁的位置

在介绍具体的锁之前,首先要注意MySQL是在不同的层有不同的锁。

应该见过MySQL的结构图:

那么应该知道MySQL有Server层和Engine层。

怎么查看每个层的锁?

Server层使用

代码语言:javascript
复制
SELECT * FROM performance_schema.metadata_locks \G;

Engine层使用

代码语言:javascript
复制
SELECT * FROM performance_schema.data_locks \G;

例如,当我们执行如下SQL,

代码语言:javascript
复制
LOCK TABLES t READ;

然后查看Server层的锁。

select * from performance_schema.data_locks \G;

可以看到在 LOCK TABLES t READ后,在server层有一个SHARED_READ_ONLY锁。

而在Engine层却没有锁。

知道了怎么查询server和engine的锁后,我们就可以开始我们的实验了!

Server Table Locks

Server Table Locks(Server 层 表级别锁)

执行如下三条SQL语句

按照我们预期,执行BEGIN后,显式开启了一个事务,然后我们执行了LOCK TABLES t READ,那么事务应该就会为t表加一个对应的锁。

但是我们查询,却发现是空的。

更诡异的是连事务都没有了!

(查询事务)

其实,这是因为Server层在执行LOCK TABLES 之前 自动进行一次COMMIT。

可以认为“LOCK TABLE + UNLOCK TABLE ”和“BEGIN + COMMIT”是互斥的。也就是说,LOCK TABLE 会自动结束掉事务,开始事务会自动UNLOCK TABLE。

InnoDB Table Locks

InnoDB Table Locks(InnoDB引擎的表级别锁)

由于lock tables会导致自动提交,而begin又会导致unlock tables。???

所以我们采用关闭自动提交的方式进行测试Table Locks。

1.关闭自动提交

代码语言:javascript
复制
SET AUTOCOMMIT = 0;

2.执行锁表语句

代码语言:javascript
复制
LOCK TABLE t READ, t1 WRITE

3.查看server锁

代码语言:javascript
复制
SELECT OBJECT_NAME,LOCK_TYPE,LOCK_STATUS,OWNER_THREAD_ID 
       FROM performance_schema.metadata_locks 
       WHERE OBJECT_SCHEMA='my_test' AND OBJECT_TYPE="TABLE";

结果如下:

4.查看Engine锁

代码语言:javascript
复制
SELECT 
         ENGINE_TRANSACTION_ID as trx_id,
         OBJECT_NAME as `table`,
         INDEX_NAME,
         LOCK_DATA,
         LOCK_MODE,
         LOCK_STATUS 
       FROM performance_schema.data_locks;

结果如下:

可以看到,我们得到了两种锁,Server级别的和Engine级别的。

我们关注点主要放在innodeb 引擎的锁:S , X

其中S 就是 Shared Locks,是一个共享锁

X 就是 Exclusive Locks,是一个排他锁

意向锁

Intention Locks(意向锁)

另一种更常见的为innodb engine表上锁的方式为

开启事务

代码语言:javascript
复制
BEGIN

插入数据

代码语言:javascript
复制
INSERT INTO t VALUES (100);

查询Innodb的表锁

代码语言:javascript
复制
SELECT 
         ENGINE_TRANSACTION_ID as trx_id,
         OBJECT_NAME as `table`,
         INDEX_NAME,
         LOCK_DATA,
         LOCK_MODE,
         LOCK_STATUS 
       FROM performance_schema.data_locks;

结果如下:

可以看到在Innodb引擎上,有一个IX 锁,也就是Intention Exclusive。在Server层也有一个SHARED_WRITRE

在这个事务中,我们继续操作,查询另一个表。

代码语言:javascript
复制
SELECT * FROM t1;

再次查询Innodb的表锁,发现没有任何变化。

只是会在Server层有锁。也就是说普通的SELECT语句不会为Engine层加锁,只会在Server层加一个SHARED_READ锁。这条查询在Server层就被保护了起来。

但是如果用SELECT ... FOR SHARE/UPDATE的话就不一样了。

继续执行如下SQL语句

代码语言:javascript
复制
SELECT * FROM t1 FOR SHARE

就会发现Innodb为t1加了IS锁,Intention shared locks.

表锁先说到这里,我们暂时梳理一下各个锁之间的关系

↓my request \ held by other→

X

S

IX

IS

X

S

IX

IS

  • 当表存在X锁时,所有其他的锁都需要等待。
  • 当存在S锁时,X,IX需要等待,S与其理解为共享,倒不如理解为保护读。只允许共享读,其他的有关X的,全都排斥掉。
  • 当存在IX锁时,X和S需要等待
  • 当存在IS锁时,X需要等待

Record Locks

Record Locks(记录级别锁)

Innodb engine有许多关于record locks。

例如一个表的记录为10,20,30。

记录就是如下,每条记录以及之前的间隙。

  • S,REC_NOT_GAP。共享的行锁,不包含间隙
  • X,REC_NOT_GAP。独占的行锁,不包含间隙
  • S,GAP。共享锁,某条记录之前的间隙
  • X,GAP。独占锁,某条记录之前的间隙
  • S。可以认为是S,REC_NOT_GAP + S,GAP
  • X。可以认为是X,REC_NOT_GAP + X,GAP
  • X,GAP,INSERT_INTENTION。间隙插入意向锁,某行之前的的间隙。能够与其他插入锁兼容
  • X,INSERT_INTENTION。插入意向锁。可以理解为锁住最后一条记录之后的间隙。

他们的兼容关系如下:

S,REC_NOT_GAP

X,REC_NOT_GA

*,GAP

S

X

*,INSERT_INTENTION

S,REC_NOT_GAP

X,REC_NOT_GAP

*,GAP

S

X

*,INSERT_INTENTION

如果某个记录存在S,REC_NOT_GAP,那么就是这行记录不能被修改,可以共享读。所以他与该记录的X互斥。

如果某个记录存在X,REC_NOT_GAP,那么就是这行记录被独占。所以与任何其他的操作本记录的锁互斥。

综合举例

假如我们在一个事务中执行如下SQL语句,那么现在的Server和Engine都存在什么锁呢?

代码语言:javascript
复制
BEGIN;

SELECT * FROM t FOR SHARE;

DELETE FROM t WHERE id=6;
INSERT INTO t VALUES (4);

首先是查询

代码语言:javascript
复制
SELECT * FROM t FOR SHARE;

按照我们猜想,应该是Server层有一个SHARED_READ锁,然后每个记录都有一个S锁。

然后DELETE,首先肯定是在表上加一个IX锁,声明下面有工人在干活了(删除记录),然后记录级别的话应该是有一个X,NOT GAP的锁的,因为我们是针对这个记录本身删除的,应该会有一个X锁,不包括间隙。

然后INSERT,首先发现表上有IX锁,OK,应该是有一个和DELETE差不多,怎么着也得有一个X锁。

结果如下:

发现结果有点不对劲。

S倒是比较容易解释,因为我们执行了SELECT * FROM t FOR SHARE

删除的记录上有X REC_NOT_GAP,也说的通。

为什么新插入的记录(5)是S,GAP锁呢?

这是因为,在插入之前,5后面本来是200,本来就有空隙,是S,GAP锁,插入5之后,5之前肯定也要产生新的间隙,那么就是从200继承过来的S,GAP了。

那么5这条记录本身为什么没有任何的锁呢?

其实在添加完后,会更新该记录的DB_TRX_ID,暂时理解成为这个事务对这个记录的声明的锁。

TODO

本文测试了几波关于Server和Engine锁,介绍了如何查看锁,以及锁的互斥性。篇幅原因先到这里。接下来的文章,会结合隔离级别结合理解下MySQL的隔离级别和锁是怎么组合工作的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 凯哥的Java技术活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档