First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 3015 评论 :: 339 引用

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

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

  • 自动增长字段
  • 手动增长字段
  • 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表。最后更新OderDetail表。但是,为了确保数据的一致性,Order与OrderDetail在更新时必须在事务保护下同时进行,即确保两表同时更行成功。显然它们是相互矛盾的。(此处表述有错误。吕震宇 2005-6-15)

【补充2005-6-15】---------------------------------------------

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

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

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

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

如果不用自动增长型字段,将是以下情景:

1、客户端发送开始事务命令
2、客户端提交主表更新
3、客户端提交从表更新
4、客户端提交事务

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

------------------------------------------------

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

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

二、手动增长型字段

既然自动增长型字段会带来如此的麻烦,我们不妨考虑使用手动增长型的字段,也就是说主键的值需要自己维护,通常情况下需要建立一张单独的表存储当前主键键值。还用上面的例子来说,这次我们新建一张表叫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,我们必须通过增加一次数据库访问来获取当前主键键值,这会增加网络和数据库的负载,当处于一个低速或断开的网络环境中时,这种做法会有很大的弊端。同时,手工维护主键还要考虑并发冲突等种种因素,这更会增加系统的复杂程度。

三、使用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型数据库作为主键键值。

四、使用“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 - 62); 
     Array.Copy(msecsArray, msecsArray.Length 
- 4, guidArray, guidArray.Length - 44); 

     
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, 22); 
     Array.Copy(guidArray, guidArray.Length 
- 4, msecsArray, 04); 

     
// 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; 
}



结语

数据库主键在数据库中占有重要地位。主键的选取策略决定了系统是否高效、易用。本文比较了四种主键选取策略的优缺点,并提供了相应的代码解决方案,希望对大家有所帮助。

posted on 2004-07-18 17:37 吕震宇 阅读(13550) 评论(59)  编辑 收藏 网摘 所属分类: 数据库技术

评论

mmm,的确有所收获!
  回复  引用  查看    

#2楼  2004-07-18 21:17 Rover [未注册用户]
使用Guid好,这样避免了数据导入导出的麻烦
  回复  引用    

不错!
“使用“COMB(Combine)”类型”,有意思
  回复  引用    

#4楼  2004-07-18 22:25 progame      
很难相信:
1、。有人做过实验,插入同样的数据量,使用UniqueIdentifier型数据做主键要比使用Integer型数据慢10到30倍。
2、经过测试,使用COMB做主键与使用INT做主键,在检索、插入、更新、删除等操作上的耗时比例为1.1:1

两者会相差这么大?

  回复  引用  查看    

#5楼  2004-07-18 22:30 progame      
假如说用前6个字节做时间 然后在查询的时候 通过 like 'XX%'查询的话倒是可以利用到索引提速的 可是用后6个字节 我想不通它怎么可能会比直接的GUID快
  回复  引用  查看    

#6楼  2004-07-19 08:56 吕震宇      
关于COMB类型的效率问题以及为什么使用后6个字节,大家可以访问

http://www.informit.com/articles/article.asp?p=25862&redir=1

这是COMB类型数据的原文,值得读一读。
  回复  引用  查看    

#7楼  2004-07-19 09:04 吕震宇      
摘录其中的一段话,说明为什么使用后6字节而不是前6字节或8字节什么的。正常的日期时间应占用8字节空间。

The guess that I had about the problem with the INSERT overhead for GUIDs was that the lack of order in the Windows 2000–generated GUIDs was giving SQL Server a hard time administering indexes under massive INSERT periods. Therefore, I tried to create an order for the GUIDs instead. I tried to CAST the current DATETIME to a BINARY(8) and put that first in the GUID. Unfortunately, that had no effect. When I investigated it further, I found that when I had a BINARY(16) value and CASTed it to a UNIQUEIDENTIFIER, some bytes were scrambled. What to do? You guessed it. I tried to compensate for the scrambling to see if that had any positive effect on throughput, but no effect occurred. Then I found out that it wasn't the first (high) byte that was important for the new ordering, but the last (low) bytes. I also learned that I didn't have to use BINARY(8) for the current DATETIME. BINARY(6) is enough for the next 77 years, so I decided to occupy only the last (low) 6 bytes with the current DATETIME. In Listing 5, you can see the result of PRINT CAST(GETDATE() AS BINARY(8)). The first (high) bytes are 0.


  回复  引用  查看    

