寻求.NET Core API生成不重复9位唯一整数的高效方案
优化方案推荐
针对你的需求,推荐以下几个低开销、高并发友好的解决方案:
1. 数据库序列(Sequence)+ 内存预取
SQL Server的序列是专门生成唯一连续值的数据库对象,原子性强,且支持批量取值,能大幅减少数据库交互次数:
步骤:
- 先在SQL Server创建序列:
CREATE SEQUENCE dbo.NineDigitSeq START WITH 1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 999999999 CYCLE OFF; -- 到达最大值后停止,避免重复
- 在.NET Core中,维护一个内存队列(
ConcurrentQueue<int>),初始化时从数据库批量获取一批值(比如100个); - 每次请求从队列取一个值,当队列剩余数量低于阈值(比如20)时,异步触发批量获取下一批值的操作;
- 取值逻辑示例:
private readonly ConcurrentQueue<int> _idQueue = new ConcurrentQueue<int>(); private readonly object _fetchLock = new object(); private const int BatchSize = 100; private const int Threshold = 20; public async Task<int> GetNextIdAsync() { if (_idQueue.TryDequeue(out var id)) { // 检查队列剩余量,不足则异步补全 if (_idQueue.Count <= Threshold) { Task.Run(FetchBatchAsync); } return id; } // 队列为空时同步获取一批 await FetchBatchAsync(); _idQueue.TryDequeue(out id); return id; } private async Task FetchBatchAsync() { // 避免并发重复获取 lock (_fetchLock) { if (_idQueue.Count > Threshold) return; } using var conn = new SqlConnection("YourConnectionString"); await conn.OpenAsync(); // 批量获取Sequence值 var cmd = new SqlCommand($"SELECT NEXT VALUE FOR dbo.NineDigitSeq FROM (VALUES(1),{(string.Join(",", Enumerable.Range(2, BatchSize-1).Select(x => "(1)")))) AS T", conn); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { _idQueue.Enqueue(reader.GetInt32(0)); } }
这个方案的优势:大部分请求直接从内存取,数据库交互极少;序列是数据库原子操作,完全避免重复;支持多实例部署(多个API实例各自预取,数据库保证全局唯一)。
2. 单实例场景:Interlocked原子递增 + 异步持久化
如果你的API是单实例部署,用Interlocked类做内存原子递增,比lock开销小一个数量级,同时异步将最新值持久化到数据库(避免重启后丢失):
步骤:
- 数据库建一张计数器表,只存一条记录:
CREATE TABLE dbo.IdCounter ( Id INT PRIMARY KEY DEFAULT 1, CurrentValue INT NOT NULL DEFAULT 0 ); INSERT INTO dbo.IdCounter DEFAULT VALUES;
- 启动时从数据库读取
CurrentValue,初始化内存中的计数器; - 每次请求用
Interlocked.Increment原子递增,然后异步更新数据库:
private int _currentId; private readonly ILogger<YourService> _logger; public async Task InitializeAsync() { using var conn = new SqlConnection("YourConnectionString"); await conn.OpenAsync(); var cmd = new SqlCommand("SELECT CurrentValue FROM dbo.IdCounter", conn); _currentId = (int)await cmd.ExecuteScalarAsync(); } public int GetNextId() { var nextId = Interlocked.Increment(ref _currentId); // 异步更新数据库,不阻塞请求 _ = UpdateDatabaseAsync(nextId); return nextId; } private async Task UpdateDatabaseAsync(int newValue) { try { using var conn = new SqlConnection("YourConnectionString"); await conn.OpenAsync(); var cmd = new SqlCommand("UPDATE dbo.IdCounter SET CurrentValue = @NewValue WHERE Id = 1", conn); cmd.Parameters.AddWithValue("@NewValue", newValue); await cmd.ExecuteNonQueryAsync(); } catch (Exception ex) { _logger.LogError(ex, "Failed to update counter in database"); // 可根据需求添加重试逻辑 } }
优势:内存操作无锁(CPU原子指令),性能极高;异步更新数据库不影响请求响应速度;仅适用于单实例,多实例会出现重复。
3. 多实例场景:内存缓存 + 数据库乐观锁
如果多实例部署但不想用序列,可结合内存缓存和乐观锁,平衡性能与一致性:
步骤:
- 同样用上述的
IdCounter表; - 每个实例内存存当前计数器值,每次请求先内存递增,然后尝试用乐观锁更新数据库;
- 如果更新失败(说明其他实例已修改),则从数据库拉取最新值,重新递增:
private int _currentId; private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public async Task<int> GetNextIdAsync() { var nextId = Interlocked.Increment(ref _currentId); try { await UpdateWithOptimisticLockAsync(nextId); return nextId; } catch (OptimisticLockException) { // 冲突时重新获取最新值 await _semaphore.WaitAsync(); try { using var conn = new SqlConnection("YourConnectionString"); await conn.OpenAsync(); var cmd = new SqlCommand("SELECT CurrentValue FROM dbo.IdCounter", conn); _currentId = (int)await cmd.ExecuteScalarAsync(); } finally { _semaphore.Release(); } // 重新递增 return Interlocked.Increment(ref _currentId); } } private async Task UpdateWithOptimisticLockAsync(int newValue) { using var conn = new SqlConnection("YourConnectionString"); await conn.OpenAsync(); var cmd = new SqlCommand("UPDATE dbo.IdCounter SET CurrentValue = @NewValue WHERE CurrentValue = @OldValue", conn); cmd.Parameters.AddWithValue("@NewValue", newValue); cmd.Parameters.AddWithValue("@OldValue", newValue - 1); var rowsAffected = await cmd.ExecuteNonQueryAsync(); if (rowsAffected == 0) { throw new OptimisticLockException("Counter update conflict"); } } // 自定义异常类 public class OptimisticLockException : Exception { public OptimisticLockException(string message) : base(message) { } }
优势:大部分请求内存操作完成,冲突时才触发数据库交互;支持多实例;性能略低于序列方案,但实现简单。
内容的提问来源于stack exchange,提问作者Farooq Awan
相关产品推荐
相关产品推荐

