MSSQL高併發下生成連續不重複的訂單號

1、肯定需求

只要作過開發的基本上都有作過訂單,只要作過訂單的基本上都要涉及生成訂單號,可能項目訂單號生成規則都不同,可是大多數規則都是連續增加。sql

因此假如給你一個這樣的需求,在高併發下,以天爲單位,生成連續不重複的訂單號,好比2017年4月12日有1000條訂單,那麼當天的訂單號是170412001至1704121000,次日13號又有2000條訂單就是170413001至1704132000。併發

2、實現需求

首先咱們創建一個訂單表高併發

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,訂單編號,錄入時間三列。測試

而後開始在代碼裏面生成訂單號。spa

 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 }

運行一下,看結果如何。code

結果不出所料,一塌糊塗!orm

3、調整戰略

所以,咱們要改變思路和戰略,重點是訂單編號不能根據當前訂單總數的基礎上加1那麼簡單了,而是必須有一個ID池,給每次請求分發ID,用後即棄。blog

至關於去銀行辦理業務,進去就會讓你去機器領號,叫到你的號碼的時候才能夠去辦理業務。隊列

那麼誰來當這個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
    --一、定義變量
    Declare @ReturnValue varchar(10),@OrderID varchar(20),@ID int,@StrID varchar(10),@IDLen int
    Declare @DocLen int
    Set @DocLen=10
    
    --二、取出當前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
    --三、處理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
    --四、返回
    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;
}

4、測試

最後一波測試

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/本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接,不然保留追究法律責任的權利。

相關文章
相關標籤/搜索