关于生成并发唯一性流水号的解决方案
2015-02-11 20:26
183 查看
看了文章《弃用数据库自增ID,曝光一下我自己用到的解决方法 》,居然还显示到首页上去。我却觉得如果新手不辨真假,盲目顺从,那么会造成误人子弟的事实。.NET
生成流水号
首先从作者的写这篇文章的目的上讲他想实现的无非是下面目的:
1、不用自增长ID,因为自增长移植的时候不方便。
2、这个存储过程可以很高效的产生唯一性的自增长ID
从我小虎的认知上来回答:
1、对于作者的第一点,完全可以用Guid来替代自增长,或者在移植的时候,可以先去掉自增长的属性。
有的人说Guid性能比不上自增长ID,这里我们先不讨论这一点,个人认为效率问题主要体现在索引技巧上。
2、关键是作者的第二点,完全是不正确的,也是我写这篇文章的首要目的。因为这个存储过程根本就没有实现在多并发(多用户)的情况
下能真正产生唯一性的主键ID。
我们看原作者的代码:
1
create procedure [dbo].[up_get_table_key]
2
(
3
@table_name varchar(50),
4
@key_value int output
5
)
6
as
7
begin
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
33
end
请看我的测试代码以及并发结果图
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(newSystem.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、存储过程代码
1
Create procedure [dbo].[GetSerialNo]
2
(
3
@sCode varchar(50)
4
)
5
6
as
7
8
--exec
GetSerialNo
9
10
begin
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
76
end
77
78
废话不多说了,看测试代码和效果图
第一张图(左)是单独对进货单执行循环多进程
第二张图(中)是单独对发货单执行循环多进程
第三张图(右)是对进货单发货单同时执行循环多进程
也就是上面三个Thread,自己注释测试就可以了。
测试并发代码
1
protected 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(newSystem.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(newSystem.Threading.ThreadStart(Run3));
10
temp.Start();
11
temp2.Start();
12
temp3.Start();
13
}
14
}
15
}
16
17
private void Run()
18
{
19
System.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
生成流水号
首先从作者的写这篇文章的目的上讲他想实现的无非是下面目的:
1、不用自增长ID,因为自增长移植的时候不方便。
2、这个存储过程可以很高效的产生唯一性的自增长ID
从我小虎的认知上来回答:
1、对于作者的第一点,完全可以用Guid来替代自增长,或者在移植的时候,可以先去掉自增长的属性。
有的人说Guid性能比不上自增长ID,这里我们先不讨论这一点,个人认为效率问题主要体现在索引技巧上。
2、关键是作者的第二点,完全是不正确的,也是我写这篇文章的首要目的。因为这个存储过程根本就没有实现在多并发(多用户)的情况
下能真正产生唯一性的主键ID。
我们看原作者的代码:
1
create procedure [dbo].[up_get_table_key]
2
(
3
@table_name varchar(50),
4
@key_value int output
5
)
6
as
7
begin
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
33
end
请看我的测试代码以及并发结果图
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(newSystem.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、存储过程代码
1
Create procedure [dbo].[GetSerialNo]
2
(
3
@sCode varchar(50)
4
)
5
6
as
7
8
--exec
GetSerialNo
9
10
begin
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
76
end
77
78
废话不多说了,看测试代码和效果图
第二张图(中)是单独对发货单执行循环多进程
第三张图(右)是对进货单发货单同时执行循环多进程
也就是上面三个Thread,自己注释测试就可以了。
测试并发代码
1
protected 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(newSystem.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(newSystem.Threading.ThreadStart(Run3));
10
temp.Start();
11
temp2.Start();
12
temp3.Start();
13
}
14
}
15
}
16
17
private void Run()
18
{
19
System.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
相关文章推荐
- 转载:关于生成并发唯一性流水号的解决方案
- 今天看见虎姐写的关于生成并发唯一性流水号的解决方案
- 关于生成并发唯一性流水号的解决方案
- 关于生成并发唯一性流水号的解决方案
- 关于生成并发唯一性流水号的解决方案
- 关于生成并发唯一性流水号的解决方案
- 关于生成并发唯一性流水号的解决方案
- 关于生成并发唯一性流水号的解决方案
- 关于SQL SERVER高并发解决方案
- 关于高负载高并发的服务器端应用,java解决方案(二)
- 请教关于pdf2swf在linux上生成swf文件的解决方案
- 关于生成订单号的解决方案
- [转]关于生成订单号的解决方案 -- 较好的解决方案
- 关于.NET解决方案批生成的一点探索(同一个解决方案下多项目批生成)
- 关于流水帐表序列号生成时的并发操作问题
- 关于生成订单号的解决方案PHP
- WCF关于svcutil生成关于绑定出现 元数据包含无法解析的引用的解决方案
- 关于生成订单号的解决方案PHP
- 关于Gridview自动生成列后,手动设定的模板列位置问题解决方案
- 关于高负载高并发的服务器端应用,java解决方案(一)