文章归档

最佳字段顺序-数据库物理设计

附注: 摘录自我翻译的《Oracle性能优化艺术》
很少有人关心如何寻找一个表的最佳字段顺序。根据环境的不同,可能一点影响也没有,也可能产生很大的开销。为了理解在哪些情形下可能带来很大的开销,必须先理解数据库引擎是如何将数据保存到数据块中的。
保存到数据块中的记录有个非常简单的结构(查看图12-1)。首先,会有一个头(H)记录这条记录的一些基本属性,诸如是否被锁住或者包含多少个字段。其次,就是它的字段。由于每个字段可能有不同的大小,所以每个字段包含两个部分。前一部分是数据的长度(Ln)。后一部分是数据本身(Dn)。

图12-1 存储在数据库库块中每一行记录的格式(H=行头,Ln=字段n的长度,Dn=字段n的数据)
理解这个格式的关键是,数据库引擎不知道一条记录中每个字段的偏移量(offset)。例如,如果需要定位字段3,必须从字段1开始。接着根据字段1的长度来定位字段2。最后,根据字段2的长度来定位字段3。因此,无论何时一条含有多个字段的记录,靠近记录开始的地方的字段定位的速度会明显快于靠近记录末尾的字段。为了更好地理解这一点,你可以运行脚本column_order.sql来进行测试,以估算搜索一个字段的额外开销。

(1) 创建一个包含250个字段的表:
create table t (n1 number,n2 number,….,n249 number,n250 number);
(2) 插入1万条记录。每条记录的每个字段都保存同样的内容。
(3) 估算下面这条语句的响应时间,针对这个表的每个字段执行一下下面的语句,一个循环执行1千次。
select count(colx) from t

图12-2概括了我的测试服务器运行这个测试的结果。特别要强调的是,引用第一个字段(位置1)的SQL语句的执行速度差不多是引用最后一个字段(位置 250)的执行速度的5倍。这是因为数据库引擎优化了每一个访问,从而避免了定位以及读出不需要处理的字段。例如,查询语句SELECT count(n3) FROM t在定位到第三个字段之后就停止了对记录的访问。图12-2也显示,在位置0,也就是查询count(*),任何字段都不需要访问。
图12-2 字段在记录中的位置和访问它需要的响应时间
由于这个原因,一般的规则是将访问频繁的字段放在前面。为了利用这一点,需要仔细确认只访问(引用)真正需要的字段。无论如何,从性能的角度看,查询不需要的字段(或者更甚,使用SELECT *引用所有的字段,即使事实上只有几个字段被应用程序使用,很遗憾,这种事经常发生)都是不好的,不仅是因为当从数据块中读出时会有额外开销,而且是因为在服务器端和客户端都需要分配更多的内存来临时保存这些数据,以及需要更多的时间和资源来通过网络传输这些数据,简单来讲,就是只要数据被处理,就会带来开销。
实际操作中,与字段顺序相关的额外开销在下面几种情况中(更加)显而易见。

当一张表包含大量的字段,并且SQL语句经常访问记录后面的很少几个字段。
当从同一个数据块中读取很多记录时,比如在做全表扫描的时候。这是因为,定位并访问一个数据块的开销要比定位并访问字段(只读取几条记录)时的开销多很多。

由于末尾的NULL值是不保存的,所以将可能包含NULL值的字段放在表的末端是合适的。这样,实际存储的物理字段数量以及相应的行的平均大小也可能降低。