关系型数据库主键总结

在基于关系型数据库设计时候,一般要为每张表指定一个主键,所谓主键就是可以惟一标识表中某一行记录的属性或属性组,一个表只能有一个主键,但能够 有多个候选索引。由于主键能够惟一标识某一行记录,因此能够确保执行数据更新、删除、修改时不出现错误。固然,其它字段能够辅助咱们在执行这些操做时消除 共享冲突,不是本文讨论的重点,再也不赘述。主键除了上述做用外,经常与外键构成参照完整性约束,防止出现数据不一致。因此数据库在设计时,主键起到了很重 要的做用。常见的数据库主键选取方式有: 自动增加式、手动增加式 、UniqueIdentifier、联合式(复合式)、时间序列+随机数式、“COMB(Combine)”类型。
1、自动增加式
不少数据库设计者喜欢使用自动增加型字段,由于它使用简单。自动增加式容许咱们在向数据库添加数据时,不考虑主键的取值,记录插入后,数据库系统会 自动为其分配一个值,确保绝对不会出现重复。若是使用SQL Server数据库的话,咱们还能够在记录插入后使用@@IDENTITY全局变量获取系统分配的主键值。 
尽管自动增加式字段会省掉咱们不少繁琐的工做,但使用它也存在潜在的问题,那就是在数据缓冲模式下,很难预先填写主键与外键的值。假设有主辅两张表:
Order(OrderID, OrderDate)  订单表
OrderDetial(OrderID, LineNum, ProductID, Price)  订单明细表
Order 表中的OrderID是自动增加型的字段。假设如今须要咱们录入一张订单,包括在Order表中插入一条记录以及在OrderDetail表中插入若干条 记录。由于Order表中的OrderID是自动增加型的字段,那么咱们在记录正式插入到数据库以前没法事先得知它的取值,只有在更新后才能知道数据库为 它分配的是什么值。这会形成如下矛盾发生: 
首先,为了能在OrderDetail的OrderID字段中添入正确的值,必须先更新 Order表以获取到系统为其分配的OrderID值,而后再用这个OrderID填充OrderDetail表的OrderID列。最后更新 OderDetail表。可是,为了确保数据的一致性,Order与OrderDetail在更新时必须在事务模式下进行的,即要么两张表同时同时更新成 功、要么所有失败,显然它们是相互矛盾的。 
 其次,当咱们须要在多个数据库间进行数据的复制时(SQL Server的数据分发、订阅机制容许咱们进行库间的数据复制操做),自动增加式字段可能形成数据合并时的主键冲突及表关联关系的丢失。设想一个数据库中 的Order表向另外一个库中的Order表复制数据库时,OrderID到底该不应自动增加呢?若是自动增加,其子表OrderDetial的关联关系会 丢失,若是不增加就会和现有数据主键重复,是否是很矛盾呢? 
再次,自增量的值都是须要在系统中维护一个全局的数据值,每次插入数据时即对这次值进行增量取值。当在产生惟一标识的并发环境中,每次的增量取值都必须为此全局值加锁解锁以保证增量的惟一性。形成并发瓶颈,下降查询性能。
    还有当数据表足够大或频繁的更改和插入操做致使主键类型值超出范围,这种状况通常不多碰到,但也是咱们进行数据表设计时必须考虑的一个问题数据库

在实际开发中中容易出现主键冲突 主键冲突就是主键出现重复值
好比 t_user t_admin 都有主键 都是id 自增方式
Insert into t_user (id)
Select id from t_admin
两表中都有主键为 7的时候 插入数据不成功 由于主键冲突
解决办法
UPDATE t_admin SET id=-id
WHERE id IS NOT NULL AND id>0编程

Commit;网络

