一、确定需求
只要做过开发的基本上都有做过订单,只要做过订单的基本上都要涉及生成订单号,可能项目订单号生成规则都不一样,但是大多数规则都是连续增长。
所以假如给你一个这样的需求,在高并发下,以天为单位,生成连续不重复的订单号,比如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 stringGetOrderNo()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(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 returnresult;19 }
接着我们开10个线程,每个线程都执行插入100次订单表,每次插入之前都从这个方法里获取订单编号。
1 static void Main(string[] args)2 {3 for (int i = 0; i < 10; i++)4 {5 Thread thread = new Thread(newThreadStart(InserOrder));6 thread.Start();7 }8 }9
10 public static voidInserOrder()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, --用于自增的列,每次用后自增加1CONSTRAINT [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 asDocIDCOMMIT TRAN
RETURN
GO
修改获取订单编号的方法,从存储过程中获取
public static string GetOrderNo(stringprefix)
{string result = string.Empty;
DynamicParameters param= newDynamicParameters();
param.Add("@PreName", prefix);using (IDbConnection conn =SqlHelper.OpenConnection())
{string returnValue = conn.Query("sp_GetDocID", param, null, true, null, CommandType.StoredProcedure).FirstOrDefault();if (!string.IsNullOrEmpty(returnValue))
{
result=returnValue;
}
}
result= DateTime.Now.ToString("yyMMdd") +result;returnresult;
}
四、测试
最后一波测试
static void Main(string[] args)
{for (int i = 0; i < 10; i++)
{
Thread thread= new Thread(newThreadStart(InserOrder));
thread.Start();
}
}public static voidInserOrder()
{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) });
}
}
}
结果:
作者:黄昌
出处:/h-change/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。