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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

posted @ 2017-04-12 16:06  某C  阅读(3590)  评论(0编辑  收藏  举报