2、手动增加型字段
既然自动增加型字段会带来如此的麻烦,咱们不妨考虑使用手动增加型的字段,也就是说主键的值须要本身维护,一般状况下须要创建一张单独的表存储当前 主键键值。为了叙述上的方便仍然利用上面的例子进行阐述,新建一张表叫IntKey,包含两个字段,KeyName以及KeyValue。就像一个 HashTable,给一个KeyName,就能够知道目前的KeyValue是什么,而后手工实现键值数据递增。在SQL Server中能够编写这样一个存储过程,让取键值的过程自动进行。代码以下:
CREATE PROCEDURE [GetKey] 
@KeyName char(10), 
@KeyValue int OUTPUT 
AS 
UPDATE IntKey SET @KeyValue = KeyValue = KeyValue + 1 WHERE KeyName = @KeyName 
GO
这样,经过调用存储过程,咱们能够得到最新键值,确保不会出现重复。若将OrderID字段设置为手动增加式字段,咱们的程序能够由如下几步来实 现:首先调用存储过程,得到一个OrderID,而后使用这个OrderID填充Order表与OrderDetail表,最后在事务机制下对两表进行更 新。 
使用手动增加式字段做为主键在进行数据库间数据复制时,能够确保数据合并过程当中不会出现键值冲突,只要为不一样的数据表分配不一样的主键取值段 就好了。可是,使用手动增加型字段会增长网络的负担,必须经过增长一次数据库访问来获取当前主键键值,这会增长网络和数据库的负载,当处于一个低速或断开 的网络环境中时,这种作法会有很大的弊端。同时,手工维护主键还要考虑并发冲突等种种因素,这更会增长系统的复杂程度。
3、使用UniqueIdentifier
SQL Server为咱们提供了UniqueIdentifier数据类型,并提供了一个生成函数NEWID( ),使用NEWID( )能够生成一个惟一的UniqueIdentifier。UniqueIdentifier在数据库中占用16个字节,出现重复的几率几乎为0,号称全球 惟一标识。咱们常常从注册表或WINDOWS程序出现错误须要调试时看到相似 768427bf-9b37-4776-97ca-000365e160d5或{45F0EB02-0727-4F2E-AAB5- E8AEDEE0CEC5} 的东西实际上就是一个UniqueIdentifier,Windows用它来作COM组件以及接口的标识,防止出现重复。在.NET中 UniqueIdentifier称之为GUID(Global Unique Identifier)。在C#中可使用以下命令生成一个GUID: 
Guid u = System.Guid.NewGuid(); 
对 于上面提到的Order与OrderDetail的程序,若是选用UniqueIdentifier做为主键的话,咱们彻底能够避免上面提到的增长网络 RoundTrip的问题。经过程序直接生成GUID填充主键,不用考虑是否会出现重复。 可是UniqueIdentifier 字段也存在严重的缺陷:首先,它的长度是16字节,是整数的4倍长,会占用大量存储空间。更为严重的是,UniqueIdentifier的生成毫无规律 可言,也就是说是无序的,要想在上面创建索引(绝大多数数据库在主键上都有索引)是一个很是耗时的操做。有人作过实验,当数据表记录比较大的时,在不一样的 数据量级别上插入一样的数据量,使用 UniqueIdentifier型数据作主键要比使用Integer型数据慢,且尚未考虑到表关联的状况,出于效率考虑,尽量避免使用 UniqueIdentifier型数据库做为主键值,但随着现代计算机计算速度愈来愈快,在中小型项目中使用UniqueIdentifier式主键也 是一个选项。
4、使用业务字段联合主键
    
