只要作過開發的基本上都有作過訂單,只要作過訂單的基本上都要涉及生成訂單號,可能項目訂單號生成規則都不同,可是大多數規則都是連續增加。sql
因此假如給你一個這樣的需求,在高併發下,以天爲單位,生成連續不重複的訂單號,好比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,訂單編號,錄入時間三列。測試
而後開始在代碼裏面生成訂單號。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
所以,咱們要改變思路和戰略,重點是訂單編號不能根據當前訂單總數的基礎上加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; }
最後一波測試
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/本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接,不然保留追究法律責任的權利。