在 SQL 里所谓“给现金字段加锁”有两层含义:
- 业务角度:在并发场景下,确保「现金字段」的读取和修改不会互相覆盖(避免脏读、丢失更新)。
- 数据库角度:实际加的是行锁或表锁,而不是“只锁一列”。InnoDB 的行锁永远是加在索引记录上,锁住整行,不能只锁单个字段。
下面给出 3 种最常用、最安全且面试常问的写法,并解释各自加的锁类型。
- 悲观锁(SELECT … FOR UPDATE)
场景:提现/转账时先把余额读出来再扣减。
sql
START TRANSACTION;
-- 锁住 id = 1001 这一行(X 锁,行锁)
SELECT cash
FROM account
WHERE id = 1001
FOR UPDATE;
-- 业务校验 cash 是否足够
UPDATE account
SET cash = cash - 200
WHERE id = 1001;
COMMIT;- 锁类型:对主键 id = 1001 的聚簇索引记录加 记录锁(Record Lock)。
- 特点:其他事务不能同时读写这行,直到当前事务提交。
- 乐观锁(版本号 / CAS)
场景:并发量高、冲突概率低,提高吞吐量。
表结构增加一列version INT或直接使用金额本身做 CAS。
sql
UPDATE account
SET cash = cash - 200
WHERE id = 1001
AND cash >= 200; -- 业务条件- 不加显式锁,依赖 行级写锁(隐式 X 锁)+ 条件过滤保证原子性。
- 如果更新返回
0 rows affected,说明余额不足或并发修改,业务层重试。
- 表锁(不推荐,仅演示)
sql
LOCK TABLES account WRITE;
UPDATE account
SET cash = cash - 200
WHERE id = 1001;
UNLOCK TABLES;- 锁住整张表,粒度最大,不会出现死锁,但并发极低,生产基本不用。
面试追问示例
Q1:如果 cash 字段上没有索引,SELECT … FOR UPDATE 会加什么锁?
A:InnoDB 退化为全表扫描,会对所有记录加 Next-Key Lock(行锁 + 间隙锁),相当于表锁效果,性能急剧下降。
Q2:能否只锁 cash 一列?
A:不能。InnoDB 的行锁最小粒度是索引记录(聚簇索引或二级索引),锁住整行,不能只锁单个字段;如需字段级控制,只能由业务层通过乐观锁或应用逻辑保证。
一句话总结
在 MySQL/InnoDB 中,“给现金字段加锁”本质上是给包含该字段的行加行级 X 锁;常见写法是 SELECT … FOR UPDATE(悲观)或带条件的 UPDATE(乐观/CAS)。