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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM