1、什么是主键、外键: html
关系型数据库中的一条记录中有若干个属性,若其中某一个属性组(注意是组)能惟一标识一条记录,该属性组就能够成为一个主键主键、外键和索引的区别? sql
|
主键 数据库 |
外键 c# |
索引 网络 |
定义: 并发 |
惟一标识一条记录,不能有重复的,不容许为空 app |
表的外键是另外一表的主键, 外键能够有重复的, 能够是空值 数据库设计 |
该字段没有重复值,但能够有一个空值 ide |
做用: 函数 |
用来保证数据完整性 |
用来和其余表创建联系用的 |
是提升查询排序的速度 |
个数: |
主键只能有一个 |
一个表能够有多个外键 |
一个表能够有多个唯一索引 |
汇集索引和非汇集索引的区别?
汇集索引必定是惟一索引。但惟一索引不必定是汇集索引。
汇集索引,在索引页里直接存放数据,而非汇集索引在索引页里存放的是索引,这些索引指向专门的数据页的数据。
3、数据库中主键和外键的设计原则
主键和外键是把多个表组织为一个有效的关系数据库的粘合剂。主键和外键的设计对物理数据库的性能和可用性都有着决定性的影响。
必须将数据库模式从理论上的逻辑设计转换为实际的物理设计。而主键和外键的结构是这个设计过程的症结所在。一旦将所设计的数据库用于了生产环境,就很难对这些键进行修改,因此在开发阶段就设计好主键和外键就是很是必要和值得的。
主键:
关系数据库依赖于主键---它是数据库物理模式的基石。主键在物理层面上只有两个用途:
1. 唯一地标识一行。
2. 做为一个能够被外键有效引用的对象。
基于以上这两个用途,下面给出了我在设计物理层面的主键时所遵循的一些原则:
1. 主键应当是对用户没有意义的。若是用户看到了一个表示多对多关系的链接表中的数据,并抱怨它没有什么用处,那就证实它的主键设计地很好。
2. 主键应该是单列的,以便提升链接和筛选操做的效率。
注:使用复合键的人一般有两个理由为本身开脱,而这两个理由都是错误的。其一是主键应当具备实际意义,然而,让主键具备意义只不过是给人为地破坏数据库提供了方便。其二是利用这种方法能够在描述多对多关系的链接表中使用两个外部键来做为主键,我也反对这种作法,理由是:复合主键经常致使不良的外键,即当链接表成为另外一个从表的主表,而依据上面的第二种方法成为这个表主键的一部分,然,这个表又有可能再成为其它从表的主表,其主键又有可能成了其它从表主键的一部分,如此传递下去,越靠后的从表,其主键将会包含越多的列了。
3. 永远也不要更新主键。实际上,由于主键除了唯一地标识一行以外,再没有其余的用途了,因此也就没有理由去对它更新。若是主键须要更新,则说明主键应对用户无心义的原则被违反了。
注:这项原则对于那些常常须要在数据转换或多数据库合并时进行数据整理的数据并不适用。
4. 主键不该包含动态变化的数据,如时间戳、建立时间列、修改时间列等。
5. 主键应当有计算机自动生成。若是由人来对主键的建立进行干预,就会使它带有除了唯一标识一行之外的意义。一旦越过这个界限,就可能产生认为修改主键的动机,这样,这种系统用来连接记录行、管理记录行的关键手段就会落入不了解数据库设计的人的手中。
4、数据库主键选取策略
咱们在创建数据库的时候,须要为每张表指定一个主键,所谓主键就是可以惟一标识表中某一行的属性或属性组,一个表只能有一个主键,但能够有多个候选索引。由于主键能够惟一标识某一行记录,因此能够确保执行数据更新、删除的时候不会出现张冠李戴的错误。固然,其它字段能够辅助咱们在执行这些操做时消除共享冲突,不过就不在这里讨论了。主键除了上述做用外,经常与外键构成参照完整性约束,防止出现数据不一致。因此数据库在设计时,主键起到了很重要的做用。
常见的数据库主键选取方式有:
1自动增加型字段
不少数据库设计者喜欢使用自动增加型字段,由于它使用简单。自动增加型字段容许咱们在向数据库添加数据时,不考虑主键的取值,记录插入后,数据库系统会自动为其分配一个值,确保绝对不会出现重复。若是使用SQL Server数据库的话,咱们还能够在记录插入后使用@@IDENTITY全局变量获取系统分配的主键键值。
尽管自动增加型字段会省掉咱们不少繁琐的工做,但使用它也存在潜在的问题,那就是在数据缓冲模式下,很难预先填写主键与外键的值。假设有两张表:
Order(OrderID, OrderDate)
OrderDetial(OrderID, LineNum, ProductID, Price)
Order表中的OrderID是自动增加型的字段。如今须要咱们录入一张订单,包括在Order表中插入一条记录以及在OrderDetail表中插入若干条记录。由于Order表中的OrderID是自动增加型的字段,那么咱们在记录正式插入到数据库以前没法事先得知它的取值,只有在更新后才能知道数据库为它分配的是什么值。这会形成如下矛盾发生:
首先,为了能在OrderDetail的OrderID字段中添入正确的值,必须先更新Order表以获取到系统为其分配的OrderID值,而后再用这个OrderID填充OrderDetail表。最后更新OderDetail表。可是,为了确保数据的一致性,Order与OrderDetail在更新时必须在事务保护下同时进行,即确保两表同时更行成功。显然它们是相互矛盾的。
除此以外,当咱们须要在多个数据库间进行数据的复制时(SQL Server的数据分发、订阅机制容许咱们进行库间的数据复制操做),自动增加型字段可能形成数据合并时的主键冲突。设想一个数据库中的Order表向另外一个库中的Order表复制数据库时,OrderID到底该不应自动增加呢?
ADO.NET容许咱们在DataSet中将某一个字段设置为自动增加型字段,但千万记住,这个自动增加字段仅仅是个占位符而已,当数据库进行更新时,数据库生成的值会自动取代ADO.NET分配的值。因此为了防止用户产生误解,建议你们将ADO.NET中的自动增加初始值以及增量都设置成-1。此外,在ADO.NET中,咱们能够为两张表创建DataRelation,这样存在级联关系的两张表更新时,一张表更新后另一张表对应键的值也会自动发生变化,这会大大减小了咱们对存在级联关系的两表间更新时自动增加型字段带来的麻烦。
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表,最后在事务保护下对两表进行更新。
使用手动增加型字段做为主键在进行数据库间数据复制时,能够确保数据合并过程当中不会出现键值冲突,只要咱们为不一样的数据库分配不一样的主键取值段就好了。可是,使用手动增加型字段会增长网络的RoundTrip,咱们必须经过增长一次数据库访问来获取当前主键键值,这会增长网络和数据库的负载,当处于一个低速或断开的网络环境中时,这种作法会有很大的弊端。同时,手工维护主键还要考虑并发冲突等种种因素,这更会增长系统的复杂程度。
3使用UniqueIdentifier
SQL Server为咱们提供了UniqueIdentifier数据类型,并提供了一个生成函数NEWID( ),使用NEWID( )能够生成一个惟一的UniqueIdentifier。UniqueIdentifier在数据库中占用16个字节,出现重复的几率很是小,以致于能够认为是0。咱们常常从注册表中看到相似
{45F0EB02-0727-4F2E-AAB5-E8AEDEE0CEC5}
的东西实际上就是一个UniqueIdentifier,Windows用它来作COM组件以及接口的标识,防止出现重复。在.NET里管UniqueIdentifier称之为GUID(Global Unique Identifier)。在C#中可使用以下命令生成一个GUID:
对于上面提到的Order与OrderDetail的程序,若是选用UniqueIdentifier做为主键的话,咱们彻底能够避免上面提到的增长网络RoundTrip的问题。经过程序直接生成GUID填充主键,不用考虑是否会出现重复。
UniqueIdentifier字段也存在严重的缺陷:首先,它的长度是16字节,是整数的4倍长,会占用大量存储空间。更为严重的是,UniqueIdentifier的生成毫无规律可言,要想在上面创建索引(绝大多数数据库在主键上都有索引)是一个很是耗时的操做。有人作过实验,插入一样的数据量,使用UniqueIdentifier型数据作主键要比使用Integer型数据慢,因此,出于效率考虑,尽量避免使用UniqueIdentifier型数据库做为主键键值。
4使用“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类型要快上一些。关于测试数据能够参考我2004年7月21日的随笔。
除了使用存储过程实现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; }