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)  
    5
    6  as
    7
    8--exec
    GetSerialNo 

    9
    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));
    8                System.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



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

  • 相关阅读:
    luogu P1353 [USACO08JAN]跑步Running
    bzoj 2002: [Hnoi2010]Bounce 弹飞绵羊
    [USACO3.2]Sweet Butter
    [SDOI2009]Elaxia的路线
    [USACO5.4]Telecowmunication
    [洛谷1681]最大正方形II
    [清华集训2014]奇数国
    [洛谷2814]家谱
    [洛谷1868]饥饿的奶牛
    [HNOI2003]激光炸弹
  • 原文地址:https://www.cnblogs.com/tianxiang2046/p/2496502.html
Copyright © 2011-2022 走看看