主键的故事
1969年8月8日,在北京协和医院降生了一个漂亮的小女孩。接生的阿姨说,她的声音这么大,好象想要全世界的人都听到。
后来,她的父亲为她取了一个很好听的名字,叫“王菲”。于是,所有的小朋友就叫她“王菲”,“王菲”就是她童年的主键。
在她上初二的时候,认识了二班另一个叫“王菲”的同学,而且和她同一天生日。不过,同学们常常将她俩弄错,后来,就分别叫她们“大王菲”和“小王菲”。“大王菲”就是她在同学们心目中的主键。
在大王菲18岁的那一年,她领到了她的身份证。从此,她有了新的身份标识“100321690808022”,这一标识可以唯一区别中国大陆的每一个人。同时,原来二班的“小王菲”也领到了她自己的身份证,“100321690808006”。于是,人们就可以用身份证号,唯一标识两个“王菲”了。身份证号就是她成年后的主键。
由于她的歌唱的非常好,没多久就成了歌星。后来她去了香港发展,娱乐公司非要将她的名字更改为“王靖雯”,虽然她并不喜欢这个名字,但为了事业的发展还是同意了。“王靖雯”就成了她在娱乐圈里的主键。
“王靖雯”的歌打动了许许多多的歌迷,很快就成了歌坛天后。歌迷们将“王靖雯”这一主键与无数条动听的歌曲记录关联在了一起,并形成了一个庞大的歌迷数据库。
没多久,王靖雯和一个弹电吉他的小子相爱了。那小子说,还是“王菲”这个名字好听,于是,“王靖雯”又变回“王菲”了。主键被那小子修改了,这下麻烦大了。歌迷们都糊涂了,是将她的歌关联到“王菲”还是“王靖雯”呢?这一主键的修改,导致歌迷数据库的一次大混乱。
再后来呢,她和那个弹电吉他的小子结婚了,香港政府将他们的身份证号码,用结婚证书关联起来。这本也算一桩好事,可是,月老在酒醒之后发现了这一错误,就将关联的记录删除了。阴差阳错,命运使然,她和那个弹电吉他的小子只好分手了。
但是,正如接生的阿姨说的那样,她的声音的确让全世界的人都听到了!
讲完这个故事之后,我们是否能领悟到数据库设计的一些哲理呢?
什么是主键?
关系数据库说,为了唯一区分表的每一行记录,必须为表确定一个主键。主键可以是一个或多个列组成,这些主键列的值不能重复。主键是两个表进行关联的基础,所谓“关系”体现的是一个表的字段与另一个表的主键的关联。
在日常的应用项目开发中,我们常常都会面临“该拿什么字段来做主键”的难题。
用名字做主键?一班的“王菲”和二班的“王菲”可能重复。
用身份证号做主键?在大陆也许不会重复,去香港后,人家却不认。本不相信第二代身份证号会重复,但俺的一位同事正好撞上!
同样,主键值的更新也是麻烦事儿。“王靖雯”变成“王菲”那段日子,货架上一张唱片是王靖雯的,另一张又是王菲的,许多歌迷还以为又有新人出现了。
请不要说可以级联更新,将你那个做主键的字段加长两位试试?想当年,当身份证号从15位增加到18位时,给银行、证券、政府、企业...等多条战线上的兄弟们制造了怎样的痛苦啊!
为什么会出现这些问题呢?
原因就是:错误地把对象属性当作对象标识!
对象属性和对象标识都是对象数据库中的概念。
什么是对象属性呢?
就是业务逻辑上涉及的任何可变信息,什么“姓名”、“性别”、“身份证号”、“订单号”...通通都是对象属性。对象属性总会变化的,只是有些变得快,有些变得慢而已。
对象标识是啥?就是唯一区分数据对象的鉴别符,对象标识存在的唯一目的就是区分对象,除此之外没有任何业务逻辑上的意义。
不管王菲的属性值怎样变化,但王菲还是王菲,不是二班的那个“王菲”。也就是说,王菲的灵魂未变,她是不会改变的,就象哲学上所说的“不以人的意志为转移”。这种唯一表示对象本身的东西,就是对象标识!
对象标识是唯一的。也就是说,即使两个对象,他们的属性完全一样,但它们的对象标识是不同的。毕竟,同名同姓甚至同一天出生的大王菲和小王菲是两个不同的人。
对象的标识是永恒不变的。一旦对象产生,它的标识就自然地、唯一地产生了。尽管王菲换了名,身份证号也变过,但王菲的对象标识未变。即使到了下个世纪,她的对象标识也将依然存在于歌迷们的们的心中。
对象的标识是描述关系的基础。王菲唱的歌是王菲唱的,不是初二二班的那个“王菲”唱的。王靖雯唱的歌就是王菲唱的歌,有的歌迷只将歌曲和歌手的人名关联起来,难怪会出混乱。香港政府也犯相同的错误,将王菲的身份证号码这一内部属性,跟那个弹电吉他的小子关联起来,也许就是命运的错误。
那么,我们在设计数据库结构时,到底该用什么来做主键呢?
对象数据库说,主键只能是对象标识!
至于对象属性是否唯一,那是由业务逻辑所决定的。如果业务逻辑规定订单号不能重复,就为订单号建一个唯一索引好了,但它不是主键。
所以,当我们看到有些数据库设计采用了额外的一个字段来专门充当主键,并用这个主键与其他表关联的话,那就是已经走到对象数据库的门口了。什么“内部码”,“流水号”,“序列号”,“自增数”...通通都可以算是对象标识。
为什么SQL Server要提供自增量字段以及GUID的标识字段呢?都是为了这专门的主键字段服务的。
如果我们打算向对象数据库路上走得话,就请使用对象标识来做专门的主键字段吧。
当然,怎样产生唯一的对象标识来做主键,这也是有说道的。
1.用自增量字段
自增量字段每次都会按顺序递增,可以保证在一个表里的主键不重复。除非超出了自增字段类型的最大值并从头递增,但这几乎不可能。使用自增量字段来做主键是非常简单的,一般只需在建表时声明自增属性即可。
自增量字段的长度可以很短,比如使用一个int类型就基本上够用了。简短的主键可以在大量数据和复杂的关系查询中表现出更好的性能。
自增量的值都是需要在系统中维护一个全局的数据值,每次插入数据时即对此次值进行增量取值。当在当量产生唯一标识的并发环境中,每次的增量取值都必须最此全局值加锁解锁以保证增量的唯一性。这可能是一个并发的瓶颈,会牵扯一些性能问题。
还有,如果要搞分布式数据库的话,这自增量字段就有问题了。因为,在分布式数据库中,不同数据库的同名的表可能需要进行同步复制。一个数据库表的自增量值,就很可能与另一数据库相同表的自增量值重复了。当然,这可以通过指定不同的递增起始值来错开,但总觉得不爽啊。
SQL Server中还可以使用timestamp类型的数据来做对象标识符,这可以使对象标识对整个数据库都是唯一的,而不仅仅是对表唯一。但其优缺点与表的自增字段一样。
2.随机数字段
随机生成对象标识的方法实际就是碰运气。按照某种复杂的随机算法迅速产生对象标识,碰一碰对象标识不重复的运气。只要这种算法产生的对象标识一万年才可能重复一次,那你就可以在实际开发中应用这种算法。比如,SQL Server中提供了uniqueidentifier类型,配合NEWID()函数来产生GUID,基本上可以应用于所有主键需求了。
随机生成标识不需要在系统中维护全局量,不存在自增字段那种加锁解锁的性能开销,对于大量的并发处理来说是个福音。
同时,即使在分布式数据库应用中,不同数据库产生的随机值也是不同的。因此,在数据库的同步复制中,标识相同的就一定是同一条记录,就不会存在产生主键冲突的问题了。
不过,为了保证随机的唯一性,需要大范围的数值空间。这也使得主键字段比较大,在关联查询的时候有一定的性能损失,判断GUID值是否相同总比判断int值是否相同要多费些功夫。
在实际应用中到底该用什么方案来产生对象标识需要根具体情况决定,以上方案仅仅是理论探讨。
接下来要注意的就是主键的索引结构的优化了。
主键都要整成聚集索引。聚集索引在SQL Server内部就是聚集表,聚集表是B树结构,索引值存在B树的中间节点中,而数据行就存放在B树的页节点上。也就是说,聚集索引和数据表其实是一个统一的整体结构。因此,聚集索引查找和定位数据的效率要比一般索引高出很多。
此外,专门主键字段值是永远都不变。聚集索引建值不变,聚集表的节点也不会调整,硬盘上的数据记录块基本不动窝的。这可以极大减少数据库碎片,除非删除数据行。数据库的碎片少了,查询数据自然要快些。
一般来说,我们存入数据库中的数据总是有时间顺序的,我们日常查询和使用的数据也总是近期的数据。有的常用查询甚至总是按时间顺序倒排,比如,邮件列表,论坛帖子。如果主键采用的是自增字段,我们不妨将增量值设为-1或干脆搞成倒序的主键。这样,查询的排序与扫描索引的顺序相同,毕竟硬盘的磁头总喜欢从前往后读,这会稍微提高一点读取聚集索引的速度。
同样,如果使用随机主键值的方案,我们也建议采用与时间相关的随机数值,而不是GUID。与时间相关的随机数就是,虽然主键是随机产生的,但后产生的随机数应该大于先产生的数。
为什么不用GUID呢?因为它生成的值就像顽皮的猴子,到处乱跳。于是,在每次插入记录时,聚集索引节点中相邻的值并不具有时间上的顺序。而当我们习惯性地查询近期数据时,硬盘的磁头也需要像猴子般乱跳一通之后才能读到按时间顺序的所有数据。
如果采用有时间顺序的随机值,聚集索引插入数据时总往一个方向增加数据行的页节点,与同一时期的数据行几乎总相邻。查询同期数据时,磁头就很少乱跳了。磁头一次可以读取一批数据,效率有将有所提升。如果您用电子显微镜去观察硬盘的表面,聚集索引的那颗B树将生长得很正很齐。
对磁头读写的优化,是完美主义的程序员所追求的,是否采用完全看自己的心态。总之,高兴就好。当然,等将来的大容量存储设备都用上固态盘了,没磁头了,全电信号了,这些优化就都没有什么意义了。
所以呢,追求完美也是很痛苦的。吃了程序员这碗饭,就只能被IT的洪流卷着往前走。我们不过是这洪流中的一粒沙子,不知道又会被带到何方?
王菲在一首歌中这样唱到:一路上有人太早看透生命的线条命运的玄妙,有人太晚觉悟冥冥中该来则来无处可逃...
原创作者:李战(leadzen).深圳 2008-5-10
原文地址:http://www.cnblogs.com/leadzen/archive/2008/05/10/1191010.html
【转载请注明作者及出处】