应选悲观锁(select ... for update):适用于多步写操作、高冲突场景,需显式事务、索引支持及正确回滚;乐观锁适合读多写少、低冲突场景,依赖version字段与原子update校验。

高并发写操作下,直接用 UPDATE ... WHERE id = ? 更新数据库几乎必然出错——不是数据被覆盖,就是库存超卖。必须在应用层或数据库层引入锁机制,而选乐观锁还是悲观锁,取决于你更新的频率、冲突概率和事务复杂度。
什么时候该用 SELECT ... FOR UPDATE(悲观锁)
当你的写操作涉及多步逻辑(比如查余额 → 扣款 → 记日志 → 更新状态),且中间不能被其他事务干扰时,SELECT ... FOR UPDATE 是最直接可靠的手段。它在 InnoDB 行级锁基础上加锁,只要查询条件命中索引,就只锁住目标行。
- 必须显式开启事务(
tx, _ := db.Begin()),否则FOR UPDATE无效 - 锁会持续到
tx.Commit()或tx.Rollback(),别忘了 defertx.Rollback() - 如果
WHERE条件没走索引(例如用LIKE '%xxx'或无索引字段),InnoDB 会升级为表锁,整个表卡住 - Echo 中常见错误:在
context.WithTimeout超时后没主动Rollback,导致连接池里事务长期挂起
怎么用 version 字段实现乐观锁(Go + GORM 示例)
乐观锁适合“读多写少”场景,比如用户资料修改、文章点赞数更新。核心是靠数据库 UPDATE ... WHERE id = ? AND version = ? 的原子性判断是否被改过。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- GORM 默认支持
Version字段:定义 struct 时加gorm:"version"标签,GORM 会在Save()时自动带上WHERE version = old_version - 如果
RowsAffected == 0,说明版本已变,需重试或返回409 Conflict - 不要手动递增
version字段再Save(),GORM 会自动处理;手动改会导致并发下版本跳变 - 注意:MySQL 的
UPDATE返回影响行数是可靠的,但 PostgreSQL 需用RETURNING或额外SELECT确认
UPDATE ... WHERE 条件冲突检测的边界情况
乐观锁本质是“带条件的更新”,但条件设计不当会失效。比如库存扣减写成 UPDATE items SET stock = stock - 1 WHERE id = 1 AND stock >= 1,看似安全,实则有坑:
- 多个并发请求同时读到
stock = 1,都满足stock >= 1,结果全部执行成功,变成stock = 0, -1, -2... - 正确做法是把“扣减前校验”和“扣减动作”合并进一条语句:
UPDATE items SET stock = stock - 1 WHERE id = 1 AND stock >= 1是对的,但必须检查RowsAffected是否为 1 —— 如果为 0,说明库存不足;如果为 1,才真正扣减成功 - 别依赖
SELECT stock FROM ...的结果做业务判断后再UPDATE,这是典型的“读-改-写”竞态,必须用原子条件更新替代
真正难的不是选乐观还是悲观,而是识别哪条 SQL 实际上承担了“锁”的职责。很多团队在 Echo 中加了 synchronized 或 sync.Mutex,却忘了数据库才是唯一可信的数据源——应用层锁只对单机有效,集群下毫无意义。要么交给数据库(FOR UPDATE 或 CAS 式 WHERE),要么用分布式锁兜底,别在中间地带徘徊。










