乐观锁必须显式添加where version = @expected_version条件,否则无效;正确写法是update ... where id = @id and version = @expected_version,并检查@@rowcount;推荐使用rowversion替代手动version字段;触发器无法替代where校验。

存储过程里加个 WHERE version = @expected_version 就行,不写这句等于没锁。
UPDATE语句必须显式带version条件
乐观锁不是数据库自动启用的功能,它完全依赖应用层(包括存储过程)构造的SQL是否正确。最常见错误是:查出 version = 5,更新时却只写 SET version = 6 WHERE id = @id,漏掉 AND version = 5 —— 这条语句能执行成功,但会静默覆盖别人刚提交的修改。
- 正确写法:
UPDATE order_info SET status = 'shipped', version = version + 1 WHERE id = @id AND version = @expected_version; - 执行后必须检查
@@ROWCOUNT:等于 0 表示冲突,等于 1 才算成功 -
version字段不能设DEFAULT或ON UPDATE,否则 WHERE 条件永远匹配不上
SQL Server 推荐用 ROWVERSION 替代手写 version int
ROWVERSION 是 SQL Server 原生支持的行版本机制,比人工维护 version 字段更可靠:它由引擎强制生成、不可跳过、单事务内只变一次,且无需索引就能高效等值校验。
- 建表时加字段:
row_ver ROWVERSION(注意:不是TIMESTAMP,后者已弃用) - 查询时读取:
SELECT id, data, row_ver FROM orders WHERE id = @id - 更新时校验:
UPDATE orders SET data = @new_data WHERE id = @id AND row_ver = @expected_row_ver - 失败时
@@ROWCOUNT为 0,业务需自行决定重试或报错
触发器不能替代 WHERE 校验
别指望触发器帮你“兜底”。SQL Server 的 BEFORE UPDATE 触发器无法阻止一条已构造完成的 UPDATE 执行——行锁早加了,事务早开始了,覆盖风险已经发生。
- 触发器拿不到应用层没传的
@expected_version,如果 ORM 漏传字段,触发器看到的是 NULL 或默认值,校验直接失效 - 即使触发器强行自增
version,也会导致后续所有请求因NEW.version != OLD.version + 1被拒 - 真正起作用的永远是那句
WHERE id = @id AND version = @expected_version,触发器只是额外负担
最容易被忽略的点:存储过程里漏写 AND version = @expected_version,哪怕其他逻辑再严密,也等于裸奔。乐观锁的全部效力,就压在这一个 WHERE 条件上。