#8楼  2004-07-19 09:13 progame [未注册用户]
我就搞不懂了 他既然使用COMB 为什么非得使用UNIQUEIDENTIFIER类型 直接用char不就得了
  回复  引用    

我想速度慢的原因主要来源于uniqueindenifier的生成和其长度。
  回复  引用  查看    

to: progame
为什么不实验一下?
  回复  引用  查看    

#11楼  2004-07-20 09:49 dl [未注册用户]
几乎是不可能的,也是有可能的
  回复  引用    

#12楼  2004-07-20 15:17 .net移动控件      
既然int类型最快,为什么不用int类型而要用comb呢?
  回复  引用  查看    

#13楼  2004-07-21 17:05 Adex [未注册用户]
似懂非懂的说。

  回复  引用    

#14楼  2004-07-21 20:51 dudu      
好文章!放入精华区!
  回复  引用  查看    

#15楼  2004-08-07 11:50 BB [未注册用户]
Order表中的OrderID是自动增长型的字段。现在需要我们录入一张订单,包括在Order表中插入一条记录以及在OrderDetail表中插入若干条记录。因为Order表中的OrderID是自动增长型的字段,那么我们在记录正式插入到数据库之前无法事先得知它的取值,只有在更新后才能知道数据库为它分配的是什么值。这会造成以下矛盾发生:

首先,为了能在OrderDetail的OrderID字段中添入正确的值,必须先更新Order表以获取到系统为其分配的OrderID值,然后再用这个OrderID填充OrderDetail表。最后更新OderDetail表。但是,为了确保数据的一致性,Order与OrderDetail在更新时必须在事务保护下同时进行,即确保两表同时更行成功。显然它们是相互矛盾的。

不是很明白,这种矛盾是如何产生的???
在这三种方法中,使用自动增长型的方法应该是最好的...难道大家不认为后两种的实现方法比第一种要复杂得多吗?

另外,好像这个跟主键选取策略没有什么关系吧...只是讲明对于某个字段的类型的选取而已...

  回复  引用    

#16楼  2004-09-20 17:12 jcaomao [未注册用户]
我同意楼上的,自动增长类型里面 更新两个表没必要放到同一个事务里,插入第一个表以后取得唯一的id, 这样就是别的线程再插入第一个表对原来的线程也不会影响原来的线程。
  回复  引用    

#17楼  2004-09-20 18:52 吕震宇      
我想上楼上两层没有完全明白我的意思:

自动增长字段在以下方面会带来不便:

1、ADO.NET支持离线数据。虽然DATASET允许自己定义一个字段(Element)是自动增长的,但这种自动增长不能确保与数据库同步。所以建议DataSet中的自动增长初始值与步长都设置为-1,这样可以消除DataSet中新增加记录的主键与DataSet中的现有数据主键发生冲突的可能。

2、如果1成立,那么在离线的DataSet中,主键是从-1开始递减的。当更新数据库时,这个值(-1)并不会存在数据库中,因为在存储时数据库用目前主键应当的取值替换掉这个-1(比如说10)。

现在问题来了,DataSet中的主从表中,新增记录是靠-1连接在一起的(例如主表的OrderID与从表的OrderID都是-1),当更新主表时,-1自动被替换成了应当的取值,但此时从表并不知道,仍然用-1与主表对应,当存储从表时就会失败,报告主、外键约束失败。

当然,现在也有解决办法,就是在主表的更新中添加一个查询,查询数据库分配的主键是什么,并在主表的更新完成事件中编写代码来替换从表中的-1,然后再更新从表。这样就可以保持一致了。在微软ADO.NET的书中有相关介绍(书名一时想不起来了)。
  回复  引用  查看    

