📌 今日关键词: 锁机制、死锁、锁等待、并发控制、行锁、表锁
大家好呀!我是数据库小学妹👋
我们之前学过事务,知道事务可以把多个操作打包,保证要么全成功要么全失败。但是,你有没有想过:
当两个人同时修改同一条数据------比如双十一抢购最后一件商品,A用户下单库存减1,B用户也同时下单------数据库怎么保证库存不会变成负数?怎么避免两个人的操作互相覆盖?
这就是锁的职责。锁就像数据库里的交通警察,控制着谁可以访问数据、谁需要等待,保证并发操作下数据依然正确。
今天,我们要学习数据库进阶路上的"交通规则"------锁机制(Locking)🚦,让你理解数据库背后的"并发控制"原理,写代码时少踩死锁的坑!
一、什么是锁机制?------ 数据库的"红绿灯"
想象一下,如果数据库是一个巨大的图书馆,成千上万的读者(并发请求)同时想借书、还书。如果没有管理员,大家一拥而上,书就会被撕烂或者拿错。
锁机制就是数据库的管理员,它通过给数据加上"锁",来保证在并发环境下,数据的一致性和完整性。
📌核心价值:
- 防止脏读: 读到了别人还没提交的脏数据。
- 防止不可重复读: 同一个事务里,两次读取的数据不一致。
- 防止幻读: 刚读完数据,别人就插了一条新数据进来。
🔍如何查看?
在MySQL中,我们可以通过以下命令查看锁的状态:
SQL
-- 查看当前的进程(谁在阻塞谁)
SHOW PROCESSLIST;
-- 查看最近一次死锁的信息(侦探现场)
SHOW ENGINE INNODB STATUS;
二、锁的分类(新手先掌握这些)
✅按粒度分:表锁 vs 行锁
| 类型 | 特点 | 适用引擎 | 并发性能 |
|---|---|---|---|
| 表锁 | 锁定整张表,其他事务不能读写 | MyISAM、MEMORY | 差 |
| 行锁 | 只锁定某一行,其他行可并发操作 | InnoDB(默认) | 好 |
InnoDB默认使用行锁,这也是为什么生产环境推荐InnoDB的原因。
✅按模式分:共享锁(S锁)vs 排他锁(X锁)
| 锁类型 | 别名 | 作用 | 兼容性 |
|---|---|---|---|
| 共享锁 | 读锁 | 允许其他事务同时读,但不能写 | 多个共享锁可共存 |
| 排他锁 | 写锁 | 禁止其他事务读和写 | 不与其他任何锁共存 |
自动加锁规则:
SELECT ...不加锁(快照读)SELECT ... FOR UPDATE加行级排他锁SELECT ... LOCK IN SHARE MODE加行级共享锁UPDATE、DELETE、INSERT自动加行级排他锁
三、行锁的三种具体形式(InnoDB)
| 锁类型 | 锁定范围 | 触发条件 |
|---|---|---|
| 记录锁 | 锁定单条记录 | WHERE id = 1 且id是唯一索引/主键 |
| 间隙锁 | 锁定一个范围(不包含记录) | WHERE id BETWEEN 1 AND 10 且id不是唯一索引 |
| 临键锁 | 锁定一个范围+记录 | 默认的间隙锁变种(防止幻读) |
间隙锁是InnoDB在可重复读隔离级别下防止幻读的关键。它锁定的"不存在"的行,防止其他事务插入到该范围。
四、什么是死锁?怎么避免?
死锁:两个或多个事务互相等待对方释放锁,导致谁也进行不下去。
⏳经典死锁 场景:
sql
-- 事务A
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1; -- 锁住id=1
UPDATE account SET balance = balance + 100 WHERE id = 2; -- 等待id=2的锁
-- 事务B
BEGIN;UPDATE account SET balance = balance + 100 WHERE id = 2; -- 锁住id=2
UPDATE account SET balance = balance - 100 WHERE id = 1; -- 等待id=1的锁
A等B释放id=2,B等A释放id=1 → 死锁!
MySQL的处理 :检测到死锁后,会选择回滚其中一个事务 (通常是执行代价较小的那个),并返回错误 Deadlock found when trying to get lock。
📚避免死锁的技巧
- 按固定顺序访问表/行:比如总是先更新id=1,再更新id=2
- 缩短事务:尽快提交,减少锁持有时间
- 使用低隔离级别(如读已提交)可减少间隙锁,但可能产生幻读
- 合理设计索引 :让
UPDATE/DELETE能精确命中行锁,避免升级为表锁 - 重试机制:应用程序捕获死锁错误后,自动重试事务
五、实战:查看当前锁和死锁信息
🎯查看当前正在等待的锁
sql
-- 查看当前事务和锁(MySQL 8.0)
SELECT * FROM performance_schema.data_locks;
-- 查看正在等待的锁
SELECT * FROM performance_schema.data_lock_waits;
🎯查看最近一次死锁信息
sql
SHOW ENGINE INNODB STATUS;
在输出中搜索 LATEST DETECTED DEADLOCK 段落,可以看到造成死锁的SQL。
💡 生产环境定期监控死锁,可以帮助你发现代码中的并发问题。
六、新手必看:3种最常见的"锁升级"陷阱
在使用锁机制时,如果你发现查询变慢了,通常是以下原因导致的"锁升级":
🚩没走索引的更新:
- 错误:
UPDATE accounts SET balance = 100 WHERE name = 'John';(name字段没索引) - 后果: InnoDB找不到具体的行,只能锁住整张表!
- 修正: 给
name加索引,或者确保WHERE条件走索引。
🚩间隙锁(Gap Lock)的副作用:
- 场景: 可重复读(RR)隔离级别下,为了防止幻读,InnoDB会锁住"索引之间的空隙"。
- 后果: 你可能锁住了一段不存在的数据范围,导致别人插不进去。
- 修正: 如果并发插入很高,可以考虑将隔离级别降为"读已提交(RC)"。
🚩长事务:
- 错误: 一个事务开启后,半天不提交(比如在代码里处理复杂的业务逻辑)。
- 后果: 锁一直不释放,后面的请求全堵着。
- 修正: "开启事务 -> 执行SQL -> 提交事务",这三个动作要像闪电一样快!
七、今日学习心得
- 锁是并发访问的守门员,保证数据一致性和完整性
- InnoDB行锁是并发友好的,但要注意死锁和间隙锁
- 死锁不可怕,关键是要捕获并重试,设计好访问顺序
👋 我是数据库小学妹 一个用设计师思维学数据库的转行人。我们一起,把复杂的技术变得简单有趣!💕
本文示例基于 MySQL 8.0,InnoDB 引擎。不同隔离级别下锁的行为有差异,建议查阅官方文档。