MSSQL高并发下生成连续不重复的订单号
一、确定需求
只要做过开发的基本上都有做过订单,只要做过订单的基本上都要涉及生成订单号,可能项目订单号生成规则都不一样,但是大多数规则都是连续增长。
所以假如给你一个这样的需求,在高并发下,以天为单位,生成连续不重复的订单号,比如2017年4月12日有1000条订单,那么当天的订单号是170412001至1704121000,第二天13号又有2000条订单就是170413001至1704132000。
二、实现需求
首先我们建立一个订单表
CREATE TABLE [dbo].[tbOrder]( [ID] [int] IDENTITY(1,1) NOT NULL, [OrderNo] [varchar](50) NULL, [InputTime] [datetime] NULL, CONSTRAINT [PK_tbOrder] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
表中只有自增ID,订单编号,录入时间三列。
然后开始在代码里面生成订单号。
1 public static string GetOrderNo() 2 { 3 string result = string.Empty; 4 using (IDbConnection conn = SqlHelper.OpenConnection()) 5 { 6 string sql = "SELECT ISNULL(COUNT(*),0)+1 FROM tbOrder WHERE DATEDIFF(DAY,InputTime,GETDATE())=0"; 7 int num = conn.ExecuteScalar<int>(sql); 8 if (num < 1000) 9 { 10 result = num.ToString().PadLeft(3, '0'); 11 } 12 else 13 { 14 result = num.ToString(); 15 } 16 } 17 result = DateTime.Now.ToString("yyMMdd") + result; 18 return result; 19 }
接着我们开10个线程,每个线程都执行插入100次订单表,每次插入之前都从这个方法里获取订单编号。
1 static void Main(string[] args) 2 { 3 for (int i = 0; i < 10; i++) 4 { 5 Thread thread = new Thread(new ThreadStart(InserOrder)); 6 thread.Start(); 7 } 8 } 9 10 public static void InserOrder() 11 { 12 using (IDbConnection conn = SqlHelper.OpenConnection()) 13 { 14 for (int i = 0; i < 100; i++) 15 { 16 conn.Execute("INSERT INTO tbOrder(OrderNo,InputTime)VALUES(@OrderNo,GETDATE())", new { OrderNo = GetOrderNo() }); 17 } 18 } 19 }
运行一下,看结果如何。
结果不出所料,一塌糊涂!
三、调整战略
因此,我们要改变思路和战略,重点是订单编号不能根据当前订单总数的基础上加1那么简单了,而是必须有一个ID池,给每次请求分发ID,用后即弃。
相当于去银行办理业务,进去就会让你去机器领号,叫到你的号码的时候才可以去办理业务。
那么谁来当这个ID池呢?
这里有三个方案:
1.SQL表
2.Redis的Incr
3.队列
这里我使用的第一种。
首先我们建立一张表,用来存放ID
CREATE TABLE [dbo].[tbDocID]( [PreName] [varchar](50) NOT NULL,--标识,用于区分不同的业务 [ID] [int] NOT NULL, --用于自增的列,每次用后自增加1 CONSTRAINT [PK_tbDocID] PRIMARY KEY CLUSTERED ( [PreName] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
然后创建一个存储过程,存储过程主要负责根据这张表返回ID
--根据前导字符获取ID值 --参数:前导字符 --返回:字符串 CREATE PROCEDURE [dbo].[sp_GetOrderNo] ( @PreName varchar(20) ) AS BEGIN TRAN SET NOCOUNT ON --1、定义变量 Declare @ReturnValue varchar(10),@OrderID varchar(20),@ID int,@StrID varchar(10),@IDLen int Declare @DocLen int Set @DocLen=10 --2、取出当前ID值+1,然后更新当前的值 Select @ID=ID+1 From [tbDocID] WITH(ROWLOCK,UPDLOCK) where PreName=@PreName IF ISNULL(@ID,0)=0 Set @ID=0 IF @ID=0 BEGIN INSERT INTO [tbDocID]WITH(HOLDLOCK)(PreName,ID)VALUES(@PreName,0) SET @ID=1 END Update [tbDocID] Set ID=ID+1 where PreName=@PreName --3、处理ID的长度 Set @StrID=convert(varchar(10),@ID) Set @IDLen=Len(@StrID) Select @StrID=CASE @IDLen WHEN 1 THEN '00'+@StrID WHEN 2 THEN '0'+@StrID ELSE @StrID End Set @ReturnValue=@StrID --4、返回 Set @OrderID=@ReturnValue Select @OrderID as DocID COMMIT TRAN RETURN GO
修改获取订单编号的方法,从存储过程中获取
public static string GetOrderNo(string prefix) { string result = string.Empty; DynamicParameters param = new DynamicParameters(); param.Add("@PreName", prefix); using (IDbConnection conn = SqlHelper.OpenConnection()) { string returnValue = conn.Query<String>("sp_GetDocID", param, null, true, null, CommandType.StoredProcedure).FirstOrDefault(); if (!string.IsNullOrEmpty(returnValue)) { result = returnValue; } } result = DateTime.Now.ToString("yyMMdd") + result; return result; }
四、测试
最后一波测试
static void Main(string[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new ThreadStart(InserOrder)); thread.Start(); } } public static void InserOrder() { using (IDbConnection conn = SqlHelper.OpenConnection()) { for (int i = 0; i < 100; i++) { string perfix = string.Format("ORDER_{0}", DateTime.Now.ToString("yyMMdd")); conn.Execute("INSERT INTO tbOrder(OrderNo,InputTime)VALUES(@OrderNo,GETDATE())", new { OrderNo = GetOrderNo(perfix) }); } } }
结果:
作者:黄昌
出处:http://www.cnblogs.com/h-change/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。