#18楼  2005-01-05 09:49 Kozen      
其实还可以将Combine类型的前10个字节进行编码压缩,以进一步减少存储空间,但同时会增加主键生成策略的复杂度。这就是系统设计者需要权衡的事情了。
  回复  引用  查看    

#19楼  2005-01-05 11:39 smartkid [未注册用户]
>>很难相信:
>>1、。有人做过实验,插入同样的数据量,使用UniqueIdentifier型数据做主键要比使用Integer型数据慢10到30倍。

原因:
UniqueIdentifier因为其值可认为是随机,其索引项会均匀地分布在索引的每一页中。因而在大批量插入的时候数据库服务器的索引缓冲利用率很低,当数据量增长到一定程度之后,每一条新插入的数据都会使数据库服务器将一个新的索引页装入缓存。
而integer做主键时,新插入的数据总是存放在最后的一个索引页中。
  回复  引用    

#20楼 [楼主] 2005-01-06 07:39 吕震宇      
@smartkid

抱歉,我原来写的“使用UniqueIdentifier型数据做主键要比使用Integer型数据慢10到30倍”是没有经过验证的数据。在我自己验证过后,发现差别没有这么大。使用UniqueIdentifier型数据做主键要比使用Integer型数据慢不到两倍的速度。我在文中已经修改过来了。只是评论中没有特殊注明。
  回复  引用  查看    

#21楼 [楼主] 2005-01-06 07:40 吕震宇      
@Kozen

感觉你说的很有道理。性能与空间有时总是矛盾的。
  回复  引用  查看    

#22楼  2005-01-08 11:28 GUID [未注册用户]
请教:

MS SQL 2000中 的 uniqueidentifier 类型,如何binding ? (C#)

偶用 “数据窗体向导”生成一个窗体时,居然也不能生成GUID字段的固定binging

偶用textBox binding 后,
输入一串GUID(例如291FFECD-C4F3-4FCE-866F-95D600B74D9C),
光标移走时,这串GUID就没有了,

就象用TextBox binding 一个int的数据字段一样,当输入一个字符时,会自动清除这个字符,只能输入数字

  回复  引用    

#23楼  2005-01-29 11:20 Justin [未注册用户]
In our project, we create a counter class to generate Primary Key. User can specify counter format also. It is a combination of Alpha numeric charcters or Interger itself.

For example, Track ID = 'TRK00000002'. In this example, TRACKID is Counter name. System will store 2 as the current counter value. So the next Track ID to be generated will be TRK00000003. Although it's a bit more complex, but it provides users more flexibility and usability.
  回复  引用    

#24楼 [楼主] 2005-01-29 18:16 吕震宇      
That's a good idea, I also use this method to create Primary Key for different data subscribers. But it will take another network roundtrip to get the correct key (that the same work as your counter class do).
  回复  引用  查看    

#25楼  2005-02-02 13:19 小萍 [未注册用户]
使用COMB类型和uniqueidentifier 类型做主键是不是意味着不用主键排序了?
  回复  引用    

#26楼 [楼主] 2005-02-02 13:36 吕震宇      
其实建立主键的目的各不相同,不都是用来排序用的,是吧?
  回复  引用  查看    

#27楼  2005-02-02 13:57 小萍 [未注册用户]
一般都会按插入的时间来排序的,所以我和前面那位的困惑是一样的。为什么不把时间那一段放在前面。PS:那一段鸡肠看懂有一点难度:)

不过一般数据都会有一个插入时间的,可能还要加多一个最后修改时间。如果主键顺序可以等同插入时间的顺序是最好的了。

这个想法是不是菜了点。
  回复  引用    

#28楼 [楼主] 2005-02-02 14:15 吕震宇      
我个人感觉时间字段好改,而主键最好不要动。“主键顺序可以等同插入时间的顺序”并不能取代在时间字段上建立索引的作用。
  回复  引用  查看    

