全部博文(2065)
分类: 数据库开发技术
2010-02-06 23:02:01
反范式的应用
反范式的讨论在网上已经很多,之所以应用反范式,当然是为了查询的效率,当点评网成为众多喜欢美食的网友每天必上的网站之后,数据库增加了大量的数据,如果完全按照范式来进行设计,那查询将是致命的。
举个例子,要查询社区里面最新的10个帖子,如果按照范式设计,那么将关联两个表,一张是帖子表,另外一张是会员表,整个查询如下:
SELECT TOP 10 N.帖子标题, U.会员昵称,N.会员ID FROM 帖子表 N
JOIN 会员表 U
ON N.会员ID=U.会员ID
ORDER BY N.帖子ID DESC
这样的查询将产生两个聚集索引扫描,一个在帖子表上,另外一个在会员表上,然后再进行嵌套循环,当数据不多的时候,这样的查询没有问题,当两张 表都是百万数量级的时候,问题出现了,这个查询动不动就是几百毫秒,甚至更慢,这样的查询效率根本不能满足点评网对于网页速度的要求(一般不能超过100 毫秒),怎么办,当然要反范式,在帖子表里面添加冗余字段——会员昵称,这样我们就可以通过下面的查询达到同样的目的:
SELECT TOP 10 帖子标题, 会员昵称,会员ID FROM 帖子表 ORDER BY 帖子ID DESC
这个查询只需要通过一个聚集索引扫描就可以得到(用到哪个索引还会跟ORDER BY后面的字段有关,例子中用的是Primary Key作为排序的字段,所以用到的是聚集索引扫描),将两个查询放在一起查看执行计划,就会发现,第一个查询开销占了92%,而第二个才8%,也就是说, 第二个查询比起第一个查询,效率上优化了10倍以上,成果显著啊。
于是,在点评网的这个阶段,大量的冗余字段(这个就不按范式处理)出现在表的设计当中,主要集中在会员的昵称以及商户的名称上,一方面,这些信息在其他应用中频繁的用到,另外一方面,这两个信息所在的主表都是特别大的表,做关联查询消耗太大,而且容易造成资源的争夺。
但是新的问题随之产生,每当一个会员去更新自己的昵称的时候,我们会执行一个存储过程,这个存储过程的目的就是去更新大量的会员昵称的冗余字 段,这些更新对于一个活跃会员来说,将是非常耗时的,因为需要更新的数据实在太多,而Web 2.0的精髓之一就是个性化,这样的设计对于个性化来说,有着不可调和的矛盾。于是,范式就像王者一样归来了。
王者归来
范式这个王者的归来,也是需要一定的时机的。
时机1:网站规模的发展,已经造成反范式的副作用越来越大;
时机2:公司规模的发展,已经有足够的财力建立更好的硬件平台;
于是,我们选择了, 一个分布式的缓存系统,我们将会员信息以实体类的方式保存在Memcached里面,只要是可序列化的数据,经过装箱和拆箱,都可以保存到 Memcached中并随时可以快速的访问到这些对象,Memcached可以解决大量数据的缓存并保持多台Web Server得到的缓存数据是一致的,于是,前面那个例子可以通过这样的查询来进行
SELECT TOP 10 帖子标题,会员ID FROM 帖子表 ORDER BY 帖子ID DESC
然后再通过缓存系统封装的批量读取的方法得到这些会员的昵称,再显示到网页上,于是,访问速度快了,冗余数据没了,只要有一套完善的缓存管理机 制,问题就迎刃而解了。有时候,你还会因为这样的查询得到一些惊喜,因为会员ID是一个整型的字段,当你用一些非PK的字段作为排序的字段时,可以把会员 ID包含在其索引里面(SQL Server 2005新特性),这样在索引扫描后可以避免一次聚集索引扫描,再一次的提高了查询效率。
相信对于这个问题的讨论是不会结束的,随着点评网的继续发展,新的问题必然还会产生,互联网就是要随需应变,到时候再来跟大家分享。