INSERT 处理过程概述
首先让我们快速地看看插入一行时的处理步骤。这些步骤中的每一步都有优化的潜力,对此我们在后面会一一讨论。
insert 的替代方案
在详细讨论 insert 的优化之前,让我们先考虑一下 insert 的两种替代方案:load 和 import。import 实用程序实际上是 SQL INSERT 的一个前端,但它的某些功能对于您来说也是有用的。load 也有一些有用的额外功能,但是我们使用 load 而不使用 insert 的主要原因是可以提高性能。
load 直接格式化数据页,而避免了由于插入导致的对每一行进行处理的大部分开销(例如,日志记录在这里实际上是消除了)。而且,load 可以更好地利用多处理器机器上的并行性。在 V8 load 中有两个新功能,它们对于 load 成为 insert 的替代方案有着特别的功效,这两个功能是:从游标装载和从调用层接口(CLI)应用程序装载。
从游标装载
这种方法可用于应用程序的程序代码(通过 db2Load API),或用于 DB2 脚本。下面是后一种情况的一个例子: declare staffcursor cursor forselect * from staff;
load from staffcursor of cursor insert into myschema.new_staff;
这两行可以用下面一行替代: insert into myschema.new_staff select * from staff
同等效的 INSERT ... SELECT 语句相比,从游标装载几乎可以提高 20% 的性能。
从 CLI 装载
这种方法显然只限于调用层接口(CLI)应用程序,但是它非常快。这种技巧非常类似于数组插入,DB2 附带了这样的示例,使用 load 时的速度是使用经过完全优化的数组插入时的两倍,几乎要比未经优化的数组插入快 10 倍。
所有 insert 可以改进的地方
让我们看看插入处理的一些必要步骤,以及我们可以用来优化这些步骤的技巧。
1. 语句准备
作为一条 SQL 语句,INSERT 语句在执行之前必须由 DB2 进行编译。这一步骤可以自动发生(例如在 CLP 中,或者在一次 CLI SQLExecDirect 调用中),也可以显式地进行(例如,通过一条 SQL Prepare、CLI SQLPrepare 或 JDBC prepareStatement 语句)。该编译过程牵涉到授权检查、优化,以及将语句转化为可执行格式时所需的其他一些活动。在编译语句时,语句的访问计划被存储在包缓存中。
如果重复地执行相同的 INSERT 语句,则该语句的访问计划(通常)会进入到包缓存中,这样就免除了编译的开销。然而,如果 insert 语句对于每一行有不同的值,那么每一条语句都将被看成是惟一的,必须单独地进行编译。因此,将像下面这样的重复语句: insert into mytable values (1, 'abc')
insert into mytable values (2, 'def')
等等,
换成带有参数标记的语句,一次准备,重复执行,这样做是十分可取的: insert into mytable values (?, ?)
使用参数标记可以让一系列的 insert 的运行速度提高数倍。(在静态 SQL 程序中使用主机变量也可以获得类似的好处。)
2. 发送列值到服务器
可以归为这一类的优化技巧有好几种。最重要的一种技巧是在每条 insert 语句中包括多行,这样就可以避免对于每一行都进行客户机-服务器通信,同时也减少了 DB2 开销。可用于多行插入的技巧有:
如果不可能在一条 insert 语句中传递多行,那么最好是将多条 insert 语句组成一组,将它们一起从客户机传递到服务器。(不过,这意味着每条 insert 都包含不同的值,都需要准备,因而其性能实际上要比使用参数标记情况下的性能更差一些。)将多条语句组合成一条语句可以通过 Compound SQL 来实现:
下面是关于该话题的其他一些建议:
3. 找到存储行的地方
DB2 使用三种算法中的一种来确定将行插入到哪里。(如果使用了多维群集(Multi-dimensional Clustering,MDC),则另当别论,我们在这里不予讨论。)
缺省模式是,DB2 搜索散布在表的各页上的自由空间控制记录(Free Space Control Records,FSCR),以找到有足够自由空间存放新行的页。显然,如果每页上的自由空间都比较少的话,就要浪费很多的搜索时间。为了应付这一点, DB2 提供了 DB2MAXFSCRSEARCH 注册表变量,以便允许将搜索范围限制为少于缺省的 5 页。
当表是通过 ALTER TABLE 以 APPEND 模式放置时,就要使用第二种算法。这样就完全避免了 FSCR 搜索,因为只需简单地将行直接放到表的末尾。
当表有群集索引(clustering index)时,就要用到最后一种算法。在这种情况下,DB2 试图将每一行插入到有相似键值的一页中。如果那一页没有空间了,DB2 就会尝试附近的页,如果附近的页也没有空间,DB2 就进行 FSCR 搜索。
如果只考虑插入时间的优化,那么使用 APPEND 模式对于批量插入是最快的一种方法,但是这种方法的效果远不如我们这里讨论的很多其他方法那么成效显著。第二好的方法应该是采用缺省算法,但是,如果在最佳环境中,更改 DB2MAXFSCRSEARCH 的值影响很小,而在一个 I/O 约束较少的环境中,这种更改所造成的影响就比较可观了。
如果有群集索引,则对 insert 的性能会有很大的负面影响,这一点也不惊奇,因为使用群集索引的目的就是通过在插入时做额外的工作来提高查询(即 select)性能的。如果的确需要群集索引,那么可以通过确保有足够的自由空间来使其对插入的影响降至最小:使用 ALTER TABLE 增加 PCTFREE,然后使用 REORG 预留自由空间。不过,如果允许太多自由空间的存在,则可能导致查询时需要读取额外的页,这反而大大违反了使用群集索引的本意。另一种选择是,在批量插入之前先删除群集索引,而后再重新创建群集索引,也许这是最优的方法(创建群集索引的开销跟创建常规索引的开销差不多,都不是很大,只是在插入时有额外的开销)。