MySQL锁机制详解,行锁表锁间隙锁到底怎么回事

并发更新数据库的时候,你有没有遇到过"锁等待超时"或者死锁?

MySQL的锁机制是保证并发安全的核心,但也是出了名的复杂。今天把行锁、表锁、间隙锁这些概念一次讲清楚。

先分清:存储引擎决定锁类型

MySQL的锁机制和存储引擎密切相关:

存储引擎 支持的锁 说明
InnoDB 行锁 + 表锁 默认行锁,支持事务
MyISAM 只有表锁 不支持行锁,不支持事务

现在基本都用InnoDB了,所以今天主要聊InnoDB的锁。

InnoDB的行锁:基于索引

InnoDB的行锁不是真的"锁行",而是锁索引。如果查询没有走索引,行锁会退化成表锁。

sql 复制代码
-- 假设id是主键
-- 走索引,只锁id=1这一行
BEGIN;
UPDATE users SET name = '李四' WHERE id = 1;
-- 其他事务可以正常操作其他行

-- 假设name没有索引
-- 不走索引,锁全表!
BEGIN;
UPDATE users SET age = 26 WHERE name = '张三';
-- 其他事务的UPDATE都会被阻塞

教训:UPDATE和DELETE一定要走索引,否则行锁变表锁。

锁的类型

InnoDB有几种关键的锁模式:

共享锁(S锁):读锁。多个事务可以同时持有。

sql 复制代码
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 其他事务可以加S锁读,但不能加X锁写

排他锁(X锁):写锁。独占,其他事务不能加任何锁。

sql 复制代码
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 或任何UPDATE/DELETE操作
-- 其他事务不能读也不能写
对比 S锁(共享) X锁(排他)
读-读 兼容 不兼容
读-写 兼容 不兼容
写-写 不兼容 不兼容

间隙锁(Gap Lock)

间隙锁是InnoDB在REPEATABLE READ隔离级别下的特殊锁------它锁的不是已有的数据行,而是索引之间的间隙。

sql 复制代码
-- 表中有 id = 1, 5, 10 三条记录
-- 执行范围查询
SELECT * FROM users WHERE id BETWEEN 1 AND 5 FOR UPDATE;

-- 锁住了:
-- id=1 这一行(记录锁)
-- id=5 这一行(记录锁)
-- id在(1,5)之间的间隙(间隙锁)
-- 甚至 id>5 到下一个记录之前的间隙

间隙锁的作用是防止幻读------阻止其他事务在间隙中插入新记录。

sql 复制代码
-- 事务A
SELECT * FROM users WHERE id BETWEEN 1 AND 5 FOR UPDATE;

-- 事务B(会被阻塞)
INSERT INTO users (id, name) VALUES (3, '王五'); -- 阻塞!因为在间隙中

死锁:两个事务互相等待

sql 复制代码
-- 事务A                          -- 事务B
UPDATE users SET age=26           UPDATE users SET age=27
  WHERE id=1;                       WHERE id=2;
                                  -- 事务A锁了id=1,事务B锁了id=2

UPDATE users SET age=26           UPDATE users SET age=27
  WHERE id=2;                       WHERE id=1;
-- 事务A等id=2(被事务B锁住)      -- 事务B等id=1(被事务A锁住)
-- 死锁!

MySQL有死锁检测机制,发现后会自动回滚其中一个事务(选择回滚代价小的那个)。

sql 复制代码
-- 查看死锁信息
SHOW ENGINE INNODB STATUS;

-- 配置死锁检测
SET GLOBAL innodb_deadlock_detect = ON;  -- 默认开启
SET GLOBAL innodb_lock_wait_timeout = 10; -- 锁等待超时(秒)

减少锁冲突的实践建议

建议 说明
索引必加 UPDATE/DELETE走索引,避免行锁变表锁
事务尽量短 减少持锁时间
按固定顺序访问 所有事务按相同顺序操作资源,避免死锁
降低隔离级别 READ COMMITTED没有间隙锁,并发度更高
乐观锁替代 用版本号代替悲观锁,减少锁冲突

乐观锁示例:

sql 复制代码
-- 不加锁,用版本号控制
UPDATE users SET age = 26, version = version + 1
WHERE id = 1 AND version = 1;
-- 如果version已经不是1了,说明被别人改过了,更新失败

MySQL锁机制是并发编程中容易出问题的地方。理解了行锁的本质(基于索引)、间隙锁的作用(防幻读)、死锁的产生条件,就能排查大部分锁相关的问题。

你们项目里遇到过锁等待或死锁的问题吗?

相关推荐
宇宙无敌暴龙战士6669 分钟前
MySQL 8 库表操作学习笔记:SQL 基础、库表管理、约束与用户授权全流程
数据库·sql·mysql·学习笔记·后端开发
猿究院_夏燊18 分钟前
MySQL 的四种隔离级别
java·数据库·mysql
paopaokaka_luck3 小时前
灾害应急物资管理系统(AI应急助手、Echarts可视化数据看板、捐赠入库联动、 物资全流程管理、库存变动追溯、灾情资讯与互动反馈、物资发放公示)
java·前端·javascript·spring boot·mysql·tomcat·echarts
这个DBA有点耶3 小时前
为什么删掉索引后,查询瞬间变快了?探讨MySQL的随机I/O与顺序I/O博弈
数据库·mysql·代码规范
GreatSQL5 小时前
GreatSQL 8.0回流MySQL 5.7主从复制中断分析排查过程
mysql
数据库小学妹5 小时前
MySQL磁盘IO瓶颈:iostat与刷脏fsync
数据库·mysql·性能优化
Umi·6 小时前
MySQL之DQL使用实例
数据库·mysql
贾公子7 小时前
检测结果怎么落库——表结构、状态机与三类对账
后端·mysql
z06.7 小时前
Linux系统中MySQL的安装
linux·mysql
Small-Young8 小时前
数据库乐观锁深度解析:MySQL、PostgreSQL 实战 + Spring Boot 集成指南
数据库·spring boot·mysql·postgresql·乐观锁·并发控制