C# EF6:数据录入异常时Identity值被占用致跳号的问题
Identity列跳号问题的原因与解决办法
问题原因
- Identity列采用预分配ID的设计:SQL Server在事务启动时就会为插入操作分配对应的Identity值,即便后续事务因异常回滚,已分配的ID也不会被回收复用。这是数据库为保证并发场景下ID唯一性的原生机制,避免多个事务争抢同一ID导致冲突。
- 你的代码中,每次
SaveChangesAsync()触发异常时,事务回滚但已消耗的Identity值无法恢复,后续正常插入就会出现ID不连续的情况。
解决办法
1. 接受ID不连续(推荐)
Identity列的核心作用是作为唯一标识符,而非生成连续序列。如果业务对ID连续性没有强制要求,这是最省心的方案,无需修改现有逻辑,也不会引入额外的并发风险。
2. 提前校验数据,减少事务回滚
在执行数据库插入操作前,先对实体数据做完整校验(必填项、格式、业务规则等),提前拦截错误,避免进入数据库事务后因异常回滚消耗ID。
修改后的示例代码:
public async Task<T?> CreateAsync(T entity) { // 提前校验数据,不符合规则直接抛出异常 ValidateEntity(entity); using RetailDbContext context = _contextFactory.CreateDbContext(); EntityEntry<T> createResult = await context.Set<T>().AddAsync(entity); await context.SaveChangesAsync(); return createResult.Entity; } private void ValidateEntity(T entity) { // 实现自定义校验逻辑,比如检查必填字段、数据格式等 // 校验失败时抛出ArgumentException或自定义业务异常 }
3. 改用数据库序列(适配强连续ID需求)
如果业务必须要求ID连续,可以用数据库序列(Sequence)替代Identity,手动控制ID生成:
- 第一步:创建数据库序列(关闭缓存避免跳号,注意会降低并发性能)
CREATE SEQUENCE dbo.EntityIdSequence START WITH 1 INCREMENT BY 1 NO CACHE;
- 第二步:在EF Core中配置实体使用序列生成ID
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<YourEntityType>() .Property(e => e.Id) .HasDefaultValueSql("NEXT VALUE FOR dbo.EntityIdSequence"); }
注意:关闭序列缓存会导致每次获取ID都需要和数据库交互,不适合高并发插入场景。
4. 手动回收未使用ID(不推荐)
可以额外维护一张表记录被跳过的ID,后续插入时优先使用这些回收的ID。但这种方式需要处理并发竞争、ID重复等问题,逻辑复杂,容易引发新的bug,仅适合低并发、对ID连续性有极端要求的场景。
内容的提问来源于stack exchange,提问作者xRooT
相关产品推荐
相关产品推荐

