如何在C#和SQL Server生成唯一自定义RecordNo并确保并发下唯一性?
生成带年份的自增RecordNo并保障唯一性的最优方案
针对你需要生成ABC-YYXXXX格式编号、解决并发重复的需求,推荐优先在数据库端实现编号生成逻辑——这是保障并发安全最可靠的方式,以下是具体方案:
方案1:专用计数器表+存储过程(兼容所有SQL Server版本,推荐)
核心思路是用一张单独的表存储每年的编号最大值,通过带锁的事务获取并更新下一个编号,从根源上避免并发冲突。
步骤1:创建计数器表
CREATE TABLE RecordNoCounter ( [Year] INT PRIMARY KEY, -- 存储完整年份(如2024) CurrentMax INT NOT NULL DEFAULT 0 -- 当年已生成的最大编号后缀 );
步骤2:编写生成编号的存储过程
这个存储过程会在事务中锁定当前年份的记录,确保同一时间只有一个请求能获取下一个编号:
CREATE PROCEDURE GetNextRecordNo @RecordNo VARCHAR(10) OUTPUT AS BEGIN SET NOCOUNT ON; DECLARE @CurrentYear INT = YEAR(GETDATE()); DECLARE @YearSuffix INT = @CurrentYear % 100; DECLARE @NextNum INT; BEGIN TRANSACTION; -- 使用UPDLOCK+HOLDLOCK锁定记录,防止并发读取导致重复 SELECT @NextNum = CurrentMax + 1 FROM RecordNoCounter WITH (UPDLOCK, HOLDLOCK) WHERE [Year] = @CurrentYear; -- 如果是当年第一条记录,初始化计数器 IF @NextNum IS NULL BEGIN SET @NextNum = 1; INSERT INTO RecordNoCounter ([Year], CurrentMax) VALUES (@CurrentYear, @NextNum); END -- 否则更新计数器 ELSE BEGIN UPDATE RecordNoCounter SET CurrentMax = @NextNum WHERE [Year] = @CurrentYear; END -- 格式化最终编号 SET @RecordNo = 'ABC-' + RIGHT('0' + CAST(@YearSuffix AS VARCHAR(2)), 2) + RIGHT('0000' + CAST(@NextNum AS VARCHAR(4)), 4); COMMIT TRANSACTION; END
步骤3:C#端调用存储过程
直接从数据库获取生成好的编号,再插入业务表:
public string GetNextRecordNo() { string recordNo = string.Empty; var connectionString = "你的数据库连接字符串"; using (var conn = new SqlConnection(connectionString)) { conn.Open(); using (var cmd = new SqlCommand("GetNextRecordNo", conn)) { cmd.CommandType = CommandType.StoredProcedure; var outputParam = new SqlParameter("@RecordNo", SqlDbType.VarChar, 10) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(outputParam); cmd.ExecuteNonQuery(); recordNo = outputParam.Value.ToString(); } } return recordNo; }
方案2:SQL Server序列(仅适用于2012及以上版本)
如果你的SQL Server版本在2012+,可以用序列实现自增,但需要额外处理跨年重置的问题:
创建序列
CREATE SEQUENCE RecordNoSeq START WITH 1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 9999 NO CYCLE; -- 到达9999后停止生成,避免重复
每年重置序列
需要创建SQL Server代理作业,在每年1月1日执行以下语句重置序列起始值:
ALTER SEQUENCE RecordNoSeq RESTART WITH 1;
生成编号
在插入业务表时直接生成:
INSERT INTO YourBusinessTable (RecordNo, ...) VALUES ( 'ABC-' + RIGHT('0' + CAST(YEAR(GETDATE())%100 AS VARCHAR(2)),2) + RIGHT('0000' + CAST(NEXT VALUE FOR RecordNoSeq AS VARCHAR(4)),4), ... );
唯一性保障的最后防线
无论使用哪种方案,都必须在业务表的RecordNo字段上创建唯一约束:
ALTER TABLE YourBusinessTable ADD CONSTRAINT UQ_YourBusinessTable_RecordNo UNIQUE (RecordNo);
这样即使前端或存储过程逻辑出现异常,数据库会直接拒绝重复值插入,C#端可以捕获SqlException(错误号2601)并重试获取编号。
内容的提问来源于stack exchange,提问作者Frank Martin
相关产品推荐
相关产品推荐

