• 转载:关于生成并发唯一性流水号的解决方案


    看了文章《弃用数据库自增ID,曝光一下我自己用到的解决方法 》,居然还显示到首页上去。我却觉得如果新手不辨真假,盲目顺从,那么会造成误人子弟的事实。

    首先从作者的写这篇文章的目的上讲他想实现的无非是下面目的:

    1、不用自增长ID,因为自增长移植的时候不方便。

    2、这个存储过程可以很高效的产生唯一性的自增长ID

     

    从我小虎的认知上来回答:

    1、对于作者的第一点,完全可以用Guid来替代自增长,或者在移植的时候,可以先去掉自增长的属性。

    有的人说Guid性能比不上自增长ID,这里我们先不讨论这一点,个人认为效率问题主要体现在索引技巧上。

    2、关键是作者的第二点,完全是不正确的,也是我写这篇文章的首要目的。因为这个存储过程根本就没有实现在多并发(多用户)的情况

    下能真正产生唯一性的主键ID。

    我们看原作者的代码:

     

    1create procedure [dbo].[up_get_table_key] 
    2( 
    3   @table_name     varchar(50), 
    4   @key_value      int output 
    5) 
    6as 
    7begin 
    8     begin tran 
    9         declare @key  int 
    10         
    11         --initialize the key with 1 
    12         set @key=1 
    13         --whether the specified table is exist 
    14         if not exists(select table_name from table_key where table_name=@table_name) 
    15            begin 
    16              insert into table_key values(@table_name,@key)        --default key vlaue:1 
    17            end 
    18         -- step increase 
    19         else     
    20            begin 
    21                select @key=key_value from table_key with (nolock) where table_name=@table_name 
    22                set @key=@key+1 
    23                --update the key value by table name 
    24                update table_key set key_value=@key where table_name=@table_name 
    25            end 
    26        --set ouput value 
    27    set @key_value=@key 
    28 
    29    --commit tran 
    30    commit tran 
    31        if @@error>0 
    32      rollback tran 
    33end

    请看我的测试代码以及并发结果图

    protected void Page_Load(object sender, EventArgs e) 
        { 
            if (!IsPostBack) 
            { 
                for (int i = 0; i < 100; i++) 
                { 
                    System.Threading.Thread temp3 = new System.Threading.Thread(new System.Threading.ThreadStart(Run3)); 
     
                      temp3.Start(); 
     
                  } 
            } 
        } 
     
         
     
          private void Run3() 
        { 
            System.Data.SqlClient.SqlParameter[] p = { 
                        new System.Data.SqlClient.SqlParameter("@table_name", "test"), 
                        new System.Data.SqlClient.SqlParameter("@key_value",System.Data.SqlDbType.Int) }; 
            p[1].Direction = System.Data.ParameterDirection.Output; 
            SqlHelper.ExecuteStoredProcedure("up_get_table_key", p); 
            Response.Write(p[1].Value.ToString() + "<br/>"); 
        } 
     

     

    结果图1

     

    从上面多线程的测试效果上来说,绝对不要去按照原作者的方法去做。

      


     

     

    本来这么晚了,我不想在写了,但是不想让别人说我不厚道,说我只说不做,所以,我打算就再写一个切实可行的例子,供大家参考,仅仅作为抛砖引玉。

    但是本人是经过多线程测试的,至少在我测试情况下不会出现并发出差错的情况。

    1、表结构和效果图,这个表是用来存储基础因子的,需要的可以拓展字段,比如,升序,降序,起始序号等。

     

    CREATE TABLE [dbo].[SerialNo]( 
        [sCode] [varchar](50) NOT NULL,--主键也是多个流水号的类别区分 
        [sName] [varchar](100) NULL,--名称,备注形式 
        [sQZ] [varchar](50) NULL,--前缀 
        [sValue] [varchar](80) NULL,--因子字段 
     CONSTRAINT [PK_SerialNo] PRIMARY KEY CLUSTERED 

        [sCode] ASC 
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, 
     
     ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY] 
    ) ON [PRIMARY] 
     

     

     

    2、存储过程代码 

     

    1Create procedure [dbo].[GetSerialNo]   
    2(   
    3    @sCode varchar(50)   
    4)   

    6  as 

    8--exec GetSerialNo   

    10begin 
    11 
    12   Declare @sValue  varchar(16),   
    13 
    14           @dToday   datetime,           
    15 
    16           @sQZ  varchar(50)  --这个代表前缀 
    17 
    18   Begin Tran     
    19 
    20   Begin Try   
    21 
    22     -- 锁定该条记录,好多人用lock去锁,起始这里只要执行一句update就可以了 
    23    --在同一个事物中,执行了update语句之后就会启动锁 
    24     Update SerialNo set sValue=sValue where sCode=@sCode   
    25 
    26     Select @sValue = sValue From SerialNo where sCode=@sCode   
    27 
    28     Select @sQZ = sQZ From SerialNo where sCode=@sCode   
    29 
    30     -- 因子表中没有记录,插入初始值   
    31 
    32     If @sValue is null   
    33 
    34     Begin 
    35 
    36       Select @sValue = convert(bigint, convert(varchar(6), getdate(), 12) + '000001')   
    37 
    38       Update SerialNo set sValue=@sValue where sCode=@sCode   
    39 
    40     end else   
    41 
    42     Begin               --因子表中没有记录   
    43 
    44       Select @dToday = substring(@sValue,1,6)   
    45 
    46       --如果日期相等,则加1   
    47 
    48       If @dToday = convert(varchar(6), getdate(), 12)   
    49 
    50         Select @sValue = convert(varchar(16), (convert(bigint, @sValue) + 1))   
    51 
    52       else              --如果日期不相等,则先赋值日期,流水号从1开始   
    53 
    54         Select @sValue = convert(bigint, convert(varchar(6), getdate(), 12) +'000001')   
    55 
    56           
    57 
    58       Update SerialNo set sValue =@sValue where sCode=@sCode   
    59 
    60     End 
    61 
    62     Select result = @sQZ+@sValue     
    63 
    64     Commit Tran   
    65 
    66   End Try   
    67 
    68   Begin Catch   
    69 
    70     Rollback Tran   
    71 
    72     Select result = 'Error' 
    73 
    74   End Catch   
    75 
    76end 
    77 
    78

     废话不多说了,看测试代码和效果图

     

         

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    第一张图(左)是单独对进货单执行循环多进程

    第二张图(中)是单独对发货单执行循环多进程

    第三张图(右)是对进货单发货单同时执行循环多进程

    也就是上面三个Thread,自己注释测试就可以了。

     

    测试并发代码

     

    1protected void Page_Load(object sender, EventArgs e) 
    2    { 
    3        if (!IsPostBack) 
    4        { 
    5            for (int i = 0; i < 100; i++) 
    6            { 
    7                System.Threading.Thread temp = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); 
    8System.Threading.Thread temp2 = new System.Threading.Thread(new System.Threading.ThreadStart(Run2)); 
    9                System.Threading.Thread temp3 = new System.Threading.Thread(new System.Threading.ThreadStart(Run3)); 
    10                temp.Start(); 
    11                temp2.Start(); 
    12                temp3.Start(); 
    13            } 
    14        } 
    15    } 
    16 
    17    private void Run() 
    18    { 
    19System.Data.SqlClient.SqlParameter[] p = { 
    20                    new System.Data.SqlClient.SqlParameter("@sCode", "JHD") }; 
    21        Response.Write(SqlHelper.ExecuteStoredProcedure("GetSerialNo", p).Rows[0][0].ToString() + "<br/>"); 
    22    } 
    23    private void Run2() 
    24    { 
    25        System.Data.SqlClient.SqlParameter[] p = { 
    26                    new System.Data.SqlClient.SqlParameter("@sCode", "XSD") }; 
    27        Response.Write(SqlHelper.ExecuteStoredProcedure("GetSerialNo", p).Rows[0][0].ToString() + "<br/>"); 
    28    } 
    29    private void Run3() 
    30    { 
    31        System.Data.SqlClient.SqlParameter[] p = { 
    32                    new System.Data.SqlClient.SqlParameter("@table_name", "test"), 
    33                    new System.Data.SqlClient.SqlParameter("@key_value",System.Data.SqlDbType.Int) }; 
    34        p[1].Direction = System.Data.ParameterDirection.Output; 
    35        SqlHelper.ExecuteStoredProcedure("up_get_table_key", p); 
    36        Response.Write(p[1].Value.ToString() + "<br/>"); 
    37    } 
    38

    总结:我写的整个方法和存储过程如果要实现流水号的话,还是相当可以的。在当前测试过程中是可以避免并发而导致数据的同步性出错的情况。

    请注明出处[小虎原创]:http://www.52rs.net/ArticleView.aspx?gID=71bd9b1d-ad30-4f6e-896d-fed7dfbc1b3d

  • 相关阅读:
    近期Android学习II
    近期Android学习
    Java中AQS基本实现原理
    Java中CAS 基本实现原理
    SpringBoot 消息国际化配置
    SpringBoot2.0 配置多数据源
    浅谈Java 线程池原理及使用方式
    Java并发编程之闭锁与栅栏
    Java 8 Stream API实例
    第二阶段考试
  • 原文地址:https://www.cnblogs.com/robinli/p/3126702.html
Copyright © 2020-2023  润新知