关于数据库主键和外键

一、什么是主键、外键:

关系型数据库中的一条记录中有若干个属性,若其中某一个属性组(注意是组)能唯一标识一条记录,该属性组就可以成为一个主键 
比如  
学生表(学号,姓名,性别,班级) 
其中每个学生的学号是唯一的,学号就是一个主键 
课程表(课程编号,课程名,学分) 
其中课程编号是唯一的,课程编号就是一个主键 
成绩表(学号,课程号,成绩) 
成绩表中单一一个属性无法唯一标识一条记录,学号和课程号的组合才可以唯一标识一条记录,所以 学号和课程号的属性组是一个主键 
  
成绩表中的学号不是成绩表的主键,但它和学生表中的学号相对应,并且学生表中的学号是学生表的主键,则称成绩表中的学号是学生表的外键 
  
同理 成绩表中的课程号是课程表的外键 
  
定义主键和外键主要是为了维护关系数据库的完整性,总结一下:
1.主键是能确定一条记录的唯一标识,比如,一条记录包括身份正号,姓名,年龄。

身份证号是唯一能确定你这个人的,其他都可能有重复,所以,身份证号是主键。 
2.外键用于与另一张表的关联。是能确定另一张表记录的字段,用于保持数据的一致性。

比如,A表中的一个字段,是B表的主键,那他就可以是A表的外键。

 

 

二、  主键、外键和索引的区别 

主键、外键和索引的区别?

 

主键

外键

索引

定义:

唯一标识一条记录,不能有重复的,不允许为空

表的外键是另一表的主键, 外键可以有重复的, 可以是空值

该字段没有重复值,但可以有一个空值

作用:

用来保证数据完整性

用来和其他表建立联系用的

是提高查询排序的速度

个数:

主键只能有一个

一个表可以有多个外键

一个表可以有多个惟一索引

 

聚集索引和非聚集索引的区别?

聚集索引一定是唯一索引。但唯一索引不一定是聚集索引。  

聚集索引,在索引页里直接存放数据,而非聚集索引在索引页里存放的是索引,这些索引指向专门的数据页的数据。

 

 

 

 

三、数据库中主键和外键的设计原则

主键和外键是把多个表组织为一个有效的关系数据库的粘合剂。主键和外键的设计对物理数据库的性能和可用性都有着决定性的影响。

必须将数据库模式从理论上的逻辑设计转换为实际的物理设计。而主键和外键的结构是这个设计过程的症结所在。一旦将所设计的数据库用于了生产环境,就很难对这些键进行修改,所以在开发阶段就设计好主键和外键就是非常必要和值得的。

主键:

  关系数据库依赖于主键---它是数据库物理模式的基石。

  主键在物理层面上只有两个用途:

        1. 惟一地标识一行。

        2. 作为一个可以被外键有效引用的对象。

  基于以上这两个用途,下面给出了我在设计物理层面的主键时所遵循的一些原则:

        1. 主键应当是对用户没有意义的。如果用户看到了一个表示多对多关系的连接表中的数据,并抱怨它没有什么用处,那就证明它的主键设计地很好。

        2. 主键应该是单列的,以便提高连接和筛选操作的效率。

        注:使用复合键的人通常有两个理由为自己开脱,而这两个理由都是错误的。其一是主键应当具有实际意义,然而,让主键具有意义只不过是给人为地破坏数据库提供了方便。其二是利用这种方法可以在描述多对多关系的连接表中使用两个外部键来作为主键,我也反对这种做法,理由是:复合主键常常导致不良的外键,即当连接表成为另一个从表的主表,而依据上面的第二种方法成为这个表主键的一部分,然,这个表又有可能再成为其它从表的主表,其主键又有可能成了其它从表主键的一部分,如此传递下去,越靠后的从表,其主键将会包含越多的列了。

        3. 永远也不要更新主键。实际上,因为主键除了惟一地标识一行之外,再没有其他的用途了,所以也就没有理由去对它更新。如果主键需要更新,则说明主键应对用户无意义的原则被违反了。

       注:这项原则对于那些经常需要在数据转换或多数据库合并时进行数据整理的数据并不适用。

        4. 主键不应包含动态变化的数据,如时间戳、创建时间列、修改时间列等。

        5. 主键应当有计算机自动生成。如果由人来对主键的创建进行干预,就会使它带有除了惟一标识一行以外的意义。一旦越过这个界限,就可能产生认为修改主键的动机,这样,这种系统用来链接记录行、管理记录行的关键手段就会落入不了解数据库设计的人的手中。

 

四、数据库主键选取策略

我们在建立数据库的时候,需要为每张表指定一个主键,所谓主键就是能够唯一标识表中某一行的属性或属性组,一个表只能有一个主键,但可以有多个候选索引。因为主键可以唯一标识某一行记录,所以可以确保执行数据更新、删除的时候不会出现张冠李戴的错误。当然,其它字段可以辅助我们在执行这些操作时消除共享冲突,不过就不在这里讨论了。主键除了上述作用外,常常与外键构成参照完整性约束,防止出现数据不一致。所以数据库在设计时,主键起到了很重要的作用。

常见的数据库主键选取方式有:

· 自动增长字段

· 手动增长字段

· 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表。最后更新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:

Guid u = System.Guid.NewGuid();

对于上面提到的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; 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/381029.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sql server charindex函数和patindex函数详解(转)

charindex和patindex函数常常用来在一段字符中搜索字符或字符串。假如被搜索的字符中包含有要搜索的字符&#xff0c;那么这两个函数返回一个非零的整数&#xff0c;这个整数是要搜索的字符在被搜索的字符中的开始位数。patindex函数支持使用通配符来进行搜索&#xff0c;然而c…

Win10最新批量激活

W269N-WFGWX-YVC9B-4J6C9-T83GX 先来说下使用激活码使用方法&#xff1a; 1、同时按下Win键X&#xff0c;然后选择命令提示符&#xff08;管理员&#xff09; 2、在命令提示符中依次输入&#xff1a; slmgr.vbs /upk &#xff08;此时弹出窗口显未“已成功卸载了产品密钥”…

dosbox 伪指令dd为什么会报错_什么是SQL函数?为什么使用SQL函数可能会带来问题?...

本文已收录GitHub&#xff0c;更有互联网大厂面试真题&#xff0c;面试攻略&#xff0c;高效学习资料等函数在计算机语言的使用中贯穿始终&#xff0c;在 SQL 中我们也可以使用函数对检索出来的数据进行函数操作&#xff0c;比如求某列数据的平均值&#xff0c;或者求字符串的长…

最新webshell大合集

收集与整理了各种webshell&#xff0c;以便在日后的项目中做Webshell检测训练。 https://github.com/tennc/webshell 各种webshell集合 https://github.com/ysrc/webshell-sample webshell样本 https://github.com/xl7dev/WebShell Webshell && Backdoor Collection…

JavaScript 参考教程(二)——转载

对象化编程 JavaScript 是使用“对象化编程”的&#xff0c;或者叫“面向对象编程”的。所谓“对象化编程”&#xff0c;意思是把 JavaScript 能涉及的范围划分成大大小小的对象&#xff0c;对象下面还继续划分对象直至非常详细为止&#xff0c;所有的编程都以对象为出发点&…

centos7 docker删除端口映射_容器Docker详解

概述基本概念Docker是一个开源的应用容器引擎&#xff0c;基于Go语言并遵从Apache2.0协议开源。Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&a…

深入解析结构化异常处理(SEH) - by Matt Pietrek

尽管以前写过一篇SEH相关的文章《关于SEH的简单总结》&#xff0c; 但那真的只是皮毛&#xff0c;一直对Windows异常处理的原理似懂非懂&#xff0c; 看了下面的文章 &#xff0c;一切都豁然开朗. 1997年文章&#xff0c;Windows技术的根一直没变&#xff1a;http://www.micr…

IE Mobie6清除浮动

看来已经有很多中window的毒了&#xff0c;平时操作系统是window&#xff0c;现在还好多人用window mobie&#xff0c;看来已经习惯了window的使用方式。做前端开发&#xff0c;有个IE 6已经够受的了&#xff0c;没想到半路杀出个IE Mobie。期待IE6被埋葬的一天。 做几个页面&a…

@requestbody 接受int参数_C++之指针作为函数参数

C 允许您传递指针给函数&#xff0c;只需要简单地声明函数参数为指针类型即可。下面的实例中&#xff0c;我们传递一个无符号的 long 型指针给函数&#xff0c;并在函数内改变这个值&#xff1a;#include <iostream> #include <ctime>using namespace std; void ge…

hash函数查找和ASL计算

Hash表的“查找成功的ASL”和“查找不成功的ASL” ASL指的是 平均查找时间 关键字序列&#xff1a;&#xff08;7、8、30、11、18、9、14&#xff09; 散列函数&#xff1a; H(Key) (key x 3) MOD 7 装载因子&#xff1a; 0.7 处理冲突&#xff1a;线性探测再散列法 查…

Jquery CheckBox全选方法

方法如下&#xff1a; function CheckAll(val) {$("input[namechkJob]").each(function() {this.checked val;});$("#chkAll").attr("checked", val);//设定全选按钮状态}val 该参数传递的是全选按钮的选择状态 namechkJob 这个为列表中的check…

c++怎么将文件中的数据读出并赋值给字符串_web前端开发过程中如何写JavaScript程序?...

script标签使用< script >< /script >src : 外联script 标签(立即调用,不能在该标签内写代码)< script >// 在标签内书写内容< /script >< script srcjavascript.js >// 这里面不能写任何内容< /script >一切准备就绪&#xff0c;让我们从…

简美电子相册

下载地址&#xff1a;微客互联 转载于:https://www.cnblogs.com/Witkey/archive/2010/06/10/1755755.html

强制进程结束(内核函数)

windows给我们结束进程的函数是PsTerminateSystemThread function&#xff0c;通过反汇编我们可以看到PsTerminateSystemThread 调用了未导出的函数PspTerminateThreadByPointer &#xff0c;然后这个函数又调用了APC和PspExitThread函数对进程实现强制结束。

3说明书_知行翻译:做化妆品说明书翻译时,这3点需要谨记

经过20多年的迅猛发展&#xff0c;被称为“美丽经济”的中国化妆品产业已经取得非常大的成就&#xff0c;在这20多年里&#xff0c;中国化妆品产业从小到大&#xff0c;从弱到强&#xff0c;从简单粗放到科技领先&#xff0c;已经逐步成为全世界最大的新兴市场&#xff0c;很多…

拉拢中小网站 淘宝百度暗战升级...

拉拢中小网站 淘宝百度暗战升级... 重庆晚报 讯 近日&#xff0c;大量淘宝联盟站长反映其网站在百度搜索结果大幅缩水&#xff0c;疑遭集体封杀。百度的理由是“清扫垃圾页面”。迟迟未表态的淘宝近日对前者进行了公开回应。至此&#xff0c;淘宝跟百度之间为拉拢中小网站的暗战…

转——idapython import site failed

打开IDA出现这个错误提示&#xff0c;问题根源是IDA找不到python&#xff0c;安装2.7版本的python并设置%PYTHONHOME%变量为python安装目录就可以了 如果已经安装了python可以在命令行下用echo %PYTHONHOME%看看配置有没有问题

matlab中将数据保存为txt文件_matlab中将数据输出保存为txt格式文件的方法 (1)

总结网上各大论坛&#xff0c;主要有三种方法。第一种方法&#xff1a;save(最简单基本的)具体的命令是&#xff1a;用save *.txt -ascii xx为变量*.txt为文件名,该文件存储于当前工作目录下&#xff0c;再打开就可以打开后,数据有可能是以指数形式保存的.例子&#xff1a;a [1…

有1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数

有1、2、3、4个数字&#xff0c;能组成多少个互不相同且无重复数字的三位数&#xff1f; 步骤&#xff1a;先定一个函数&#xff0c;把数据储存在一个列表&#xff0c;然后传入函数中就行了 代码 def threeDN(a):组成多少个互不相同且无重复数字的三位数num0for i1 in a:for…

.Net Remoting(基本操作) - Part.2 (转)

Remoting 构架 1.客户端(客户应用程序) 客户端的处理包含三个基本的组成部分&#xff0c;代理(Proxy)、格式器(Formatter) 和 通道(Channel)。 2.服务端(宿主应用程序) 服务端主要由 通道(Channel)、格式器(Formatter)、Stack Builder组成。 在服务端&#xff0c;宿主程序保持着…