[SQLServer]为什么不要用uniqueidentifier做主键

去年有个项目,PM给出的数据库设计中所有表的主键都是uniqueidentifier类型的,其值由nhibernate中指定的算法自动生成。后来他辞职之后,继任维护这个项目的PM对这个设计大为不满:白白耗费了存储空间不说,可读性还很差。另一个team的架构师也给我发过来一篇博文,其中主要讲了三点:

1 NEWID产生的uniqueidentifier可读性差,如果非得用这个类型,可以考虑NEWSEQUENTIALID

2 以uniqueidentifier为主键的表只能通过Default约束来生成默认值,且insert之后无法通过简单的数据库内建机制取得生成的值

3 NEWID产生的uniqueidentifier主键会造成更多的碎片,占用更多的页和磁盘空间

当时我看完那篇文章的时候,并没有很好的理解第三点。最近抽时间读了一点SQLServer的index基础的东西,算是对这个问题有点明白了,就也拿来说一说。

 

这个问题还要从SQLServer的数据存储结构说起。SQLServer中,数据以行为单位存储在B树(貌似是二叉的?)之中,树的节点为页。每页的存储空间当然有限,当一页快满的时候就会引发页拆分。于是既然是B树,就自然存在大小比较的问题——比我大的节点肯定在我的右子树,比我小的节点则肯定在我的左子树。那么以什么作为比较的标准呢?大家肯定马上就想到用主键。于是上面的问题就很好理解了:试想如果用有序的主键例如identity column,那么新insert的肯定会被放在B树的最右端,相应的需要页拆分的机会也就比较少;而无序生成的uniqueidentifier(使用NEWID或者像我们的项目使用HEX算法)不仅会造成碎片和空间浪费,在插入的时候效率也自然低。

以上的分析到底对不对呢?呵呵我自然不敢厚着脸皮说对。至少有一个地方就不完全对:就是我们假设在插入B树时的比较大小的标准是主键。这种说法只能说“往往”是正确的。什么情况下不正确呢?呵呵很简单,比如一个表根本没有主键的情况。虽然没有主键的表不符合数据库设计的一些规范,但实际运用中还是偶尔能用到。实际情况是SQLServer为每一行标记一个rowid,插入B树时的比较大小的标准是rowid。

那么SQLServer又是由什么来生成rowid的呢?由clustered index。clustered index在每个表上只有一个,其余index都是non-clustered index。而我们create table的时候如果指定了主键,SQLServer会自动把主键作为clustered index,从而从结果上造成了“用主键作为比较大小的标准”,所以上面说这种说法只是“往往”是正确的。我们可以通过指定其他的clustered index来改变这种默认的行为。

于是又扯出的一个题外的问题是:为什么要指定其他的clustered index?这就要理解clustered index和non-clustered index的区别。刚才说过clustered index决定了数据的物理存储结构,其结点是真正的数据。而non-clustered index,据我的理解,会每个non-clustered index以自己的排序方式创建一棵自己的B树,其中的结点存储的不是真正的数据(也不可能是,因为数据的存储结构是被clustered index规定好的)而是指向真正数据的指针。所以,虽然同样是索引,但clustered index更迅猛:)而这好钢也许要用在刀刃上,而不一定是主键上。

posted @ 2010-06-05 01:32  jiaxingseng  阅读(3388)  评论(1编辑  收藏  举报