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

  • 相关阅读:
    MYSQL函数 Cast和convert的用法详解
    MySQL5.7.9(GA)的安装
    TMS Scripter importtool的使用
    MySQL 高可用架构在业务层面的应用分析
    UNIGUI:How to redirect and close session?
    HTML URL 编码:请参阅:http://www.w3school.com.cn/tags/html_ref_urlencode.html
    js 解决函数加载的问题
    必备函数
    Action 分离
    JavaScript.Remove
  • 原文地址:https://www.cnblogs.com/h-change/p/6699683.html
Copyright © 2011-2022 走看看