基于DEPHI和 POWERBUILDER等数据库工具开发C/S系统的数据库设计人员,习惯上用有业务意义的字段组合成复合主键作数据表主键。使用业务主键固然有其与生 俱来的好处,通常状况下数据库系统会在默认条件下创建聚簇索引,并且这个聚簇索引基于主键升序排列,当数据量比较小时,咱们感受不到这种差异,当数据量比 较大时,这种基于主键定义的聚簇索引的优点就显现出来,这就使得数据表在每次存取数据时按照索引准确确认数据插入或更新的磁盘物理位置,减小磁头寻址时 间,从而提升数据库性能,并且可以从业务意义上保证数据的完整性,增长程序的可靠性。可是基于业务字段的联合索引,当业务字段选用比较多时会占用比较多的 磁盘空间,并且索引页会占用更多的内存页面,从而致使查询命中率下降;另外使用业务主键,当涉及到主键数据的修改时,要在编程过程当中记录新值和原值的关系 表,在更新时又要进行新值和原值的比对,增长编写程序的复杂度。
5、时间序列+随机数主键
采用精确到毫秒甚至钠秒级的时间和一个随机产生的两位数作主键,如200911282311528+两位随机数,不失为解决主键问题的一个有效办 法。这样产生的主键既避免了UniqueIdentifier型字段作主键时的无序,又能有效避免自动增加型主键带来的诸如复制和数据导入的麻烦。但在使 用用户众多的网络实时系统中,在时间和空间上仍然不能保证惟一性的问题。
6、使用“COMB(Combine)”类型
既然上面五种主键类型选取策略都存在各自的缺点,那么到底有没有好的办法加以解决呢?答案是确定的。经过使用COMB类型(数据库中没有COMB类 型,它是Jimmy Nilsson在他的“The Cost of GUIDs as Primary Keys”一文中设计出来的),能够在以上众多的主键策略之间采用中庸之道,找到一个很好的平衡点。
COMB数据类型的基本设计思路是这样的:既然UniqueIdentifier数据因毫无规律可言形成索引效率低下,影响了系统的性能,那么咱们 能不能经过组合的方式,保留UniqueIdentifier的前10个字节,用后6个字节表示GUID生成的时间(DateTime),这样咱们将时间 信息与 UniqueIdentifier组合起来,在保留UniqueIdentifier的惟一性的同时增长了有序性,以此来提升索引效率。也许有人会担忧 UniqueIdentifier减小到10字节会形成数据出现重复,其实不用担忧,后6字节的时间精度能够达到1/300秒,两个COMB类型数据彻底 相同的可能性是在这1/300秒内生成的两个GUID前10个字节彻底相同,这几乎是不可能的!在SQL Server中用SQL命令将这一思路实现出来即是:
DECLARE @aGuid UNIQUEIDENTIFIER 
SET @aGuid = CAST(CAST(NEWID() AS BINARY(10)) 并发

  • CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)
    通过测试,使用COMB作主键比使用INT作主键,在检索、插入、更新、删除等操做上仍然显慢,但比Unidentifier类型要快上一些。除了使用存储过程实现COMB数据外,咱们也可使用C#生成COMB数据,这样全部主键生成工做能够在客户端完成。
    C#代码以下: 
    复制代码代码以下:

//================================================ 
/////<summary> 
/// 返回 GUID 用于数据库操做,特定的时间代码能够提升检索效率 
/// </summary> 
/// <returns>COMB (GUID 与时间混合型) 类型 GUID 数据</returns> 
public static Guid NewComb() 

byte[] guidArray = System.Guid.NewGuid().ToByteArray(); 
DateTime baseDate = new DateTime(1900,1,1); 
DateTime now = DateTime.Now; 
// Get the days and milliseconds which will be used to build the byte string 
TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks); 
TimeSpan msecs = new TimeSpan(now.Ticks - (new DateTime(now.Year, now.Month, now.Day).Ticks));
// Convert to a byte array 
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
byte[] daysArray = BitConverter.GetBytes(days.Days); 
byte[] msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds/3.333333)); 
// Reverse the bytes to match SQL Servers ordering 
Array.Reverse(daysArray); 
Array.Reverse(msecsArray); 
// Copy the bytes into the guid 
Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2); 
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4); 
return new System.Guid(guidArray); 

//================================================ 
/
//// <summary> 
/// 从 SQL SERVER 返回的 GUID 中生成时间信息 
/// </summary> 
/// <param name="guid">包含时间信息的 COMB </param> 
/// <returns>时间</returns> 
public static DateTime GetDateFromComb(System.Guid guid) 

DateTime baseDate = new DateTime(1900,1,1); 
byte[] daysArray = new byte[4]; 
byte[] msecsArray = new byte[4]; 
byte[] guidArray = guid.ToByteArray(); 
// Copy the date parts of the guid to the respective byte arrays. 
Array.Copy(guidArray, guidArray.Length - 6, daysArray, 2, 2); 
Array.Copy(guidArray, guidArray.Length - 4, msecsArray, 0, 4); 
// Reverse the arrays to put them into the appropriate order 
Array.Reverse(daysArray); 
Array.Reverse(msecsArray); 
// Convert the bytes to ints 
int days = BitConverter.ToInt32(daysArray, 0); 
int msecs = BitConverter.ToInt32(msecsArray, 0); 
DateTime date = baseDate.AddDays(days); 
date = date.AddMilliseconds(msecs * 3.333333); 
return date; 

综上述六种主键选取策略,我认为使用“COMB(Combine)”类型作主键是比较恰当的主键应用策略,但在实际使用过程当中要根据客观实践、因时因事选取适当的主键,切不可生搬硬套、弄巧成拙。app

相关文章
相关标签/搜索