二、手动增长型字段
  既然自动增长型字段会带来如此的麻烦,我们不妨考虑使用手动增长型的字段,也是说主键的值需要自己维护,通常情况下需要建立一张单独的表存储当前主键键值。还用上面的例子来说,这次我们新建一张表叫IntKey,包含两个字段,KeyName以及KeyValue。像一个HashTable,给一个KeyName,可以知道目前的KeyValue是什么,然后手工实现键值数据递增。在SQLServer中可以编写这样一个存储过程,让取键值的过程自动进行。代码如下:
  CREATEPROCEDURE[GetKey]
  @KeyNamechar(10),
  @KeyValueintOUTPUT
  AS
  UPDATEIntKeySET@KeyValue=KeyValue=KeyValue+1WHEREKeyName=@KeyName
  GO
  这样,通过调用存储过程,我们可以获得新键值,确保不会出现重复。若将OrderID字段设置为手动增长型字段,我们的程序可以由以下几步来实现:首先调用存储过程,获得一个OrderID,然后使用这个OrderID填充Order表与OrderDetail表,后在事务保护下对两表进行更新。
  使用手动增长型字段作为主键在进行数据库间数据复制时,可以确保数据合并过程中不会出现键值冲突,只要我们为不同的数据库分配不同的主键取值段行了。但是,使用手动增长型字段会增加网络的RoundTrip,我们必须通过增加一次数据库访问来获取当前主键键值,这会增加网络和数据库的负载,当处于一个低速或断开的网络环境中时,这种做法会有很大的弊端。同时,手工维护主键还要考虑并发冲突等种种因素,这更会增加系统的复杂程度。
  三、使用UniqueIdentifier
  SQLServer为我们提供了UniqueIdentifier数据类型,并提供了一个生成函数NEWID(),使用NEWID()可以生成一个的UniqueIdentifier。UniqueIdentifier在数据库中占用16个字节,出现重复的概率非常小,以至于可以认为是0。我们经常从注册表中看到类似
  {45F0EB02-0727-4F2E-AAB5-E8AEDEE0CEC5}
  的东西实际上是一个UniqueIdentifier,Windows用它来做COM组件以及接口的标识,防止出现重复。在.NET里管UniqueIdentifier称之为GUID(GlobalUniqueIdentifier)。在C#中可以使用如下命令生成一个GUID:
  Guidu=System.Guid.NewGuid();
  对于上面提到的Order与OrderDetail的程序,如果选用UniqueIdentifier作为主键的话,我们完全可以避免上面提到的增加网络RoundTrip的问题。通过程序直接生成GUID填充主键,不用考虑是否会出现重复。
  UniqueIdentifier字段也存在严重的缺陷:首先,它的长度是16字节,是整数的4倍长,会占用大量存储空间。更为严重的是,UniqueIdentifier的生成毫无规律可言,要想在上面建立索引(绝大多数数据库在主键上都有索引)是一个非常耗时的操作。有人做过实验,插入同样的数据量,使用UniqueIdentifier型数据做主键要比使用Integer型数据慢,所以,出于效率考虑,尽可能避免使用UniqueIdentifier型数据库作为主键键值。
  四、使用“COMB(Combine)”类型
  既然上面三种主键类型选取策略都存在各自的缺点,那么到底有没有好的办法加以解决呢?答案是肯定的。通过使用COMB类型(数据库中没有COMB类型,它是JimmyNilsson在他的“TheCostofGUIDsasPrimaryKeys”一文中设计出来的),可以在三者之间找到一个很好的平衡点。
  COMB数据类型的基本设计思路是这样的:既然UniqueIdentifier数据因毫无规律可言造成索引效率低下,影响了系统的性能,那么我们能不能通过组合的方式,保留UniqueIdentifier的前10个字节,用后6个字节表示GUID生成的时间(DateTime),这样我们将时间信息与UniqueIdentifier组合起来,在保留UniqueIdentifier的性的同时增加了有序性,以此来提高索引效率。也许有人会担心UniqueIdentifier减少到10字节会造成数据出现重复,其实不用担心,后6字节的时间精度可以达到1/300秒,两个COMB类型数据完全相同的可能性是在这1/300秒内生成的两个GUID前10个字节完全相同,这几乎是不可能的!在SQLServer中用SQL命令将这一思路实现出来便是:
  DECLARE@aGuidUNIQUEIDENTIFIER
  SET@aGuid=CAST(CAST(NEWID()ASBINARY(10))
  +CAST(GETDATE()ASBINARY(6))ASUNIQUEIDENTIFIER)
  经过测试,使用COMB做主键比使用INT做主键,在检索、插入、更新、删除等操作上仍然显慢,但比Unidentifier类型要快上一些。关于测试数据可以参考我2004年7月21日的随笔。
  除了使用存储过程实现COMB数据外,我们也可以使用C#生成COMB数据,这样所有主键生成工作可以在客户端完成。C#代码如下:
///<summary>
///返回GUID用于数据库操作,特定的时间代码可以提高检索效率
///</summary>
///<returns>COMB(GUID与时间混合型)类型GUID数据</returns>
publicstaticGuidNewComb()
{
byte[]guidArray=System.Guid.NewGuid().ToByteArray();
DateTimebaseDate=newDateTime(1900,1,1);
DateTimenow=DateTime.Now;
//Getthedaysandmillisecondswhichwillbeusedtobuildthebytestring
TimeSpandays=newTimeSpan(now.Ticks-baseDate.Ticks);
TimeSpanmsecs=newTimeSpan(now.Ticks-(newDateTime(now.Year,now.Month,now.Day).Ticks));
//Converttoabytearray
//NotethatSQLServerisaccurateto1/300thofamillisecondsowedivideby3.333333
byte[]daysArray=BitConverter.GetBytes(days.Days);
byte[]msecsArray=BitConverter.GetBytes((long)(msecs.TotalMilliseconds/3.333333));
//ReversethebytestomatchSQLServersordering
Array.Reverse(daysArray);
Array.Reverse(msecsArray);
//Copythebytesintotheguid
Array.Copy(daysArray,daysArray.Length-2,guidArray,guidArray.Length-6,2);
Array.Copy(msecsArray,msecsArray.Length-4,guidArray,guidArray.Length-4,4);
returnnewSystem.Guid(guidArray);
}
///<summary>
///从SQLSERVER返回的GUID中生成时间信息
///</summary>
///<paramname="guid">包含时间信息的COMB</param>
///<returns>时间</returns>
publicstaticDateTimeGetDateFromComb(System.Guidguid)
{
DateTimebaseDate=newDateTime(1900,1,1);
byte[]daysArray=newbyte[4];
byte[]msecsArray=newbyte[4];
byte[]guidArray=guid.ToByteArray();
//Copythedatepartsoftheguidtotherespectivebytearrays.
Array.Copy(guidArray,guidArray.Length-6,daysArray,2,2);
Array.Copy(guidArray,guidArray.Length-4,msecsArray,0,4);
//Reversethearraystoputthemintotheappropriateorder
Array.Reverse(daysArray);
Array.Reverse(msecsArray);
//Convertthebytestoints
intdays=BitConverter.ToInt32(daysArray,0);
intmsecs=BitConverter.ToInt32(msecsArray,0);
DateTimedate=baseDate.AddDays(days);
date=date.AddMilliseconds(msecs*3.333333);
returndate;
}
  结语
  数据库主键在数据库中占有重要地位。主键的选取策略决定了系统是否高效、易用。本文比较了四种主键选取策略的优缺点,并提供了相应的代码解决方案,希望对大家有所帮助。