关系型数据库主键总结

在基于关系型数据库设计时候,通常要为每张表指定一个主键,所谓主键就是能够唯一标识表中某一行记录的属性或属性组,一个表只能有一个主键,但可以 有多个候选索引。因为主键可以唯一标识某一行记录,所以可以确保执行数据更新、删除、修改时不出现错误。当然,其它字段可以辅助我们在执行这些操作时消除 共享冲突,不是本文讨论的重点,不再赘述。主键除了上述作用外,常常与外键构成参照完整性约束,防止出现数据不一致。所以数据库在设计时,主键起到了很重 要的作用。常见的数据库主键选取方式有: 自动增长式、手动增长式 、UniqueIdentifier、联合式(复合式)、时间序列+随机数式、“COMB(Combine)”类型。
一、自动增长式
很多数据库设计者喜欢使用自动增长型字段,因为它使用简单。自动增长式允许我们在向数据库添加数据时,不考虑主键的取值,记录插入后,数据库系统会 自动为其分配一个值,确保绝对不会出现重复。如果使用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;

二、手动增长型字段
既然自动增长型字段会带来如此的麻烦,我们不妨考虑使用手动增长型的字段,也就是说主键的值需要自己维护,通常情况下需要建立一张单独的表存储当前 主键键值。为了叙述上的方便仍然利用上面的例子进行阐述,新建一张表叫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表,最后在事务机制下对两表进行更 新。?
使用手动增长式字段作为主键在进行数据库间数据复制时,可以确保数据合并过程中不会出现键值冲突,只要为不同的数据表分配不同的主键取值段 就行了。但是,使用手动增长型字段会增加网络的负担,必须通过增加一次数据库访问来获取当前主键键值,这会增加网络和数据库的负载,当处于一个低速或断开 的网络环境中时,这种做法会有很大的弊端。同时,手工维护主键还要考虑并发冲突等种种因素,这更会增加系统的复杂程度。
三、使用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式主键也 是一个选项。
四、使用业务字段联合主键
????
基于DEPHI和 POWERBUILDER等数据库工具开发C/S系统的数据库设计人员,习惯上用有业务意义的字段组合成复合主键做数据表主键。使用业务主键当然有其与生 俱来的好处,一般情况下数据库系统会在默认条件下建立聚簇索引,而且这个聚簇索引基于主键升序排列,当数据量比较小时,我们感觉不到这种差别,当数据量比 较大时,这种基于主键定义的聚簇索引的优势就显现出来,这就使得数据表在每次存取数据时按照索引准确确认数据插入或更新的磁盘物理位置,减少磁头寻址时 间,从而提高数据库性能,而且能够从业务意义上保证数据的完整性,增加程序的可靠性。但是基于业务字段的联合索引,当业务字段选用比较多时会占用比较多的 磁盘空间,而且索引页会占用更多的内存页面,从而导致查询命中率降低;另外使用业务主键,当涉及到主键数据的修改时,要在编程过程中记录新值和原值的关系 表,在更新时又要进行新值和原值的比对,增加编写程序的复杂度。
五、时间序列+随机数主键
采用精确到毫秒甚至钠秒级的时间和一个随机产生的两位数做主键,如200911282311528+两位随机数,不失为解决主键问题的一个有效办 法。这样产生的主键既避免了UniqueIdentifier型字段做主键时的无序,又能有效避免自动增长型主键带来的诸如复制和数据导入的麻烦。但在使 用用户众多的网络实时系统中,在时间和空间上仍然不能保证唯一性的问题。
六、使用“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)”类型做主键是比较恰当的主键应用策略,但在实际使用过程中要根据客观实践、因时因事选取适当的主键,切不可生搬硬套、弄巧成拙。

原文地址:http://blog.51cto.com/10975663/2064917

时间: 2024-08-26 18:40:30

关系型数据库主键总结的相关文章

关于数据库主键和外键

一.什么是主键.外键: 关系型数据库中的一条记录中有若干个属性,若其中某一个属性组(注意是组)能唯一标识一条记录,该属性组就可以成为一个主键 比如  学生表(学号,姓名,性别,班级) 其中每个学生的学号是唯一的,学号就是一个主键 课程表(课程编号,课程名,学分) 其中课程编号是唯一的,课程编号就是一个主键 成绩表(学号,课程号,成绩) 成绩表中单一一个属性无法唯一标识一条记录,学号和课程号的组合才可以唯一标识一条记录,所以 学号和课程号的属性组是一个主键   成绩表中的学号不是成绩表的主键,但它

SQL 数据库主键 ,外键

