select * from t where id = 1 for update 锁分析

01 数据初始化

1.1 创建表

sql 复制代码
CREATE TABLE `tb_account` (

`id` bigint(20) NOT NULL,

`user_id` bigint(20) NOT NULL,

`account_type` int(11) NOT NULL,

PRIMARY KEY (`id`),

KEY `idx_user_id_account_type` (`user_id`,`account_type`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

1.2 初始化数据

sql 复制代码
INSERT INTO `tb_account` (`id`, `user_id`, `account_type`)

VALUES

(1, 1239095, 32);

INSERT INTO `tb_account` (`id`, `user_id`, `account_type`)

VALUES

(3, 121123, 4);

INSERT INTO `tb_account` (`id`, `user_id`, `account_type`)

VALUES

(4, 123123, 8);

1.3 开启锁监控

sql 复制代码
set GLOBAL innodb_status_output=ON;

set GLOBAL innodb_status_output_locks=ON;

02 事务隔离级别为读已提交

2.1 聚簇索引查询一条存在的记录

开始事务,查询一条存在的记录

sql 复制代码
select * from tb_account where id = 1 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TABLE LOCK table mall.tb_account trx id 1871 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
trx id 1871 lock_mode X locks rec but not gap Record lock 表明该语句加了一个 X 型的记录锁

2.2 聚簇索引查询一条不存在的记录

开始事务,查询一条不存在的记录

sql 复制代码
select * from tb_account where id = 2 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TTABLE LOCK table mall.tb_account trx id 1972 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
0 row lock(s) 表明聚簇索引查询不存在记录时是不加锁的

2.3 二级索引查询一条存在的记录

开始事务,查询一条存在的记录

sql 复制代码
select * from tb_account where user_id = 1239095 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TABLE LOCK table mall.tb_account trx id 1973 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
index idx_user_id_account_type of table mall.tb_account trx id 1973 lock_mode X locks rec but not gap 表明该语句在二级索引上加了一个 X 型的记录锁
index PRIMARY of table mall.tb_account trx id 1973 lock_mode X locks rec but not gap 表明该语句在聚簇索引上加了一个 X 型的记录锁,这是由于对于 for update 来说,系统会认为接下来还会更新数据,会顺便在聚簇索引上加锁的

2.4 二级索引查询一条不存在的记录

开始事务,查询一条不存在的记录

sql 复制代码
select * from tb_account where user_id = 1232112 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TABLE LOCK table mall.tb_account trx id 1974 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
0 row lock(s) 表明聚簇索引查询不存在记录时是不加锁的

2.5 无索引查询一条存在的记录

sql 复制代码
select * from tb_account where account_type = 8 for update;

查看执行记录如下图:

根据执行记录可以得到此查询走的全二级索引扫描

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

index idx_user_id_account_type of table mall.tb_account trx id 1976 lock_mode X locks rec but not gap 表明该语句在二级索引上加了一个 X 型的记录锁
index PRIMARY of table mall.tb_account trx id 1976 lock_mode X locks rec but not gap 表明该语句在聚簇索引上加了一个 X 型的记录锁

2.6 无索引查询一条不存在的记录

开始事务,查询一条不存在的记录

sql 复制代码
select * from tb_account where account_type = 25 for update;

查看执行记录如下图:

根据执行记录可以得到此查询走的全二级索引扫描

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TABLE LOCK table mall.tb_account trx id 1979 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
0 row lock(s) 表明二级索引查询不存在记录时是不加锁的

03 事务隔离级别为可重复读

3.1 聚簇索引查询一条存在的记录

sql 复制代码
select * from tb_account where id = 1 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TABLE LOCK table mall.tb_account trx id 1875 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
index PRIMARY of table mall.tb_account trx id 1875 lock_mode X locks rec but not gap Record lock 表明该语句是在主键索引上加了一个 X 型的记录锁
heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

在唯一确定并且存在的记录下,可重复读和读已提交都是在该记录上加 X 型记录锁

3.2 聚簇索引查询一条不存在的记录

sql 复制代码
select * from tb_account where id = 2 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TABLE LOCK table mall.tb_account trx id 1876 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
index PRIMARY of table mall.tb_account trx id 1876 lock_mode X locks gap before rec Record lock 表明该语句是在主键索引上加了间隙锁

3.3 二级索引查询一条存在的记录

sql 复制代码
select * from tb_account where user_id = 1239095 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TABLE LOCK table mall.tb_account trx id 1893 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
RECORD LOCKS space id 27 page no 4 n bits 72 index idx_user_id_account_type of table mall.tb_account trx id 1896 lock_mode X

Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0

0: len 8; hex 73757072656d756d; asc supremum;;

supremum 指得是页里面的最后一条记录(伪记录,通过select查不到的,并不是真实的记录),heap no=1 , Infimum 表示的是页里面的第一个记录(伪记录)

可以简单的认为:

supremum 为upper bounds,正去穷大

Infimum 为Minimal bounds,负无穷大

index idx_user_id_account_type of table mall.tb_account trx id 1893 lock_mode X Record lock; 表明该语句是在主键索引上加了一个 X 型的 next key lock (记录锁 + gap 锁),gap 锁的范围是 1239095 索引列到正无穷
index PRIMARY of table mall.tb_account trx id 1896 lock_mode X locks rec but not gap

Record lock 假如由于查询的值在索引中覆盖了,是不会访问聚簇索引,所以不会在聚簇索引上加记录锁的,但对于 for update 来说,系统会认为接下来还会更新数据,会顺便在聚簇索引上加锁的

查询非最后一条记录时:

sql 复制代码
select * from tb_account where user_id = 123123 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

对比发现:这次多了一个间隙锁 `trx id 1895 lock_mode X locks gap before rec

Record lock`

非唯一索引的情况下,innodb 引擎会扫描下一条数据来判断是否符合条件,由于扫描到的数据都会加上 next key lock,由于不符合 next lock 会退化成间隙锁

3.4 二级索引查询一条不存在的记录

sql 复制代码
select * from tb_account where user_id = 1232112 for update;

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

TABLE LOCK table mall.tb_account trx id 1898 lock mode IX 表明该表上面有一个表锁,这个锁的模式为IX(排他意向锁)
RECORD LOCKS space id 27 page no 4 n bits 72 index idx_user_id_account_type of table mall.tb_account trx id 1898 lock_mode X locks gap before rec

Record lock 表明该语句是在二级索引上加了一个间隙锁

3.5 无索引查询一条存在的记录

sql 复制代码
select * from tb_account where account_type = 8 for update;

查看执行记录如下图:

根据执行记录可以得到此查询走的全二级索引扫描

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

RECORD LOCKS space id 27 page no 4 n bits 72 index idx_user_id_account_type of table mall.tb_account trx id 1899 lock_mode X 下面具体的锁记录 4 条,表明在二级索引上都加了 next key 锁
RECORD LOCKS space id 27 page no 3 n bits 72 index PRIMARY of table mall.tb_account trx id 1899 lock_mode X locks rec but not gap 下面具体的锁记录 3条,表明在聚簇索引上都加了记录锁

3.6 无索引查询一条不存在的记录

sql 复制代码
select * from tb_account where account_type = 25 for update;

查看执行记录如下图:

根据执行记录可以得到此查询走的全二级索引扫描

执行 show engine innodb status\G; 查看数据库锁状态,结果如下图:

二级索引上都加了 next key 锁,聚簇索引上只加了记录锁

04 总结

  1. Read Commit 隔离级别下,执行 select * from t where id = 1 for update 语句,当存在id 等于 1 的记录时,加 X 型记录锁,不存在不加锁,当id 为二级索引的时候,还是给该记录对应的聚簇索引加上 X 型的记录锁

  2. Read Commit 隔离级别下,执行 select * from t where id = 1 for update 语句,

  3. Repeatable Read 隔离级别下,执行 select * from t where id = 1 for update 语句时,

3.1 对于唯一索引或聚簇索引

当存在 id 等于 1 的记录时,加 next key lock。

不存在的话会加间隙锁。

3.2 对于二级索引

当存在 id 等于 1 的记录时,会在二级索引上加 next key lock,非唯一索引的情况下,innodb 引擎会扫描下一条数据来判断是否符合条件,由于扫描到的数据都会加上 next key lock,由于不符合条件 next lock 会退化成间隙锁,并且给该记录对应的聚簇索引加上 X 型的记录锁。

当不存在 id 等于 1的记录时,会在所有扫描到的二级索引上加 next key lock,并且给对应记录的聚簇索引加上 X 型的记录锁。

3.3 对于无索引

假如走的是二级索引全表扫描会在二级索引上都加了 next key 锁,聚簇索引上只加了记录锁,否则在主键索引上加 next key 锁。

相关推荐
FinTech老王23 分钟前
集中式 vs 分布式数据库:金融用户如何选择?——金仓数据库的双架构实践与选型指南
数据库·分布式·金融
q***925130 分钟前
MySQL 启动失败 (code=exited, status=1FAILURE) 异常解决方案
数据库·mysql
Leon-Ning Liu33 分钟前
Oracle Data Guard Broker RedoRoutes 属性配置文档
数据库·oracle
JIngJaneIL1 小时前
远程在线诊疗|在线诊疗|基于java和小程序的在线诊疗系统小程序设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序·毕设·在线诊疗小程序
川石课堂软件测试2 小时前
自动化过程中验证码的解决思路
数据库·python·功能测试·测试工具·单元测试·tomcat·自动化
IT利刃出鞘2 小时前
WordPress插件--Redis Object Cache对象缓存插件的用法
数据库·redis·缓存
面向星辰2 小时前
sql通配符(大量查找搜索索引)
数据库·sql
斐硕人2 小时前
SQL滚动求和
数据库·sql·mysql·maxcompute
爬山算法3 小时前
Redis(135)Redis的网络模型是什么?
网络·数据库·redis
L.EscaRC3 小时前
Redis大Key与内存不足问题深度解析与应对策略
数据库·redis·缓存