#29楼  2005-02-02 16:08 mikespook      
在没有具体问题的时候纯粹的来讨论什么样的主键好似乎意义不大~~~
做一个范围的限定吧~~~

COMB看起来好是好,但是如果是主从表中大量使用还是相当可怕的开销。
比如我前段时间做的一个附加在CRM上的模块,总共30几张表,其实都是围绕一个核心表来记录。那么都用COMB,16位的数据量,按照预期的主表将达到百万级一条主从记录,光键连用的空间就足够大了~~~~
这样的情况下,就不该用COMB了,而应该从记录中提取本身有意义,而不会重复的一个或几个字段来做主键。而从表做聚集索引~~会好得多~
  回复  引用  查看    

#30楼  2005-02-20 00:07 bigpond [未注册用户]
This is a very good article and has demonstrated the pros abd cons of using different primary key techniques. The suggestion is to use 16 bytes primary key, it's still a bit expensive. Talking about the uniqueness of primary key, the author wants to build a 16 bytes string, there is no huge difference in terms of speed.

Just a side question, if you generate the order number as "45F0EB02-0727-888888" for the example given out, how can you tell your customer about the order number? It looks like different situations have to use different technique.

I agree with mikespook. It will the best to discuss the technique with real world problems.


  回复  引用    

#31楼  2005-03-24 17:40 EXPLORER [未注册用户]
用先取得ID的策略,会造成网络流量加大;那么可以一次网络流量取N个回来。前端慢慢用就行了;
INT的从效益和复杂度来说都比COMBO要好;
  回复  引用    

#32楼  2005-04-04 17:18 豌豆 [未注册用户]
十分关注!有没有其他的更好的方法了,如果哪位兄弟有的话,通知我一声,十分感激!我现在用的就是第二种方式.
  回复  引用    

#33楼  2005-04-05 10:23 新月 [未注册用户]
我转载了本文:http://blog.csdn.net/newmoon2004/archive/2005/04/05/336827.aspx
  回复  引用    

#34楼  2005-04-08 14:08 firstsee [未注册用户]
You know what a nightmare it can be when doing a manual merge between two tables with INTEGERs as primary keys. Not only do you have to create a new sequence for the union of both tables, but you also must change all the foreign keys for all the dependent tables. If GUIDs are used, this sort of problem does not arise.
好像这是不使用 Integer作为Primary Key的主要原因吧
  回复  引用    

#35楼  2005-05-27 21:28 听棠.NET      
楼主好,对于你说的:自动增长型在事务中遇到的取不到值的问题,不知道你有没有测过,其他朋友也有没有测过,首先要说明,订单主档与明细的添加肯定是要放在事务里的,有位朋友说没有必要,这是绝对排除的,现不讨论这个。主档放在事务中提交时,通过@@IDENTITY 就可以取到生成值的,因此可以传给明细当外键用,而且在事务发生错误回滚时,主档记录也会被回滚取消的,大家可能是在想当然,以为事务回滚不能取消主档记录的插入吧,我测试过的。通过。因此楼主说的第一个问题根本不成立,但我也不支持自动增长主键,因为不方便移植数据,跟楼主同感。这可以参考我的文章:数据库主键设计之思考
 第二:关于主键采用GUID,楼主说要在主键上创建索引不好,其实在主键上创建索引本身就是一个误导,其实在主键建聚集索引并不提倡的,一般是提供在订单的“生成日期”字段上建的,这要效率上有非常大的提高,在主键上建索引,一般是小数据量的没有“日期”字段的表,而这种表,建与不建,影响小的可以忽略不计。具体关于索引的创建,可以看文章 :SQL Server 索引结构及其使用(一)[转]
 SQL Server 索引结构及其使用(二)[转]

  回复  引用  查看    

#36楼 [楼主] 2005-06-15 09:42 吕震宇      
@听棠.NET