主键 数据库主键是指表中一个列或列的组合,其值能唯一地标识表中的每一行.这样的一列或多列称为表的主键,通过它可强制表的实体完整性.当创建或更改表时可通过定义 PRIMARY KEY约束来创建主键.一个表只能有一个 PRIMARY KEY 约束,而且 PRIMARY KEY 约束中的列不能接受空值.由于 PRIMARY KEY 约束确保唯一数据,所以经常用来定义标识列. 主键的作用 主键的主要作用如下: (1)保证实体的完整性: (2)加快数据库的操作速度: (3) 在表中添加新记录时,数据库会自

数据库主键

在我们的数据库设计中,不可逃避的就是数据库表的主键,可能有很多朋友没有深入思考过,主键的设计对整个数据库的设计影响很大,因此我们不得不要重视起来. 主键的必要性 : 在有些数据库中,虽然主键不是必需的,但最好为每个表都设置一个主键,不管是单主键还是复合主键.它存在代表着表结构的完整性,表的记录必须得有唯一区分的字段,主键主要是用于其他表的外键关联,以及本记录的修改与删除. 主键的无意义性 : 在开发过程中,读者可能会看到将一些表使用有意义的字段表示主键,例如"用户登录信息表"将&quo

数据库主键和外键

1 什么是主键 外键 学生表(学号,姓名,性别,班级)   学号是一个主键 课程表(课程号,课程名,学分) 课程号是一个主键 成绩表(学号,课程号,成绩) 学号和课程号的属性组构成一个主键 成绩表中的学号不是成绩表的主键,不过是学生表的主键,成绩表的外键,同理课程号也是成绩表的外键 定义:如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键 以一个关系的外键作主关键字的表被称为主表,具有此外键的表被称为主表的从表 2 外键的作用 外键用于保持数据一致性,完整性 主要目的

数据库主键自增插入显示值

SQL Server 2008 数据库主键自增插入显示值 前几天在工作的时候遇到在删除数据库中表的数据的时候,删除之后,重新添加的数据没有得到原来的数据的id值(表中id为主键,且设置为自增) ,使用的是SQL Server 2008 ,现在已解决,和大家分享一下! 具体情况: 1.建立表t_test,设置主键自增,如下图 2.向表中插入数据 由于表中的主键字段id为自增在插入的时候不需要指定显示插入,所以Sql 语句为 <span style="font-size:24px;"

数据库主键外键

主键 数据库主键是指表中一个列或列的组合,其值能唯一地标识表中的每一行.这样的一列或多列称为表的主键,通过它可强制表的实体完整性.当创建或更改表时可通过定义 PRIMARY KEY约束来创建主键.一个表只能有一个 PRIMARY KEY 约束,而且 PRIMARY KEY 约束中的列不能接受空值.由于 PRIMARY KEY 约束确保唯一数据,所以经常用来定义标识列. 主键的作用 主键的主要作用如下: (1)保证实体的完整性: (2)加快数据库的操作速度: (3) 在表中添加新记录时,数据库会自

关于数据库主键的选择

数据库主键的选择 (原创) 工作了这么多年什么数据库的主键都用过,各种项目各种主键的变更,硬伤等等的问题都遇见过. 关与主键首先不要整个系统只使用一种方式的主键,要彻底吃透业务需求,根据需求考虑每张表的主键要怎么设计能给你带来最大的收益. 数据库常用的主键常用的四种:自增编号.Guid.自定义唯一.联合主键. 关于数据库主键的设计,如果设计的合理,会给整个系统带来很多优势.例如:降低开发难度少写很多代码.数据库检索效率.开移植性.可扩展性.可维护性.易读性等...对与主键来说,没有最好的,只有最

oracle 数据库主键自动增长sql

我们都知道,在MySQL中,要实现主键的自动递增非常简单.只需要在主键定义的后面加上auto_increment即可, 但是在Oracle中就不是这样,需要分几步来完成 1.建立一个表 create table 户口本  (    户号                   INTEGER                         not null,    户别                   CHAR(20)                        not null,    户主

闲谈系列之一——数据库主键GUID

昨日一个行内兄弟由于工作调动要派到某二线城市工作,虽然有高额工资和补助的诱惑但也难以释怀离京蛋蛋的忧桑,约好晚上到老聚点道个别.主要是聊(chui)聊(chui)前(niu)程(bi),几瓶夺命大绿棒子下肚,这货问起我现在项目的情况(操蛋的技术控!),我简单的说了一下架子和数据库,果然在数据库主键上出现了分歧,在他心里主键就是应该是妥妥的自增整型,对于我的GUID无法容忍,并指出GUID不连续.效率低下.blablabla,最后我说了一下我的解决方案总算得到了一定程度的肯定吧... 对于程序猿来