您的位置:首页 > 数据库

弃用数据库自增ID,曝光一下我自己用到的解决方法之---终结篇

2011-01-20 14:17 267 查看
/article/5273631.html

我写这篇随笔的目的旨在澄清我在上一篇随笔“弃用数据库自增ID,曝光一下我自己用到的解决方法“中的一些事实与看法,同时,我将继续在并发的问题的作题,

我将在原来的存储过程上得用锁来解决并问题并附加上我的测试代码与测试数据。

我之所以放在首页,并不是代表我这篇文章多有水平,多专业,我只是想分享一个程序员内心里深藏着的一点点设计思路与项目经验。通过分享来提高个人!

PS:我写作的初衷重在于分享自己的技术知识,对问题的设计思路以及通过分享来得到更好的建议,同样的一件东西,并不是也并不可能适合于所有人或都所有场景,就算拿来主义也需要先去其糟粕,而后取其精华!老赵原来写过一些文章的初衷被很多人误解,而且还有一部分人在不明白作者的原意初衷上进行攻击,真的是悲哀!不过还好,老赵这厮足够扎实,顶得住压力,呵呵....

同时我针对一些评论也发下自己的看法:

  有很多人说用GUID代替,这当然也是一种方法,我如果说要曝光的方法竟然是用GUID来解决数据移值、数据分割的问题,相信肯定会有大把砖头扔过来!

  有人说用自增ID,自增ID也可以设置起始值,没错,看项目需要吧,我的随笔中真的没有提到一定要你用我的方法啊!但是我总觉得插入关联数据时不方便,总是需要先拿到关键的外键值,不过有人Peter.zhu说到,可以在插入前拿到这个值,这个我还真不知道,还望告之...,

有人说(活雷锋DorianDeng)每次看到为了数据移植等情况摒弃自增ID都觉得可笑...我一直都想不通迁移的频率或者概率有多大,如果这是十年后的问题...我觉得这话说得有点绝对,未雨绸缪并不见得就是一件坏事,从全局性看问题绝对没错,没必要把自己的那点项目经验来一并否决所有项目需要。这得看是什么类型项目了,这个世界上没有绝对的事情,我就还真碰到过!

最多人说的就是并发问题,没错,确实会有并发问题,我讲得很清楚这个是它的一个缺点。不过还是有人拿它作文章,哪有十全十美的事情,不过对于一些小的应用,应该可以满足了!当然我曾经也用过!

最后,也得感谢很多人,如:llzhzhbb,卡通一下,sinxsoft,周强,.........

  废话不多讲了!言归正传

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~separator~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1:测试前提:

测试之前,我先对原来的存储过程做一个单词的修改(当然了,我个存储过程可以优化,上一篇中有人给过不少的建议),这里我就不再罗嗦了,先看看我改后之后一个存储过程:

viewsource

print?

01
create
procedure
up_get_table_key
02
(
03
@table_name
varchar
(50),
04
@key_value
int
output
05
)
06
as
07
begin
08
begin
tran
09
declare
@
key
int
10
11
--initializethekeywith1
12
set
@
key
=1
13
--whetherthespecifiedtableisexist
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
)
--defaultkeyvlaue:1
17
end
18
--stepincrease
19
else
20
begin
21
select
@
key
=key_value
from
table_key
with
(updlock)
where
table_name=@table_name
22
set
@
key
=@
key
+1
23
--updatethekeyvaluebytablename
24
update
table_key
set
key_value=@
key
where
table_name=@table_name
25
end
26
--setouputvalue
27
set
@key_value=@
key
28
29
--committran
30
commit
tran
31
if@@error>0
32
rollback
tran
33
end
注意在查询的时候我使用了updlock,我在检索时对该行加锁,updlock的优点是允许用户读取数据(不阻塞其它事务)并在以后更新数据,同时确保自从上次读取数据后数据没有被更改。也就是说,我在本次事务调用中取到key值之后,直到更新该数据之后,锁可以保证了key值不会更改,确实不会的会话不会拿到相同的key值。

2:测试

我的测试环境:SQLserver2008,.netframework3.5

先附上我的程序代码:

viewsource

print?

01
private
void
GetIncreateID()
02
{
03
while
(!isStop)
04
{
05
int
a=Database.GetIncreaseID(
"stud"
);
06
//intb=Database.GetIncreaseID("stud");
07
//intc=Database.GetIncreaseID("stud");
08
//intd=Database.GetIncreaseID("stud");
09
Database.AddStudent(a,a.ToString());
10
//Debug.WriteLine(string.Format("a={0},b={1},c={2},d={3}",a,b,c,d));
11
12
Thread.Sleep(200);
13
}
14
}
15
16
protected
void
start_Click(
object
sender,EventArgse)
17
{
18
Threadthread1=
new
Thread(
new
ThreadStart(GetIncreateID));
19
Threadthread2=
new
Thread(
new
ThreadStart(GetIncreateID));
20
Threadthread3=
new
Thread(
new
ThreadStart(GetIncreateID));
21
Threadthread4=
new
Thread(
new
ThreadStart(GetIncreateID));
22
23
thread1.Start();
24
thread2.Start();
25
thread3.Start();
26
thread4.Start();
27
28
for
(
int
i=0;i<1000;i++)
29
{
30
Threadthread=
new
Thread(
new
ThreadStart(GetIncreateID));
31
thread.Start();
32
}
33
}
34
35
protected
void
stop_Click(
object
sender,EventArgse)
36
{
37
isStop=
true
;
38
}
  数据访问层代码:

showsourceviewsource

print?

