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

  

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

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

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

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

 

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

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

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

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

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

我们看原作者的代码:

 

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

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

None.gif None.gif protected void Page_Load( object sender, EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif    
... {
InBlock.gif        
if (!IsPostBack)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
...{
InBlock.gif            
for (int i = 0; i < 100; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
...{
InBlock.gif                System.Threading.Thread temp3
= new System.Threading.Thread(new System.Threading.ThreadStart(Run3));
InBlock.gif
InBlock.gif                  temp3.Start();
InBlock.gif
ExpandedSubBlockEnd.gif              }

ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

None.gif
None.gif    
None.gif
None.gif      
private void Run3()
ExpandedBlockStart.gifContractedBlock.gif    
... {
ExpandedSubBlockStart.gifContractedSubBlock.gif        System.Data.SqlClient.SqlParameter[] p
= ...{
InBlock.gif                    
new System.Data.SqlClient.SqlParameter("@table_name", "test"),
ExpandedSubBlockEnd.gif                    
new System.Data.SqlClient.SqlParameter("@key_value",System.Data.SqlDbType.Int) }
;
InBlock.gif        p[
1].Direction = System.Data.ParameterDirection.Output;
InBlock.gif        SqlHelper.ExecuteStoredProcedure(
"up_get_table_key", p);
InBlock.gif        Response.Write(p[
1].Value.ToString() + "<br/>");
ExpandedBlockEnd.gif    }

None.gif
None.gif

结果图1

 r_id.jpg

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

  


 

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

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

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

 

None.gif CREATE TABLE [ dbo ] . [ SerialNo ] (
None.gif    
[ sCode ] [ varchar ] ( 50 ) NOT NULL , -- 主键也是多个流水号的类别区分
None.gif
     [ sName ] [ varchar ] ( 100 ) NULL , -- 名称,备注形式
None.gif
     [ sQZ ] [ varchar ] ( 50 ) NULL , -- 前缀
None.gif
     [ sValue ] [ varchar ] ( 80 ) NULL , -- 因子字段
None.gif
CONSTRAINT [ PK_SerialNo ] PRIMARY KEY CLUSTERED
None.gif(
None.gif    
[ sCode ] ASC
None.gif)
WITH (PAD_INDEX   = OFF , STATISTICS_NORECOMPUTE   = OFF , IGNORE_DUP_KEY = OFF , ALLOW_ROW_LOCKS   = ON ,
None.gif
None.gif ALLOW_PAGE_LOCKS  
= ON ) ON [ PRIMARY ]
None.gif)
ON [ PRIMARY ]
r_bjg2.jpgNone.gif
None.gif

2、存储过程代码 

 

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

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

 

r_jhd.jpgr_xsd.jpgr_jhdxsd.jpg

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

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

 

测试并发代码

 

1 None.gif protected void Page_Load( object sender, EventArgs e)
2 ExpandedBlockStart.gifContractedBlock.gif     ... {
3InBlock.gif        if (!IsPostBack)
4ExpandedSubBlockStart.gifContractedSubBlock.gif        ...{
5InBlock.gif            for (int i = 0; i < 100; i++)
6ExpandedSubBlockStart.gifContractedSubBlock.gif            ...{
7InBlock.gif                System.Threading.Thread temp = new System.Threading.Thread(new System.Threading.ThreadStart(Run));
8InBlock.gifSystem.Threading.Thread temp2 = new System.Threading.Thread(new System.Threading.ThreadStart(Run2));
9InBlock.gif                System.Threading.Thread temp3 = new System.Threading.Thread(new System.Threading.ThreadStart(Run3));
10InBlock.gif                temp.Start();
11InBlock.gif                temp2.Start();
12InBlock.gif                temp3.Start();
13ExpandedSubBlockEnd.gif            }

14ExpandedSubBlockEnd.gif        }

15ExpandedBlockEnd.gif    }

16 None.gif
17 None.gif     private void Run()
18 ExpandedBlockStart.gifContractedBlock.gif     ... {
19ExpandedSubBlockStart.gifContractedSubBlock.gifSystem.Data.SqlClient.SqlParameter[] p = ...{
20ExpandedSubBlockEnd.gif                    new System.Data.SqlClient.SqlParameter("@sCode", "JHD") }
;
21InBlock.gif        Response.Write(SqlHelper.ExecuteStoredProcedure("GetSerialNo", p).Rows[0][0].ToString() + "<br/>");
22ExpandedBlockEnd.gif    }

23 None.gif     private void Run2()
24 ExpandedBlockStart.gifContractedBlock.gif     ... {
25ExpandedSubBlockStart.gifContractedSubBlock.gif        System.Data.SqlClient.SqlParameter[] p = ...{
26ExpandedSubBlockEnd.gif                    new System.Data.SqlClient.SqlParameter("@sCode", "XSD") }
;
27InBlock.gif        Response.Write(SqlHelper.ExecuteStoredProcedure("GetSerialNo", p).Rows[0][0].ToString() + "<br/>");
28ExpandedBlockEnd.gif    }

29 None.gif     private void Run3()
30 ExpandedBlockStart.gifContractedBlock.gif     ... {
31ExpandedSubBlockStart.gifContractedSubBlock.gif        System.Data.SqlClient.SqlParameter[] p = ...{
32InBlock.gif                    new System.Data.SqlClient.SqlParameter("@table_name", "test"),
33ExpandedSubBlockEnd.gif                    new System.Data.SqlClient.SqlParameter("@key_value",System.Data.SqlDbType.Int) }
;
34InBlock.gif        p[1].Direction = System.Data.ParameterDirection.Output;
35InBlock.gif        SqlHelper.ExecuteStoredProcedure("up_get_table_key", p);
36InBlock.gif        Response.Write(p[1].Value.ToString() + "<br/>");
37ExpandedBlockEnd.gif    }

38 None.gif

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

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

 

转载于:https://www.cnblogs.com/52rs/archive/2011/01/24/1942853.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值