上一节我们学习了事务。
事务解决:
一组操作要么全部成功,要么全部失败。
但是还有一个问题:
多个事务同时操作同一份数据怎么办?
例如:
商品库存:
商品A 库存:1两个用户同时购买:
用户A 下单 用户B 下单两个事务同时读取:
库存=1然后:
库存 - 1结果:
库存=-1这就是并发问题。
一、为什么需要锁?
锁的本质:
控制多个事务对共享资源的访问顺序,保证数据一致性。
简单理解:
没有锁:
事务A | | 修改数据 | 事务B | | 同时修改数据可能产生冲突。
有锁:
事务A | | 获取锁 | | 修改数据 | | 释放锁 事务B | | 等待保证数据安全。
二、MySQL 中锁的分类
MySQL 锁很多,我们先掌握核心分类。
按照粒度:
锁 │ ├── 表级锁 │ └── 行级锁按照功能:
锁 │ ├── 共享锁(S锁) │ └── 排他锁(X锁)按照思想:
锁 │ ├── 悲观锁 │ └── 乐观锁三、表锁(Table Lock)
什么是表锁?
锁住整张表。
例如:
user表 +----+ | | | 🔒 | | | +----+其他事务:
不能修改。
示例
加表锁:
LOCK TABLE user WRITE;释放:
UNLOCK TABLES;四、表锁特点
优点:
简单。
加锁成本低。
缺点:
并发能力差。
例如:
用户表:
100万用户。
修改其中一个用户:
UPDATE user SET name='Tom' WHERE id=1;如果使用表锁:
整个 user 表被锁。
其他用户:
不能修改。
所以:
表锁适合低并发场景。
五、行锁(Row Lock)⭐⭐⭐⭐⭐
什么是行锁?
只锁定某一条数据。
例如:
修改:
UPDATE user SET name='Tom' WHERE id=1;锁:
id=1 🔒其他:
id=2 id=3仍然可以操作。
六、行锁的优势
假设:
用户表:
100万数据。
两个事务:
事务A:
UPDATE user SET name='Tom' WHERE id=1;事务B:
UPDATE user SET name='Jack' WHERE id=2;因为:
锁不同数据。
可以同时执行。
所以:
行锁大幅提高并发能力。
七、为什么 InnoDB 支持行锁?
MySQL 存储引擎:
MyISAM
只支持:
表锁InnoDB
支持:
表锁 + 行锁所以:
企业项目通常使用:
InnoDB八、共享锁(Shared Lock)
简称:
S锁也叫:
读锁。
作用:
多个事务可以同时读取,但不能修改。
例如:
事务A:
SELECT * FROM user WHERE id=1 FOR SHARE;加共享锁。
事务B:
可以:
SELECT✅
但是:
UPDATE user SET name='Tom' WHERE id=1;❌
等待锁释放。
九、排他锁(Exclusive Lock)
简称:
X锁也叫:
写锁。
作用:
一个事务获得后,其他事务不能读写。
例如:
SELECT * FROM user WHERE id=1 FOR UPDATE;加排他锁。
事务A:
修改:
UPDATE user SET name='Tom' WHERE id=1;可以。
事务B:
修改同一条:
UPDATE user SET name='Jack' WHERE id=1;等待。
十、共享锁和排他锁区别
| 共享锁 S | 排他锁 X | |
|---|---|---|
| 作用 | 读取 | 修改 |
| 别人能读 | ✅ | ❌(部分情况) |
| 别人能写 | ❌ | ❌ |
| 使用场景 | 保证读取一致 | 保证修改安全 |
十一、悲观锁(Pessimistic Lock)⭐⭐⭐⭐⭐
思想:
数据很可能会被别人修改,所以提前加锁。
例如:
库存扣减:
库存:
100用户购买:
先锁:
SELECT stock FROM product WHERE id=1 FOR UPDATE;然后:
UPDATE product SET stock=stock-1 WHERE id=1;特点:
安全。
但是:
并发低。
十二、乐观锁(Optimistic Lock)⭐⭐⭐⭐⭐
思想:
默认认为不会冲突,提交时检查有没有被修改。
常见方式:
增加版本号。
表:
product id stock version数据:
id=1 stock=100 version=1事务A:
读取:
stock=100 version=1事务B:
修改:
stock=99 version=2事务A提交:
UPDATE product SET stock=99, version=2 WHERE id=1 AND version=1;发现:
影响行数:
0说明:
数据已经被修改。
提交失败。
十三、悲观锁 vs 乐观锁
| 悲观锁 | 乐观锁 | |
|---|---|---|
| 思想 | 提前锁住 | 提交检查 |
| 方式 | 数据库锁 | 版本号 |
| 并发能力 | 低 | 高 |
| 安全性 | 高 | 需要处理冲突 |
| 适合 | 冲突频繁 | 冲突较少 |
十四、企业实际案例:库存扣减
假设:
库存:
stock=1两个请求:
错误方式
查询:
SELECT stock FROM product WHERE id=1;返回:
1两个请求都看到:
1然后:
UPDATE product SET stock=0问题:
超卖。
方案1:悲观锁
BEGIN; SELECT stock FROM product WHERE id=1 FOR UPDATE; UPDATE product SET stock=stock-1 WHERE id=1; COMMIT;方案2:乐观锁
增加:
version执行:
UPDATE product SET stock=stock-1, version=version+1 WHERE id=1 AND version=10;成功:
影响行数=1
失败:
重新尝试。
十五、锁等待和死锁
锁等待
事务A:
锁住:
用户1事务B:
想修改:
用户1结果:
B等待。
死锁(Dead Lock)
例如:
事务A:
锁:
A数据等待:
B数据事务B:
锁:
B数据等待:
A数据形成:
A 等 B B 等 A数据库检测后:
回滚其中一个事务。
十六、本节总结
| 概念 | 说明 |
|---|---|
| 锁 | 控制并发访问 |
| 表锁 | 锁整张表 |
| 行锁 | 锁具体数据行 |
| 共享锁 | 读锁 |
| 排他锁 | 写锁 |
| 悲观锁 | 先加锁再操作 |
| 乐观锁 | 提交时检查 |
| 死锁 | 多个事务互相等待 |
🎯 本节核心一句话:
锁是数据库保证并发安全的机制,InnoDB 通过行锁让多个事务能够安全并发执行。