DB2中INSERT的优化方法

数据库
DB2数据库中INSERT的效率如何提高,是很多人都在思考的问题,本文将为您介绍DB2数据库中INSERT 处理过程,及对应优化方法,供您参考,希望对您有所启迪。

DB2数据库中INSERT的效率如何提高,是很多人都在思考的问题,本文将为您介绍DB2数据库中INSERT 处理过程,及对应优化方法,供您参考,希望对您有所启迪。

语句准备

动态

自动(例如在 CLP 中,或者在一次 CLI SQLExecDirect 调用中)
显式(例如,通过一条 SQL Prepare、CLI SQLPrepare 或 JDBC prepareStatement 语句)
静态 一次编译

发送列值到服务器

在 VALUES 子句中包含多行的内容,示例程序 sqllib/samples/cli/tbload.c
在 JDBC 中使用批处理操作
使用 load 将数据快速地装入到一个 staging 表中,然后使用 INSERT ... SELECT 填充主表。
将多条语句组合成一条语句可以通过 Compound SQL 来实现 ??

让客户机与要存取的数据库使用相同的代码页
减少自动执行数据类型转换
将应用程序中与插入相关的设置开销最小化 ??

找到存储行的地方(分配和选择页)

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 约束较少的环境中,这种更改所造成的影响就比较可观了。 #p#

如果有群集索引,则对 insert 的性能会有很大的负面影响,这一点也不惊奇,因为使用群集索引的目的就是通过在插入时做额外的工作来提高查询(即 select)性能的。如果的确需要群集索引,那么可以通过确保有足够的自由空间来使其对插入的影响降至最小:使用 ALTER TABLE 增加 PCTFREE,然后使用 REORG 预留自由空间。不过,如果允许太多自由空间的存在,则可能导致查询时需要读取额外的页,这反而大大违反了使用群集索引的本意。另一种选择是,在批量插入之 前先删除群集索引,而后再重新创建群集索引,也许这是***的方法(创建群集索引的开销跟创建常规索引的开销差不多,都不是很大,只是在插入时有额外的开销)。

缓冲池、I/O 和页清除

一旦像前面讨论的那样指定了页,那么在将行添加到该页之前,该页必须已经在缓冲池中。对于批量插入,大部分页都是***指派给表的,因此让我们关注一下对新页的处理。

SMS表空间中,当需要新页时,缺省情况下是从文件系统中分别为每一页分配空间。但是,如果对数据库运行了 db2empfa 命令,那么每个 SMS 表空间就会为新页一次性分配一个区段。

DMS 时,更改区段大小并没有明显的效果。

如果表上有索引,则对于每个插入的行,都要添加一个条目到每条索引。这要求在缓冲池中存在适当的索引页。

随着插入的进行,越来越多的页中将填入被插入的行,但是,DB2 不要求在 insert 或 Commit 后将任何新插入的或更新后的数据或索引写入到磁盘。(这是由于 DB2 的 writeahead 日志记录算法。但是有一个例外,这将在关于日志记录的小节中论述到。)然而,这些页需要在某一时刻写到磁盘上,这个时刻可能会在数据库关闭时才会轮到。

一般来说,对于批量插入,您会希望积极地进行 异步页清除(asynchronous page cleaning),这样在缓冲池中就总有可用于新页的空余位置。

如果在同一情况下进行了积极的页清除,则批量插入过程可能要花更长的时间,但是此后缓冲池中的脏页要少一些,从 而使得随后的任务执行起来性能更佳。至于那些结果中到底哪个要更好些,我们并不是总能分得清,但是通常来说,将所有脏页都存储在缓冲池中是不可能的,所以 为了取得***性能,采取有效的页清除是有必要的。

为了尽可能好地进行页清除:将 CHNGPGS_THRESH 数据库配置参数的值从缺省的 60 减少到 5 这么低。这个参数决定缓冲池中脏页的阈值百分比,当脏页达到这个百分比时,就会启动页清除。#p#

