开源项目Vase5技术解析:从项目探索到架构设计的完整指南
2026/5/11 17:39:46
面试高频 + 实战常用的并发控制手段
核心问题:什么时候锁别人,什么时候先干再说?
思想:
“我觉得你一定会和我抢,所以我先把门锁上再干活。”
SELECT ... FOR UPDATE等。思想:
“我先干活,最后再确认一下期间有没有人改过,如果有就重来。”
一句话总结:
常见悲观锁语法:
-- 排他锁(写锁):锁住选中的行,其他事务不能修改/加排他锁SELECT*FROMproductWHEREid=1FORUPDATE;-- 共享锁(读锁):允许多个事务加共享锁,但不能加排他锁SELECT*FROMproductWHEREid=1LOCKINSHAREMODE;特点:
BEGIN;SELECTstockFROMproductWHEREid=1FORUPDATE;-- 检查库存并更新UPDATEproductSETstock=stock-1WHEREid=1;COMMIT;FOR UPDATE同一行,会被阻塞直到锁释放或超时。数据表设计:
CREATETABLEproduct(idBIGINTPRIMARYKEY,nameVARCHAR(50),stockINT,versionINTNOTNULL);业务流程:
SELECTid,stock,versionFROMproductWHEREid=1;UPDATEproductSETstock=stock-1,version=version+1WHEREid=1ANDstock>0-- 防止扣成负数ANDversion=#{oldVersion};UPDATE返回影响行数 = 1:说明版本匹配,更新成功;**本质:**把“加锁串行化”变成“无锁 + 成功就成功,失败就重试/提示”。
时间戳字段:
通过比较update_time是否等于原值来判断是否被修改。
字段值对比:
不单独用版本号,而是直接比较旧值:
UPDATEuserSETbalance=balance+100WHEREid=1ANDbalance=#{oldBalance};compareAndSet(oldValue, newValue),仅在值没变时才更新。遇到高并发 & 高冲突的热点数据:
并不能完全避免“丢更新”,真正安全依赖 SQL 条件:
| 对比维度 | 悲观锁 | 乐观锁 |
|---|---|---|
| 冲突假设 | 相信“会冲突” | 相信“很少冲突” |
| 控制方式 | 先加锁,再操作 | 不加锁,提交时校验版本 |
| 读写关系 | 写时阻塞其他读/写(视锁类型而定) | 读写通常不互相阻塞 |
| 失败代价 | 一般不会失败(除死锁或异常) | 可能更新失败,需要重试或提示 |
| 实现位置 | 多在数据库层(行锁、表锁等) | 多在业务/应用层(version 字段、CAS 等) |
| 场景类型 | 推荐方案 |
|---|---|
| 银行转账、资金扣减 | 悲观锁 + 严格事务 |
| 秒杀、抢购库存 | 通常结合多种手段(限流、队列),乐观锁只是其中一环 |
| 一般配置更新、个人信息修改 | 乐观锁 |
| 复杂多步更新且高度冲突 | 更倾向悲观锁 |
| 分布式系统跨服务修改 | 多采用乐观锁 + 重试,或分布式锁 |
-- 案例:扣减库存(悲观锁版本)BEGIN;SELECTstockFROMproductWHEREid=1FORUPDATE;-- 检查 stock >= 1UPDATEproductSETstock=stock-1WHEREid=1;COMMIT;注意:
-- 先查SELECTid,stock,versionFROMproductWHEREid=1;-- 再更新(乐观锁)UPDATEproductSETstock=stock-1,version=version+1WHEREid=1ANDstock>0ANDversion=#{oldVersion};在业务代码中判断:
例如使用 JPA / Hibernate:
@EntitypublicclassProduct{@IdprivateLongid;privateIntegerstock;@VersionprivateIntegerversion;}@Version字段后,JPA 在更新时会自动加上版本条件:WHERE id = ? AND version = ?可以记一个简单判断维度:
冲突概率高不高?
能不能接受重试?
并发量大小?
一致性要求:
悲观锁:
SELECT ... FOR UPDATE、行锁、表锁等。乐观锁:
如何选:
真正的生产系统里,往往是两种都用: