知识大全 小议数据库主键选取策略

Posted

篇首语:常勤精进,譬如水长流,则能穿石。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 小议数据库主键选取策略相关的知识,希望对你有一定的参考价值。

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

  常见的数据库主键选取方式有   ●自动增长字段  ●手动增长字段  ●UniqueIdentifier  ● B(Combine) 类型

  一 自动增长型字段

  很多数据库设计者喜欢使用自动增长型字段 因为它使用简单 自动增长型字段允许我们在向数据库添加数据时 不考虑主键的取值 记录插入后 数据库系统会自动为其分配一个值 确保绝对不会出现重复 如果使用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在更新时必须在事务保护下同时进行 即确保两表同时更新成功

  听棠 NET指出 主档放在事务中提交时 通过@@IDENTITY 就可以取到生成值的 因此可以传给明细当外键用 而且在事务发生错误回滚时 主档记录也会被回滚取消的

  吕震宇补充 使用自动增长字段会增加网络的roundTrip 尽管可以使用@@IDENTITY取得主键的值 但在更新过程中 不得不增加一次数据往返(以C/S结构为例)

   客户端发送开始事务命令   客户端提交主表更新   服务器返回@@IDENTITY   客户端根据返回的主键更新从表缓冲   客户端将从表提交服务器更新   客户端提交事务

  在这里多了一次往返就会增加了事务处理的时间 降低并发性能

  如果不用自动增长型字段 将是以下情景    客户端发送开始事务命令   客户端提交主表更新   客户端提交从表更新   客户端提交事务

  因此我不赞成使用自动增长型字段作为主键与外键链接的纽带

  除此之外 当我们需要在多个数据库间进行数据的复制时(SQL Server的数据分发 订阅机制允许我们进行库间的数据复制操作) 自动增长型字段可能造成数据合并时的主键冲突 设想一个数据库中的Order表向另一个库中的Order表复制数据库时 OrderID到底该不该自动增长呢?

  ADO NET允许我们在DataSet中将某一个字段设置为自动增长型字段 但千万记住 这个自动增长字段仅仅是个占位符而已 当数据库进行更新时 数据库生成的值会自动取代ADO NET分配的值 所以为了防止用户产生误解 建议大家将ADO NET中的自动增长初始值以及增量都设置成 此外 在ADO NET中 我们可以为两张表建立DataRelation 这样存在级联关系的两张表更新时 一张表更新后另外一张表对应键的值也会自动发生变化 这会大大减少了我们对存在级联关系的两表间更新时自动增长型字段带来的麻烦

  二 手动增长型字段

  既然自动增长型字段会带来如此的麻烦 我们不妨考虑使用手动增长型的字段 也就是说主键的值需要自己维护 通常情况下需要建立一张单独的表存储当前主键键值 还用上面的例子来说 这次我们新建一张表叫IntKey 包含两个字段 KeyName以及KeyValue 就像一个HashTable 给一个KeyName 就可以知道目前的KeyValue是什么 然后手工实现键值数据递增 在SQL Server中可以编写这样一个存储过程 让取键值的过程自动进行 代码如下   CREATE PROCEDURE [GetKey]   @KeyName char( )   @KeyValue int OUTPUT   AS   UPDATE IntKey SET @KeyValue = KeyValue = KeyValue + WHERE KeyName = @KeyName   GO

  这样 通过调用存储过程 我们可以获得最新键值 确保不会出现重复 若将OrderID字段设置为手动增长型字段 我们的程序可以由以下几步来实现 首先调用存储过程 获得一个OrderID 然后使用这个OrderID填充Order表与OrderDetail表 最后在事务保护下对两表进行更新

  使用手动增长型字段作为主键在进行数据库间数据复制时 可以确保数据合并过程中不会出现键值冲突 只要我们为不同的数据库分配不同的主键取值段就行了 但是 使用手动增长型字段会增加网络的RoundTrip 我们必须通过增加一次数据库访问来获取当前主键键值 这会增加网络和数据库的负载 当处于一个低速或断开的网络环境中时 这种做法会有很大的弊端 同时 手工维护主键还要考虑并发冲突等种种因素 这更会增加系统的复杂程度

  三 使用UniqueIdentifier

  SQL Server为我们提供了UniqueIdentifier数据类型 并提供了一个生成函数NEWID( ) 使用NEWID( )可以生成一个唯一的UniqueIdentifier UniqueIdentifier在数据库中占用 个字节 出现重复的概率非常小 以至于可以认为是 我们经常从注册表中看到类似   F EB F E AAB E AEDEE CEC

  的东西实际上就是一个UniqueIdentifier Windows用它来做组件以及接口的标识 防止出现重复 在 NET里管UniqueIdentifier称之为GUID(Global Unique Identifier) 在C#中可以使用如下命令生成一个GUID   Guid u = System Guid NewGuid();

  对于上面提到的Order与OrderDetail的程序 如果选用UniqueIdentifier作为主键的话 我们完全可以避免上面提到的增加网络RoundTrip的问题 通过程序直接生成GUID填充主键 不用考虑是否会出现重复

  UniqueIdentifier字段也存在严重的缺陷 首先 它的长度是 字节 是整数的 倍长 会占用大量存储空间 更为严重的是 UniqueIdentifier的生成毫无规律可言 要想在上面建立索引(绝大多数数据库在主键上都有索引)是一个非常耗时的操作 有人做过实验 插入同样的数据量 使用UniqueIdentifier型数据做主键要比使用Integer型数据慢 所以 出于效率考虑 尽可能避免使用UniqueIdentifier型数据库作为主键键值

  四 使用 B(Combine) 类型

  既然上面三种主键类型选取策略都存在各自的缺点 那么到底有没有好的办法加以解决呢?答案是肯定的 通过使用B类型(数据库中没有B类型 它是Jimmy Nilsson在他的 The Cost of GUIDs as Primary Keys 一文中设计出来的) 可以在三者之间找到一个很好的平衡点

  B数据类型的基本设计思路是这样的 既然UniqueIdentifier数据因毫无规律可言造成索引效率低下 影响了系统的性能 那么我们能不能通过组合的方式 保留UniqueIdentifier的前 个字节 用后 个字节表示GUID生成的时间(DateTime) 这样我们将时间信息与UniqueIdentifier组合起来 在保留UniqueIdentifier的唯一性的同时增加了有序性 以此来提高索引效率 也许有人会担心UniqueIdentifier减少到 字节会造成数据出现重复 其实不用担心 后 字节的时间精度可以达到 / 秒 两个B类型数据完全相同的可能性是在这 / 秒内生成的两个GUID前 个字节完全相同 这几乎是不可能的!在SQL Server中用SQL命令将这一思路实现出来便是   DECLARE @aGuid UNIQUEIDENTIFIER   SET @aGuid = CAST(CAST(NEWID() AS BINARY( ))   + CAST(GETDATE() AS BINARY( )) AS UNIQUEIDENTIFIER)

  经过测试 使用B做主键比使用INT做主键 在检索 插入 更新 删除等操作上仍然显慢 但比Unidentifier类型要快上一些 关于测试数据可以参考我 年 月 日的随笔

  除了使用存储过程实现B数据外 我们也可以使用C#生成B数据 这样所有主键生成工作可以在客户端完成 C#代码如下   /**////

  /// 返回 GUID 用于数据库操作 特定的时间代码可以提高检索效率   ///   /// B (GUID 与时间混合型) 类型 GUID 数据   public static Guid NewComb()        byte[] guidArray = System Guid NewGuid() ToByteArray();     DateTime baseDate = new DateTime( );     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 / th of a millisecond so we divide by     byte[] daysArray = BitConverter GetBytes(days Days);     byte[] msecsArray = BitConverter GetBytes((long)(msecs TotalMilliseconds/ ));     // 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 guidArray guidArray Length );     Array Copy(msecsArray msecsArray Length guidArray guidArray Length );     return new System Guid(guidArray);      /**////   /// 从 SQL SERVER 返回的 GUID 中生成时间信息   ///   /// 包含时间信息的 B   /// 时间   public static DateTime GetDateFromComb(System Guid guid)        DateTime baseDate = new DateTime( );     byte[] daysArray = new byte[ ];     byte[] msecsArray = new byte[ ];     byte[] guidArray = guid ToByteArray();     // Copy the date parts of the guid to the respective byte arrays     Array Copy(guidArray guidArray Length daysArray );     Array Copy(guidArray guidArray Length msecsArray );     // Reverse the arrays to put them into the appropriate order     Array Reverse(daysArray);     Array Reverse(msecsArray);     // Convert the bytes to ints     int days = BitConverter ToInt (daysArray );     int msecs = BitConverter ToInt (msecsArray );     DateTime date = baseDate AddDays(days);     date = date AddMilliseconds(msecs * );     return date;   

  结语

cha138/Article/program/SQL/201311/16432

相关参考

知识大全 常用Hibernate主键生成策略

常用Hibernate主键生成策略  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  今天学习到了关

知识大全 checkpoint小议

  什么是checkpoint?     checkpoint是一个数据库事件它将已修改的数据从高速缓存刷新到磁盘并更新控制文件和数据文件     什么时候发生checkpoint

知识大全 Oracle9i数据库WITH查询语法小议

Oracle9i数据库WITH查询语法小议  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Ora

知识大全 JSP中如何取得MSSQL数据库表中自动增长的ID主键值?

JSP中如何取得MSSQL数据库表中自动增长的ID主键值?  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一

知识大全 讨论SQL Server 表的主键问题

讨论SQLServer表的主键问题  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  关于数据库的逻

知识大全 oracle数据库中sql基础

  一关系数据库的一些概念    主键的值一般不可以改变    外键指向另一个表或本表的主键或唯一键的字段外键的值一定要和某一主键相同或者为空    数据库对像表视图序列索引同义词程序(进程函数sql

知识大全 脱机数据的排序、搜索和筛选

  DataTable类的搜索和筛选功能  根据主键值查找行  DataRowCollection类的Find方法接受包含要查找行的主键值为参数因为是根据主键值查找所以仅返回一个DataRow&nbs

国外主要的策略指数

国外主要的策略指数作为对策略指数部分内容的补充说明,我们选取部分成熟的策略指数进行介绍。这些策略指数主要来源于德意志银行、法兴和标准普尔,所采用的方法主要是选股策略。1、S&P:STARS策略指数标准

知识大全 Oracle数据库的安全策略

Oracle数据库的安全策略  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Oracle是关系型

知识大全 Oracle数据库系统性能优化策略

Oracle数据库系统性能优化策略  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  一个数据库系统