尝试启用注册表变量 DB2_USE_ALTERNATE_PAGE_CLEANING(在 DB2 V8 FixPak 4 中***提供)。通过将这个变量设置成 ON,可以为页清除提供一种比缺省方法(基于 CHNGPGS_THRESH 和 LSN 间隙触发器)更积极的方法。

至于 I/O 本身,当需要建立索引时,可以通过使用尽可能大的缓冲池来将 I/O 活动减至最少。如果不存在索引,则使用较大的缓冲池帮助不大,而只是推迟了 I/O。也就是说,它允许所有新页暂时安放在缓冲池中,但是最终仍需要将这些页写到磁盘上。

当发生将页写到磁盘的 I/O 时,通过一些常规的 I/O 调优步骤可以加快这一过程,例如:

将表空间分布在多个容器(这些容器映射到不同磁盘)。

尽可能使用最快的硬件和存储管理配置,这包括磁盘和通道速度、写缓存以及并行写等因素。

避免 RAID5(除非是与像 Shark 这样有效的存储设备一起使

为获得和释放锁而产生的 CPU 开销。
由获得每一行之上的一个 X 锁以及后来释放该锁引起的 CPU 开销是比较可观的。对于每个新行之上的锁,惟一可以替代的是表锁(DB2 中没有页锁)。当使用表锁时,耗时减少了 3%。

运行 ALTER TABLE LOCKSIZE TABLE。这将导致 DB2 为随后使用该表的所有 SQL 语句使用一个表锁,直到 locksize 参数改回到 ROW。
运行 LOCK TABLE IN EXCLUSIVE MODE。这将导致表上立即上了一个 X 锁。注意,在下一次提交(或回滚)的时候,这个表将被释放,因此,如果您要运行一个测试,测试中每 N 行提交一次,那么就需要在每次提交之后重复执行 LOCK TABLE。
使用缺省锁,但是让 LOCKLIST 和 MAXLOCKS 数据库配置参数的值比较小。当获得少量的行锁时,行锁就会自动地逐渐升级为表锁。

可能由于锁冲突而导致的并发问题。

对于 V8 FixPak 4,或许也可以通过 DB2_EVALUNCOMMITTED 注册表变量来减少锁冲突:如果将该变量设置为 YES,那么在很多情况下,只能获得那些符合某个谓词的行上的锁,而并不是获得被检查的所有行上的锁。#p#

发出一个 COMMIT 命令以释放锁,因此如果更频繁地提交的话就足以减轻锁冲突的负担。

日志记录

缺省情况下,每条 insert 都会被记录下来,以用于恢复。日志记录首先被写到内存中的日志缓冲池,然后再写到日志文件,通常是在日志缓冲池已满或者发生了一次提交时写到日志文件的。 对批量插入的日志记录的优化实际上就是最小化日志记录写的次数,以及使写的速度尽可能快。

这里首先考虑的是日志缓冲池的大小,这由数据库配置参数 LOGBUFSZ 来控制。该参数缺省值为 8 页或 32 K,这与大多数批量插入所需的理想日志缓冲池大小相比要小些。

减少日志写的另一种可能性是对新行要插入到的那个表使用“ALTER TABLE ACTIVATE NOT LOGGED INITIALLY”(NLI)。如果这样做了,那么在该工作单元内不会记录任何 insert 操作,但是这里存在两个与 NLI 有关的重要问题:

如果有一条语句失败,那么这个表将被标记为不可访问的,并且需要被删除掉。这与其他恢复问题(请参阅 SQL Reference 关于 Create Table 的讨论)一起使得 NLI 在很多情况下不能成为可行的方法。
在工作单元***进行的提交,必须等到在此工作单元内涉及的所有脏页都被写到磁盘之后才能完成。这意味着这种提交 要占用大量的时间。如果没有积极地进行页清除,那么在使用 NLI 的情况下,Insert 加上提交所耗费的总时间要更长一些。将 NLI 与积极的页清除一起使用的时候,可以大大减少耗时。如果使用 NLI,就要瞪大眼睛盯紧提交操作所耗费的时间。

至于提高日志写的速度,有下面一些可能性:

将日志与新行所要插入到的表分别放在不同的磁盘上。

在操作系统层将日志分放到多个磁盘。

考虑为日志使用原始设备(raw device),但是要注意,这样管理起来要更困难些。

避免使用 RAID 5,因为它不适合于写密集型(write-intensive)活动#p#

提交

提交迫使将日志记录写到磁盘上,以保证提交的插入肯定会存在于数据库中,并且释放新行上的锁。这些都是有 价值的活动,但是因为 Commit 总是要牵涉到同步 I/O(对于日志),而 insert 则不会,所以 Commit 的开销很容易高于 insert 的开销。因此,在进行批量插入时,每一行都提交一次的做法对于性能来说是很糟糕的,所以应确保不使用自动提交(对于 CLI 和 CLP 来说缺省情况正是如此)。建议大约每 1000 行提交一次:当每 1000 行而不是一两行提交一次时,性能可以提高大概 10 倍。不过,一次提交多于 1000 行只能节省少量的时间,但是一旦出现失败,恢复起来所花的时间要更多。

对上述方法的一种修正:如果 MINCOMMIT 数据库配置参数的值大于 1 (缺省值),则 DB2 就不必对每次 commit 都进行一次同步 I/O,而是等待,并试图与一组事件一起共享日志 I/O。对于某些环境来讲,这样做是有好处,但是对于批量插入常常没有作用,甚至有负作用,因此,如果要执行的关键任务是批量插入,就应该让 MINCOMMIT 的值保持为 1。

索引维护

对于插入的每一行,必须添加一个条目到表上的每个索引中(包括任何主键索引)。这一过程主要有两方面的代价:

遍历每个索引树,在树的每一层搜索一个页,以确定新条目必须存储在哪里(索引条目总是按键顺序存储的),这一过程所引起的 CPU 开销;

将所有搜索到的页读入缓冲池,并最终将每个更新后的页写到磁盘上的 I/O 开销。

更坏的场景是,在索引维护期间有大量的随机 I/O。假设要插入 10,000 行,在索引的缓冲池中有 5000 页,并且要插入的各行的键值随机分布在整个键范围内。那么,有 10,000 个这么多的叶子页(可能还有些非叶子页)需要进入缓冲池,以便对它们进行搜索和/或更新,对于一个给定的叶子页,它预先已经在缓冲池中的概率只有 10%。对于每次的 insert,需要读磁盘的概率如此之高,使得这种场景往往性能很差。

对于逐行插入,将新行添加到已有的索引中比起创建一个新索引来代价要高得多。如果是插入到一个空表,应该总是在进行了列插入之后创建索 引。(注意,如果使用了 load,则应该 预先创建索引。)如果要插入到一个已经填充过的表,那么在列插入之前删除索引,并在列插入之后重新创建索引,这种方法可能是最快的,但是只有在要插入相当 多的行 -- 大概大于表的 10-20% 的时候,才能这么说。如果为索引表空间使用较大的缓冲池,并且尽可能地将不同 insert 排序,以便键值是排好序的,而不是随机的,就可以帮助加快索引维护。
 

责任编辑:段燃 来源: 博客园
相关推荐

2009-04-10 08:56:16

DB2Insert性能

2010-11-04 15:39:40

DB2 SQL语句

2011-03-21 09:51:04

DB2性能优化

2010-08-17 16:13:32

DB2 并行版本

2011-05-27 16:00:10

DB2

2010-11-04 15:34:20

DB2索引优化

2010-11-02 14:08:29

DB2创建用户

2010-11-03 14:16:29

DB2增量备份

2010-11-04 13:25:16

DB2在线导出

2010-11-02 13:40:34

DB2函数调用

2011-05-27 14:28:33

DB2

2011-05-27 15:24:28

DB2

2012-08-01 14:23:35

IBMdW

2010-11-02 11:43:11

DB2动态SQL

2010-11-02 14:37:58

DB2临时表定义

2010-09-01 11:17:29

DB2备份

2010-11-03 14:10:23

DB2在线备份

2010-11-03 14:57:44

DB2备份所有表

2010-09-01 14:00:01

DB2表空间

2010-11-02 11:08:11

DB2循环查询
点赞
收藏

51CTO技术栈公众号