SQL Server 报 IDENTITY_INSERT OFF:普通新增不要传自增 ID
SQL Server 自增列收到显式 ID,普通新增省略该列;历史数据迁移按同一会话使用 SET IDENTITY_INSERT,并核对约束、权限和恢复。
SQL Server 新增记录时提示 Cannot insert explicit value for identity column,并说明 IDENTITY_INSERT is set to OFF。如果表的 ID 列定义为 IDENTITY,而 INSERT 明确提供了 ID,先确认这次操作是普通新增,还是必须保留历史编号的迁移。
普通新增通常应从插入列清单中省略自增 ID,让数据库生成编号。只有明确需要保留编号时,才考虑在受控会话中开启 IDENTITY_INSERT。不要为了每次新增都能传一个 ID,就删除表的自增定义。
核对真正发送的列清单
查看当前数据库里的表定义,以及应用实际生成的 INSERT。对象模型中 ID 的默认值是零,不代表 ORM 没有把零写进 SQL;关键是这列是否出现在插入清单和绑定参数中。
下面假设 dbo.Tasks 已存在,ID 是自增列,Title 是普通业务字段。这是写入结构示例,应先在隔离测试表验证,不能直接写入业务表:
INSERT INTO dbo.Tasks (Title)
VALUES (N'demo task');
示例没有列出 ID,因此 SQL Server 负责生成它。若应用固定输出 (ID, Title) 并给 ID 绑定一个值,需要调整新增入口或 ORM 的生成值映射,不能只在应用对象里把编号改成另一个数字。
插入应明确列清单,不依赖表中列的顺序。若删除 ID 参数后其他字段仍错位,应同步修正列与参数的对应关系。编号存在间隔本身不表示失败,不能用“填满空号”作为普通新增的目标。
迁移保留编号时,使用会话范围的 SET
导入历史记录且编号参与业务关联时,先备份数据、核对主外键及重复编号,并确认执行账号拥有表或具有该表的 ALTER 权限。这是迁移权限,不应为普通应用新增长期扩大权限。
正确的配置语法是 SET IDENTITY_INSERT dbo.Tasks ON,不是 ALTER TABLE ... SET IDENTITY_INSERT。以下只展示开启、写入和关闭的对应关系;名称与编号都是示例,正式流程还必须包含事务与错误清理:
SET IDENTITY_INSERT dbo.Tasks ON;
INSERT INTO dbo.Tasks (ID, Title)
VALUES (123, N'imported task');
SET IDENTITY_INSERT dbo.Tasks OFF;
这三步必须作用于同一个会话。一个窗口打开设置,另一个窗口或连接池新借出的连接执行 INSERT,并不能继承它。同一会话同一时刻只能为一张表开启该选项,迁移多个表时应逐个完成并关闭。
开启选项不会跳过主键、外键或其他约束。插入重复编号仍会失败;失败路径也要保证关闭会话中的设置,不能让连接直接回池后继续承担无关业务。实际迁移宜使用明确拥有的专用连接,结束后关闭,避免把会话状态清理依赖于连接池的实现。
检查后续自动编号及已提交数据
在正向增长的 IDENTITY 列中,显式插入的值超过当前编号时,SQL Server 会把这个值作为新的当前编号。保留历史编号可能改变后续自动分配的起点,不能将它当成只影响这一行的开关;也不要照搬 PostgreSQL 的序列行为来判断 SQL Server。
变更前记录表定义、编号状态和迁移范围,在隔离副本检查普通新增与保留编号两条路径。正式迁移采用已审定的事务与恢复流程,避免错误后重跑已提交部分。
验收时,普通新增不再发送 ID,取得数据库生成的编号并能关联正确记录;迁移入口保存预期编号,所有约束成立,关闭选项后下一次普通新增符合预期。数据内容与业务关联也需要核对,不能只看报错消失。
回退应用插入代码或执行 SET ... OFF 不会删除已提交的导入记录,也不保证撤销编号状态变化。若迁移需要恢复,应按此前准备的备份与对账方案处理,不能为恢复号码直接重置整表编号。


