回滚事务中DBCC CHECKIDENT(RESEED)的异常行为合理性咨询
先复现你的测试代码方便参考:
DROP TABLE IF EXISTS __Test; CREATE TABLE __Test (Id INT IDENTITY NOT NULL PRIMARY KEY); GO BEGIN TRAN; SET IDENTITY_INSERT __Test ON; INSERT INTO __Test (Id) VALUES (100); SET IDENTITY_INSERT __Test OFF; DBCC CHECKIDENT('__Test', RESEED, 1); ROLLBACK TRAN; GO SELECT IDENT_CURRENT('__Test'); -- returns 100 instead of 1
你的预期和实际结果的差异,本质是IDENTITY列的标识值(由IDENT_CURRENT返回)在两种操作下的事务行为完全不同:
1. IDENTITY_INSERT插入触发的标识值更新是不可回滚的
当你通过IDENTITY_INSERT插入自定义标识值时,SQL Server会自动将当前标识值更新为你插入的最大值——这是为了保证后续自动生成的标识值不会和已存在的值重复。关键在于:这个标识值的更新是直接写入系统元数据的,完全不受事务回滚影响。哪怕你回滚了插入的行,系统也不会把标识值退回去,因为如果允许回滚,可能会导致其他并发事务生成重复的标识值,直接违反IDENTITY列的唯一性设计目标。
在你的测试里,插入ID=100后,IDENT_CURRENT就已经被设置为100,这个修改已经“生效”,和事务是否提交无关。
2. DBCC CHECKIDENT的RESEED操作是可以被事务回滚的
DBCC CHECKIDENT('__Test', RESEED, 1)属于显式修改标识值的元数据操作,这类操作在SQL Server中支持事务回滚。当你在事务内执行这个RESEED时,标识值暂时被改为1,但一旦事务回滚,这个修改就会被撤销,系统会恢复到RESEED之前的标识值——也就是之前IDENTITY_INSERT插入时设置的100。
这就是为什么你最终查询IDENT_CURRENT('__Test')会返回100而非预期的1:回滚只撤销了RESEED的修改,却无法撤销IDENTITY_INSERT带来的标识值更新。
总结来说:IDENTITY列的标识值设计优先保证唯一性,而非事务一致性。IDENTITY_INSERT触发的标识值更新是持久化的元数据修改,不受事务回滚影响;而显式的RESEED操作属于可回滚的元数据修改,事务回滚后会恢复到操作前的状态。
内容的提问来源于stack exchange,提问作者Palo Mraz

