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

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

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

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));
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

 

posted @ 2013-03-12 23:31  王涛1  阅读(574)  评论(0编辑  收藏  举报