sql server用rowversion列自动管理行版本,postgresql可用txid_current()或触发器模拟,mysql宜用uuid_short()或条件更新;跨库应统一采用where version = ? + 检查影响行数的乐观锁模式。

SQL Server 中用 ROWVERSION 列实现自动行版本控制
SQL Server 原生支持行版本号自动更新,不需要手写累加逻辑。只要在表中定义 ROWVERSION(旧称 TIMESTAMP)列,每次 UPDATE 或 INSERT 时 SQL Server 就会自动为该行生成唯一、递增的二进制值。它不依赖存储过程,也不受事务隔离级别影响,是底层引擎级保障。
常见错误是试图用 INT 列 + MAX()+1 手动模拟——这在并发场景下必然出错,且无法检测中间更新冲突。
-
ROWVERSION列不能手动插入或更新,也不能设为NULL,定义后由系统完全托管 - 一个表最多只能有一个
ROWVERSION列,且不能作为主键或索引键的一部分(但可建非聚集索引) - 它不是时间戳,和系统时间无关;值大小只反映修改顺序,不可用于排序业务时间
PostgreSQL 中用 txid_current() 或触发器模拟行版本号
PostgreSQL 没有内置等价于 ROWVERSION 的类型,但可通过两种方式接近效果:txid_current() 获取当前事务 ID(轻量、快),或用触发器维护 INT 版本列(可控、可审计)。前者适合乐观锁校验,后者适合需要显式版本号的场景。
直接在存储过程中用 txid_current() 更新字段是可行的,但要注意:事务 ID 在事务提交后才全局可见,若只读事务中调用,返回的是只读事务分配的虚拟 ID,不具比较意义。
- 用触发器方式时,必须在
BEFORE UPDATE触发器里执行NEW.version := OLD.version + 1,避免竞态 - 不要在
INSERT触发器里初始化版本为 0 —— 应设默认值DEFAULT 1,否则触发器可能覆盖显式插入值 - 如果用
txid_current(),需配合txid_snapshot_xmin()等函数做范围判断,不能直接比大小
MySQL 中靠 BINARY(8) + UUID_SHORT() 或应用层协调
MySQL 没有原生行版本列,INFORMATION_SCHEMA.INNODB_TRX 里的事务信息不可靠,也不建议查 SELECT MAX(version) 来累加。较稳妥的做法是:用 BINARY(8) 存 UUID_SHORT()(保证单机单调递增),或让应用传入版本号并由存储过程校验后自增。
典型错误是在存储过程中写 UPDATE t SET version = (SELECT MAX(version)+1 FROM t WHERE id = ?) —— 这会产生全表扫描、死锁风险,且在高并发下版本号重复或跳变。
- 若用
UUID_SHORT(),注意它依赖服务器启动时间和服务器 ID,多主环境需额外处理 - 若坚持用整数版本号,必须加
SELECT ... FOR UPDATE锁住目标行,再执行UPDATE,否则并发 UPDATE 会覆盖彼此 - 存储过程参数里应接收旧版本号(如
@old_version),先SELECT version INTO @cur FROM t WHERE id = @id校验一致性,再更新
跨数据库兼容写法:别依赖自动累加,改用条件更新 + 返回影响行数
真正健壮的行版本控制,不靠“自动累加”,而靠“更新前校验”。所有主流数据库都支持 WHERE version = ? 条件更新,并通过返回影响行数(ROW_COUNT() / GET DIAGNOSTICS)判断是否被其他事务抢先修改。
这个模式比任何自动累加都可靠,且不绑定具体数据库特性。存储过程里只需把版本号当普通参数参与 WHERE 和 SET,无需特殊函数或列类型。
- MySQL 存储过程中用
SELECT ROW_COUNT() INTO @affected判断是否更新成功 - PostgreSQL 使用
GET DIAGNOSTICS rowcount = ROW_COUNT - SQL Server 可直接用
IF @@ROWCOUNT = 0 RAISERROR(...)
行版本号本身要不要“自动累加”其实是个伪命题——重点是你能否在更新时原子性地校验并拒绝过期写入。自动累加只是手段,不是目的;而条件更新才是那个绕不开的底层事实。











