zoukankan      html  css  js  c++  java
  • 转载:关于生成并发唯一性流水号的解决方案

    看了文章《弃用数据库自增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

  • 相关阅读:
    HDOJ 2095 find your present (2)
    HDOJ 2186 悼念512汶川大地震遇难同胞——一定要记住我爱你
    九度 1337 寻找最长合法括号序列
    九度 1357 疯狂地Jobdu序列
    HDOJ 1280 前m大的数
    九度 1343 城际公路网
    九度 1347 孤岛连通工程
    HDOJ 2151 Worm
    九度 1342 寻找最长合法括号序列II
    九度 1346 会员积分排序
  • 原文地址:https://www.cnblogs.com/robinli/p/3126702.html
Copyright © 2011-2022 走看看