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

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

相关推荐
鱼很腾apoc1 小时前
【MySQL】 第2期 查询与函数系统详解:从基础 CRUD 到复合多表查询
linux·数据库·学习·mysql·dml
stillstream_ink3 小时前
一个缺失的状态校验,让 Mall4j 的订单系统开了 4 个后门
mysql·测试
PC2005-cloud4 小时前
MySQL学习笔记:主从复制集群搭建以及原理详解
笔记·学习·mysql
夜雪一千4 小时前
MySQL中修改JSON类型的数据实战
数据库·mysql·json
for_ever_love__4 小时前
MySQL 优化器与 OPTIMIZER_TRACE:把‘为什么选这条计划‘问到水落石出
数据库·mysql·优化器·trace·optimizer·执行计划分析·cic
靠沿5 小时前
MySQL数据库基础——事务
java·数据库·mysql
夜雪一千5 小时前
MySQL 中查询和操作JSON类型的数据
数据库·mysql·json
AIgorithmGEEK13 小时前
MySQL 索引全解(下):建、查、删实战与知识速查表
数据库·mysql·b+树·索引
Lost_the_wind13 小时前
MySQL学习笔记四
mysql