我想对于你提出的第一个问题,确实是我表述不当。我完全赞同你所说的在事务中可以确保同时成功则成功,任何一个失败则失败的观点。我想说的是,使用自动增长字段会增加网络的roundTrip。尽管可以使用@@IDENTITY取得主键的值,但在更新过程中,不得不增加一次数据往返(以C/S结构为例):

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

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

如果不用自动增长型字段,将是以下情景:

1、客户端发送开始事务命令
2、客户端提交主表更新
3、客户端提交从表更新
4、客户端提交事务

因此我不赞成使用自动增长型字段作为主键与外键链接的纽带。(正文内容我会尽快修改)。
  回复  引用  查看    

#37楼  2005-12-01 14:08 ken170 [未注册用户]
楼主以"一次往返就会增加了事务处理的时间"来反对自增列,似乎没有足够的依据, 请问网络返回一个int类型的变量一次要多少资源? 个人认为返回一个int数据所消耗的资源可以忽略不计! 楼主可以用大量的插入操作来做实验,我相信从性能来说,即使需要多一次的往返,但还是自增型主键仍然是最快的,只要及时提交事务并不会影响并发操作!



  回复  引用    

#38楼  2005-12-19 11:04 enjoyo [未注册用户]
Integer GUID和Comb做主键的效率测试(Delphi+access)

http://sinoprise.com/read.php?tid=859&fpage=1
  回复  引用    

#39楼  2005-12-27 10:13 myth      
用自动增长列不好吗?同时插入两个表,所有的动作都可以在一次批查询中一次完成
  回复  引用  查看    

#40楼  2006-01-15 09:39 piggybank [未注册用户]
呵呵,我在我的团队也提倡不用自增 ID
一般用 UUID 做主键,存储过程中可用 NewID,程序中可用 API CoCreateGUID,.NET 可用 System.GUID.New()
然后,用日期或其它字段作索引和排序,或创建自增 ID 做索引和排序。

  回复  引用    

#41楼  2006-05-18 23:14 faramita [未注册用户]
我的自增列用法:
订单和订单明细数据同时传递给一个存储过程,在存储过程的事务中提交订单-取得订单自增id-提交订单明细。没出现过什么矛盾。

关于使用日期做簇索引,我觉得自增id比较好:
1.因通常使用自增列做关系,这样连接表查询时直接使用自增列关联到叶子页,而不必先关联到日期之类的叶子簇索引。
2.日期毕竟是有意义的字段,万一修改的话可能造成不必要的麻烦。
  回复  引用    

#42楼  2006-06-14 10:47 辉狼      
您是老大,写的很好!!!有前途!
  回复  引用  查看    

#43楼  2006-09-12 14:12 linbaba      
我建议采取手动增长型,
我们使用在内部订单管理,
如果只是B/S模式,没有什么问题.现在客户端还使用VB+Access离线编辑模式.
在资料互导的时候产生很多麻烦...当然,这种情况使用GUID最好操作.
  回复  引用  查看    

#44楼  2006-10-27 17:20 SoftWareBoy      
认真关注!继续关注您的Blog!


  回复  引用  查看    

#45楼  2007-03-25 03:16 陈招展      
突然间发现这文章,长见识啦。
  回复  引用  查看    

#46楼  2007-04-09 11:37 yunhuasheng      
Great.
  回复  引用  查看    

#47楼  2007-06-10 17:48 无名 [未注册用户]
学习中,不是很赞同楼主的观点。。。。我到是认为主键的设计通常都是有意义的字段

表的主键也就是记录的码,通常应该是有意义的,没有意义的码标识一个记录,这个记录本身就没有什么意义了,因为他和其它记录不能有意义的区分开来,
另外,使用没有意义的字段做主键的表,这样的表 在功能上就 不单一了, 一个不是单一功能的表就就会造成程序设计上的复杂化。

楼住提出的列子 “订单号”的例子, 其实是两个步骤,1.保存原历史记录,2修改订单内容以及相关表的内容。而不应该采用一个无意义的主键来解决这个问题,