01
public
static
int
GetIncreaseID(
string
tableName)
02
{
03
04
DbCommandcommand=CreateCommand();
05
command.CommandType=System.Data.CommandType.StoredProcedure;
06
command.CommandText=
"up_get_table_key"
;
//GetSequence
07
08
DbParameterpara1=command.CreateParameter();
09
para1.ParameterName=
"@table_name"
;
10
para1.Value=tableName;
11
para1.DbType=System.Data.DbType.String;
12
para1.Size=50;
13
14
DbParameterpara2=command.CreateParameter();
15
para2.ParameterName=
"@key_value"
;
16
para2.DbType=System.Data.DbType.Int32;
17
para2.Direction=System.Data.ParameterDirection.Output;
18
19
command.Parameters.Add(para1);
20
command.Parameters.Add(para2);
21
22
int
increaseId=0;
23
24
try
25
{
26
command.Connection.Open();
27
command.ExecuteNonQuery();
28
increaseId=Convert.ToInt32(command.Parameters[
"@key_value"
].Value);
29
}
30
catch
(DbExceptionex)
31
{
32
throw
ex;
33
}
34
finally
35
{
36
command.Connection.Close();
37
}
38
39
return
increaseId;
40
}
41
42
public
static
void
AddStudent(
int
id,
string
name)
43
{
44
DbCommandcommand=CreateCommand();
45
command.CommandType=System.Data.CommandType.Text;
46
command.CommandText=
string
.Format(
"insertintostudvalues({0},'{1}')"
,id,name);
47
48
try
49
{
50
command.Connection.Open();
51
command.ExecuteNonQuery();
52
}
53
catch
(DbExceptionex)
54
{
55
throw
ex;
56
}
57
finally
58
{
59
command.Connection.Close();
60
}
61
}
  我开启了至少1000个线程来插入数据,插入数据id,name,其中id是表stud表的主键值,如果存在相同的ID值,插入数据时肯定会抛异常!

再看看我的结果:



  我插入了700多万条数据,,纯粹只为测试并发性,没有发生相同的ID情况!

BTW,我根据llzhzhbb的建议,也试着去实现它来测试并发性!这是一种全新的做法,先看看思路:

1:定义静态int变量来取代自增ID

2:应用程序启动时初始化该静态变量为表MAXID值

3:插入数据时,先对静态变量Interlocked.Increment自增1,然后再插入新的数据

这种做法实际就是绕过数据库,利用应用程序来解决并发性,Interlocked.Increment会以生成原子操作来进行递增并存储结果,它可以保证多线程的并发时同步性,从而使得自增ID的唯一性。

如果不是使用Interlocked.Increment会有同样的并发问题,因为很大一部分计算机上,自增操作并不是一种原子操作,因为在递增的过程中CPU会先把你要递增的变量值先读取到寄存器中,然后再对它进行自增操作,最后再把寄存器中已自增的值保存到变量中,明显,这中间发生了三个操作。因此无法保证在多线程下的并发问题。

当然,这只是一种思路,来看看它的实际应用情况吧,还是用数据说话,先看看代码:

viewsource

print?

01
public
static
class
TableMappingVariable
02
{
03
public
static
int
CourseMappingID=0;
04
05
static
TableMappingVariable()
06
{
07
DbCommandcommand=Database.CreateCommand();
08
command.CommandType=System.Data.CommandType.Text;
09
command.CommandText=
"selectmax(cour_id)fromcourse"
;
10
11
12
try
13
{
14
command.Connection.Open();
15
DbDataReaderreader=command.ExecuteReader();
16
if
(reader.Read())
17
{
18
CourseMappingID=reader.GetInt32(0);
19
}
20
}
21
catch
(DbExceptionex)
22
{
23
throw
ex;
24
}
25
finally
26
{
27
command.Connection.Close();
28
}
29
}
30
}
  为了简单起见,我直接把它写在静态构造函数了,初始化时取最大ID值。

同样,我在测试程序中,开启了至少1000个线程来插入数据。

viewsource

print?

01
protected
void
btnStart_Click(
object
sender,EventArgse)
02
{
03
Threadthread1=
new
Thread(
new
ThreadStart(GetIncreateID));
04
Threadthread2=
new
Thread(
new
ThreadStart(GetIncreateID));
05
Threadthread3=
new
Thread(
new
ThreadStart(GetIncreateID));
06
Threadthread4=
new
Thread(
new
ThreadStart(GetIncreateID));
07
08
thread1.Start();
09
thread2.Start();
10
thread3.Start();
11
thread4.Start();
12
13
for
(
int
i=0;i<1000;i++)
14
{
15
Threadthread=
new
Thread(
new
ThreadStart(AddCourse));
16
thread.Start();
17
}
18
}
19
20
public
void
AddCourse()
21
{
22
while
(!isStop)
23
{
24
Database.AddCourse(Interlocked.Increment(
ref
TableMappingVariable.CourseMappingID),TableMappingVariable.CourseMappingID.ToString());
25
Thread.Sleep(200);
26
}
27
}
28
29
protected
void
btnStop_Click(
object
sender,EventArgse)
30
{
31
isCourseStop=
true
;
32
}
再看看测试数据:



我插入了400多万的数据。测试结果非常正确!这种方法相对于MAX(ID)+1来说,确实减少了数据库访问的次数,MAX(ID)+1在每次插入时都需要去表中检索一次来得到MAX(ID).这种方法,我只需要读取一次就够了!当然不包括程序挂掉的情况........

总结,我不想讲太多,当然了,你可以继续使用GUID,自增ID,或者MAX(ID)+1.....等来作为你数据库的主键,不过这些对我来说,并不重要,我不懂金谍K3,SQLLITE,对ORACLE懂得也不是很多,更不知道什么是copmiere,更不懂得他们的设计思想,如果能公开,那就最好不过了。这个东西纯粹只为测试并发性!有可能并不适合你!

欢迎评论!

最后,附上我的测试代码:(点击下载)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