MySQL查看事务与锁

目录

[1. MySQL如何查看事务与锁🔒](#1. MySQL如何查看事务与锁🔒)

[2. 不同事务隔离级别下的锁类型验证](#2. 不同事务隔离级别下的锁类型验证)

[2.1 RC级别下的验证](#2.1 RC级别下的验证)

[2.1.1 共享锁(S锁)](#2.1.1 共享锁(S锁))

[2.1.2 排它锁(X锁🔒)](#2.1.2 排它锁(X锁🔒))

[1. Update操作](#1. Update操作)

[2. Delete操作](#2. Delete操作)

[3. 隐式锁和RC级别没有Gap锁的验证(Select...for update与Insert)](#3. 隐式锁和RC级别没有Gap锁的验证(Select...for update与Insert))

[2.2 RR级别下的验证](#2.2 RR级别下的验证)

[2.2.1 共享锁(S锁)](#2.2.1 共享锁(S锁))

[2.2.2 排它锁(X锁🔒)](#2.2.2 排它锁(X锁🔒))

[1. Update操作](#1. Update操作)

[2. Delete操作](#2. Delete操作)

[3. RR级别Next-Key锁验证](#3. RR级别Next-Key锁验证)


推荐先看一下这篇文章:MySQL-InnoDB锁、事务与MVCC


1. MySQL如何查看事务与锁🔒

sql 复制代码
-- 1. 查看当前运行的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

-- 2. 查看锁信息
-- 2.1 MySQL 5.7
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

-- 2.2 MySQL 8.0+
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;

从【MySQL-InnoDB锁、事务与MVCC】文章可以看出来,锁有下面几种分类:

  • 按锁模式分为:共享锁(S锁)、排它锁(X锁)。
  • 按锁粒度分为:表锁、行锁。
  • 表锁分为 :表级S锁、表级X锁、表级IS锁(意向共享锁)、表级IX锁(意向排它锁)。
    • IS锁和IX锁的目的 :只是为了后续 在加表级别的S锁和X锁时 判断 表中是否有已经被加锁的 记录,以避免用遍历的方式来查看表中有没有上锁的记录。
  • 行锁分为记录锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next-key Lock,Gap锁 + 记录锁)、隐式锁

对于MySQL的InnoDB存储引擎来说,不同事务隔离级别下使用的锁不同

  • READ COMMITTED(RC,读已提交)只有 记录锁(Record Lock),没有 间隙锁(Gap Lock)或 临键锁(Next-Key Lock)
  • REPEATABLE READ(RR,可重复读)默认使用 Next-Key Lock(记录锁+间隙锁),注意这里说的是【默认】,并不意味着RR级别下使用行级锁时全都用的是 Next-Key。

2. 不同事务隔离级别下的锁类型验证

以下测试基于MySQL_8.0.30

用来验证的cus_info表结构:

sql 复制代码
-- test.cus_info definition

CREATE TABLE `cus_info` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cus_id` varchar(21) NOT NULL COMMENT '客户编号',
  `phone_num` varchar(11) NOT NULL COMMENT '手机号',
  `nick_name` varchar(20) NOT NULL COMMENT '昵称',
  `sex` varchar(1) NOT NULL COMMENT '性别,0-女,1-男',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_ci` (`cus_id`) USING BTREE, -- 二级索引:唯一索引
  UNIQUE KEY `uk_pn` (`phone_num`) USING BTREE -- 唯一索引
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='客户信息表';

-- 插入语句
INSERT INTO cus_info (cus_id, phone_num, nick_name, sex) VALUES('200606011708203560002', '10000000002', '盖世无双', '0');

-- 更新语句
UPDATE cus_info SET cus_id='200606011708203560002', phone_num='10000000002', nick_name='盖世无双', sex='0' WHERE id=2;

-- 查询语句
SELECT id, cus_id, phone_num, nick_name, sex FROM cus_info WHERE id=2;

表数据如下:id(1, 2, 3, 5, 7, 8)


2.1 RC级别下的验证

先查看一下事务隔离级别:

sql 复制代码
-- 切换到test库
mysql> use test;

-- 查询事务隔离级别
mysql> show variables like '%isolation%';
+-----------------------+----------------+
| Variable_name         | Value          |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set (0.00 sec)

2.1.1 共享锁(S锁)

普通的select语句,InnoDB存储引擎不会加任何锁

sql 复制代码
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

-- 普通的select语句,InnoDB存储引擎不会加任何锁。可以通过 performance_schema.data_locks表查看。
mysql> select * from cus_info where id > 2 and id < 7;
-- 查询结果展示省略

-- 加 共享锁(S锁)
mysql> select * from cus_info where id > 2 and id < 7 for share;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  3 | 200606011708203560003 | 10000000003 | 默默      | 0   |
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
+----+-----------------------+-------------+-----------+-----+
2 rows in set (0.00 sec)

查询锁信息:performance_schema.data_locks

sql 复制代码
select `engine`, object_schema, object_name, index_name, lock_type, lock_mode, lock_status, lock_data from performance_schema.data_locks;

可以看到:

① 给表cus_info加了 表级别的IS锁(意向共享锁)。

② 给id(主键)为3和5的两条记录加了 Record Lock(记录锁)、S锁(共享锁),并且不是Gap锁。

sql 复制代码
-- 测试完后提交或回滚事务
mysql> rollback; -- 或者 commit
Query OK, 0 rows affected (0.00 sec)

2.1.2 排它锁(X锁🔒)

sql 复制代码
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

开启事务后,我们先来看一下 information_schema库的INNODB_TRX视图信息,可以看到没有任何数据,由此也可以看出来 start transaction; 开启一个事务并没有让 MySQL真正开始分配事务编号(trx_id)

sql 复制代码
mysql> select * from information_schema.INNODB_TRX;
Empty set (0.00 sec)

再执行一个 普通的select语句,再看看 INNODB_TRX 视图信息:

trx_id(事务编号)、trx_state(事务状态,running运行中)、trx_started(事务开始时间)、trx_isolation_level(事务隔离级别)。注意trx_id(事务编号)的值,等下看看会发生什么变化。此时还没有加任何锁,所以 performance_schema.data_locks表中没有任何信息。

sql 复制代码
mysql> select * from cus_info where id > 2 and id < 7;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  3 | 200606011708203560003 | 10000000003 | 默默      | 0   |
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
+----+-----------------------+-------------+-----------+-----+
2 rows in set (0.00 sec)

-- 这里只查看事务的部分信息(INNODB_TRX视图列太多了,只展示现在需要的)
mysql> select trx_id, trx_state, trx_started, trx_isolation_level from information_schema.INNODB_TRX;
+-----------------+---------------------+-----------+---------------------+---------------------+
| trx_id          | trx_mysql_thread_id | trx_state | trx_started         | trx_isolation_level |
+-----------------+---------------------+-----------+---------------------+---------------------+
| 284035063683520 |                   8 | RUNNING   | 2025-12-28 17:28:27 | READ COMMITTED      |
+-----------------+---------------------+-----------+---------------------+---------------------+
1 row in set (0.00 sec)

mysql> select * from performance_schema.data_locks;
Empty set (0.00 sec)
1. Update操作
sql 复制代码
mysql> update cus_info set sex=1 where id=5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 查看 information_schema.INNODB_TRX
mysql> select trx_id, trx_mysql_thread_id, trx_state,
    -> trx_started, trx_isolation_level from information_schema.INNODB_TRX;
+--------+---------------------+-----------+---------------------+---------------------+
| trx_id | trx_mysql_thread_id | trx_state | trx_started         | trx_isolation_level |
+--------+---------------------+-----------+---------------------+---------------------+
|  20301 |                   8 | RUNNING   | 2025-12-28 17:28:27 | READ COMMITTED      |
+--------+---------------------+-----------+---------------------+---------------------+
1 row in set (0.00 sec)

查看 information_schema.INNODB_TRX,发现 trx_id 发生了变化。

查看锁信息:performance_schema.data_locks,可以看到:

① 给表cus_info加了 表级别的IX锁(意向排他锁)。

② 给id(主键)为5的记录加了 Record Lock(记录锁)、X锁(排他锁),并且不是Gap锁。

2. Delete操作

接着上面的继续执行

sql 复制代码
mysql> delete from cus_info where id=3;
Query OK, 1 row affected (0.00 sec)

查看锁信息:performance_schema.data_locks

为了不影响下面的 select ... for update; 这里我们先回滚事务。

sql 复制代码
-- 为了不影响下面的 select ... for update; 这里我们先回滚事务
mysql> rollback; -- 或者 commit
Query OK, 0 rows affected (0.00 sec)
3. 隐式锁和RC级别没有Gap锁的验证(Select...for update与Insert)

1)、开启事务1,并执行 select...for update 操作:

sql 复制代码
-- 开启事务1(spring框架中的事务管理器 DataSourceTransactionManager 就是这么开启一个事务的)
mysql> set autocommit = OFF;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from cus_info where id > 3 and id < 8 for update;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
|  7 | 200606011708203560007 | 10000000007 | 赵        | 1   |
+----+-----------------------+-------------+-----------+-----+
2 rows in set (0.00 sec)

查看锁信息如下 :只对(3, 8)这个区间里面 已存在的数据(id = 5 和 id = 7)进行加锁操作,不会****对(3, 8)这个区间范围本身加锁。注意看这里加的 不是Gap锁,而是 行级排它锁。

这次,我们多查看一个列(engine_transaction_id,事务编号),当前事务1的事务编号为20307

2)、开启事务2 ,执行 insert 操作,可以看到 insert 成功了,说明事务1确实没有对(3, 8)这个区间范围本身加锁。由此可以说明,RC事务隔离级别下对于表中的记录 没有 Gap锁(间隙锁),只有 记录锁(Record Lock)

sql 复制代码
-- 1. 开启事务2
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

-- 2. 在insert前先select验证一下
mysql> select * from cus_info where id > 3 and id < 8;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
|  7 | 200606011708203560007 | 10000000007 | 赵        | 1   |
+----+-----------------------+-------------+-----------+-----+
2 rows in set (0.00 sec)

-- 3. insert id = 6 的记录成功了,说明事务1确实没有对(3, 8)这个区间范围本身加锁。
mysql> INSERT INTO cus_info (id, cus_id, phone_num, nick_name, sex) VALUES(6, '200606011708203560006', '10000000006', '阿文', '0');
Query OK, 1 row affected (0.00 sec)

-- 4. 再次select验证insert是否成功
mysql> select * from cus_info where id > 3 and id < 8;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
|  6 | 200606011708203560006 | 10000000006 | 阿文      | 0   |
|  7 | 200606011708203560007 | 10000000007 | 赵        | 1   |
+----+-----------------------+-------------+-----------+-----+
3 rows in set (0.00 sec)

我们再次来看 performance_schema.data_locks 锁信息,可以看到事务2(事务编号为20313)只有【1条】表级别的 IX锁(意向排它锁),并没有对 id = 6 的记录加任何锁(实际上,在MySQL的内存中,也就是在 Buffer Pool(缓冲池)的 数据页(页是MySQL中内存和磁盘进行数据交互的基本单位,页分为数据页、undo log页、redo log页[block]等等)上有一条 id=6的记录,并且该记录有一个【隐藏列 trx_id(事务编号)】,取值为 20313

3)、再回到事务1,执行 普通select 与 update操作(这里我们就更新 事务2插入的 id=6 的新纪录)。

① 先执行普通的 select语句,可以得出结论:事务1确实看不到事务2刚刚插入的id=6的新纪录(因为事务还没有commit,RC级别下,一个事务不会读取到另一个事物未提交的数据);

② 看是看不到(因为事务隔离级别是 RC,读已提交),但是更新操作却会阻塞,直到等待超时。

sql 复制代码
-- 1. 先执行普通的 select语句,可以得出结论:事务1确实看不到事务2刚刚插入的id=6的新纪录(因为事务还没有commit)
mysql> select * from cus_info where id > 3 and id < 8;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
|  7 | 200606011708203560007 | 10000000007 | 赵        | 1   |
+----+-----------------------+-------------+-----------+-----+
2 rows in set (0.00 sec)

-- 2. 看是看不到(因为事务隔离级别是 RC,读已提交),但是更新操作却会阻塞,直到等待超时。
mysql> update cus_info set sex=1 where id=6;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

我们 在update语句报错(报出 Lock wait timeout) 之前 再来查看锁信息,可以看到:

① 事务2给 id = 6 的记录 加了 行级排它锁(X锁,不是Gap锁);

② 事务1的update语句因为要获取 事务2持有的锁,所以进入 waiting(等待状态)。

之所以会这样,是因为 insert语句 一开始给记录加的是【隐式X锁】,当update执行的时候,它会去表的 主键索引 对应的 B+树 去搜索,发现 id=6 的记录的 隐藏列trx_id=20313(事务2的事务编号),并且事务编号为20313的事务处于活跃状态(即,事务没有提交,information_schema.INNODB_TRX表中 trx_id=20313 的 trx_state(事务状态)为 running),这个时候就会给 performance_schema.data_locks 添加两条记录(即①、②)。

处于等待状态的锁++要么在等待超时后死亡;要么在超时前事务2提交成功后获取到锁++

一个事务对新插入的记录可以不显式的加锁,但是由于【事务id】的存在,相当于加了一个【隐式锁】。其他事务对这条记录加S锁或者X锁时,由于隐式锁的存在,会先帮助当前事务生成一把锁,然后自己再生成一把锁并进入等待状态


2.2 RR级别下的验证

修改事务隔离级别:

sql 复制代码
-- 1. 设置事务隔离级别为:可重复读
mysql> set transaction_isolation = 'REPEATABLE-READ';
Query OK, 0 rows affected (0.00 sec)

-- 2. 查看设置是否成功
mysql> show variables like '%isolation%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)

表数据如下:id(1, 2, 3, 5, 7, 8)

2.2.1 共享锁(S锁)

sql 复制代码
-- 1. 开启事务
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

-- 2. 共享锁,select...for share
mysql> select * from cus_info where id > 2 and id < 7 for share;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  3 | 200606011708203560003 | 10000000003 | 默默      | 0   |
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
+----+-----------------------+-------------+-----------+-----+
2 rows in set (0.00 sec)

查看锁信息:performance_schema.data_locks

可以看到:

① 给表cus_info加了 表级别的IS锁(意向共享锁)。

② 加 Next-key Lock(临键锁,记录锁 + 间隙锁),给区间(2, 7)和 记录7 加锁。

sql 复制代码
-- 测试完后提交或回滚事务
mysql> rollback; -- 或者 commit
Query OK, 0 rows affected (0.00 sec)

2.2.2 排它锁(X锁🔒)

1. Update操作
sql 复制代码
-- 1. 开启事务
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

-- 2. 更新
mysql> update cus_info set sex=1 where id=5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

查看锁信息:performance_schema.data_locks。

注意看,这里加的行级锁为:X锁(排它锁)、not gap(不是gap锁)。唯一索引的等值查询(包括主键索引、唯一二级索引),Next-Key锁会退化为记录锁

sql 复制代码
-- 3. 根据 phone_num(唯一索引)更新
mysql> update cus_info set sex=1 where phone_num='10000000005';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

再次查看锁信息:performance_schema.data_locks。注意看 index_name:uk_pn、primary

sql 复制代码
-- 4. 更新区间 (3, 8)
mysql> update cus_info set sex=0 where id>3 and id<8;
Query OK, 1 row affected (0.00 sec)
Rows matched: 2  Changed: 1  Warnings: 0

再次查看锁信息:performance_schema.data_locks。这里又是对 (3, 8] 这个区间加 Next-Key Lock(临键锁,记录锁 + 间隙锁)。

2. Delete操作
sql 复制代码
mysql> delete from cus_info where id=3;
Query OK, 1 row affected (0.00 sec)

查看锁信息:performance_schema.data_locks

注意看,这里加的行级锁为:X锁(排它锁)、not gap(不是gap锁)。

sql 复制代码
-- 为了不影响下面的 select ... for update; 这里我们先回滚事务
mysql> rollback; -- 或者 commit
Query OK, 0 rows affected (0.00 sec)
3. RR级别Next-Key锁验证

1)、开启事务1,并执行 select...for update 操作:

sql 复制代码
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from cus_info where id > 3 and id < 8 for update;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
|  7 | 200606011708203560007 | 10000000007 | 赵        | 1   |
+----+-----------------------+-------------+-----------+-----+
2 rows in set (0.00 sec)

查看锁信息:performance_schema.data_locks。给区间(3, 8)和 记录8 加锁,即给区间 (3, 8] 加了锁。事务1的编号为20320

2)、开启事务2,执行 insert 操作,可以看到 insert 失败了,说明事务1确实对(3, 8] 这个区间范围本身加了 gap锁+记录锁。

sql 复制代码
-- 1. 开启事务2
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

-- 2. insert前先执行 普通select查询看看
mysql> select * from cus_info where id > 3 and id < 8;
+----+-----------------------+-------------+-----------+-----+
| id | cus_id                | phone_num   | nick_name | sex |
+----+-----------------------+-------------+-----------+-----+
|  5 | 200606011708203560005 | 10000000005 | 王        | 0   |
|  7 | 200606011708203560007 | 10000000007 | 赵        | 1   |
+----+-----------------------+-------------+-----------+-----+
2 rows in set (0.00 sec)

-- 3. insert id=6的记录 失败,因为事务1给 (3, 8] 区间加了 gap锁+记录锁。
mysql> INSERT INTO cus_info (id, cus_id, phone_num, nick_name, sex) VALUES(6, '200606011708203560006', '10000000006', '阿文', '0');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

我们 在insert语句报错(报出 Lock wait timeout) 之前 再来查看锁信息,可以看到:事务2(事务编号为20321)生成的行锁为:X锁(排它锁)、Gap(间隙锁)、insert intention(插入意向锁),并处于 waiting 状态。

相关推荐
TDengine (老段)2 小时前
从“被动养护”到“主动预警”,TDengine IDMP 让智慧桥梁靠数据“说话”
大数据·数据库·人工智能·物联网·时序数据库·tdengine·涛思数据
白日做梦Q2 小时前
【MySQL】9.吃透关键SQL语法:从正则表达式、窗口函数、条件函数到结果集合并的实战拆解
数据库·sql·mysql·正则表达式
likuolei2 小时前
正则表达式 - 元字符
数据库·mysql·正则表达式
侧耳倾听1112 小时前
mysql中的binlog-介绍
数据库·mysql
少云清2 小时前
【接口测试】4_PyMySQL模块 _操作数据库
服务器·网络·数据库
IndulgeCui2 小时前
Kingbase-金仓企业级统一管控平台KEMCC一键部署主备集群及转换读写分离集群
数据库
数据库生产实战2 小时前
Oracle升级避坑指南:APEX卸载后sys.htmldb_system无效对象的处理方法
数据库·oracle
冰冰菜的扣jio2 小时前
SQL语句是如何在MySQL中执行的
数据库·sql
么么...2 小时前
掌握 MySQL:数据类型、数据定义语言DDL、数据操作语言DML
数据库·经验分享·sql·mysql