本文的目的是要将复杂的数据库性能调优的工作简化为一套步骤和指南,以供 IBM DB2 Universal Database的新用户参考使用。关于这一主题的更完整的讨论,请参见 DB2 Administration Guide以及第三方的有关数据库设计与性能的书籍。
对于一个应用程序的性能来说,其中数据库的性能是一个重要因素。由于应用程序及其相关的数据总会随着时间的推移而发生变化,因此必须不断地对数据库进行调优从而使其保持最佳水准。然而,花在调优上的努力应该在一个合理的范围之内。调优应该有一个度,超过了这个度的一切努力只能产生负面影响。如果应用程序的性能还不能令人满意,那么就应该考虑其他的变通办法,比如将该应用程序移到更快的平台上。
本文中提到的命令和语法是基于 DB2 UDB V7 的,如果您使用的是 DB2 UDB V8,可能会稍有差异。
数据库设计方面的考虑
数据库调优始于设计阶段。假设硬件的选择是基于其他方面的考虑的,那么第一个要决定的就是存储架构。DB2 所使用的驱动器越多、越快,则潜在的性能将越好。对于表空间(tablespaces )和其他对象(日志,备份文件,等等)的位置应该小心仔细地加以规划。尤其重要的是,要尽量保证日志和备份处在不同的驱动器上,这样做不但是为了性能,也是为了便于恢复。
表空间设计是整个数据库设计中的一个重要部分。通过创建不止一个的用户表空间可以增强性能。在下面三种情况下,使用多个表空间就很有用:
控制 I/O,如果这些表空间可以位于不同的驱动器上的话。
使用不同的页面大小(pagesize)。
控制缓冲池。
在大多数情况下,隔离的表空间是为索引和大型对象而创建的。以相同的页面大小创建多于一个的表空间并没有什么好处。
比起系统管理的表空间来,数据库管理(Database-managed)的表空间(尤其是在原始设备上)能够提供更好的性能。在决定页面大小时要记住,DB2 在一页上最多只能放 255 行,剩余的空间将不被使用。例如,如果平均行长度是 50 字节,那么一页最多使用的空间是 50*255=12750 字节。如果将该表放在页面大小为 16K 或者 32K 的表空间中,那么有些页就会被浪费。反之,如果有些表有更长的行,或者有很多的列,(具体的限制参见 SQL 参考手册中 CREATE TABLE 语句),那么页面大小就需要大于 4K。如果要以一种连续的方式(例如,群集表)来访问数据,那么采用更大的页面大小可以获得更好的性能,相反,如果对数据的访问采用的是随机方式,那么最好使用尽可能小的页面大小。
每个表空间都与具有相同页面大小的一个缓冲池相关联(一个缓冲池可以与不止一个的表空间相关联)。在使用多个缓冲池的时候要谨慎。由于可用的存储是有限的,为某个缓冲池分配过多的空间势必减少其他缓冲池的宽度,从而导致整体性能的降低。缓冲池调优最好是在检测数据库性能和基准的基础上进行。DB2 善于动态地管理可用空间,因此,在大多数情况下使用最少数量的缓冲池可以得到较好的性能。
长期以来,表设计的重要性就在于标准化。无冗余数据占据着最少的空间,并且具有最好的完整性。然而,无冗余数据并不能提供最好的性能。为了消除一点点的冗余,需要创建额外的表,这使得查询时需要额外地结合这额外创建的表,从而增加查询的复杂性。在平衡这两方面的需求时,需要有正确的判断。通常,通过生成冗余数据可以增加性能,但是这要采取一种受约束的方式,即冗余数据所采取的形式必须是索引和汇总表。如果要经常访问汇总数据,那么后者可以明显增加性能。对刷新频率的评估应该以信息需要保持的新鲜程度为依据。
索引是性能调优中最重要的方面之一。通常,对表的访问都是基于一些标准的。根据组成这些标准的一些列构建索引,可以动态地减少查询相关的开销。对于在线维护的不稳定的表应该创建少量的索引(一个或两个),而对于大型的历史性的表,由于需要通过多种方式进行查询,则需要创建很多的索引。一条索引中的列数应该尽量地少,除非很多查询都可以通过一个“index only”搜索来完成。为了这个目的, INCLUDE 选项允许将其他字段附加到索引上,其开销则小于完全索引方式。可以选择一个表的某一索引作为群集索引,或者在 REORGANIZE 命令中指定该索引。表数据将保持由该索引指定的顺序。当大量的查询基于该索引访问大量的行时,这种做法很有用。索引通常被放在它们自己的表空间中,拥有它们自己的缓冲池,以防止数据页数量很多时会将索引页挤出。
应用程序设计方面的考虑
应用程序设计同样会影响到数据库。首要的一步就是要确保应用程序只要求数据库管理器做必需的工作。例如,通过使用 SELECT * 来请求所有的列在一定程度上可以使得程序的速度加快,但是这样做却降低了性能,因为需要额外的数据移动,而且阻止了“index only”扫描。在查询中包括不必要的子句,例如 ORDER BY 或者 DISTINCT ,就是请求数据库管理器做额外工作的一个例子。如果列的顺序对应用程序的运行没有影响,那么就可以省下排序所花的时间。
控制锁(locking)特性对于增加数据库的吞吐量非常重要。即使是对于只读事务,提交也具有极大的重要性,因为对于这样的事务同样也要使用锁。选择正确的隔离级别非常重要。应该使用尽可能低的隔离级别,只要在这种级别上应用程序能够运行就行了。对于锁来说,使用可重复读隔离级别是极其昂贵的,并且也减少了并发性。只要不打算对结果集进行更新,那么就应该包括 FOR READ ONLY 子句。这样就可以保证独占的锁不被获得。 FOR UPDATE 子句将消除对重新获得更高级别锁的需求。在某些环境下,在查询之前通过应用程序获得一个表锁可以防止获取很多的行锁,从而防止了对锁的逐步升级。
查询优化是另一个可以节省大量资源的方面。优化的级别可以通过数据库配置参数 dft_queryopt 进行设置。并且,在静态 SQL 中可以通过 PREP 和 BIND 命令进行重设,在动态 SQL 中可以通过 SET CURRENT QUERY OPTIMIZATION 语句进行重设。对于复杂的查询,可能需要第 5 级或者更高的级别。可以使用 db2batch 工具来评测花在编译和执行 SQL 语句上的时间。至于结果,要记住,静态 SQL 语句通常是编译一次,执行多次;对于动态 SQL 也是一样,因为结果要缓存。
初始调优
在创建了数据库和表空间之后,可以使用 Performance Wizard 来设置初始数据库配置。选择数据库以及“Configure Performance Using Wizard”选项。这将允许更快地装载数据。在创建了数据对象之后,就应该装载数据。
调优的第一步就是使用 RUNSTATS 命令收集统计信息。为了获得整套的统计信息,应该指定“WITH DISTRIBUTION AND INDEXES ALL”选项。 RUNSTATS 应该是数据库维护的一个常规的部分。应该根据数据库的更新率有规律地(每日,每周,每月)调用 RUNSTATS 。如果对数据作了大的更改(装载或者删除了大量的行),也应运行 RUNSTATS 命令。统计信息可用于决定对于一个查询来说哪一个访问计划是最有效的。在执行了 RUNSTATS 命令之后,受到影响的包应该重新绑定。
在此之后应该再次执行 Performance Wizard,这一次是为了指定要填充的数据库。Performance Wizard 将更改某些数据库配置参数。如果正确地解决了这些问题,那么由 Performance Wizard 产生的值通常会比较理想。Performance Wizard 使您可以在第一屏恢复先前的配置(如果有的话)。您应该认真阅读窗口中的解说。最后一屏将显示老的和新的数据库配置,并对所有的更改用粗体进行高亮显示。
如果在测试时性能不令人满意,那么就应该使用 Database System Monitor(参见 System Monitor Guide and Reference以了解细节),或者将问题的源头缩小至几个事务,来查探这一问题的起因。对于特定的查询,解释工具(参见 Administration Guide 以了解细节)提供了有关性能问题可能的起因的有价值的信息。根据这些信息,可以对索引结构或者数据库参数作出更改。
结束语
先前的讨论强调了调优时需要考虑的的一些主要方面。调优是一个反复的过程。随着时间的推移,数据库中的数据以及应用程序需要更改。这时,为了适应新的需要,应该对性能进行检测并对数据库作出更改。对数据库的配置参数或者其他方面所做的更改要有基准,要打破常规。在某些时候,有些更改看上去是对的,实际上却会对数据库产生负面的影响。