如果按楼主的做法,那么一定存在其它表对这个表对应关系不唯一的情况,这样的情况又该如何解决???
另外,我想,使用无意的主键其实是对系统不了解,对需求分析不透,而做了偷懒事。。。
(个人观点,请大家唾弃^_^)
  回复  引用    

#48楼  2007-09-09 00:34 Camenix [未注册用户]
自动增长字段确实有问题,主要在数据库转移和同步的时候。
但是如果能够使用存储过程,无论何种方式都不会增加ROUNDTRIP
以下表中
Order(OrderID, OrderDate)
OrderDetial(OrderID, LineNum, ProductID, Price)
OrderDetial使用复合主键,导致隐含的意义,如果采用完全无意义的主键,应使用OrderDetial(OrderDetialID,OrderID, ProductID, Price)
  回复  引用    

#49楼  2007-09-23 10:34 蛙蛙池塘      
还是觉得guid和com方式不妥。
  回复  引用  查看    

#50楼  2008-04-16 10:46 Andyson Zeng      
--引用--------------------------------------------------
吕震宇: @听棠.NET

我想对于你提出的第一个问题,确实是我表述不当。我完全赞同你所说的在事务中可以确保同时成功则成功,任何一个失败则失败的观点。我想说的是,使用自动增长字段会增加网络的roundTrip。尽管可以使用@@IDENTITY取得主键的值,但在更新过程中,不得不增加一次数据往返(以C/S结构为例):

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

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

如果不用自动增长型字段,将是以下情景:

1、客户端发送开始事务命令
2、客户端提交主表更新
3、客户端提交从表更新
4、客户端提交事务

因此我不赞成使用自动增长型字段作为主键与外键链接的纽带。(正文内容我会尽快修改)。
--------------------------------------------------------
不赞同楼主这段话,在一般实现中,类似这种情形应该在存储过程中进行事务处理而不是在客户端,因此不会出现楼主所说的增加一次往返!
  回复  引用  查看    

#51楼  2008-06-26 08:55 Tracy.Chuang      
俺是做电子商务的。
电子商务系统本身的两大特点:
1] 效率要求高
2] 变动频繁
所以,在大型电子商务应用这个特定的场景下,会更加偏向于用自增长的INT。好处如下:
1] 无意义的字段作为主键可以有利于今后的扩展,适应频繁的变动
2] 插入和查询的效率都比字符型更高
如何避免楼主提到的“在数据缓冲模式下,很难预先填写主键与外键的值”,事实上,在实际操作中,电子商务网站的插入一般采用队列的形式作异步插入,可以避免这个问题。
  回复  引用  查看    

#52楼  2008-09-02 10:44 王建平 [未注册用户]
GUID的用处举例:
有下列两个表,用来记录某物流公司的发货客户信息及发货明细情况

表名:客户信息
字段:GUID,姓名,电话,地址

表名:发货记录明细
字段:ID,发货日期,发货客户GUID,收货人姓名,收货人电话

假设该物流公司有两个收货地点,都使用这该表记录日常的发货信息,而两个收货点之间不能联网,每天需要把当天录入的资料拷贝到总公司进行数据合并,这时候“客户信息”表的主键就必修是GUID,才能保证合并以后的“发货记录明细”表不会出错。
  回复  引用    

#53楼  2008-09-12 17:04 David Liao      
再举个例子:
流程设计的两个表,

表名:流程基本资料,
字段:设计ID(自动增长), 流程名, 描述

表名:流程节点
字段:节点ID(自动增长),流程设计ID,描述

因为设计流程时,流程的节点不能确定哪个是新增,哪个是更新过的,目前的做法是,删除相关流程节点,删除流程基本资料,再增加流程基本资料,增加流程节点,提交事务。
做起来又累,效率又不高!如果用GUID的话,就只可以解决了!

结论:主表与明细表不能同时操作时,用GUID更好。
  回复  引用  查看    

#54楼  2008-09-19 14:09 袁枫      
反正是看完了...........................
  回复  引用  查看    





标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交