首页 informix系统管理维护手册

informix系统管理维护手册

举报
开通vip

informix系统管理维护手册informix系统管理维护手册 Informix系统管理维护手册 监控实例活动 IDS 实例是指 Informix 共享内存、Informix 处理器、Informix 数据库以及分配给 Informix 的物理设备。以下是部分需要监控的最重要的实例活动。 操作方式 第一个也是最重要的实例活动当然是 IDS 的操作方式。IDS 运行正常还是有问题,或是已当机了, onstat -p 命令捕获了 IDS 的当前操作方式,如下所示: Informix Dynamic Server 2000 Version...

informix系统管理维护手册
informix系统管理维护手册 Informix系统管理维护手册 监控实例活动 IDS 实例是指 Informix 共享内存、Informix 处理器、Informix 数据库以及分配给 Informix 的物理设备。以下是部分需要监控的最重要的实例活动。 操作方式 第一个也是最重要的实例活动当然是 IDS 的操作方式。IDS 运行正常还是有问题,或是已当机了, onstat -p 命令捕获了 IDS 的当前操作方式,如下所示: Informix Dynamic Server 2000 Version 9.21.UC4 -- On-Line -- Up 01:01:17 -- 1654784 Kbytes Profile dskreads pagreads bufreads %cached dskwrits pagwrits bufwrits %cached 86923 101304 3116565 97.21 1651 15022 26196 93.70 isamtot open start read write rewrite delete commit rollbk 2585879 118500 286631 1032967 1972 914 2 2 0 gp_read gp_write gp_rewrt gp_del gp_alloc gp_free gp_curs 0 0 0 0 0 0 0 ovlock ovuserthread ovbuff usercpu syscpu numckpts flushes 0 0 0 478.11 71.63 13 26 bufwaits lokwaits lockreqs deadlks dltouts ckpwaits compress seqscans 3502 0 7065882 0 0 0 1266 11280 ixda-RA idx-RA da-RA RA-pgsused lchwaits 10120 51 69387 79557 482 也可以查询 sysmaster 数据库中的 sysprofile 表来获取同样的统计信息。 输出的第一行显示了当前的 IDS 操作方式。本例中,Informix 引擎是―On-Line‖。总共有六种操作方式,其中三种特别重要:Off-Line、Quiescent 和 On-Line。Off-Line 方式表明 IDS 当前没有在运行。Quiescent 方式表明 IDS 正在以单用户方式运行,在这种方式下,只有 DBA 可以进行管理和维护工作。On-Line 方式表明 IDS 正在正常运行,所有用户都可以连接到数据库服务器,并可以执行各种数据库操作。在大多数情况下,IDS 应该始终处于 On-Line 方式。如果因为种种原因 IDS 当机了或处于 Off-Line 方式,那么上面的命令将显示下面的消息: Shared memory not initialized for INFORMIXSERVER 'cassprod_shm' 在这种情况下,您需要检查消息日志或 Informix 联机日志,以进一步确定问题的根源, 除了当前的操作方式以外,上面的输出还提供了一些重要的 Informix 实例性能统计信息。两个 %cache 字段表明 IDS 目前使用内存高速缓存的效率。第一个 %cache 字段显示了读高速缓存比例的百分比,而第二个则显示了写高速缓存比例。读高速缓存比例和写高速缓存比例会随应用程序及正在操作的数据的类型和大小而动态变化。但读高速缓存比例和写高速缓存比例一般都应该在 80 到 90 个百分点之间。这是十分保守的数字,应该根据具体环境加以调整。如果这些比例始终低于 80%,那么您需要考虑提高 Informix 配置文件中 BUFFERS 参数的值,以获取较高的读写高速缓存比例。较低的读写高速缓存比例表明 IDS 正在进行的磁盘读写操作比它应该进行的要多得多,这会大大降低数据库引擎的整体性能。 输出的 seqscan 字段表明自数据库启动或联机以来执行了多少次顺序扫描。如果这个数字相当大,比如说超过了 100000,并且还在不断增加,那么这可能表明性能有问题,当系统处于 OLTP 环境时更是如此。因而,您需要做进一步的调查以搞清楚出现过多顺序扫描的根源。在本文的后面我们将更详细地讨论这一问题。 ovlock 字段表明 IDS 在使用了最大数量的锁之后尝试过再使用锁的次数。如果该数字非零,那么您可能需要提高配置文件中 LOCKS 参数的值。 ovbuf 字段表明 IDS 在使用了最大数量的缓冲区之后尝试过再使用缓冲区的次数。如果该数字很大,比如说超过 100000,那么您需要提高 BUFFERS 参数,以便用户在需要从磁盘访问数据时不必等待缓冲区。这会缩短响应时间,因而可以改善整体性能。我们还需要检查与 LRU 有关的参数,将它们的值调整到较低的 bufwait。请参考 Administrator's Guide for Informix Dynamic Server 以获取更多详细信息。 另一组重要字段包括 ixda-RA、idx-RA、da-RA 及 RA-pgused 。这些字段组合在一起表明 IDS 使用 Informix 预读机制的效率。预读是这样一种操作:它在顺序扫描或索引读期间提前将数据页的数目从磁盘读入内存。理想情况是,预读的页数(即 ixda-RA、idx-RA 和 da-RA 之和)等于顺序扫描或索引读期间所使用的页数(即 RA-pgused )。这表明预读的页百分之百地用于顺序扫描和索引读。如果二者之间存在显著的差异,比如正负差值达到 10000 以上,那么 IDS 目前就没有很有效地使用预读,而您可能需要调优您的预读参数(即 RA_PAGES 和 RA_THRESHOLD)以获取更好的性能。请参考 Administrator's Guide for Informix Dynamic Server(本文称为 Administrator's Guide)以获取有关如何调优这些参数的详细信息。 消息日志 消息日志也称为联机日志。它含有各种有关关键实例活动的信息,如检查点的时间和持续时间、实例启动和停止、备份和恢复状态、逻辑日志备份状态以及对主要配置参数的更改。消息日志还包含关键的错误(Informix 称之为断言失败),如磁盘 I/O 错误、镜像错误、当机块、数据完整性错误以及共享内存错误等等。在发生断言失败时,消息日志通常会将我们引向有关断言失败的(―af.xxx‖)文件,该文件会记录在数据库引擎当机时有关实例活动的更详细信息,还会就如何解决这一问题给我们提供一些建议。以下内容摘自消息日志: 00:57:53 00:57:53 Assert Failed: Unexpected virtual processor termination, pid =586, exit = 0x9 00:57:53 Who: Session(13709, omcadmin@nvlsys, 6538, 654709000) Thread(13740, sqlexec, 2704a558, 1) 00:57:53 Results: Fatal Internal Error requires system shutdown 00:57:53 Action: Restart OnLine 00:57:53 See Also: /var/tmp/af.35acfee1 00:57:53 Stack for thread: 13740 sqlexec 上面的输出告诉我们:某个 Informix 虚拟处理器终止了,并毁坏了数据库引擎。当用户―omcadmin‖登录到名为 nvlsys 的机器并执行了一些数据库操作(大部分是未正确执行的 SQL 查询),该机器上发生了这一错误。文件 /var/tmp/af.35acfeel 记录了出错时有关数据库引擎状态的详细统计信息。 块状态 块是物理存储设备。它们应该始终联机。如果有任何块当机了,那么这表明数据遭到毁坏,需要立即引起注意。 onstat -d 命令监控当前的块状态,以下是该命令的输出: Informix Dynamic Server 2000 Version 9.21.UC4 -- On-Line -- Up 7 days 23:35:56 -- 1654784 Kbytes Dbspaces address number flags fchunk nchunks flags owner name 6510c7d0 1 0x1 1 1 N informix rootdbs 65866468 2 0x1 2 4 N informix airgen_idx_dbs 658665b0 3 0x1 3 3 N informix spare 658666f8 4 0x1 4 5 N informix logs 65866840 5 0x1 5 2 N informix pm1 65866988 6 0x1 7 1 N informix pm_gen 65866ad0 7 0x2001 8 1 N T informix temp_dbspace2 65866c18 8 0x1 10 2 N informix pm2 65866d60 9 0x1 11 3 N informix airgen_main_dbs 65866ea8 10 0x1 14 1 N informix mso_meta 65867018 11 0x1 16 2 N informix pm3 65867160 12 0x2001 18 1 N T informix temp_dbspace3 658672a8 13 0x2001 20 1 N T informix temp_dbspace1 658673f0 14 0x1 25 2 N informix pm4 65867538 15 0x2001 29 1 N T informix temp_dbspace4 15 active, 2047 maximum Chunks address chk/dbs offset size free bpages flags pathname 6510c918 1 1 0 63069 51985 PO- /usr/informix/dblink 6514b5f0 2 2 65000 750000 1 PO- /usr/informix/dblink 6514b760 3 3 815000 60000 59747 PO- /usr/informix/dblink 6514b8d0 4 4 875000 125000 4947 PO- /usr/informix/dblink 6514ba40 5 5 0 1000000 299290 PO- /usr/informix/dblink1 6514bbb0 6 2 0 1000000 207877 PO- /usr/informix/dblink2 6514bd20 7 6 0 200000 179043 PO- /usr/informix/dblink3 6514be90 8 7 200000 250000 249939 PO- /usr/informix/dblink3 6510ca88 9 3 450000 250000 249997 PO- /usr/informix/dblink3 6510cbf8 10 8 0 1000000 299086 PO- /usr/informix/dblink4 6510cd68 11 9 0 1000000 4 PO- /usr/informix/dblink5 6513c830 12 9 0 500000 10 PO- /usr/informix/dblink6 6513c9a0 13 8 500000 300000 299997 PO- /usr/informix/dblink6 6513cb10 14 10 800000 200000 27596 PO- /usr/informix/dblink6 6513cc80 15 9 0 1000000 782331 PO- /usr/informix/dblink7 6513cdf0 16 11 0 1000000 296827 PO- /usr/informix/dblink8 65865018 17 4 0 400000 9997 PO- /usr/informix/dblink9 65865188 18 12 400000 250000 249947 PO- /usr/informix/dblink9 658652f8 19 5 0 300000 299997 PO- /usr/informix/dblink10 65865468 20 13 300000 250000 249947 PO- /usr/informix/dblink10 658655d8 21 4 550000 150000 14997 PO- /usr/informix/dblink10 65865748 22 4 0 350000 4997 PO- /usr/informix/dblink11 658658b8 23 11 350000 300000 299997 PO- /usr/informix/dblink11 65865a28 24 2 0 1000000 999997 PO- /usr/informix/dblink12 65865b98 25 14 0 1000000 299014 PO- /usr/informix/dblink13 65865d08 26 2 0 750000 749997 PO- /usr/informix/dblink14 65865e78 27 4 750000 250000 39997 PO- /usr/informix/dblink14 65866018 28 14 0 300000 299997 PO- /usr/informix/dblink15 65866188 29 15 300000 250000 249939 PO- /usr/informix/dblink15 658662f8 30 3 550000 50000 49997 PO- /usr/informix/dblink15 30 active, 2047 maximum 上面的输出包含两部分。第一部分列出了所有的 dbspace,第二部分则列出了所有的块。在块(Chunk)部分中,我们需要特别注意 flags 字段。该字段的第一个字符表明块是主(P)块还是镜像(M)块。第二个字符表明块的当前状态,是联机(O)还是脱机(D)。由于 O 和 D 看起来很相象,尤其是您匆匆一瞥时,因此您可能想将结果用管道输入到 grep PD ,即 onstat -d |grep PD ,以确保您不会遗漏任何当机块。如果有任何主块当机,那么您需要立即从备份磁带执行冷或暖恢复,以确保数据完整性。我们也可以查询 sysmaster 数据库中的 syschunks 和 sysdbspaces 表来获取类似的统计信息。 检查点 检查点是使磁盘上的页与共享内存缓冲池中的页同步的过程。在检查点期间,IDS 阻止用户线程进入临界会话,并阻止所有的事务活动。因此,如果检查点持续时间过长,那么用户可能会经历系统挂起。在存在几千个事务并且响应时间至关重要的 OLTP 环境中,情况尤其如此。正如上面所解释的那样,可以通过查看消息日志来监控检查点持续时间,但更好更快的方法是使用 onstat -m 命令。以下是该命令的样本输出: 15:25:10 Checkpoint Completed: duration was 0 seconds. 15:25:10 Checkpoint loguniq 231, logpos 0x1bb2018 15:35:30 Checkpoint Completed: duration was 19 seconds. 15:35:30 Checkpoint loguniq 231, logpos 0x31b9018 Fri Dec 20 11:48:02 2002 11:48:02 Checkpoint Completed: duration was 7 seconds. 11:48:02 Checkpoint loguniq 231, logpos 0x32e5018 14:27:37 Logical Log 231 Complete. 14:27:40 Process exited with return code 142: /bin/sh /bin/sh -c /usr/informix/etc/log_full.sh 2 23 "Logical Log 231 Complete." "Logical Log 231 Complete." 14:28:24 Checkpoint Completed: duration was 22 seconds. 14:28:24 Checkpoint loguniq 232, logpos 0x458018 14:38:46 Checkpoint Completed: duration was 7 seconds. 14:38:46 Checkpoint loguniq 232, logpos 0x10f5018 如果检查点持续时间始终超过 10 秒,那么您可能需要减少 LRU_MIN_DIRTY 和 LRU_MAX_DIRTY 配置参数的值以获取更短的检查点持续时间。同样,如果 onstat -F 的输出显示极高的块写(比如高于 10000),并且这个数字还在不断增加,那么这可能表明出现了以下两个问题中的一个:要么检查点时间间隔太短,从而在检查点之间清除程序没有足 够的时间将所有经过修改的缓冲区写入磁盘,要么 AIO VP 太少,无法在检查点期间共享繁重的磁盘写。这样,您需要重新检查 CKPINTVL、LRUS、CLEANERS 和 NUMAIOVPS 配置参数的设置,并相应地增加它们的值。我们可能还需要查看 onstat -F 的输出来作为确定那些参数值的参考。有关如何调优那些参数的详细信息,请参考 Administrator's Guide。 dbspace使用情况 Informix 数据库管理员要不断了解各个 dbspace 中的空间,这一点十分重要。如果某个 dbspace 缺少空间或把空间用完了,那么 IDS 会碰到麻烦。各种问题都可能出现:无法导入任何数据库,无法创建任何表和索引,甚至无法对任何表和索引执行插入和更新操作。这一点对于生产数据库至关重要。我们需要监控每个 dbspace 的增长,以便能够对这些问题采取更主动的方法。下面的脚本报告了各个 dbspace 的当前空间使用情况,并计算其百分比。 select name dbspace, sum(chksize) allocated, sum(nfree) free, round(((sum(chksize) - sum(nfree))/sum(chksize))*100) pcused from sysdbspaces d, syschunks c where d.dbsnum = c.dbsnum group by name order by name 输出如下所示: dbspace allocated free pcused airgen_idx_dbs 1000000 763405 24 airgen_main_dbs 1500000 295789 80 llog 1000000 9947 99 rootdbs 50000 36220 28 temp1 250000 249947 0 temp2 250000 249939 0 上面的输出有助于我们确定哪些 dbspace 已把空间用完了。要取得主动,请考虑在某个 dbspace 的磁盘使用情况接近 90% 时向该 dbspace 分配额外的磁盘空间;本例中,我们需要特别注意 llog dbspace,并且可能的话,就给它分配更多空间,以防止它把空间用完。 dbspace I/O Dbsapce I/O 是由磁盘读和磁盘写来衡量的。如果某些 dbspace 有繁重的磁盘读写操作,而另外一些 dbspace 几乎不进行任何读写操作,那么系统可能会出现一些磁盘 I/O 瓶颈。平衡得较好的 dbspace I/O 将减轻系统磁盘 I/O 负载,从而会改善系统的整体性能。以下脚本将显示各个 dbspace 的当前 I/O 统计信息: select d.name, fname[15,25] path_name, sum(pagesread) diskreads, sum(pageswritten) diskwrites from syschkio c, syschunks k, sysdbspaces d where d.dbsnum = k.dbsnum and k.chknum = c.chunknum group by 1, 2 order by 1 输出如下所示: name path_name diskreads diskwrites airgen_idx_dbs uild95/ltmp 3672 7964 airgen_main_dbs uild95/ltmp 13545 32903 llog uild95/ltmp 19 51633 rootdbs uild95/ltmp 211 43117 temp1 uild95/ltmp 3015 3122 temp2 uild95/ltmp 3218 3317 我们的目标是要使所有的 dbspace 都有平衡的磁盘读写操作。在大多数情况下,这是不现实的,但上面的输出至少让您对 dbspace I/O 的分配方式有了一个概念,可以帮助您标识―最热门的‖dbspace — 那些磁盘读写最多的 dbspace。如果有些 dbspace 的磁盘读写操作相当繁忙而另外一些的读写操作则相当空闲,那么您可能需要为 Informix 引擎调整甚至重新安 onstat -g ioq 获得类似的信息,前者显示各个排物理磁盘布局。我们可以使用 onstat -D 和 块的磁盘读和写,而后者显示磁盘 I/O 等待队列信息。 您可以通过查询 sysmaster 数据库中的 sysptprof 表来进一步标识哪些表具有最多的磁盘读写操作: select dbsname, tabname, (isreads + pagreads) diskreads, (iswrites + pagwrites) diskwrites from sysptprof order by 3 desc, 4 desc 输出类似于: dbsname tabname diskreads diskwrites airgen_10_0 fanout_param 84567 3094 airgen_cm_db sysindices 78381 0 airgen_10_0 ne_nmo_i 75819 5 airgen_10_0 ne_nmo 75440 297 airgen_cm_db sysprocbody 62610 28322 airgen_10_0 systables 37342 466 airgen_10_0 syscolumns 34539 4609 airgen_10_0 457_484 32838 42 airgen_10_0 453_480 30009 1 airgen_10_5_old syscolumns 29531 4550 airgen_10_5 syscolumns 28824 4552 airgen_10_0 456_483 25448 14 airgen_10_0 458_485 23278 177 airgen_10_5_old 452_483 22412 31 根据从这个查询获得的输出,您可能需要在 dbspace 间移动一些表以使磁盘 I/O 平衡得更好。 共享内存段 太多的虚拟共享内存段(通常多于三个)表明:最初的虚拟共享内存段太小,数据库引擎必 须不断分配额外的虚拟共享内存段。这反过来影响了 IDS 性能,并且最终会损害系统的性能。 onstat -g seg 命令显示了 Informix 数据库引擎目前拥有多少共享内存段: Informix Dynamic Server 2000 Version 9.21.UC4 -- On-Line -- Up 28 days 15:49:33 -- 205824 Kbytes Segment Summary: id key addr size ovhd class blkused blkfree 0 1381386241 a000000 177209344 220688 R 42984 280 1 1381386242 14900000 8388608 856 V 2048 0 2 1381386243 15100000 1048576 632 M 164 92 3 1381386244 15200000 8388608 856 V 2048 0 4 1381386245 15a00000 8388608 856 V 2008 40 5 1381386246 16200000 8388608 856 V 50 1998 Total: - - 211812352 - - 49302 2410 (* segment locked in memory) 如果输出显示虚拟共享内存段多于三个,那么您需要提高配置文件中 SHMVERSIZE 参数的值。其思想是,让 IDS 在初始化时分配足够的虚拟共享内存,以便在用户登录到系统并执行数据库操作时无需分配更多的虚拟共享内存。您可能还想使用 UNIX? ipcs 命令来查看 Informix 共享内存的大小。有关如何计算 IDS 虚拟共享内存段大小的详细信息操作系统的整体性能 由于 Informix 数据库引擎总是安装在某个操作系统(主要是 UNIX)上,以准确地监控或评估 IDS 性能,因此我们需要将操作系统的行为作为一个整体来考虑,在数据库引擎驻留在非专用数据库服务器上时尤其要这样考虑。如果 IDS 占用了太多 RAM(例如,如果系统有 512MB RAM,而 IDS 占用了 400MB 或更多作为其共享内存),那么当用户执行内存密集型操作时,操作系统可能会经历频繁的交换和挂起。当内存不足时,系统必须将一些数据页从内存交换到磁盘,以便为新数据腾出更多空间。如果系统内存不足,那么 CPU 可能也会遭殃。有不少 UNIX 实用程序可以监控操作系统 CPU 和内存的整体利用率。以下是来自―top‖实用程序的输出: load averages: 1.12, 1.02, 1.07 10:17:30 123 processes: 120 sleeping, 1 zombie, 2 on cpu CPU states: 70.5% idle, 26.5% user, 2.8% kernel, 0.3% iowait, 0.0% swap Memory: 3072M real, 76M free, 588M swap in use, 440M swap free PID USERNAME THR PRI NICE SIZE RES STATE TIME CPU COMMAND 28349 omcadmin 4 0 0 86M 55M cpu10 970:25 6.85% CS_App.prt 17782 informix 5 30 -10 1631M 1594M sleep 50.0H 4.66% oninit.exe 17784 informix 5 59 -10 1631M 1594M sleep 102.9H 4.12% oninit.exe 17786 informix 5 59 -10 1631M 1591M sleep 25.5H 2.53% oninit.exe 571 root 1 58 0 361M 129M sleep 19.0H 1.36% em_mis 17785 informix 5 59 -10 1631M 1592M sleep 57.8H 1.05% oninit.exe 5470 omcadmin 1 0 0 1960K 1408K cpu15 0:00 0.26% top 上面的输出包含两部分。第一部分为您汇总了操作系统的 CPU 和内存的整体使用情况,第 二部分则提供了有关每个处理器的详细信息。其它实用程序(如 vmstat、iostat、ps -ef 和 sar )在收集操作系统当前的性能统计信息方面也很有用。 vmstat 显示目前操作系统交换了多少内存; iostat 和 sar 显示了当前在所有物理磁盘中磁盘 I/O 的分配;而 ps -ef 打印出当前各个处理器的登录时间、CPU 及内存使用情况的详细信息。此外也有许多图形工具可用,这些工具使您能够绘制操作系统资源利用率和性能的动态变化。 监控数据库活动 对数据库活动进行监控的目的在于确保每个数据库时刻都将其能力发挥到了极致。这意味着:您必须留意潜在的性能问题,确定其根源并将其消灭在萌芽状态。以下是要留意的几个方面。 表扩展块 扩展块是一块物理上连续的页。然而,如果一个表有多个扩展块,那就不能保证这些扩展块是连续的;扩展块可能会散布在表所驻留的整个 dbspace 上。物理页的连续性对于性能十分重要。当数据页连续时,访问磁盘数据所用的时间就最短,而数据库也能连续地读取行。如果表有太多扩展块,那么那些扩展块极有可能相互交错。这极大地影响了性能,因为当您检索某个表的数据时,磁头需要对属于该表的多个非连续扩展块进行寻道,而不是对具有连续物理页的一个大扩展块进行寻道。这会显著地降低磁盘寻道速度。下面的脚本检测具有多个扩展块的数据库表: select t.tabname, count(*) num_ext from sysmaster:sysextents e, sysmaster:systables t where e.tabname=t.tabname and dbsname = "airgen" and t.tabname not like "sys%" group by 1 having count(*) > 1 order by 2 desc 输出如下所示: tabname num_ext nmoattrclassmap 14 attrclass 11 networkmoclass 3 fanout_param 3 fanout_comp 2 ne_nmo 2 nenmoclassmap 2 join_map 2 如果除了大型分段表以外,任何表的扩展块超过了 10 个,那么您应该考虑重新构建这些表以合并扩展块。对于较大的数据库或者大小设置不是很好的数据库,我们可能还会关注扩展块的最大数目,或者会担心针对索引的 32GB 限制。有关如何对表估计和分配数据块大小的详细信息, 索引层 索引的层数也可能会对性能产生不利影响。索引层越多,IDS 到达索引叶节点所需的探测也就越多。而且,如果叶节点被拆分或合并,那么整个索引对这一变化进行调整将要花费更多的时间。例如,如果索引只有两层,那么只需要调整两层,但如果索引有四层,那么相应地就需要对所有四层进行调整。用于这一调整的时间当然也就长得多。在 OLTP 环境中会进行频繁的插入、删除和更新,这些操作会导致不断地对索引进行拆分和合并,因此上述问题也就格外明显。下面的脚本标识每个索引的层数: select idxname, levels from sysindexes order by 2 desc 输出如下所示: idxname levels objdesc 3 fanout_param_i 3 458_485 3 457_484 3 idxname 2 tabgtor 2 tabgtee 2 如果哪个索引超过了 4 层,您可能就需要考虑删除和重新构建该索引,从而合并其层,以获取更好的性能。 索引唯一性 索引的重复程度很高会严重地影响更新和删除的性能。假定表 customer 的 customer_type 列上有一个索引,而可能的 customer_type 代码只有五种。如果这个表有一百万行,那么可能有 200000 行具有相同的 customer_type 代码。B-树存储键值,其后跟一个指向每个物理行的指针列表。在必须删除或更新任何键值时,问题出现了。IDS 必须找遍所有的重复内容,直到找到要删除或更新的正确键为止~ 下面的脚本用来标识重复程度很高的索引: select tabname, idxname, nrows, nunique from systables t, sysindexes I where t.tabid =i.tabid and t.tabid > 99 and nrows > 0 and nunique > 0 输出如下所示: tabname idxname nrows nunique bsc_dte bscdte_i 6 6 omcgttready 231_413 1 1 systemrelease 451_478 3 3 neclass 452_479 31 12 sysrelneclassmap 453_480 33 3 proxynemgrmap 454_481 1 1 networkmoclass 455_482 362 199 nenmoclassmap 456_483 492 12 attrclass 457_484 1191 924 nmoattrclassmap 458_485 2901 199 fanout_comp fanout_comp_i 915 199 fanout_comp fanout_comp_i2 915 199 fanout_comp fanout_comp_i3 915 82 fanout_param fanout_param_i 2894 196 在理想情况下, nunique列中所出现的全部值都应该与 nrow列中的全部值相等,即索引中的每个键都是唯一的。根据行数(上面的 nrows列)和唯一键数(上面的 nunique列),我们可以计算每个索引唯一性的百分率: ( nunique/nrows)*100 百分率越高,索引的唯一性就越高。为了避免因索引重复程度很高而引起的性能瓶颈,您可以使用复合索引来替换原来的索引,复合索引结合了重复程度很高的列与唯一性比较高的列。利用上面的示例,您可以将主键列 customer_id 添加到原来的索引,将它变成一个复合列(例如,―create index index_name on customer ( customer_type , customer_id )‖)。 顺序扫描 对表进行顺序存取有时会降低性能,因为数据库引擎必须扫描整个表以选取满足查询条件的行。如果表很小,比如说几百行,那么顺序存取不会对性能造成什么影响;因为当数据库引擎第一次扫描它时,该表会驻留于内存中,而当数据库引擎下一次扫描它时,可以直接从内存检索该表中的所有数据。这实际上是使用顺序扫描的有效方式。但如果表很大,比如说超过了 100000 行,那么重复的顺序扫描会对性能造成致命的影响。下面的脚本将标识具有多重顺序扫描的表: select dbsname, tabname, sum(seqscans) tot_scans from sysptprof where seqscans > 0 and dbsname not like "sys%" group by 1,2 order by 3 desc 输出如下所示: dbsname tabname tot_scans airgen_10_0 systemrelease 2352 airgen_10_5_old systemrelease 1596 airgen_10_5 systemrelease 1596 airgen_10_0 fanout_comp 1587 airgen_10_5_old sysusers 1248 airgen_10_0 sysusers 1241 airgen_10_5 sysusers 1231 airgen_10_0 join_map 1036 airgen_10_0 fanout_param 958 airgen_10_0 func_call 770 airgen_10_5 nenmoclassmap 586 airgen_10_5_old nenmoclassmap 586 从上面的输出可以看出 airgen_10_0 表的顺序扫描数很高。如果它是一个具有几千甚至几百万行的大表,那么您可能需要考虑向该表添加一些索引,或者考虑使用程序伪指令来强制内部查询优化器为访问该表中的数据选择索引而不是顺序扫描。 监控会话活动 有关会话活动的统计信息在确定潜在的性能问题及故障诊断方面很有用。使用本文前面讨论的监控工具,我们可以收集哪些会话活动统计信息呢, 常规会话统计信息 sysmaster 数据库中的 syssessions 表存储各个会话的常规信息,如登录名、登录时间、会话所登录的主机机器、操作系统的进程标识和当前状态等等。可以使用以下查询来查询该表来获取全部此类信息: select sid, username, hostname, connected logint_time, hex(state) s_state from syssessions 输出类似于: sid username hostname logint_time s_state 233989 omcadmin localhost 1041348844 0x00080021 233982 omcadmin gcsys-e1 1041348608 0x00080001 233981 omcadmin nysys4-e1 1041348608 0x00080001 233980 omcadmin nysys5-e1 1041348608 0x00080001 233979 omcadmin ffsys-e1 1041348608 0x00080001 233973 omcadmin nysys1 1041348608 0x00080001 233781 wsadmin2 gcmmi 1041346036 0x00080001 233697 omcadmin localhost 1041344008 0x00080001 233694 wsadmin4 nymmi1 1041343932 0x00080001 233693 wsadmin4 nymmi1 1041343932 0x00080001 230550 omcadmin nysys1 1041293396 0x00080001 230476 omcadmin ffsys-e1 1041292665 0x00080001 230421 omcadmin gcsys-e1 1041292365 0x00080001 230278 omcadmin nysys5-e1 1041291208 0x00080001 s_tate 是一个指出会话的当前活动状态的十六进制数,以获取对这其中每个活动状态的详细描述。 login_time 是一个指出会话登录时间的整数,使用 C 程序很容易将它转换成常规时间格式。可以向 Informix 技术支持请求以获取该 C 程序。 sysmaster 数据库中的 syssesprof 表提供了各会话的更多详细信息。使用以下查询,您可以更好地理解各个会话是如何与数据库交互的: select sid, (isreads+bufreads+bufwrites+pagreads+pagwrites) access, locksheld, seqscans, total_sorts, dsksorts from syssesprof 输出如下所示: Sid access locksheld seqscans tot_sorts disksorts 233982 246 0 2 0 0 230421 7789 12 456 1000 0 225679 9981 213 669 876 2 247869 10098 440 578 98 2 78 70 45 6 0 0 447 46 89 6 0 0 access 字段显示了会话命中数据库的次数。 locksheld 显示各会话正使用多少锁。 seqscans 表明各会话使用顺序扫描访问数据的次数;如果该数字太高,比如说高于 100000,那么您可能要质疑会话是否曾经使用过索引来检索数据,而且要更仔细地检查其查询执行计划以确定它是否最佳。 total_sorts 和 dsksorts 表明各会话使用内存进行排序操作的效率。您可能会使用以下公式来计算各会话使用内存进行排序的百分数: (( total_sorts - dsksorts)/ total_sorts)*100 该百分数越高,排序操作效率也就越高。您可以将 syssessions 表与 syssesprof 表连接以进一步确定每个会话的用户名和主机机器名,以便了解哪些地方可能会出现数据库和系统问题。以下查询可以检索所有这类信息: select username, hostname, (isreads+bufreads+bufwrites+pagreads+pagwrites) access, locksheld, seqscans, total_sorts, dsksorts from syssessions s, syssesprof f where s.id =f.sid 您也可以将 syssessions 表与 syslocks 表连接以获取关于锁的更多详细信息,如哪个会话当前锁定了哪个数据库中的哪个表,以此来帮助您确定各用户之间潜在的锁冲突: select owner, username, hostname, dbsname, tabname, type from syssessions s, syslocks l where sid = owner and tabname not like "sys%" 输出类似于: Owner username hostname dbsname tabname type 1422 wsine apple prod customer S 1567 jlinder sys3524 dev products S 2237 ejhonson case prod orders X 6679 cjz020 sys4800 dev shipment S 889654 jfjianing omega test prices X 77622 hong build50 test items S 如果在锁使用方面存在某些冲突,例如某个用户需要对已被别的用户锁定的表进行专有访问,那么您可以方便地确定该锁的所有者,并根据用户的优先级发出 onmode -z sid 命令来杀死会话,然后释放该锁; sid 这个编号是从上面输出中的 owner 字段中获取的;请注意,只有用户―Informix‖可以执行该命令。 查询统计信息 查询统计信息对于故障诊断和查询优化至关重要。 onstat -g sql sid 命令捕获当前会话的查询及相关统计信息;其中 sid 是会话标识,它可以通过硬编码手工插入,也可以通过 UNIX shell 脚本动态插入。例如,如果您想知道某个会话正在执行什么查询,那么您可能要首先使用命令 onstat -g ses 来查明其会话标识,然后将该标识插入上面的命令。例如,如果您想监控的会话标识为 28953,那么您可以使用 onstat -g sql 28953 来捕获其当前查询。 输出如下所示: Informix Dynamic Server 2000 Version 9.21.UC4 -- On-Line -- Up 38 days 11:26:22 -- 1654784 Kbytes session #RSAM total used id user tty pid hostname threads memory memory 134709 omcadmin 3 29580 localhos 1 65536 61120 tid name rstcb flags curstk status 147311 sqlexec 6511e728 Y--P--- 1648 6511e728 cond wait(netnorm) Memory pools count 1 name class addr totalsize freesize #allocfrag #freefrag 134709 V 669b9020 65536 4416 162 6 name free used name free used overhead 0 1648 scb 0 96 opentable 0 6000 filetable 0 920 log 0 2152 temprec 0 1608 keys 0 192 ralloc 0 20480 gentcb 0 1256 ostcb 0 2520 sort 0 56 sqscb 0 11960 sql 0 40 rdahead 0 640 hashfiletab 0 280 osenv 0 1584 buft_buffer 0 4272 sqtcb 0 3240 fragman 0 2176 Sess SQL Current Iso Lock SQL ISAM F.E. Id Stmt type Database Lvl Mode ERR ERR Vers 134709 SELECT mso_db CR Not Wait 0 0 9.03 Current SQL statement : SELECT ne_type, config_set_version FROM ne WHERE ne_inst = 46176 AND msospace_id = 1 Last parsed SQL statement : SELECT ne_type, config_set_version FROM ne WHERE ne_inst = 46176 AND msospace_id = 1 输出的第一部分给出了关于正在执行的查询的一些常规统计信息,如对哪个数据库执行查询、其隔离级别以及锁方式。最有趣的两个字段是 SQL error 和 ISAM error 。如果这些字段非零,就表明查询出了一些问题,没有正确地执行。可以使用 Informix finderr 实用程序来查明到底是什么问题,随后在解决了该问题之后,杀死该会话并重新执行该查询。 ―Current SQL statement‖部分显示了正在执行的查询的完整 SQL 语法。它对于诊断问题查询和查询优化非常有帮助。如果您发现了该查询的一些问题,如响应时间太长,消耗了太多的系统 CPU 或内存,那么您可以按其显示状态为该查询制作一个副本,以便稍后研究和分析之用。您随后可以对 Informix dbaccess 实用程序运行同一个查询,以获取该查询性能方面更多详细的统计信息(如其执行计划和连接策略),从而确定问题的根源。根据研究所收集的统计信息,您可以进一步优化查询以获取更好的性能。―Last parsed SQL statement‖部分显示了在内存中已经过解析的查询。由于在内存中已经解析过的查询在大多数情况下都是正在执行的查询,因此这一部分与前一部分在大多数时候都是相同的。 调优 Informix SQL 调优标准 调优标准基本上是依据个人的业务需求而定的。一般,主要涉及系统和数据库的性能。那么您又该如何配置系统和数据库性能呢,有多方面的考虑,但是最关键的就是 响应时间和 资源利用率。响应时间指的是用户等待他们的请求(即 SQL 查询)得到完成的时间。资源利用率指的是执行 SQL 查询时,CPU、内存和磁盘等系统资源的使用。 通常,如果完成调优过程之后,调优之后的 SQL 查询的响应时间变短了,而系统资源的利用率降低了,那么您就可以充满信心地推断:您已经调优该 SQL 查询,并获得了更高的效率。如何测量 SQL 查询的响应时间和资源利用率呢,Unix 和 Informix 提供了一些工具和实用程序,帮助我们科学、定量地测量响应时间和资源利用率 调优工具 Unix工具 Time和timex 您可以使用 time 实用程序来报告一些系统操作的响应时间,例如文件传送、数据库的查询执行以及其他活动。下面的实例展示了如何使用 time 实用程序来测量一个简单数据库查询 的响应时间: sys3523:omcadmin > time dbaccess airgen_cm_db sel.sql Database selected. (count(*)) 5958 1 row(s) retrieved. Database closed. real 0m0.09s user 0m0.01s sys 0m0.06s 上面输出的最后一部分给出了详细的操作时间统计数据,该操作在本例中就是数据库查询: , ―real‖字段告诉您从查询开始到结束时所经过的时间。 , ―user‖字段告诉您用户处理器为该操作而占用的 CPU 时间总和。 , ―sys‖字段告诉您整个系统占用的 CPU 时间总和。 我们最感兴趣的是―real‖字段;该字段表示操作的响应时间。让我们仔细查看一下上面的这个例子。通过输出,我们可以确定该查询的响应时间是 0.09 秒。 Timex 只是 time 实用程序的另一个变种,它将以更易读的格式显示时间。下面是同一查询 的 timex 输出: sys3523:omcadmin > timex dbaccess airgen_cm_db sel.sql Database selected. (count(*)) 5958 1 row(s) retrieved. Database closed. real 0.09 user 0.02 sys 0.04 Vmstat vmstat 实用程序深入挖掘系统并报告系统资源使用的统计数据,主要涉及运行的处理器、虚拟内存、磁盘 I/O、中断和 CPU。它还显示一行系统重启或启动以后的虚拟内存活动的摘要信息。下面是一个输出示例: sys3523:omcadmin > vmstat 1 10 procs memory page disk faults cpu r b w swap free re mf pi po fr de sr m0 m1 m4 m5 in sy cs us sy id 0 0 0 1959208 1294824 141 824 1 1 1 0 0 0 0 0 0 906 946 700 2 3 95 0 0 0 1995568 1260288 0 46 0 0 0 0 0 0 0 0 0 834 386 213 0 0 100 0 0 0 1995568 1260288 0 40 0 0 0 0 0 0 0 0 0 884 265 199 0 1 99 0 0 0 1995568 1260288 0 40 0 0 0 0 0 0 0 0 0 834 325 186 0 0 100 0 0 0 1995568 1260288 43 286 0 0 0 0 0 0 0 0 0 869 1682 242 0 1 99 0 0 0 1995352 1260048 658 3503 0 0 0 0 0 0 0 0 0 827 21930 375 3 14 83 0 0 0 1995408 1260240 662 3495 0 0 0 0 0 0 0 0 0 825 22010 387 4 13 83 0 0 0 1995568 1260288 121 691 0 0 0 0 0 0 0 0 0 834 4310 261 1 3 96 0 0 0 1995568 1260288 0 40 0 0 0 0 0 0 0 0 0 824 250 188 0 0 100 0 0 0 1995568 1260288 0 40 0 0 0 0 0 0 0 0 0 824 365 214 0 0 100 vmstat 实用程序接收两个整数参数:time interval 和 count。Time interval 指定 vmstat 刷新的间隔,而 count 指定它将刷新的最大次数。如果未指定任何参数,vmstat 就只会列出系统的前一次统计数据,而且不会进行刷新。本例中,它所提供的统计数据是不准确的,也不是最新的。如果指定了 interval,vmstat 将立即反复按照最新的间隔 总结 初级经济法重点总结下载党员个人总结TXt高中句型全总结.doc高中句型全总结.doc理论力学知识点总结pdf 系统活动的概要信息。如果给定了 count,vmstat 就会重复获取系统的统计数据,直到达到了所指定的 count 次数。 输出中最让我们感兴趣的是―r‖、―po‖和―id‖字段。 , ―r‖字段告诉我们系统就绪队列中有多少任务在等待要执行的资源。 , ―po‖字段告诉我们当前内存中有多少页面被换出。如果该数字极其庞大,并持续增 长,这通常表示物理存储器或 RAM 不足,您可能需要安装更多存储器。 , ―id‖字段告诉我们当前使用了多少系统 CPU 资源。 这些字段一起将让您较好地了解当前系统资源的使用情况。 Informix工具 Informix 提供的用来收集详细 SQL 查询计划和执行统计数据的最全面的工具是 SET EXPLAIN 实用程序。该实用程序将生成一个名为 sqexplain.out 的文件,并详细记录查询的每个执行步骤。此外,它还提供估算的查询成本,并预计查询结果。通过检查 SET EXPLAIN 输出文件,您可以确定是否可以采取措施来提高该查询的性能。以下示例展示了一个极其复杂查询的 set explain 输出: QUERY: ------ SELECT --+AVOID_FULL(omchn)+AVOID_FULL(daphn) omchn.omc_hn_uanc, nvl(daphn.gtt_version,"0000000000000000000"), nvl(idachn.egt4_version,"0000000000000000000"), nvl(ihlrhn.hlr_timestamp,"00000000000000"), vsgw_hn.hn_igw_uanc, nvl(vsgw_hn.hn_igw_version, "00000000000000") FROM omchn, daphn, idachn, ihlrhn, vsgw_hn WHERE daphn.dap_hn_inst = omchn.omc_hn_inst AND idachn.idac_hn_inst = omchn.omc_hn_inst AND ihlrhn.hlr_hn_inst = omchn.omc_hn_inst AND vsgw_hn.vsgw_hn_inst = omchn.omc_hn_inst DIRECTIVES FOLLOWED: AVOID_FULL ( omchn ) AVOID_FULL ( daphn ) DIRECTIVES NOT FOLLOWED: Estimated Cost: 8 Estimated # of Rows Returned: 1 1) root.idachn: SEQUENTIAL SCAN 2) root.daphn: INDEX PATH (1) Index Keys: dap_hn_inst (Serial, fragments: ALL) Lower Index Filter: root.daphn.dap_hn_inst = root.idachn.idac_hn_inst NESTED LOOP JOIN 3) root.vsgw_hn: SEQUENTIAL SCAN NESTED LOOP JOIN 4) root.omchn: INDEX PATH Filters: root.vsgw_hn.vsgw_hn_inst = root.omchn.omc_hn_inst (1) Index Keys: omc_hn_inst (Serial, fragments: ALL) Lower Index Filter: root.idachn.idac_hn_inst = oot.omchn.omc_hn_inst NESTED LOOP JOIN--+ 5) root.ihlrhn: INDEX PATH (1) Index Keys: hlr_hn_inst (Serial, fragments: ALL) Lower Index Filter: root.ihlrhn.hlr_hn_inst = root.omchn.omc_hn_inst NESTED LOOP JOIN 可将以上输出分成三个部分: , 第一部分展示了查询语法。 , 第二部分展示了该查询的估算成本。 , 第三部分详细地解释了所执行查询的每一步。 我们最感兴趣的是第二和第三部分。估算成本是优化器用来比较查询计划的成本单元。这些单元不直接转换成时间;它们表示的是典型磁盘访问的相对时间。 优化器将选择该查询计划,因为其执行的估算成本是所有被估计划中最低的。与具有较低估算成本的运行相比,具有较高估算成本的查询的运行时间通常更长一些。第三部分对于查询调优是至关重要的,因为它提供了大量有用的信息,例如查询所用的数据访问方法和连接方法。以上示例展示了如何使用连续扫描和索引来进行数据检索,以及如何使用嵌套循环连接方法来连接所有表。稍后我将在本文中详细讨论这一点。 该实用程序易于使用。如果需要知道某个 SQL 查询的详细查询执行计划,只需在最初的查询之前添加如下所示 SET EXPLAIN ON 语句即可: set explain on; select count(*) from acg; 然后,Informix 服务器将在用户 home 目录中生成一个名为 sqexplain.out 的文件,正如上面谈到的,其中将记录详细的查询执行计划及其成本。该文件是累积的,换言之,如果 SET EXPLAIN ON 语句之后有多个 SQL 查询,每个查询的执行计划及其成本都会被追加到文件中,直到将之删除。同样,对于存储过程,您需要对原始的存储过程执行 UPDATE STATISTICS,以获取详细的执行计划,因为在更新统计数据时,存储过程只可以更新它们的查询执行计划。例如,如果您需要查看存储过程 dap_int 的详细执行计划,就需要进行下列操作: set explain on; update statistics for stored procedure dap_int(); 从版本 9.3 开始,该实用程序得到了较好的提高;您可以获取详细的查询执行计划,而不必执行该查询。这使您可以在真正的生产环境中获取查询执行计划。要使用这项新功能,需要在 SET EXPLAIN ON 语句中使用关键字 AVOID_EXECUTE,如下所示: set explain on avoid_execute; select count(*) from act; 通用方法 那么我们如何在 SQL 查询的调优中应用那些工具呢,不同的人可能有不同的方法,但是,一般应按照下列方法和步骤进行: , 收集关于原始SQL查询的统计数据。在这一步中,需要使用上面讨论的工具来获 取该查询的统计数据:其响应时间、详细的执行计划和成本,以便稍后进行深入分 析。 , 分析统计数据。在这一步中,需要挖掘上面所收集的统计数据,并仔细查看查询执 行计划。如上所述,性能是查询调优中主要关心的问题。在检查查询计划时,需要 考虑影响性能的所有因素:访问方法、连接方法、子查询、表和索引区段、表和索 引分段等。稍后我将在本文的第 2 部分中逐个详细地讨论这些因素。 , 设置测试环境。这是一个极其重要的步骤。就硬件和软件配置而言,应将测试环境 设置为与运行查询的生产系统完全相同或极其相似。例如,如果生产机器有6个 400HM的CPU,那么测试机器也应该有6个400HM 的CPU;否则,随后的测试 将是无效且不可靠的。请记住,所有那些查询最终都将在生产中运行。 , 修改并测试新的查询。这是调优中的一个主要步骤,同时也是最冗长乏味的一个步 骤。一次修改原始查询的一个地方,并进行测试,以查看是否提高了性能(减少了 响应时间)。记录测试细节,例如您所做的修改、响应时间和执行计划。进行了修 改之后,如果查询性能没有优于您原始查询的,就撤销修改。测试必须是有效且可 靠的;换言之,测试必须是可重复的。例如,如果对同一查询进行两次相同的测试, 第一次输出了一个极佳的响应时间(比如说 10 秒),但是第二次,响应时间就增 加到 30 秒,那么您的测试就是不可重复的,因为响应时间的差异过大。您需要重 新检查测试过程,并识别那两次测试之间的差异。如果测试是可重复的,那么测试 结果之间的差异应该是极小的。 , 分析测试结果。在分析测试结果时,我们需要检查测试结果的有效性和可靠性。我 们需要检查硬件、软件、工作负载以及其他所有因素,以确保测试结果是有效且可 靠的。 , 在生产系统中实现改进。在实现之前,您需要进行最新的详细检查,并确保新的查 询在生产中不会导致任何问题 优化器及其角色 与Oracle和SQL Server 等其他关系数据库管理系统一样,Informix 也有内部优化器,负责选择最佳的查询执行计划。分析了 SQL 查询之后,优化器将通过分析磁盘 I/O 和 CPU 成本等因素,考虑所有执行查询的可能方法。然后,它会用由下至上、宽度优先(bottom up, breadth-first)的搜索策略,同时构造所有可行的计划。换言之,优化器首先将构造所有可能的连接计划,然后删除所有花费较高的冗余对(redundant pair),这些冗余对是包含相同表以及产生与另一连接对相同的行集的连接对。如果某查询使用了附加的表,那么优化器就将每个剩余对连接一个新表,以形成所有可能的连接三元组,排除代价更为昂贵的冗余三元组,如此一来就连接了每个附加的表。当生成了非冗余的可能连接组合集时,优化器就选择看上去具有最低执行成本的计划。例如,优化器必须确定是否应使用索引。如果该查询包含了连接,那么优化器就必须确定连接计划(hash、sort merge 或 nested loop),以及评估或连接表的次序。 优化器将根据在每个表中检索的行数来评估查询成本。被评估的行数则基于 WHERE 子句里所使用的每个条件表达式的选择性。优化器使用 UPDATE STATISTICS 所生成的数据分布信息来计算查询中筛选器的选择性。然而在缺乏数据分布信息的情况下,优化器将基于表索引计算不同类型筛选器的选择性。例如,如果索引列包含字面值和 NULL 值,那么其选择性就等于索引中不同的键的数目。关于在缺乏数据分布的情况下,优化器用以计算选 10 章。但是,使用该方式计算的选择性不择性的详细表,请参阅 Performance Guide 的第 如使用数据分布计算的选择性准确。 因此,选择性估算的准确性显然基于您运行 UPDATE STATISTICS 的频率。如果频繁地运行 UPDATE STATISTICS,优化器就会更准确地计算出选择性,因为每次运行 UPDATE STATISTICS 时,都会更新数据分布,除了运行带有 low 选项的 UPDATE STATISTIC 之外。 当优化器创建查询计划时,它将使用下列系统目录信息: , 从最新的 UPDATE STATISTICS 语句起,表中的行数。 , 将哪一列限定为惟一的。 , 当请求 UPDATE STATISTICS 语句中带有 MEDIUM 或 HIGH 关键字时,列值 的分布情况。 , 包含行数据的磁盘页的数目。 , 存在于表上的索引,包括它们索引的列,哪些是升序或降序的,以及哪些是群集的。 , 索引结构的深度(衡量执行索引查找所需的工作量)。 , 索引项占用的磁盘页数目。 , 索引中的惟一项数目,可用于估算等式筛选器所返回的行数。 , 索引列中第二大和第二小的键值。 优化器的行为受三个关键因素的影响:Informix 配置文件中 OPTCOMPIND 参数的值、查询命令和内部统计数据的准确性。 OPTCOMPIND 参数 OPTCOMPIND 是一个环境变量,或者是 Informix 配置文件中的一个参数。优化器用其值来选择数据访问方法。其值是 0、1 和 2 这三个中的一个,分别表示下列意思: , 如果将其值设置为 0,那么当存在合适的索引时,优化器就选择索引扫描而非表扫 描,甚至不考虑估算成本。 , 如果将其值设置为 1,那么当事务隔离模式不是可重复读(Repeatable Read)时, 优化器的行为就与值为 0 时一样。如果事务隔离模式是可重复读(Repeatable Read),那么优化器会将其选择完全基于估算成本。 , 如果将其值设置为 2,优化器就会使用估算成本来确定执行计划,而不管事务隔离 模式如何。 您可以将 OPTCOMPIND 设置为环境变量,也可以设置为配置文件中的参数,但是,将之设置为参数将优先执行。 查询命令 影响优化器的另一种方式就是使用查询命令。查询命令是 SQL 查询中的提示,指示优化器如何执行查询。一共有 4 种类型的查询命令,如下所示: , 访问计划指示,强制优化器使用指定的访问方法进行数据检索,要么是连续扫描, 要么是索引扫描。 , 连接次序指示,强制优化器按照指定次序连接表。 , 连接计划指示,强制优化器使用指定的连接方法来连接查询中的表,要么是嵌套循 环连接、分类合并连接,要么是动态哈希连接。 , 目标指示,强制优化器使用指定的规则来返回查询结果。 内部统计数据 通过内部统计数据,我指的是系统目录中的统计数据,优化器可以确定最低成本的查询执行计划。为了确保优化器选择最佳的查询计划,使内部统计数据保持为最新并且最准确的十分重要。数据库服务器将初始化表、索引、存储过程和触发器等数据库对象的统计配置文件,并在创建数据库表时,将数据分布置于系统目录中,但不自动更新那些统计数据。 要使统计配置文件保持最新,您需要定期运行 UPDATE STATISTICS;否则,您系统的统计配置文件可能无法反映您系统的当前状态,而优化器则可能无法在众多查询执行计划中做出正确选择。UPADTE STATISTICS 有三种运行模式,一般每次执行修改了大量表数据的大量批作业之后,以及每次向表添加索引之后,您都需要运行 UPDATE STATISTICS。关于如何执行 UPDATE STATISTICS 的详细信息,请参阅 IBM Informix Dynamic Server Administrator's Guide, Version 9.4。其经验法则就是:越频繁地运行 UPDATE STATISTICS,系统的统计配置文件将越新、越准确,而优化器则越可能选择最佳的查询执行计划。 虽然如上面所谈论的,优化器的行为受 OPTCOMPIND 和查询命令的影响,但优化器通常是基于下列指导方针来选择查询计划: , 如果查询从一个表检索大量数据,优化器就不会使用索引。例如,如果您公司的客 户非常均匀地分布在所有 50 个州中,而您需要检索除了纽约之外的每个州的客户 信息,那么您需要执行下列查询: SELECT * FROM customer WHERE STATE <> "NEW YORK"; 优化器将立刻检测您可能要检索的表中的 98% 的数据,它认为连续读或扫描表要 比遍历索引(以及随后的数据页)更加高效,然后它将检索相关数据。 , 如果表上定义了多个索引,那么优化器就使用可以排除表中最多数据的索引。例如, 如果您公司拥有 200,000 位纽约的客户,而只有约 1000 位客户在同一天发出了 订单,比如 1997 年 1 月 20 日,那么您可以使用下列查询来获取他们的姓名和 地址: SELECT name, address FROM customer WHERE state = "NEW YORK" AND order_date = "01/20/97" 优化器极可能会选择使用 order_date 上的索引,而不是选择使用 state 上的索引。 , 如果查询中没有指示,那么优化器通常会首先从带有最多限制性筛选器的表中检索 数据。让我们查看下列查询: SELECT * FROM customer, orders WHERE customer.customer_num = orders.customer_num AND customer.state = "NEVADA"; NEVADA 的这个条件,因为本例中,优化器所做的第一件事就是评估 state 等于 这将排除表中的大量数据行。然后,它将连接两个表。其思想就是尽可能多地减少 数据库服务器的工作负载。如果优化器首先连接两个表,那么连接结果可能十分庞 大,并可能使用大量系统资源,如 CPU 和内存。如果您拥有 1,000,000 位活动客 户,平均每人每月发出一份订单,那么连接结果将至少返回 1,000,000 条记录,这 肯定会损害您的系统性能。 , 如果连接列上不存在索引,那么优化器将选择动态哈希连接。在前一个实例中,如 果 customer.customer_num 和 orders.customer_num 都没有被索引,那么优化器将 选择动态哈希连接来作为最佳的执行计划。 , 如果满足下列条件,优化器将选择嵌套循环连接: , 在数据库服务器使用所有表筛选器之后,从外部表检索出的行数将很少,而内 部表具有一个可用于执行连接的索引。 , 可以用最外面的表上的索引,以 ORDER BY 子句的次序返回行,从而消除排 序的需要 访问方法 访问方法是指优化器从数据库表中读取(更确切地说是检索)数据的方法。基本上有两 种方法。 , 最简单的方法是连续读取表中的数据,就是按照我们通常调用它的方法来执行表扫 描。当无论如何都必须读取表中大多数数据时,或者当表没有索引时,优化器就会 选择执行表扫描。 , 另一种方法就是使用索引。如果一些列上有索引,那么优化器也许能使用键索引扫 描(key-only index scan)。 优化器将比较每种方法的成本,确定使用最好的一种。它将评估查询执行成本的方方面 面,例如所需的磁盘 I/O 操作数目、将被检索的行数、排序成本等。如果表中的数据进行 了群集化处理,或者是有物理次序的,那么表扫描的成本就可能相当低。当请求磁盘页上的 第一行时,就将该磁盘页读入内存中的一个缓冲页(buffer page)。下次请求该页上的数据时,就不需要再从磁盘进行读取该页。对该页上的后续行的请求都可以在这个缓冲区中得到满足,直到将该页上的所有行都处理完为止。当已经用完一个页面时,就必须读入下一行集的页。 为了加速这个过程,当第一个页面仍然在内存中时,Informix 就可以将下一数据页读入内存。为了有效利用这项功能,必须在配置文件中正确设置两个参数:RA_PAGES 和 RA_THRESHOLD。关于如何设置这些参数的细节,请参阅 IBM Informix Dynamic Server Administrator's Guide, Version 9.4。如果您数据库中的表只占用了少数几个区段,那么连续行的磁盘页将被放置在磁盘中的连续位置上,在读取数据页时,磁盘访问臂(access arm)就不必移动太多。此外,延时成本也会十分低。 对于小型表(有 1,000 行或更少行)来说,表扫描的成本不会很昂贵,尤其在 SQL 或应用程序中重复使用某个表的时候,这样,该表会一直保存在内存中。实际上,对内存中的表进行表扫描要比通过索引搜索同一表快得多,尤其是在使用 Informix 轻型扫描机制时。但是如果某个表有 100,000 或更多的行,那么重复的连续扫描将对性能产生致命影响。您可能需要考虑向该表添加索引,并将索引用作从表中访问数据的方法。 实际上,除了群集表之外,几乎所有表中的数据都不是有序的。拥有繁重事务的系统就是如此,例如银行系统。该例中,表扫描极其昂贵,因为 Informix 引擎必须随机地访问数据。当以与磁盘上其物理次序无关的顺序读取表中的行时,磁盘 I/O 成本更高。因为不是从磁盘连续读取页面的,所以在读取每个页面之前,都需要进行查找并存在循环延迟。因此,与按顺序读取相比,按随机顺序读取数据时的磁盘访问时间要长得多。更好的方法就是使用键索引扫描。 索引是用来定位数据页的顺序指针或键的集合。索引扫描是从下面的根页向叶子页面进行的。因为根页使用十分频繁,所以它通常驻留在内存缓冲区中。用以读取叶子页面的成本取决于索引的大小、查询的筛选器以及索引的惟一性。如果每个值仅在索引中出现一次,且查询是一个连接(join),那么所连接的每一行都需要非连续地查找索引,然后非连续地访问表中相关联的行。然而,如果每个不同的索引值都有许多重复的行,而所关联的表是高度群集的,那么通过索引添加的连接成本可能很低。 虽然索引项是连续的,但是不保证包含相邻索引项的行将驻留在同一数据页上。大多数情况下,对于通过索引定位的每一行,都必须进行单独的磁盘访问来读取该页。如果某个行的大小大于页大小(即大于 2 KB),那么包含该行的页面可能被置换出内存。换言之,在后续数据页被读入内存并被处理之前,它们可能已经从缓冲区中删除,并写回磁盘。那么,在以后处理这些后续页时,还必须再次将它们读入内存。 优化器将决定是否可以使用索引来评估筛选器。为此,索引列是一个包含索引的单列,或者是复合索引中指定的第一个列。如果只需要索引中包含的值,而不需要读取这些行。那么,每次可以直接从索引中读取值时,省略数据页的查找方法都要快得多。 在下列情况下,优化器将使用索引: , 当索引了一列时。数据库服务器首先通过查找索引中的行,定位表中的相关行。本例中, 键索引将比表扫描快得多。 , 当索引了一列,且要进行比较的值是另一表中的一行(连接表示)时。数据库服务器使 用索引来查找匹配的值。 , 当处理 ORDER BY 子句时。如果该子句中所有的列都是以用单个索引要求的顺序出现 的,那么数据库服务器就使用索引来按顺序读取行,因而避免了排序。 , 当处理 GROUP BY 子句时。如果该子句中所有的列都出现在一个索引中,那么数据库 服务器就从索引中读取具有相等键的分组,而无需在从其表中检索行之后进行附加的处 理。 如何为查询判定哪一种访问方法是最有效的呢,这没有固定的标准,因为每个组织的业务需求都是不同的。然而,有一些通用的指导原则。如果查询中的表极其小(通常少于 1,000 行)或者是群集表,而且查询根本没有选择性的话,那么使用表扫描会更好一些。但是如果这些表极其庞大,有许多行(比如几百万行),而且查询具有一定的选择性的话,那么使用索引就好得多。 优化器在选择访问方法方面通常非常智能,但是如果它没有按您所期望的方式进行,那么您可以通过使用查询命令影响其行为,强迫优化器选择您认为最好的访问方法。您可以分析查询执行计划输出,以确定这是否是必需的。 连接方法 如果查询包含不止一个表,那么就应该使用筛选器来连接那些表,以避免笛卡尔(Cartesian)连接。当评估查询执行计划时,优化器计算并比较每种连接方法的成本,然后选择要使用的最佳方法。最常用的连接方法就是嵌套循环连接、分类合并连接和动态哈希连接。现在,让我们详细查看每一种连接。 嵌套循环连接 在嵌套循环连接中,将扫描第一个(或外部)表,以查找满足查询规则的行。对于在外部表中找到的每一行,数据库服务器将在第二个(或内部)表中搜索其相应的行。通过索引扫描还是表扫描来访问外部表则取决于该表。如果有筛选器,数据库服务器首先会应用它们。如果内部表没有索引,那么数据库服务器就会将在表上构建索引的成本与连续扫描的成本进行比较,然后选择成本最低的那一种方法。总成本取决于连接列上是否有索引。如果连接列上有一个索引,那么其成本会相当低;否则,数据库服务器就必须对所有表(外部和内部表)执行表扫描。 分类合并连接 当连接表的连接列上没有可用索引时,通常使用该连接方法。连接开始之前,如果有筛选器,那么数据库服务器首先会应用它们,然后对连接列上每个表中的行进行分类。一旦实现了对行的分类,连接两个表的算法就十分容易:数据库服务器仅仅连续地读取两个已分类表,并合并所有相匹配的行。因为该方法在进行表连接之前,必须将所有的连接表分类,所以其成本通常极其高。在 IDS 版本 7 以及更高版本中,该连接方法已被动态哈希连接所取代。 动态哈希连接 正如上面所提到的,动态哈希连接是 IDS 版本 7 以及更高版本中的新连接方法。当一个或多个连接表上没有索引时,或者当数据库服务器必须从所有连接表中读取大量行时,就使用这种方法。在该方法中,需要扫描其中的一个表,通常扫描较小的那个表,用它在内存中创建一个哈希表。通过哈希函数,将具有相同哈希值的行放在一个 bucket 中。在扫描完第一个表并将它放在哈希表中之后,就扫描第二个表,并在哈希表中查找该表中的每一行, 看是否可以进行连接。如果连接中有更多表,那么数据库服务器将对每个连接表执行相同的操作。 动态哈希连接包含两个动作:构建哈希(或者是我们所称的构建阶段),以及探测哈希表(或探测阶段)。在构建阶段,数据库服务器读取一个表,并且在应用所有现有筛选器之后,在内存中创建一个哈希表。可以在概念上将哈希表认为是一系列的 bucket,每个 bucket 所拥有的地址是通过应用哈希函数从键值导出的。数据库服务器不会在特定的哈希 bucket 中对键进行分类。在探测阶段,数据库服务器将读取连接中的其他表,如果存在筛选器,就应用它们。在满足筛选器条件的每个行中,数据库服务器将对键应用哈希函数,并探测哈希表以查找匹配的键值。哈希连接通常比分类合并连接快,因为它没有涉及到分类操作。 子查询 子查询是 SQL 语句里的 SQL 语句,更确切地说,是嵌套的 SQL 语句。这些 SQL 语句可以用于许多情况下,如 SELECT、DELETE、UPDATE 和 INSERT。例如,下列 SQL 语句使用子查询来计算薪水最少的雇员数目: select count(*) form employee where salary = (select min(salary) from employees); 有两种类型的子查询: 不相关的和相关的。不相关的子查询不依靠主查询来获取信息;因此,它们增加了主查询的能力和灵活性。另一方面,相关子查询需要一些信息来定位内部表中的行,这暗示在选择符合该查询条件的每一行时,数据库服务器都必须运行相关查询。以上查询包含一个相关子查询,因为主查询和子查询依靠于同一个表,即 employees 表。相关子查询将极大地降低数据检索的速度,对于拥有几百万个行但没有索引的大型表的非选择性查询更是如此。我们需要注意相关子查询,并尽可能地避免使用它们。 表和索引的区段 区段(extent)是一块物理位置连续的空间。然而,无法保证区段本身也是连续的。因此,如果一个表或索引只有一个区段,那么它会将其所有的数据或键保存在一块物理空间中。否则,其数据或键将分散在该表或索引所占用的所有区段中。物理数据或键的连续性对于表扫描或键-索引扫描的速度十分重要。当数据连续时,用于访问磁盘上数据的时间最少,数据库服务器可以更快地读取数据。这对于键来说也是一样的。如果表拥有太多区段,那些区段就很可能是交错的。这通常会损害性能,因为当您为某个表检索数据时,磁盘头必须查找属于该表的多个非连续的区段,而不是查询一个包含连续物理页面的大型区段。这会相当大地减慢磁盘查找速度。 如果表或索引有十个或更多的区段,那么您需要考虑重新构建除大型分段表之外的表和索引,合并其区段,以获得更好的性能。关于如何对表和索引进行评估并为它们分配区段大小的细节,请参阅 IBM Informix Dynamic Server Performance's Guide, Version 9.4。 索引层次和惟一性 索引的层次和惟一性也会影响数据的访问速度。索引层次越多,Informix Dynamic Server 就必须执行越多的读取来获取索引叶子节点,并且就将花更多的时间来获得真正的数据。此外,如果在执行数据插入、删除或更新操作的过程中拆分或合并叶子节点,那么可能要花费更长的时间来让整个索引适应其更改。例如,如果一个索引只有两层,那么只需调整这两个层即可,但是,如果它有四个层,那么如果在索引的任一部分中进行了节点拆分或合并,那么就需要对所有的四层进行相应的调整。四层索引的调整时间当然就比仅仅两层索引的调整时间要长得多。OLTP 环境中尤其如此,该环境中的事务十分巨大,并且不断地要插入、删除和更新一些数据。因此,如果索引超过四层,那么您可能需要考虑删除一些层或用较少的层次来重新构建它,以便获得更好的性能。 索引的惟一性是指一个索引拥有多少个重复键。一个高度重复的索引可能会严重影响访问、更新和删除数据的速度。假设 customer 表中的 customer_type 列上有一个索引,那么只会有 5 种可能的 customer_type 代码。如果该表有一百万行,那么平均 200,000 行就会有相同的 customer_type 代码。B-树将存储键值,以及一个指向每个物理行的指针列表。在必须删除或更新任一键值时,就会出现问题。Informix Dynamic Server 必须搜索所有重复键,删除或更新它们,以找出正确的键,这样的话,要搜索 200,000 个键~ 理想情况是,索引中的每个键值都是惟一的。现在,我们如何查明索引是不是惟一的呢,可以通过查询两个系统表来获得这方面的信息:systables 和 sysindexes。下面是该查询: select nrows, nunique from systables a, sysindexes b where a.tabid = b.tabid and idxname = index_name 分段和 PDQ 优先级 分段和 PDQ 优先级也对于数据的检索速度有极大的影响,并有助于表扫描。分段是一种将数据库表和索引巧妙地划分成更小单位(叫作―分段表‖)的方法;每个分段表都是表或索引中的一组行或索引键。用于分段的算法称作―分布模式‖。Informix Dynamic Server 支持两种分布模式:轮循和基于表达式的分布模式。轮循分布模式使用 Informix 的内部规则将表和索引分段。而基于表达式的分布模式则使用用户定义的规则将表和索引分段。 分段的最终目标就是通过直接访问来包含满足 SQL 查询的数据分段表,减少检索时间。例如,如果将一个 100,000 行的大型表分成 10 个分段表,每个分段表包含 10,000 行,那么 Informix Dynamic Server 就可以直接进入包含查询所请求数据的分段表中,不用对其他分段表进行扫描。这将限制 IDS 仅扫描 100,000 行中的 10,000 个相关行。那是一个极大的节省,不是吗,关于分段的细节,请参阅 IBM Informix Dynamic Server Administrator's Guide, Version 9.4。 当应用 PDQ 优先级时,运用分段存储工作得最好。PDQ 优先级的工作原理是―各个击破(divide and conquer)‖,Informix Dynamic Server 将复杂查询拆散成多个较小的部分。将每个小部分分配给不同的线程,然后,每个线程则并行地执行部分查询。当它们完成属于它们的那部分查询处理时,数据库服务器就重组结果数据集,并对其进行处理,然后将最后的答案展示给终端用户。因为 PDQ 优先级使用比常规方法更多的线程来处理一个查询,所以它将缩短数据访问时间。接下来的问题是,我们如何维持系统和 Informix 资源的使用平衡,使它不会受损害,更确切地说是不会减慢非 PDQ 查询的执行呢,这在 OLTP 环境中尤为 重要,该环境中的事务十分巨大,而且查询响应时间十分关键。 示例和分析 现在,让我们获得一些我在工作中收集的具体的 SQL 调优示例,并看一看如何可以应用上面所讨论的这些调优指导原则。我们的公司是一家通信公司,其数据库被用来存储通信性能管理数据、事件订阅列表以及网络元素配置信息。数据库中包含几百个表,大约有 40 GB 的数据。它运行在有 6 个 CPU 和 4 GB RAM 的 Sun Solaris 2.8 操作系统上。硬件平台是一台 Sun Enterprise 3500,带有 6 个 18 GB 的磁盘和两个相互镜像的 36 GB 的磁盘。 示例 1 原始查询及其执行计划: QUERY: ------ SELECT UNIQUE MNE.MSO_ID, MNE.NE_INST, ANE.MSO_ID, 1 FROM NE MNE, NE ANE, CELL, NEIGH, CELL NCELL WHERE MNE.NE_TYPE = 0 {ACG} AND CELL.ACG_INSTANCE = MNE.NE_INST AND NEIGH.ADV_CELL_INST = CELL.CELL_INSTANCE AND NCELL.CELL_INSTANCE = NEIGH.NEIGH_CELL_INST AND ANE.NE_INST = NCELL.ACG_INSTANCE AND ANE.MSO_ID != MNE.MSO_ID AND MNE.WORKSPACE_ID = 41 AND ANE.WORKSPACE_ID = 41 AND CELL.WORKSPACE_ID = 41 AND NEIGH.WORKSPACE_ID = 41 AND NCELL.WORKSPACE_ID = 41 Estimated Cost: 13555 Estimated # of Rows Returned: 38 1) root.cell: INDEX PATH (1) Index Keys: workspace_id tz_inst (Serial, fragments: ALL) Lower Index Filter: root.cell.workspace_id = 41 2) omcadmin.mne: INDEX PATH Filters: omcadmin.mne.ne_type = 0 (1) Index Keys: workspace_id ne_inst (Serial, fragments: ALL) Lower Index Filter: (root.cell.acg_instance = omcadmin.mne.ne_inst AND omcadmin.m ne.workspace_id = 41 ) NESTED LOOP JOIN 3) root.neigh: INDEX PATH (1) Index Keys: workspace_id adv_cell_inst neigh_cell_inst (Key-Only) (Serial, fra gments: ALL) Lower Index Filter: (root.neigh.adv_cell_inst = root.cell.cell_instance AND root. neigh.workspace_id = 41 ) NESTED LOOP JOIN 4) omcadmin.ncell: INDEX PATH (1) Index Keys: workspace_id cell_instance (Serial, fragments: ALL) Lower Index Filter: (omcadmin.ncell.cell_instance = root.neigh.neigh_cell_inst AN D omcadmin.ncell.workspace_id = 41 ) NESTED LOOP JOIN 5) omcadmin.ane: INDEX PATH Filters: omcadmin.ane.mso_id != omcadmin.mne.mso_id (1) Index Keys: workspace_id ne_inst (Serial, fragments: ALL) Lower Index Filter: (omcadmin.ane.ne_inst = omcadmin.ncell.acg_instance AND omcad min.ane.workspace_id = 41 ) NESTED LOOP JOIN 优化的查询及其执行计划: QUERY: ------ select unique mne.mso_id, mne.ne_inst, ane.mso_id, 1 from ne mne, ne ane, cell, neigh, cell ncell where cell.workspace_id = 41 and cell.acg_instance = mne.ne_inst and cell.workspace_id = mne.workspace_id and mne.ne_type = 0 and mne.mso_id !=ane.mso_id and mne.workspace_id=ane.workspace_id and ane.ne_inst=ncell.acg_instance and cell.cell_instance = neigh.adv_cell_inst and ncell.cell_instance = neigh.neigh_cell_inst and cell.workspace_id = ncell.workspace_id and ncell.workspace_id = neigh.workspace_id Estimated Cost: 6555 Estimated # of Rows Returned: 38 1) root.cell: INDEX PATH (1) Index Keys: workspace_id tz_inst (Serial, fragments: ALL) Lower Index Filter: root.cell.workspace_id = 41 2) omcadmin.mne: INDEX PATH Filters: omcadmin.mne.ne_type = 0 (1) Index Keys: workspace_id ne_inst (Serial, fragments: ALL) Lower Index Filter: (root.cell.acg_instance = omcadmin.mne.ne_inst AND root.cell. workspace_id = omcadmin.mne.workspace_id ) NESTED LOOP JOIN 3) root.neigh: INDEX PATH (1) Index Keys: workspace_id adv_cell_inst neigh_cell_inst (Key-Only) (Serial, fra gments: ALL) Lower Index Filter: (root.cell.cell_instance = root.neigh.adv_cell_inst AND omcad min.mne.workspace_id = root.neigh.workspace_id ) NESTED LOOP JOIN 4) omcadmin.ncell: INDEX PATH (1) Index Keys: workspace_id cell_instance (Serial, fragments: ALL) Lower Index Filter: (omcadmin.ncell.cell_instance = root.neigh.neigh_cell_inst AN D omcadmin.mne.workspace_id = omcadmin.ncell.workspace_id ) NESTED LOOP JOIN 5) omcadmin.ane: INDEX PATH Filters: omcadmin.mne.mso_id != omcadmin.ane.mso_id (1) Index Keys: workspace_id ne_inst (Serial, fragments: ALL) Lower Index Filter: (omcadmin.ane.ne_inst = omcadmin.ncell.acg_instance AND root. cell.workspace_id = omcadmin.ane.workspace_id ) NESTED LOOP JOIN 正如您可以从以上输出中观察到的,我们进行的惟一更改就是添加了一个条件来限制原 始查询(为 cell 表指定 workspace_id),并将原始查询中的所有表连接在一起。那些更改将 原始查询的成本缩减了一半,同时也极大地减少了查询响应时间。 示例 2 原始查询及其执行计划: QUERY: ------ select min(ds0_start) from srate where srate.line_instance = 99930 and srate.workspace_id = 41 and srate.ds_instance in ( select ds_inst from ds where stream_type = 10 and workspace_id = 41) Estimated Cost: 5175 Estimated # of Rows Returned: 1 1) root.srate: INDEX PATH Filters: root.srate.ds_instance = ANY (1) Index Keys: workspace_id line_instance (Serial, fragments: ALL) Lower Index Filter: (root.srate.line_instance = 99930 AND root.srate.workspace_id = 41 ) Subquery: --------- Estimated Cost: 5171 Estimated # of Rows Returned: 6400 1) root.ds: INDEX PATH Filters: root.ds.stream_type = 10 (1) Index Keys: workspace_id owner_ne (Serial, fragments: ALL) Lower Index Filter: root.ds.workspace_id = 41 优化的查询及其执行计划: QUERY: ------ select min(ds0_start) from srate , ds where srate.line_instance = 99930 and srate.workspace_id = 41 and srate.ds_instance = ds.ds_inst and ds.stream_type = 10 and srate.workspace_id = ds.workspace_id Estimated Cost: 7 Estimated # of Rows Returned: 1 1) root.srate: INDEX PATH (1) Index Keys: workspace_id line_instance (Serial, fragments: ALL) Lower Index Filter: (root.srate.line_instance = 99930 AND root.srate.workspace_id = 41 ) 2) root.ds: INDEX PATH Filters: root.ds.stream_type = 10 (1) Index Keys: workspace_id ds_inst (Serial, fragments: ALL) Lower Index Filter: (root.srate.ds_instance = root.ds.ds_inst AND root.srate.work space_id = root.ds.workspace_id ) NESTED LOOP JOIN 这里我们所做的惟一更改就是去掉了原始查询中的子查询。我们直接将两个表连接在一 起,而不再使用子查询。因为该连接是在索引上执行的,所以其成本要低得多,而数据访问 则快得多。 示例 3 原始查询及其执行计划: QUERY: ------ SELECT workspace_impctlog.ne_instance, 1, br_status FROM workspace_impctlog, ne WHERE workspace_impctlog.workspace_id = 40 AND ((workspace_impctlog.impct_type = 10 AND workspace_impctlog.trans_type =! 2) OR workspace_impctlog.impct_type = 30) AND workspace_impctlog.ne_instance = ne.ne_inst AND ne.workspace_id = 40 AND ne.ne_type =! 8 AND ne.ne_inst NOT IN (SELECT ne_inst from ne WHERE ne.workspace_id = 40 and ne_type = 8) Estimated Cost: 7880 Estimated # of Rows Returned: 1 1) root.workspace_impctlog: SEQUENTIAL SCAN Filters: (root.workspace_impctlog.workspace_id = 40 AND ((root.workspace_impctlog .impct_type = 10 AND root.workspace_impctlog.trans_type != 2 ) OR root.workspace_impctlog .impct_type = 30 ) ) 2) root.ne: INDEX PATH Filters: root.ne.ne_type != 8 (1) Index Keys: workspace_id ne_inst (Key-First) (Serial, fragments: ALL) Lower Index Filter: (root.workspace_impctlog.ne_instance = root.ne.ne_inst AND ro ot.ne.workspace_id = 40 ) Key-First Filters: (root.ne.ne_inst != ALL ) NESTED LOOP JOIN Subquery: --------- Estimated Cost: 7878 Estimated # of Rows Returned: 16 1) root.ne: INDEX PATH Filters: root.ne.ne_type = 8 (1) Index Keys: workspace_id manager_ne_inst (Serial, fragments: ALL) Lower Index Filter: root.ne.workspace_id = 40 优化的查询及其执行计划: QUERY: ------ SELECT ne_inst from ne WHERE ne.workspace_id = 40 union SELECT ne_inst from ne WHERE ne.workspace_id = 40 AND ne_type = 8 into temp ne_temp1 Estimated Cost: 103 Estimated # of Rows Returned: 10957 1) root.ne: INDEX PATH (1) Index Keys: workspace_id ne_inst (Key-Only) (Serial, fragments: ALL) Lower Index Filter: root.ne.workspace_id = 40 Union Query: ------------ 1) root.ne: INDEX PATH Filters: root.ne.ne_type = 8 (1) Index Keys: workspace_id manager_ne_inst (Serial, fragments: ALL) Lower Index Filter: root.ne.workspace_id = 40 QUERY: ------ SELECT workspace_impctlog.ne_instance, 1, br_status FROM workspace_impctlog, ne WHERE workspace_impctlog.workspace_id = 40 AND workspace_impctlog.impct_type = 10 AND workspace_impctlog.trans_type =! 2 AND workspace_impctlog.ne_instance = ne.ne_inst AND ne.workspace_id = 40 AND ne.ne_type =! 8 AND ne.ne_inst NOT IN (SELECT * from ne_temp1) union SELECT workspace_impctlog.ne_instance, 1, br_status FROM workspace_impctlog, ne WHERE workspace_impctlog.workspace_id = 40 AND workspace_impctlog.impct_type = 30 AND workspace_impctlog.ne_instance = ne.ne_inst AND ne.workspace_id = 40 AND ne.ne_type =! 8 AND ne.ne_inst NOT IN (SELECT * from ne_temp1) Estimated Cost: 5 Estimated # of Rows Returned: 2 1) root.workspace_impctlog: SEQUENTIAL SCAN Filters: ((root.workspace_impctlog.impct_type = 10 AND root.workspace_impctlog.wo rkspace_id = 40 ) AND root.workspace_impctlog.trans_type != 2 ) 2) root.ne: INDEX PATH Filters: root.ne.ne_type != 8 (1) Index Keys: workspace_id ne_inst (Key-First) (Serial, fragments: ALL) Lower Index Filter: (root.workspace_impctlog.ne_instance = root.ne.ne_inst AND ro ot.ne.workspace_id = 40 ) Key-First Filters: (root.ne.ne_inst != ALL ) NESTED LOOP JOIN Subquery: --------- Estimated Cost: 1 Estimated # of Rows Returned: 1 1) omcadmin.ne_temp1: SEQUENTIAL SCAN Union Query: ------------ 1) root.workspace_impctlog: SEQUENTIAL SCAN Filters: (root.workspace_impctlog.impct_type = 30 AND root.workspace_impctlog.wor kspace_id = 40 ) 2) root.ne: INDEX PATH Filters: root.ne.ne_type != 8 (1) Index Keys: workspace_id ne_inst (Key-First) (Serial, fragments: ALL) Lower Index Filter: (root.workspace_impctlog.ne_instance = root.ne.ne_inst AND ro ot.ne.workspace_id = 40 ) Key-First Filters: (root.ne.ne_inst != ALL ) NESTED LOOP JOIN Subquery: --------- Estimated Cost: 1 Estimated # of Rows Returned: 1 1) omcadmin.ne_temp1: SEQUENTIAL SCAN 我们首先尽可能多地替换原始查询中的子查询,然后使用 UNION 集合运算符代替 OR 运算符。优化后的执行计划输出显示出了极大的提高。 示例 4 下列查询是找出 Willson Market 公司发出了多少订单,并检索其 合同 劳动合同范本免费下载装修合同范本免费下载租赁合同免费下载房屋买卖合同下载劳务合同范本下载 信息,例如地址、电 话等。该查询及其执行计划如下所示: QUERY: ------ select companyname, address, city, state, country, phone, fax, email, count(*) from customer a, order b where a.custid = b.custid and a.custid = 'WILMK' group by 1,2,3,4,5,6,7,8 Estimated Cost: 1135675 Estimated # of Rows Returned: 1 Temporary Files Required For: Group By 1) omcadmin.a: SEQUENTIAL SCAN Filters: informix.a.custid = 'WILMK' 2) omcadmin.b: SEQUENTIAL SCAN Filters: Table Scan Filters: informix.b.custid = 'WILMK' DYNAMIC HASH JOIN Dynamic Hash Filters: informix.a.custid = informix.b.custid 其成本太高。在检查这些表时,我们发现两个没有索引的连接列:customer.custid 和 order.custid。这导致 Informix server 执行连续扫描,本例中,连续扫描要比索引键扫描昂贵得多,因为 customer 和 order 表非常大;每个都包含几百万条记录。因此,我们在这些连接列上添加了索引,从下列查询执行计划中我们可以看到,查询成本减少了很多。 QUERY: ------ select companyname, address, city, state, country, phone, fax, email, count(*) from cust a, order b where a.custid = b.custid and a.custid = 'WILMK' group by 1,2,3,4,5,6,7,8 Estimated Cost: 15 Estimated # of Rows Returned: 1 Temporary Files Required For: Group By 1) omcadmin.a: INDEX PATH (1) Index Keys: custid (Serial, fragments: ALL) Lower Index Filter: informix.a.custid = 'WILMK' 2) omcadmin.b: INDEX PATH (1) Index Keys: custid (Key-Only) (Serial, fragments: ALL) Lower Index Filter: informix.a.custid = informix.b.custid NESTED LOOP JOIN IDS 基本体系结构及调优 简介 IDS 的基本体系结构包括三个主要组件:数据存储器、内存结构和后台处理器或虚拟处理器。本文将详细地讨论每一个组件,并提供一些关于如何主动监控数据库引擎的性能,以及如何更为积极主动地进行调优的实用技巧。 确切地说,本文将详细地讨论下列主题: , 数据存储器及调优指南 , 内存结构及调优指南 , 后台处理器及调优指南 数据存储器及调优指南 磁盘IDS的基本数据存储设备。IDS 可以使用两种类型的磁盘设备:成熟的(cooked) raw)磁盘设备。成熟的磁盘设备指的是安装到操作系统的磁盘区域,磁盘设备和原始的( 其中包含了常规文件和文件系统。成熟的磁盘设备对用户来说是可视的。在 Unix 系统中,您可以使用命令 df -k 来显示系统上的所有成熟磁盘设备。例如: sys3508:omcadmin > df -k Filesystem kbytes used avail capacity Mounted on /dev/md/dsk/d0 1094523 669404 370393 65% / /dev/md/dsk/d8 7560147 5601614 1882932 75% /usr /proc 0 0 0 0% /proc fd 0 0 0 0% /dev/fd mnttab 0 0 0 0% /etc/mnttab /dev/md/dsk/d36 2285263 1440973 798585 65% /var swap 1025776 8 1025768 1% /var/run swap 1060400 34632 1025768 4% /tmp /dev/md/dsk/d16 10912273 6242461 4560690 58% /usr/gsm/ne_data /dev/md/dsk/d12 3009327 1121085 1828056 39% /usr/gsm/logs 以上输出指出了系统上所有的成熟磁盘设备。Filesystem 列显示了所有成熟磁盘设备,而 Mounted on 列显示了相应设备上安装的所有文件系统。例如,设备 /dev/md/dsk/d0 安装在 / 或根文件系统上,/dev/md/dsk/d36 安装在 /var 文件系统上,/dev/md/dsk/d16 安装在 /usr/gsm/ne_data 文件系统上等。Kbytes 列展示了该设备所占的磁盘总空间,used 展示了该设备当前已使用的磁盘空间,而 avail 列则展示了该设备可用的剩余磁盘空间。另一方面,原始磁盘设备指的是无法安装到操作系统的磁盘片,对用户来说是不可视的。Informix 建议尽可能地使用原始磁盘设备,因为原始磁盘设备不用安装到操作系统上,并可以将数据服务器内存中的数据直接传送到磁盘上,而无需先将之复制到操作系统的内存池中,这比成熟的磁盘设备要快得多。此外,原始设备比成熟设备安全,因为对用户而言,它们是不可视的(除了 root 用户)。使用文件系统作为数据存储设备有一个潜在的危险,当文件系统由于某些操作系统的错误而崩溃,且有一个数据库事务正在进行时,数据库服务器将认为数据库事务已经成功完成,但实际上,该事务正陷入操作系统缓冲区中;这最终将导致数据库中的某些不一致。关于如何为 IDS 分配成熟和原始磁盘设备的详细过程,Informix Dynamic Server 在 dbspace 中存储其数据。dbspace 是 Informix 的数据存储单元,它包含一个或多个块(chunk),更确切地说是由一个或多个块来支持。块(chunk)在术语上是指磁盘上的连续空间。在 IDS 9.40 之前的 IDS 版本中,对于块的最大长度具有一个硬限制;一个块不能超过 2 G。从 IDS 9.40 开始,如果在大块(large chunk)模式下配置 IDS,则取消这一限制。这是一项革命性的提高;通过这项提高,IDS 可以更有效地利用磁盘空间,并更容易地管理它们。IDS 使用 onspaces 实用程序来配置和管理 dbspaces。以下是 onspaces 实用程序的用法: $ onspaces -- Usage: onspaces { -a spacename -p path -o offset -s size [-m path offset] { { [-Mo mdoffset] [-Ms mdsize] } | -U } } | { -c { -d DBspace [-t] -p path -o offset -s size [-m path offset] } | { -b BLOBspace -g pagesize -p path -o offset -s size [-m path offset] } | { -S SBLOBspace [-t] -p path -o offset -s size [-m path offset] [-Mo mdoffset] [-Ms mdsize] [-Df default-list] } | { -x Extspace -l Location } } | { -d spacename [-p path -o offset] [-f] [-y] } | { -f[y] off [DBspace-list] | on [DBspace-list] } | { -m spacename {-p path -o offset -m path offset [-y] | -f filename} } | { -r spacename [-y] } | { -s spacename -p path -o offset {-O | -D} [-y] } | { -ch sbspacename -Df default-list } | { -cl sbspacename } -a - Add a chunk to a DBspace, BLOBspace or SBLOBspace -c - Create a DBspace, BLOBspace, SBLOBspace or Extspace -d - Drop a DBspace, BLOBspace, SBLOBspace, Extspace, or chunk -f - Change dataskip default for specified DBspaces -m - Add mirroring to an existing DBspace, BLOBspace or SBLOBspace -r - Turn mirroring off for a DBspace, BLOBspace or SBLOBspace -s - Change the status of a chunk -ch - Change default list for smart large object space -cl - garbage collect smart large objects that are not referenced default-list = {[LOGGING = {ON|OFF}] [,ACCESSTIME = {ON|OFF}] [,AVG_LO_SIZE = {1 - 2097152}] } 只有用户 Informix 或具有 DBA 权限的用户可以使用此实用程序。 那么,我们该如何配置 dbspaces 呢,没有一套硬性规则;适当的配置应基于您系统上的可用磁盘。但是为了最优化整体性能,Informix 为我们提供了一些指南,其中最为关键的就是为不同的数据配置不同的 dbspace。我们可以将 IDS 数据分成下列几类: , 逻辑日志数据;由逻辑日志生成的数据; , 物理日志数据;由物理日志生成的数据; , 表数据;数据库表中的数据; , 索引数据;由表上的索引生成的数据; , 二进制对象数据,如大型文本和内部图形; , 临时数据;通过对表进行排序、更改和创建索引而生成的数据。 首次初始化 IDS 时,它会将其所有的数据、逻辑日志数据、物理日志数据和其他临时数据 放置在根 dbspace 中。为了获得高性能,我们需要创建不同的 dbspaces,并且将逻辑日志数据和物理日志数据从 rootdbspace 移出。将索引数据从表数据中分离出来也是有益的。在为临时数据创建临时 dbspace 之后,Informix 还建议将 DBSPACETEMP 配置参数设置为您系统上所配置的多个临时 dbspace 的名称,这将减少磁盘 I/O,如果将 PDQPRIORITY 设置为大于 0 的值,这将提高创建临时表的速度,并允许并行排序或插入。 另一重要指南就是在磁盘中分散 dbspace。这当然要受到硬件拓扑的限制,但其思想就是将关键的 dbspace(例如根 dbspace、逻辑日志 dbspace 和物理日志 dbspace)放置在更快的磁盘上,因为那些 dbspace 将进行大多数的磁盘 I/O 活动,并且也会将磁盘 I/O 负载均衡地分散在磁盘中。创建了合适的 dbspace 之后,我们需要不断地监控那些 dbspace 上的磁盘 I/O 活动,识别是否存在过热点(hot spot),并且在必要时对其进行调优,以获取更好的性能。 Dbsapce I/O 是通过磁盘读写来衡量的。如果某些 dbspace 具有较多的磁盘读写,而其他磁盘却较为空闲,那么该系统可能存在磁盘 I/O 瓶颈。较为平衡的 dbspace I/O 将缓解系统磁盘 I/O 负载,从而提高系统的整体性能。下列脚本将显示当前每个 dbspace 的 I/O 统计信息: select d.name, fname[15,25] path_name, sum(pagesread) diskreads, sum(pageswritten) diskwrites from syschkio c, syschunks k, sysdbspaces d where d.dbsnum = k.dbsnum and k.chknum = c.chknum group by 1, 2 order by 1 其输出结果如下: name path_name diskreads diskwrites airgen_10_0 uild95/ltmp 3672 7964 airgen_main_dbs uild95/ltmp 13545 32903 llog uild95/ltmp 19 51633 rootdbs uild95/ltmp 211 43117 temp1 uild95/ltmp 3015 3122 temp2 uild95/ltmp 3218 3317 您可以通过查询 sysmaster 数据库中的 sysptprof 表,进一步确定哪些表具有多数磁盘读写: select dbsname, tabname, (isreads + pagreads) diskreads, (iswrites + pagwrites) diskwrites from sysptprof order by 3 desc, 4 desc 其输出如下所示: dbsname tabname diskreads diskwrites airgen_10_0 fanout_param 84567 3094 airgen_main_dbs sysindices 78381 0 airgen_10_0 ne_nmo_i 75819 5 airgen_10_0 ne_nmo 75440 297 airgen_main_dbs ne_main 62610 28322 airgen_10_0 systables 37342 466 airgen_10_0 syscolumns 34539 4609 airgen_10_0 457_484 32838 42 airgen_10_0 453_480 30009 1 airgen_main_dbs syscolumns 29531 4550 airgen_10_5 syscolumns 28824 4552 airgen_10_0 456_483 25448 14 airgen_10_0 458_485 23278 177 airgen_main_dbs 452_483 22412 31 基于该查询所获得的输出,您可以发现 airgen_cm_db 数据库中的 ne_main 表具有最多磁盘 I/O,如果当前 dbspace 所承受的磁盘 I/O 过于繁重,那么可以考虑将该表移到另一 dbspace 中。 内存结构及调优指南 内存结构是 IDS 体系结构中最为复杂的组件。因为 IDS 使用的内存部分被数据库服务器线程以及其他用户和虚拟进程共享,因此该部分内存通常被称作共享内存。由于参与进程(在本例中是虚拟处理机)不需要维护共享内存中的私有数据副本,共享内存可以使数据库服务器减少总的内存使用。共享内存还能减少磁盘 I/O,这是因为作为公共池来管理的缓冲区,是在数据库服务器级基础上而不是在每进程基础上被刷新的。而且,因为这些数据已经作为前面读取操作的结果存在于共享内存中了,所以虚拟进程通常可以避免从磁盘读取数据。磁盘 I/O 的减少也减少了执行时间。共享内存提供了最快地进行进程间通信的方法,因为它以内存转换的速度来处理读写消息。 在初始化 IDS 时,它就创建了共享内存,其中包括以下 4 个区(portion): 常驻区(resident portion); 虚拟区(virtual portion); IPC 通信或消息区(IPC communications or message portion); 虚拟扩展区(virtual-extension portion)。 现在,让我们仔细地看看每一部分,并查看其内容以及它们分别是如何工作的。 常驻部分存储数据库服务器运行时大小固定的数据结构,它具有下列几个部分: , 共享内存头 , 缓冲池 , 逻辑日志缓冲区 , 物理日志缓冲区 , 锁表 共享内存头包含共享内存中所有其他结构的描述,包括内部表和缓冲池。它还包含到这些结构位置的指针。当一个虚拟进程首次附加到共享内存中时,它读取共享内存头中的地址信息,获取指向其他所有结构的方向。共享内存头是在初始化 IDS 时创建的,并且不能进 行调优。缓冲池存储 IDS 从 dbspace 所读取的数据,更确切地说是数据库对象数据,例如表数据和索引数据,或者是从磁盘所读取的存储过程。然后,ESQL 或其他客户程序将操作该数据。 缓冲池占用了常驻区中最大的部分。所有的缓冲区将被组织到一个较长的最近最少使用(least-recently-used,LRU)缓冲区队列中,并通过最近最少使用(LRU)机制进行管理。缓冲区的获取是通过使用锁存器(latch)和锁访问信息来管理的,该锁存器称作 mutex。逻辑日志缓冲区是用来存储最后一次备份开始的逻辑日志记录的。 逻辑日志记录保存了 SQL 语句对数据库数据进行的修改。在初始化 IDS 时,它创建三个逻辑日志缓冲区。那些逻辑日志缓冲区以循环方式起作用,以确保将获得的每一条逻辑日志记录都被刷新到磁盘中。 物理日志缓冲区被用来保存缓冲池中已修改数据库记录的前映像(before-image)。在 IDS 删除或修改一条数据库记录之前,它将捕获这一原始记录的快照并存储其原始值,以防事务失败。这就称作前映像(before-image),被用于恢复数据,确保了数据库的完整性。在初始化 IDS 时,它创建了两个物理日志缓冲区,而它们与逻辑日志缓冲区一样,以循环方式运作。锁表是一个包含可用锁的池;每个用户会话都需要一定数量的锁来连接数据库服务器并执行数据库操作,例如 select、insert、delete 和 update。 除了共享内存头之外,所有这些部分都是可以控制并用 Informix 配置参数进行调优的。下表列举了与 IDS 共享内存的常驻区有关的所有配置参数: 常驻区 参数 缓冲池 BUFFERS 逻辑日志缓冲区 LOGBUFF 物理日志缓冲区 PHYSBUFF 锁表 LOCKS 整个区 RESIDENT BUFFERS 指定共享内存缓冲区的最大数目;LOCKS 指定用户会话可以使用的锁的最大数目。该数目过去是固定不变的,但在 IDS 的后来版本(IDS 9.20 以及更高版本)中,当用户会话缺少锁时,就可以动态地分配锁。LOGBUFF 指定逻辑日志缓冲区的大小,而 PHYSBUFF 指定物理日志缓冲区的大小。RESIDENT 指定数据库服务器共享内存的常驻区的位置;无论我们是在系统上的物理 RAM 中锁定它,还是让操作系统决定在 RAM 还是在虚拟内存中放置它,Informix 总是建议尽可能地在系统的物理 RAM 中锁定常驻区,以避免操作系统与磁盘交换共享内存的常驻区,从而取得更好的性能。 共享内存的虚拟区十分复杂;它存储各种各样的不同数据。以下是共享内存的虚拟区存储的数据列表: , 内部表 , 较大的缓冲区 , 会话数据 , 线程数据(堆栈和堆) , 数据分布缓存器 , 字典缓存器 , SPL 例程缓存器 , SQL 语句缓存器 , 排序池 , 全局池 内部表是在初始化 IDS 时创建的。一共有 7 种内部表。那些内部表的目的和功能是为了记录共享内存资源的状态和使用,例如缓冲区、块、dbspace、事务等。较大的缓冲区用于大型的读写 I/O 操作。例如,若数据库服务器试图执行一系列连续的读取(light scan),或者将存储在 dbspace 中的简单大对象读取到共享内存中,那么 IDS 将为该操作分配较大的缓冲区。会话数据也称作会话控制块,它存储会话 ID、用户 ID、客户机的进程 ID、主计算机名称以及客户机应用程序请求连接到数据库服务器时的各种状态标志。线程数据指的是会话的线程信息、有关该会话的基本线程和其他线程信息。它包括与该线程堆栈和堆有关的信息。排序池用于下列操作: , 包含连接、分组、聚集和排序操作的决策支持查询 , 索引构建 , SQL 中的 UPDATE STATISTICS 语句 数据库服务器分配给排序的虚拟共享内存的数量取决于要进行排序的行数、每一行的大小以及是否为服务器设置了 PDQPIORIDY。全局池存储对于数据库服务器来说是全局性的结构。例如,全局池包含消息队列,其中用于网络通信的 poll 线程存放来客户机的消息。sqlexec 线程从全局池中取得消息并加以处理。所有那些区域基本上都是由 IDS 本身来控制和管理的;我们几乎不用做任何事情。 但是,我们控制所有类型的缓存器,数据分布缓存器、字典缓存器、SPL 例程缓存器和 SQL 语句缓存器。我们还控制尺寸过大的虚拟内存。控制那些部分的方法就是调优下列配置参数: 虚拟内存区域 参数 数据分布缓存器 DS_POOLSIZE DS_HASHSIZE 数据字典缓存器 DD_HASHSIZE DD_HASMAX SPL 或 UDR 例程缓存器 PC_HASHSIZE PC_POOLSIZE SQL 语句缓存器 STMT_CACHE STMT_CACHE_SIZE 总的虚拟内存 SHMADD SHMTOTAL SHMVERSIZE STACKSIZE 虚拟区可以通过数据库服务器得到较大扩展,并且可以通过操作系统将虚拟区页移出(page out)磁盘。当数据库服务器处于联机状态并执行操作时,如果虚拟区的初始尺寸太小,数据库服务器会自动向虚拟区附加其他操作系统段。虚拟内存中的段过多将使整体性能下降。因此,Informix 建议在初始化 IDS 时,将虚拟内存区的尺寸配置得足够大,以避免进行动态的段分配。命令 onstat -g seg显示 IDS 当前共享内存虚拟区中的段的数目。以下是该命令的输出示例: Informix Dynamic Server 2000 Version 9.21.UC4 -- On-Line -- Up 28 days 15:49:33 -- 205824 Kbytes Segment Summary: id key addr size ovhd class blkused blkfree 0 1381386241 a000000 177209344 220688 R 42984 280 1 1381386242 14900000 8388608 856 V 2048 0 2 1381386243 15100000 1048576 632 M 164 92 3 1381386244 15200000 8388608 856 V 2048 0 4 1381386245 15a00000 8388608 856 V 2008 40 5 1381386246 16200000 8388608 856 V 50 1998 Total: - - 211812352 - - 49302 2410 (* segment locked in memory) 我们感兴趣的是输出的 class 列。这里的每个 V 条目都指示一个段,因此,该输出显示了过多的(通常为 5 个)段,这意味着我们配置的虚拟区的初始尺寸太小,需要增加它来获得更好的性能。您也可以使用输出的 size 列来估计虚拟区将使用多少内存,但是 Informix Administrator's Guide 提供了估算共享内存虚拟区的更准确的公式。Stacksize 也是一个极其重要的配置参数,它指示了数据库服务器为每个活动线程指派的初始堆栈的大小。如果将该参数配置得过小,那么线程将无法拥有执行其程序的足够内存空间,而且它将干扰其他线程。有关如何估算和配置 stacksize 参数的细节 如果您至少将一个连接配置为 IPC 共享内存连接,那么在初始化 IDS 时将分配处理器间的通信或消息区。通信区包含本地客户机应用程序的消息缓冲区,这些应用程序使用共享内存与数据库服务器进行通信。该区的尺寸相当小,可用 12kb 乘以共享内存通信(NETTYPE ipcshm)所需的预期连接数目来计算该尺寸。如果未指定 NETTYPE ipcshm,那么所期望的连接数目的默认值为 50。虚拟扩展区也极为小;它包含了用于 DataBlade 模块的线程 heaps 和其他在用户定义的虚拟处理器中运行的用户定义例程。 在配置 IDS 内存时,我们需要考虑总的可用内存,包括物理 RAM 、虚拟内存和系统上的日常负载。如果我们给 IDS 分配过多的内存,这将有损于一些系统操作,但是如果我们不给 IDS 分配足够的内存,那么将有损于数据库服务器。在这里,我们给出的指导方针是,不断监控系统内存的使用,查看系统是否因分页而受损。在 Unix 系统中,我们可以使用 vmstat 来监控内存的使用。以下是输出示例: sys3508:omcadmin > vmstat 1 10 procs memory page disk faults cpu r b w swap free re mf pi po fr de sr m0 m1 m4 m5 in sy cs us sy id 0 0 0 1047536 371088 242 1980 19 28 19 0 0 0 0 0 0 1036 9748 2379 11 7 82 5 0 0 692488 67704 346 3997 0 56 32 0 0 0 0 0 0 5118 25093 7623 70 19 11 3 0 0 693024 67816 168 1800 0 16 16 0 0 0 0 0 0 6409 26068 7839 72 23 5 3 0 0 692848 67896 204 2151 0 32 24 0 0 0 0 0 0 6138 25327 7215 79 17 3 4 0 0 692800 67824 196 2174 0 32 24 0 0 0 0 0 0 5533 23952 7610 74 21 6 2 1 0 695248 70272 281 2896 0 32 24 0 0 0 0 0 0 5421 28503 8094 70 21 9 2 1 0 695608 70416 212 2215 0 32 24 0 0 0 0 0 0 5535 24086 7855 75 16 9 输出的 page 部分指示系统的当前分页状态;pi 列指示读入内存的页数目,而 po 列则指 示移出内存的页数目。如果有较多 po,例如几千页,那么您的系统就可能缺乏内存。在系统高峰时期多次执行该命令,查看系统是否因分页而受损。 后台处理器及调优指南 在数据存储器和内存结构之间,有许多虚拟或后台处理器忙于处理 SQL 语句。虚拟处理器对数据库服务器的作用类似于 CPU 对计算机的作用。正如一个 CPU 允许多个操作系统进程来服务多个用户一样,一台数据库服务器虚拟处理器也可以运行多个线程来为多个 SQL 客户机应用程序提供服务。有关虚拟处理器及其功能的完整列表。 在所有虚拟处理器中,下列 3 种最为重要: , CPU , AIO , 网络处理器 让我们分别仔细地看一看这 3 种虚拟处理器。CPU 虚拟处理器是最重要的 IDS 虚拟处理器;它驱动其他虚拟处理器,如磁盘 I/O 虚拟处理器和 IDS 会话中的所有线程。AIO 的功能是在 SQL 语句访问或更新数据库数据时,执行磁盘 I/O。网络处理器进行到数据库服务器的客户机或用户连接。可以进行两种类型的连接:共享内存连接和网络连接。可以通过在配置文件中调优下列参数来控制和管理那些虚拟处理器: 虚拟处理器 参数 CPU MULTIPTOCESSOR SINGLE_CPU_VP VPCLASS AIO/KAIO NUMAIOVPS NETWORK NETTYE MULTIPROCESSOR 和 SINGLE_CPU_VP 指定您的机器是否有一个或多个处理器(CPU)。VPCLASS 是 IDS V9.2 和更高版本中的新参数。该参数用于取代几个旧的参数,例如 NUMCPUVPS、NUMAIOVPS、NOAGE、AFF_SPROC 和 AFF_NPROCS。虽然 IDS 仍然支持这些旧参数,但是 Informix 建议您使用 VPCLASS。该参数为每个类别配置虚拟处理器的数目,并指定系统是否想用数据库服务器进程来绑定物理 CPU 处理器,Informix 极力推荐通过这种方法提高性能。NUMAIOVP 指定系统上 AIO/KAIO 虚拟处理器的数目;如果您对 dbspace 使用原始磁盘空间,通常将 UMAIOVP设置为 2 就足够了,但是,如果您对 dbspace 使用成熟的磁盘空间,那么应将之设置为系统上块的数目。NETTYPE 指定 IDS 的连接类型和网络虚拟处理器的数目。当 IDS 处于联机状态时,可以使用 onmode -p来添加或删除虚拟处理器。例如: onmode -p +4 cpu 该命令将向数据库服务器添加 4 个 CPU 虚拟处理器。" + " 号表示添加,而 " - " 号则表示删除。 那么,我们如何监控那些虚拟处理器的性能,以及如何主动调优呢,我们可以使用下列两个 onstat 命令来监控虚拟处理器: onstat -g rea onstat -g ioq 第一个命令监控就绪队列中的线程数目。就绪队列包含准备运行但在等待必要资源(如 CPU 等)的线程。以下是其输出示例: Ready threads: tid tcb rstcb prty status vp-class name 6 536a38 406464 4 ready 3cpu main_loop() 28 60cfe8 40a124 4 ready 1cpu onmode_mon 33 672a20 409dc4 2 ready 3cpu sqlexec 理想的输出应显示极少的条目,或者不显示任何条目。如果某个 VP 类的输出条目持续增长(例如,在以上输出中,CPU VP 类有 3 个条目),那么我们就需要考虑在该类中添加更多 VP。 第二个命令显示磁盘 I/O 信息,并有助于确定是否需要增加 AIO/KAIO 虚拟处理器。例如: onstat -g ioq AIO I/O queues: q name/id len maxlen totalops dskread dskwrite dskcopy adt 0 0 0 0 0 0 0 msc 0 0 1 12 0 0 0 aio 0 0 4 89 68 0 0 pio 0 0 1 1 0 1 0 lio 0 0 1 17 0 17 0 kio 0 0 0 0 0 0 0 gfd 3 0 3 254 242 12 0 gfd 4 0 17 614 261 353 0 我们感兴趣的是输出的 len 列。该列的值应该总是为 0 或接近于 0。如果该列的值很高并持续增长,那么我们可能需要添加另一个 AIO/KAIO 虚拟处理器来减少磁盘 I/O 负载。我们还可以使用 SMI 表来监控虚拟处理器。有关详细信息,请参阅 Administrator's Guide。 在配置和调优虚拟处理器时,我们需要在脑海里勾画出整个系统的图像。虚拟处理器的数目,特别是 CPU 虚拟处理器的数目应该与系统中物理 CPU 成合适的比例。否则,您的系统将受到损害,整体性能也将下降。根据经验,为机器上的每个物理 CPU 设置的 CPU VP 数目应该总是不超过 2 个。 IBM Informix Dynamic Server 优化器概述 简介 IBM? Informix?Dynamic Server 优化器组件的作用是对可能的查询执行计划进行评 估,然后根据试探性规则以及成本来确定最好的计划。优化器用―智能猜测‖来确定首先扫描哪个表,这种猜测基于查询中出现的表的行数,以及这些表的索引的可用性。然后,它从余下可能的表中确定哪一个将与第一个表连接(join),接下来将哪个表与这两个表连接,依此类推,直到每个表的连接顺序都已确定为止。 优化器是如何与 SQL 引擎配合的 可以用图来表示优化器组件,如下所示: 优化器是 SQL 引擎的核心部分。当用户启动内含 SQL 语句的应用程序时,该语句被发送到服务器。在到达服务器时,该语句首先经过解析器,解析器用一组语法规则检查语句的语法是否正确。解析器会为该语句构建解析树,然后把该树传递给语义分析器。 为了将解析树转换为 SQL 结构,后端会读取数据库系统目录以检索查询中每个表的信息。这一结构会被输入到优化器。优化器生成用于处理该查询的计划。该计划包括应使用哪些索引,扫描表的顺序以及是否需要临时表以备排序或分组之用。 一般的想法认为优化器所选择的计划是处理查询的可行方法中最快的。实际上,这并不总是正确,正确的程度取决于优化算法的完善程度和统计信息的可用性及准确性。 基本术语 要理解优化过程,对要用到的术语有一个基本理解是有帮助的。 元组 元组是一行列值,它有时被称为行或记录。 扫描 扫描是读取数据的实际操作。在执行查询中可能会用到几种扫描类型。使用哪种类型的决定取决于一些具体信息,如是否使用索引以及读取数据的方式。 以下是扫描类型: , 顺序表扫描— 按数据行在表中物理存在的顺序读取它们。当要读取表的很大一部分 时,或当表没有有用的索引时,优化器就选择表扫描。 , 索引扫描— 通过索引访问表中的数据。如果查询过滤器中的列在索引中,则优化器 会选择索引扫描。索引用于只检索查询需要的那些行。 , 忽略重复索引扫描— 这是一种特殊类型的索引扫描,重复的行会被忽略。 , ROWID 扫描— 直接读取位于指定物理位置(由 ROWID 指定)的数据行。 , 首行扫描— 仅当发现第一个满足搜索条件的行时才扫描数据。 , 仅键扫描 — 如果查询的投影列表(projection list)和过滤器中包含的所有列都可在 索引中获得,那就不需要探测表中的数据行。在这样的情况下,只读取索引键。为了 举例说明,请考虑以下查询: , SELECT col1, col2 FROM tab WHERE col1 >10; 如果表上的索引包含 col1 和 col2,则无需扫描数据行,因为 col1 和 col2 就 在索引本身中。 连接 现在让我们考虑当对多个表进行查询时会怎样。假设我们有两个表,t1 和 t2。表 t1 有列 c11 和 c12,表 t2 有列 c21 和 c22。以下是我们的查询: SELECT t1.c11, t2.c22 FROM t1, t2 WHERE t1.c11 = ―John‖ AND t1.c12 = t2.c22; 当查询包含多个表时,通常用查询中的过滤器把各个表连接在一起。第一个要扫描的表通常被称为―外表(outer table)‖,为获得与从外表读取的行相匹配的行,而要搜索的第二个表被称为―内表(inner table)‖。图 2 演示了该过程。 有以下几种类型的连接: , 嵌套循环连接— 数据库服务器扫描外表;对于每个满足表级别过滤器的行,它会 扫描内表,以搜索与之匹配的行。可以对外表使用表扫描或索引扫描。对内表则更 适于使用索引扫描,因为内表将被访问的次数可能非常多。 , 动态散列连接— 散列连接分为两个阶段:构建阶段和探测阶段。在构建阶段,数 据库服务器会扫描较小的表,并通过对应用了任何合适的表级别过滤器之后所产生 的行的键应用散列函数,来构建散列表。键是用来连接表的列。当两个连接表中至 少有一个在连接列上没有索引时,或当必须从两个表读取大量行时,优化器通常选 择散列连接。 , 笛卡儿积连接— 数据库服务器将内表的所有行与外表的每一行相连接,从而产生 所有可能的行组合。当任何过滤器的连接列的索引不可用时,或当自动或动态索引 的成本大于笛卡儿积连接成本时,以及当动态散列连接方法的成本大于笛卡儿积连 接的成本时,就会使用这种连接方法。 过滤器 过滤器是查询的搜索条件,用于确定哪些行将被查询选中。有以下几种类型的过滤器: 下限索引过滤器— 用于设置索引扫描的下限或起始点的过滤器或过滤器组合。例如,可以把 col1 >= 10 作为下限索引过滤器用于列 col1 的升序索引,以便从键值 10 开始启动扫描。 , 上限索引过滤器— 用于设置索引扫描的上限或终止点的过滤器或过滤器组合。例如, 可以把 col1 <= 20 作为上限索引过滤器用于列 col1 的升序索引,以便从头扫描直到 键值达到 20 为止。 , 键优先过滤器 — 将键优先过滤器应用于索引的键值,以便确定是否必须获取相应的数 据行。请考虑以下的查询: , SELECT * , FROM t , WHERE col1 >= 10 and col2 = 20; 如果优化器选择对 col1 和 col2 组成的索引进行索引扫描,则首先会将过滤器 col2=20 应用到每个索引键。只有满足该过滤器才会获取数据页中相应的行。col1 >= 10 被用作下限索引过滤器,以便从键值为 10 行开始进行索引扫描。 , 键起始过滤器 — 键起始扫描用于具有以下模式的过滤器的查询: , col1 >= 10 and (col1 > 10 or (col2 > 20 or (col2 = 20 and col3 >= 30)) 下面将该模式用于查询: SELECT col1, col2, col3 FROM t WHERE col1 >= 10 AND (col1 > 10 OR (col2 > 20 OR (col2 = 20 AND col3 >= 30)) ORDER BY col1 ,col2 ,col3; 以上查询等价于以下 ANSI 语法: SELECT col1, col2, col3 FROM t WHERE (col1, col2, col3) >= (10,20,30) ORDER BY col1, col2, col3; 要满足该查询,必须从索引 t(col1,col2,col3) 的 (10,20,30) 处开始进行索引扫描。要实现这一点,扫描的下限索引过滤器可以是: col1 >= 10, col2 >= 20, col3 >= 30 这些过滤器被称为键起始过滤器,并且只用于设置索引扫描的起始点。对于实际数据检索,可应用最初的过滤器: col1 >= 10 AND (col1 > 10 OR (col2 > 20 or (col2 = 20 and col3 >= 30)) 存取路径 数据库服务器读表的方法被称为存取路径。有三种类型的存取路径: , 顺序路径— 数据库服务器使用顺序扫描来读表。 , 索引路径— 数据库服务器使用索引从表读取数据。 , 自动索引路径— 如果数据库服务器需要多次根据某个列探测一个大型表,而在这 个列上没有现成索引,那么优化器可能会选择实时创建一个索引以便访问表中的 行。执行相关查询时也常常倾向于使用自动索引路径。 表 表是为了从用户的角度出发逻辑地安排数据而定义的数据库对象。 首行表是设置了首行特性的表。该特性表示:在查询表时,仅当发现第一个匹配的行时才会扫描该表。首行表通常是半连接中的内表。半连接是连接的一种,在这种连接中,一旦外表的行匹配内表行的连接条件时,连接处理会移到外表的下一个值以进行条件匹配。因此,实际上连接使用内表的第一个匹配行,并且拒绝重复的匹配值。 可将首行表表示为 A(T1,T2,...,Tn),其中 A 是首行表,T1,T2,...,Tn 是优先顺序表,必须在连接首行表之前就以连接顺序对它们进行了连接。请考虑以下示例: SELECT t1.col1 FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.col2 = t1.col3) 以上查询可重新写成: SELECT t1.col1 FROM t1, t2 (FR, t1) WHERE t2.col2 = t1.col3 在上述例子中,只有发现了 col3 中每个值的首个匹配,才会探测 t2 中 col2 的值与 t1 中 col3 的值是否匹配。请注意:首行表表示法 t2(FR, t1) 是内部表示法;用户不能使用该语法。 优化过程 优化可分为两个步骤: , 改写查询 , 生成计划 改写查询 有时只需通过对查询做些修改并确保两个查询(最初的和改写的)产生的行集合保持相同,就可以减少查询的响应时间。服务器在内部确定是否有可能改写查询并且使改写后查询的输出方式与最初查询的输出相同。这是优化过程中的第一步。这一步骤完全是基于试探法,并且此时还没有出现成本计算。 以下几个示例说明了基于试探法的查询改写的重要性。 示例 1: SELECT * FROM a WHERE a.c1 > ALL (SELECT c1 FROM b); 在示例 1 中,对于外部查询中表 a 的每一行,都将进行 N 次比较,其中 N 等于表 b 中的行数。该查询可转换为以下查询: SELECT * FROM a WHERE a.c1 > (SELECT MAX(c1) FROM b); 在这个例子中,对于表 a 的每一行,仅需与子查询进行一次比较。 示例 2: SELECT c1 FROM product p WHERE EXISTS (SELECT c2 FROM order o WHERE o.prodid = p.prodid AND o.prodid = 2001) 通过子查询―扁平化‖(改写)过程,将上面的查询改写为: SELECT p.c1 FROM product p, order o(FR, p) WHERE p.prodid = o.prodid AND o.prodid=2001 将相关子查询转换为非相关子查询 将某些相关子查询转换为非相关子查询是有可能的。相关子查询的特点是:针对父语句处理的每一行都会对它进行一次求值。父语句可以是 SELECT、UPDATE 或 DELETE 语句。 相关子查询示例: SELECT * FROM tab1 WHERE tab1.c1 = 10 and tab1.c2 = (SELECT max (tab2.c2) FROM tab2 WHERE tab2.c1 = tab1.c1) 通过进行名为断言提升(predicate promotion)的处理,可以把这一相关子查询转换成非相 关子查询: SELECT * FROM tab1 WHERE tab1.c1 = 10 and tab1.c2 = (SELECT max(tab2.c2) FROM tab2 WHERE tab2.c1 = 10) 避免使用相关子查询的好处是子查询只需执行一次,而不是针对 tab1.c1 中的每一行都执行 一次。这是因为改写使服务器能够在高速缓存中存储子查询的结果。 子查询“扁平化” 目前,服务器能够―扁平化‖或改写不含聚合的 ANY、EXISTS、SOME 及 IN 子查询。改 写过程由两个步骤组成: 将 ANY、SOME 和 IN 改写为 EXISTS 子查询 将 EXISTS 子查询―扁平化‖到其父语句块中。 例如,将查询: SELECT t1.c1 FROM t1 WHERE t1.c2 > ANY (SELECT t2.c2 FROM t2 WHERE t2.c3 = t1.c3) 首先转换为: SELECT t1.c1 FROM t1 WHERE EXISTS (SELECT t2.c2 FROM t2 WHERE t2.c3 = t1.c3 and t2.c2 < t1.c2) 然后转换为: SELECT t1.c1 FROM t1, t2 (FR, t1) WHERE t2.c3 = t1.c3 and t2.c2 < t1.c2 ANSI 查询改写 ANSI 查询改写通常由以下步骤组成: , 将 ANSI 外连接转换成 ANSI 内连接 , 将 ANSI 内连接转换成 Informix 内连接。 有关连接的背景知识 在外连接中,结果包括第一个表(左外连接)或第二个表(右外连接)的所有行,以及满足连接条件的表的行组合。如果对应的输入表没有匹配的行,那么结果行中的字段将是 NULL。 让我们首先看看查询使用左外连接时的结果: SELECT * FROM t1, outer t2 WHERE t1.c1 = t2.c2; 在左外连接中,结果保留了主表(t1)中由于没有在从表(t2)发现匹配行而原本应丢弃的行。如果主表行没有匹配的从表行,则接收从表的列上 NULL 值。 如果 t1.c1 包含行 (10,20,30,40),并且 t2.c2 包含行 (10,20,50,60),则以上查询的输出为: c1 c2 10 10 20 20 30 NULL 40 NULL 另一方面,内连接是简单的连接,其结果只包含满足连接条件的表的行组合。不满足连接条件的行则被丢弃。 考虑表 t1 和 t2 中同样的数据行,则以下查询: SELECT * FROM t1, t2 WHERE t1.c1 = t2.c2; 将产生以下输出: c1 c2 10 10 20 20 ANSI 外连接特性使您能使用局部 ANSI 外连接语法,包括 ON 子句支持(它使查询能够指定在连接后的过滤器之前求值的过滤器);跟在它后面的 WHERE 子句(如果有的话)使查询能指定连接后的过滤器。还支持使用左外连接子句的查询。 SELECT * FROM t1, outer t2 ON (t1.c1 = t2.c2) WHERE t1.c1 < 30; 以上查询将产生下列输出: c1 c2 10 10 20 20 连接的实现 Informix 连接可表示为左深树(left deep tree)。例如,如果有一个查询连接了四个表:A、 B、C 和 D,则 Informix 连接树看起来如下: 上图对左深树的意思作了说明: 表 A 与表 B 连接 A-连接-B 的结果随后与表 C 连接。 结合在一起的表 A、B 和 C 的连接结果随后与表 D 连接。 因此,我们的结论是 Informix 连接的右表始终是基表。 相反,ANSI 连接可表示为丛生树(bushy tree),如下所示: 表 A 与表 B 连接。 表 C 与表 D 连接 A 和 B 的连接结果随后与 C 和 D 的连接结果连接。 在 ANSI 连接的情况中,连接的左部和右部可能是基表或连接集。这一类型的树被称为茂 密树。 ANSI 外连接向 ANSI 内连接的转换 在这一改写中,服务器尽可能地将 ANSI 外连接转换为 ANSI 内连接。结果是,查询中只有数量较少的 ANSI 外连接。尽可能使用内连接的好处在于:消除了对首先连接哪个表的限制,并有了更多连接顺序的组合。 例如,服务器可将以下查询: SELECT * FROM (t1 LEFT JOIN t2 ON (t1c1=t2c2)) WHERE t2.c3=10 转换为 SELECT * FROM (t1 JOIN t2 ON (t1c1=t2c2)) WHERE t2.c3=10 请注意:由于―拒绝空‖(NULL rejecting)过滤器 t2.c3=10 的存在,在上面的示例中转换查询是有可能的。拒绝空连接后的过滤器将丢弃那些由于不匹配主表行而被填充 NULL 值的从表行。因此,只有那些在从表的任何列上都有拒绝空过滤器的外连接查询才能转换成内连接。 ANSI 内连接向 Informix 内连接的转换 当查询只包含 ANSI 内连接时,我们可以把所有的 ANSI 内连接都转换为 Informix 内连接。因为我们没有并行地执行 ANSI 连接的茂密查询计划树,所以转换可由于并行性而提高性能。另一个好处在于连接顺序优化。Informix 内连接可以有更多的连接顺序选择,这将产生更好的最终查询计划。 例如,服务器可以将以下查询: SELECT * FROM t1 JOIN t2 ON t1.c1 = t2.c1 WHERE t1.c2 = 100 and t1.c3 = t2.c3; 转换为 SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1 and t1.c2 = 100 and t1.c3 = t2.c3; 生成计划 我们使用的优化算法是很常用的穷举搜索法。顾名思义,优化器通过查找处理查询的所有可能的方法,穷举搜索计划空间。 在这里,需要对选择率(selectivity)这一术语加以说明。过滤器的选择率是满足条件的行在表或连接的总行数中所占的比例。 请考虑下面这个查询: SELECT * FROM tab WHERE col = 50; 假设表 tab 有 1000 行。子句 col = 50 被称为过滤器。过滤器是查询中的条件,用来从表或连接中抽取满足条件的行。假设表 tab 中有 200 行是满足子句 col = 50 的。因此该过滤器的选择率就是: 200 (满足条件的行数) Selectivity = ------------------------------------- = 0.2 1000 (总行数) 请考虑另一个查询: SELECT * FROM tab1, tab2 WHERE tab1.col1 = tab2.col2; 假设表 tab1 有 100 行,表 tab2 有 200 行。有 150 行满足连接过滤器。则这个例子中的连接选择率是: 150 Selectivity = ------------ = 0.01 100 * 200 选择率在估计查询成本方面起着非常重要的作用。当在查询所涉及的列中存在统计数据时,优化器就获得计算选择率所需的最准确的信息。如果没有统计数据,那么优化器将采用一组试探性缺省值来决定过滤器的选择率。 一旦确定了查询中所有过滤器选择率的值,就找到了扫描查询中每个表的所有可能的方法。如果索引是可用的,假设索引扫描的成本小于顺序扫描的成本,则认为索引优先于整个表的顺序搜索。 表扫描或索引扫描的成本估计源自使用存储在系统目录中的统计信息。这些统计信息是用 UPDATE STATISTICS 语句来更新的。当把查询提交给优化器时,就计算处理 ORDER BY 或 GROUP BY 的成本。如果一个便于使用的索引是可用的,并且它的键与 ORDER BY 或 GROUP BY 中的列匹配,那么就会考虑使用那个索引,而且我们可能会避免一次排序,因为已经对使用该索引所获取的记录进行了排序。 控制优化器的命令 SET OPTIMIZATION 可以使用 SET OPTIMIZATION 命令设置优化的级别,如下所示: , HIGH— 优化器检查所有可能的路径。 , LOW— 优化器在每个级别选择成本最低的路径而不考虑其它可能的路径。 , FIRST_ROWS— 优化器选择不包括分块操作(如排序或构建散列表)的查询计划。 而选择有最小用户响应时间的计划。 , ALL_ROWS— 优化器选择执行时间最少且返回所有行的查询计划。 HIGH 和 ALL_ROWS 是缺省优化级别。 通常建议使用 HIGH 这一优化级别,因为它使优化器有完全的控制权来为最适宜的查询性能决定所有可能的计划。但如果查询需要过长的时间才能完成,则建议使用 LOW 这一优化级别。 如果用户响应时间(数据库服务器返回一整屏数据所需时间)在整个查询时间中占较大比例,那么应使用 FIRST_ROWS 优化。 UPDATE STATISTICS 优化器确定执行 SQL 查询的最有效策略。优化器用系统目录表的信息来确定最佳查询策略。 使用 UPDATE STATISTICS 语句更新系统目录。该语句确保提供给优化器的信息是最新的。当表被修改或删除时,数据库服务器并不自动更新系统目录中的相关统计数据。由于收集统计信息对性能的影响,您要决定何时以及如何收集那些信息并且让数据库服务器重新计算 SYSTABLES、SYSDISTRIB、SYSCOLUMNS 和 SYSINDEXES 系统目录表中的数据。对于 MEDIUM 或 HIGH 方式的 UPDATE STATISTICS,SYSDISTRIB 系统目录表保存着已更新数据分布的数据。 UPDATE STATISTICS 也可用来为 SYSPROCPLAN 系统目录表中的过程更新已优化的执行计划。每当过程执行时,如果过程中引用的任何对象更改了,则数据库服务器会重新优化它的执行计划。 当为数据库、表、列或存储过程更新统计信息时,服务器会用查询优化器确定检索查询数据的最低成本存取路径所需的所有信息,来填充系统目录表。若没有 UPDATE STATISTICS 命令提供的信息,优化器用来做决定的数据就是不准确和不完整的。 可以对整个数据库(包括它所有的表和存储过程)、对单个数据库中的表、仅对指定的表甚至对表中指定的一个或一组列运行 UPDATE STATISTICS: , UPDATE STATISTICS [LOW|MEDIUM|HIGH] — 更新整个数据库的统计信息。 , UPDATE STATISTICS [LOW|MEDIUM|HIGH] FOR TABLE [tabname] — 更新特定 表及其索引的统计信息,如果没有指定表名,则更新所有表和索引的统计信息。 , UPDATE STATISTICS [LOW|MEDIUM|HIGH] FOR TABLE tabname (colname) — 更新指定的具体列的统计信息。 UPDATE STATISTICS 的方式 有三种方式的 UPDATE STATISTICS: , LOW — 收集表、索引和列的统计信息,但不构建分布。 , MEDIUM — 收集与 LOW 相同的统计信息;还收集表、索引和列的统计信息并 且构建表示值采样准确率为 85% 到 99% 的分布块(bin)。 , HIGH — 收集与 LOW 相同的统计信息;还收集表、索引和列的统计信息并且构 建表示确切数据值的数据分布块。 当 UPDATE STATISTICS 运行于 HIGH 或 MEDIUM 方式时,数据库服务器检查指定列的内容并将它们分解为块,这些块反映数据的百分比。例如,一个块可能保存百分之二的数据,这意味着 50 个块可以保存所有数据。要设置块的大小,可使用 RESOLUTION 百分比参数。 RESOLUTION 以百分比的形式表示值。例如,RESOLUTION 为 0.5 表示每个块中的数据在整个数据中占 0.5%。因此我们总共用 200 个块表示数据,每个块保存 0.5% 的数据。 如果没有在 UPDATE STATISTICS 命令中指定 RESOLUTION 子句,那么缺省百分比 。 是 0.5 UPDATE STATISTICS MEDIUM 和 UPDATE STATISTICS HIGH 语句创建列的数据分布。在 HIGH 方式中,读取的是表中所有的行,而在 MEDIUM 方式中,行是作为样本被读取的。MEDIUM 语句将对一组列值的样本排序,然后将分布数据填充到代表该样本的 SYSDISTRIB 表中。HIGH 语句将对所有的列值排序,并在执行时用表中所有值的准确分布信息来填充 SYSDISTRIB 表。对于大型表,HIGH 方式在执行 UPDATE STATISTICS 时使用的资源和时间比 MEDIUM 方式的采样方法更多。但是,MEDIUM 方式不及 HIGH 方式准确。 不对 TEXT 或 BYTE 列创建分布。 Update Statistics 指导原则 在执行 UPDATE STATISTICS 命令时,为了确保获得最佳的统计信息以取得最适宜的查询性能并使开销最低,应按顺序遵守以下指导原则: 运行 UPDATE STATISTICS MEDIUM……只对每个表使用分布。 对每个索引的第一列运行 UPDATE STATISTICS HIGH。不要忘记主键和外键约束是通过索引实现的。对每一列单独执行 UPDATE STATISTICS 语句。接下来的步骤是区分多列索引所需的。 如果两个多列索引以同一列子集开始,则对首个不同的列运行 UPDATE STATISTICS HIGH。例如,假设有以下表和索引定义: CREATE TABLE TAB1 (a INT, b INT, c INT, d INT, e INT); CREATE INDEX ix1 ON TAB1 (a, b, c, d); CREATE INDEX ix2 ON TAB1 (a, b, e, f); 则应对列 c 和 e 运行 UPDATE STATISTICS HIGH。在步骤 2 中,可对每一列执行一个 UPDATE STATISTICS 语句。 对每个多列索引中的所有列运行 UPDATE STATISTICS LOW。可以在一个 UPDATE STATISTICS 语句中包含所有的列。可能要省略步骤 3 中更新的列,因为 HIGH 语句会填充将由 LOW 语句生成的统计信息。 要点:这些语句的顺序很重要。如果在 UPDATE STATISTICS HIGH 命令之后运行 UPDATE STATISTICS MEDIUM,则 MEDIUM 分布将覆盖 HIGH 分布。 用于执行 UPDATE STATISTICS 的步骤旨在尽可能有效地使用机器资源以取得最快的性能。因为更新小型表的开销非常小,所以不需要太多地考虑性能和资源利用。对于相对较小的表,您会发现只执行一个 UPDATE STATICTIS HIGH 语句是可接受的,例如: UPDATE STATISTICS HIGH FOR TABLE small_table; 有关使用 UPDATE STATISTICS 的更多信息,请参阅 John F. Miller III 撰写的标题为―Understanding and Tuning Update Statistics‖的 Informix Developer 专区文章,其 URL 为 使用 DBSCHEMA 显示分布信息 使用 DBSCHEMA 实用程序来显示分布和溢出信息。要显示为数据库中某个表所存储的分布信息,可使用 –hd 选项加上该表的名称。如果指定关键字 ALL 而不是表名,则显示数据库中所有表的分布信息。 dbschema -hd [owner.]tablename DBSCHEMA 的输出 以下是 DBSCHEMA 实用程序的输出示例: DBSCHEMA Schema Utility INFORMIX-SQL Version 9.30.UC3 Copyright (C) Informix Software, Inc., 1984-1998 Software Serial Number RDS#N000000 { Distribution for samar.t1.c1 Constructed on 02/17/2000 High Mode, 10.000000 Resolution --- DISTRIBUTION --- ( 22) 1: ( 6, 4, 28) 2: ( 6, 3, 31) 3: ( 6, 5, 40) 4: ( 6, 4, 56) 5: ( 4, 3, 60) --- OVERFLOW --- 1: ( 5, 25) 2: ( 4, 32) 3: ( 4, 50) 4: ( 5, 52) 5: ( 6, 55) 6: ( 4, 60) } 读取 DBSCHEMA 输出 分布信息的 DBSCHEMA 输出有以下几个部分: 分布描述 DBSCHEMA 输出的第一部分描述了为指定表创建了哪些数据分布。 分布信息 分布信息描述了为分布创建的块,表和每个块中值的范围以及每个块中不同值的数目。 溢出信息 DBSCHEMA 输出的最后部分显示了重复出现多次的值。指明的值的重复次数必须大于某个临界数量,这个临界数量大约是行数乘以 RESOLUTION 的 25%。 使用 SET EXPLAIN 了解优化路径 使用 EXPLAIN 实用程序来了解优化器所选择的路径。执行 SET EXPLAIN ON 语句后,数据库服务器生成优化器为执行查询所选择的路径,并将结果存储在文件 $PWD/sqexplain.out 中。如果当前数据库是远程的,则 sqexplain.out 文件存储在远程主机上该用户的 home 目录中。SET EXPLAIN 缺省设置为 OFF。 SQEXPLAIN 文件的各个部分 SET EXPLAIN 输出包含以下信息。为清楚起见,我们在这里将它分为不同的部分: 第 I 部分 第 I 部分包含以下内容,它们的顺序与以下列出的顺序相同: 用于查询的 SQL 语句。 , 以优化器用来比较计划的单位表示的查询成本估计值。这些单位代表查询执行的相对时 间,每个单位大约相当于一次典型的磁盘存取时间。优化器选择某个查询计划是因为执 行这个计划的估计成本在所有评估的计划中是最低的。 , 期望查询产生的行数估计。 , 执行查询所派生的最大数量的线程(如果设置了 PDQPRIORITY 的话)。 , 用于执行 ORDER BY 和,或 GROUP BY (如果需要的话)的临时文件可选项。 第 II 部分 表在这里是按访问它们的顺序列出的。对于每个表,列出了所应用的过滤器。 , 已访问表的名称 , 数据库服务器读取表所采取的存取计划 — 顺序扫描、索引路径和自动索引。另外, 如果表是分段的,则在这里列出对于这一特定查询所要访问的活动分段。 , 列出了每对表的连接计划:嵌套循环连接或动态散列连接。对于动态散列连接也列 出了执行散列连接所用到的过滤器。 第 III 部分 根据所选存取计划的不同,这一部分的内容会有所不同。对于第 II 部分中的每一个表,这一部分会出现一次。 对于顺序扫描 本部分包含要应用的过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里扩展它,它会象主查询一样包括所有的部分。 对于索引扫描和自动索引扫描 对于索引和自动索引扫描,这一部分包含以下信息: , 一些索引键,将对它们应用过滤器,跟着是以下项中的一个或全部: , 仅键项,如果它是只用到键的索引扫描。 , 聚合项,如果查询聚合了索引键。 , 键优先项,如果对索引键应用键优先过滤器。 , 下限索引过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里扩展它, 它会象主查询一样包括所有的部分。 , 上限索引过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里扩展它, 它会象主查询一样包括所有的部分。 , 要应用的键优先过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里 扩展它,它会象主查询一样包括所有的部分。 EXPLAIN 输出的示例 示例 1.在这一示例中,empname 上没有索引;需要一个临时文件来处理排序: QUERY: ---------- select * from employee order by empname Estimated Cost: 3 Estimated # of Rows Returned: 12 Temporary Files Required For: Order By 1) informix.employee: SEQUENTIAL SCAN 示例 2.在这一示例中,断言在一个有索引的列上,设置了起始键(下限索引过滤器),并且索引出现在 locnum 上。不需要用于排序的临时文件。 QUERY: ------ select * from location where locnum > 50 order by locnum Estimated Cost: 10 Estimated # of Rows Returned: 4 1) informix.location: INDEX PATH (1) Index Keys: locnum Lower Index Filter: informix.location.locnum = 50 示例 3.这一示例演示了仅键索引搜索,因为投影的所有列都在索引中,并且索引是按聚合 的列排序的: QUERY: ------ select max(deptnum) from department Estimated Cost: 1 Estimated # of Rows Returned: 1 1) informix.department: INDEX PATH (1) Index Keys: deptnum (Key-Only) (Aggregate) 示例 4.这一示例是关于分段表的,该分段表在一个列中有重叠和非连续的分段。一个等号 断言消除了所有分段: QUERY: ---------- select * from fragtab2 where col = 180 Estimated Cost: 1 Estimated # of Rows Returned: 1 1) informix.fragtab2: SEQUENTIAL SCAN (Serial, fragments: NONE) Filters: informix.fragtab2.col = 180 示例 5.这是一个查询路径,它是为一个带子查询的更新语句生成的,并且断言在未使用索 引的列上。 QUERY: ---------- update location set locnum = (select max(deptnum) from department) where locname = "Chicago" Estimated Cost: 2 Estimated # of Rows Returned: 1 1) informix.location: SEQUENTIAL SCAN Filters: informix.location.locname = 'Chicago' Subquery: --------- Estimated Cost: 1 Estimated # of Rows Returned: 1 1) informix.department: INDEX PATH (1) Index Keys: deptnum (Key-Only) (Aggregate) 示例 6.这一示例是关于分段表的,该分段表在消除分段的单一列范围断言上且有不重叠的 分段。 QUERY: ---------- select * from fragtab where col >= 50 and col <= 150 Estimated Cost: 3 Estimated # of Rows Returned: 1 1) informix.fragtab: SEQUENTIAL SCAN (Serial, fragments: 0, 1) Filters: (informix.fragtab.col >= 50 AND informix.fragtab.col <= 150 ) 示例 7.这一示例中有一个分段表,该分段表在消除分段 2 的单一列范围断言上且有不重叠的分段。分段的扫描是并行完成的,因为 PDQPRIORITY >0。PDQPRIORITY 不影响分段的消除。Maximum Threads 指明要生成的线程数(在本例中,两个分段要用到两个扫描线程)。请注意并行情况的成本与顺序情况的成本有何不同。 QUERY: ---------- select * from fragtab where col < 150 Estimated Cost: 2 Estimated # of Rows Returned: 3 Maximum Threads: 2 1) informix.fragtab: SEQUENTIAL SCAN (Parallel, fragments: 0, 1) Filters: informix.fragtab.col < 150 示例 8.在本示例中,我们将一个分段表(在单一列中且有不重叠分段)与另一个分段表(在单一列中有重叠和非连续分段)连接。在第一个表中有范围断言。需要一个临时文件来处理 Group By 排序,处理是并行执行的,生成了五个扫描线程来读取第一个表的两个分段和第二个表的三个分段 QUERY: ---------- select a.col, count(*) from fragtab a,fragtab2 b where a.col < 150 and a.col = b.col group by a.col Estimated Cost: 7 Estimated # of Rows Returned: 1 Maximum Threads: 5 Temporary Files Required For: Group By 1) informix.a: SEQUENTIAL SCAN (Parallel, fragments: 0, 1) Filters: informix.a.col < 150 2) informix.b: SEQUENTIAL SCAN (Parallel, fragments: ALL) Filters: informix.b.col < 150 DYNAMIC HASH JOIN Dynamic Hash Filters: informix.a.col = informix.b.col 示例 9.这一示例演示了一个嵌套循环连接(外表的索引扫描和内表的索引扫描): QUERY: ---------- select empname,deptname from employee e,department d where e.empdept = d.deptnum and e.empnum >= 100 and d.deptnum <= 80 Estimated Cost: 4 Estimated # of Rows Returned: 1 1) informix.e: INDEX PATH Filters: informix.e.empdept <= 80 (1) Index Keys: empnum Lower Index Filter: informix.e.empnum >= 100 2) informix.d: INDEX PATH (1) Index Keys: deptnum Lower Index Filter: informix.d.deptnum = informix.e.empdept 示例 10.这一查询将由远程服务器处理: QUERY: ---------- select * from tempdb@online50:temptab Estimated Cost: 2 Estimated # of Rows Returned: 10 1) tempdb@online50:informix.temptab: REMOTE PATH Remote SQL Request: select x0.col from tempdb:"informix".temptab x0 优化器伪指令 优化器伪指令段指定了一些关键字,可以用它们部分或完全地指定优化器的查询计划。 优化器伪指令在 SQL 查询中位于 SELECT、UPDATE 或 DELETE 关键字之后。以下是 用于指定伪指令的语法: , 双破折号(--)前缀。 , 用大括号({})括起。 , C 语言风格的注释,正斜线和星号(/* */)。 要指定多个伪指令,可用空格、逗号或任何字符来分隔它们。伪指令注释中的第一个字 符始终是加号(+)。 伪指令类型 为了影响优化器所作的查询计划选择,可改变查询的四个方面: , 存取计划 , 连接顺序 , 连接计划 , 优化目标 存取计划伪指令 存取计划是数据库服务器用来访问表的方法。数据库服务器可以按顺序读取表(全表扫描)或使用表的任何一个索引。以下伪指令会影响存取计划: INDEXindexname[,indexname] — 用这个伪指令所指定的索引来访问表。如果伪指令列出了多个索引,优化器会选择产生的成本最低的索引。 , AVOID_INDEXindexname[,indexname] — 不要使用由该伪指令列出的任何索引。 可与 AVOID_FULL 伪指令一起使用。 , FULL— 执行完全表扫描。 , AVOID_FULL— 不要对所列出的表执行全表扫描。可与 AVOID_INDEX 伪指令 一起使用。 , 在有些情况下,强制某种访问方法可以改变优化器选择的连接方法。例如,使用 AVOID_INDEX 伪指令排除某个索引可以让优化器选择散列连接而不是嵌套循环 连接。 连接顺序伪指令 连接顺序伪指令 ORDERED 强制优化器按照 SELECT 语句所列的顺序来连接表。 连接顺序对于连接计划的影响 指定连接顺序不只对如何连接表有影响。例如,请考虑下面的查询: SELECT --+ORDERED, AVOID_FULL(e) * FROM employee e, department d WHERE e.dept_no = d.dept_no AND e.salary > 5000 在这一示例中,优化器选择用散列连接来连接表。但是,如果所安排的顺序中第二个表是 employee(而且必须通过索引访问),那么使用散列连接是行不通的。 SELECT --+ORDERED, AVOID_FULL(e) * FROM department d, employee e WHERE e.dept_no = d.dept_no AND e.salary > 5000; 优化器在这一示例中选择的是嵌套循环连接。 使用视图时的连接顺序 当使用视图时,有两种情况会影响连接顺序: ORDERED 伪指令位于视图内。 在视图内的 ORDERED 伪指令只会影响视图内的表的连接顺序。必须连续地连接视图中的 表。请考虑以下的视图和查询: CREATE VIEW emp_job_view as SELECT {+ORDERED} emp.job_num, job.job_name FROM emp, job WHERE emp.job_num = job.job_num; SELECT * from dept, emp_job_view, project WHERE dept.dept_no = project.dept_num AND emp_job_view.job_num = project.job_num; ORDERED 伪指令指定表 emp 在表 job 之前。该伪指令不会影响表 dept 和表 project 的 顺序。因此,以下是所有可能的连接顺序: q emp, job, dept, project q emp, job, project, dept q project, emp, job, dept q dept, emp, job, project q dept, project, emp, job q project, dept, emp, job ORDERED 伪指令位于包含视图的查询内。 如果 ORDERED 伪指令出现在包含视图的查询中,那么查询中表的连接顺序与它们在 SELECT 语句中列出的顺序相同。对于视图内的表则按照它们在视图内列出的顺序进行连 接。 在以下查询中,连接顺序是 dept、project、emp 和 job: CREATE VIEW emp_job_view AS SELECT emp.job_num, job.job_name FROM emp, job WHERE emp.job_num = job.job_num; SELECT {+ORDERED} * FROM dept, project, emp_job_view WHERE dept.dept_no = project.dept_num AND emp_job_view.job_num = project.job_num; 这一规则有例外,即当视图无法包括到查询中时,如下面的例子所示: CREATE VIEW emp_job_view2 AS SELECT DISTINCT emp.job_num, job.job_name FROM emp,job WHERE emp.job_num = job.job_num; 在这一示例中,数据库服务器执行查询并将结果放置在一个临时表中。该查询中表的顺序是 dept、project 和 temp_table。 连接计划伪指令 连接计划伪指令影响数据库服务器连接查询中两个表的方式。 以下伪指令会影响两个表之间的连接计划: , USE_NL— 在嵌套循环连接中将所列的表作为内表使用。 , USE_HASH — 用散列连接访问所列的表。还可以选择是否使用表,通过使用 ―BUILD‖或―PROBE‖加上相应的表名,创建或探测散列表。 例如,在下面的查询中,优化器被强制使用 dept 表来构造散列表。 , SELECT /*+ USE_HASH (dept /BUILD) */ , name, title, salary, dname , FROM emp, dept, job , WHERE loc = 'Phoenix' , AND emp.dno = dept.dno , AND emp.job = job.job , AVOID_NL— 不要在嵌套循环连接中将所列的表作为内表使用。用该伪指令列出的 表仍可以作为外表参与嵌套循环连接。 , AVOID_HASH— 不要用散列连接访问所列的表。可选的方法是,允许使用散列连接, 但不允许探测该表也不允许从该表构建散列表。 优化目标伪指令 可以针对以下任意一种情况用优化目标伪指令来优化查询: , FIRST_ROWS— 选择这样的计划:对只查找满足查询的第一行这一过程进行优 化。 , ALL_ROWS— 选择这样的计划:对查找满足查询的所有行(缺省行为)这一过程 进行优化。 , 使用 FIRST_ROWS 伪指令,如果查询计划中包含非常耗时的预先活动,则优化 器可能会放弃该查询计划。例如,散列连接可能要花去过多的时间来创建散列表。 如果只有几行是必须返回的,那么优化器可能会选择嵌套循环连接。 在以下的示例中,假设数据库在 employee.dept_no 上有索引,但在 department.dept_no 上没有索引。如果没有伪指令,则优化器会选择散列连接。 SELECT * FROM employee, department WHERE employee.dept_no = department.dept_no 但是,如果使用 FIRST_ROWS 伪指令,由于创建散列表需要较高的初始开销,优化器会 选择嵌套循环连接。 SELECT {+first_rows} * FROM employee, department WHERE employee.dept_no = department.dept_no 分布式查询处理 分布式处理所需要的基本操作是: , 远程目录信息的读取 , 元组的远程获取 , 更新、插入和删除的远程执行 , 函数和过程的远程执行。 当在查询中首次引用远程表时,要从远程系统目录而不是本地系统目录中读取消息表、列、索引和权限信息。优化器随后可以使用这些信息来优化查询。 分布式查询优化器在两个方面与常规查询优化器有所不同。首先,它考虑了涉及到网络操作这一事实。因此,它把通过网络移动数据的成本包括在它的成本公式中。这一成本是基于必须发送的数据量以及发送数据所需信息包的数目计算出来的。 分布式查询优化器的第二个不同之处在于执行远程连接的能力。下表概括了各种优化选择: , 外表与内表都是本地的。这里唯一合理的选择是在本地执行连接。 , 外表是本地的,内表是远程的。这里有两种选择。优化器对两者都加以考虑,然后 选择成本较低的一种。 , 将内表移到本地位置的临时表中,然后在本地执行连接。 , 将外表的连接值发送到远程位置,然后远程执行连接。 , 外表是远程的,内表是本地的。将数据实时地从外表移到本地位置,然后在本地执 行连接。 , 外表与内表都在远程位置。 一种选择是将两个表都移到本地位置,然后在本地执行连接。必须将内表写到临时表中。 另一种选择是将外表实时地移到本地位置,将合适的连接值发送到远程位置,然后远程地执行连接。 如果两个表都在同一远程位置,则第三种选择是远程地执行连接。 视图处理 视图是基于处理它们的方法分类的。视图可以是简单的,也可以是复杂的。 简单视图 简单视图是那些不包含聚合、GROUP BY 子句、DISTINCT 子句或外连接的视图。通过将视图的组件包括到引用该视图的 SQL 语句来处理这些视图。例如: CREATE VIEW view1(vcol1, vcol2) AS SELECT col1, col2 FROM tab2 WHERE col3 = 10; SELECT t.tcol1, v.* FROM tab1 t, view1 v WHERE t.tcol2 = v.vcol2; 如下所示,视图被包括到 SELECT 语句: SELECT t.tcol1, v.col1, v.col2 FROM tab1 t, tab2 v WHERE t.tcol2 = v.col AND v.col3 = 10; 视图内的表被包括到查询的 FROM 子句。视图的相关列被包括到查询的投影列表,视图的 WHERE 子句被包括到查询的 WHERE 子句。 复杂视图 复杂视图是那些在投影列表中包含聚合、有外连接、有 GROUP BY 子句或有 UNION 子句的视图。只有通过执行视图定义中的查询、在临时表中对结果排序、然后在查询的余下部分引用临时表,才能对复杂视图进行选择和处理。例如: CREATE VIEW view1(vcol1, vcol2) AS SELECT col1, SUM(col3) FROM tab2 WHERE col2 > 10 GROUP BY tab2.col1; SELECT t.tcol1, v.* FROM tab1 t, view1 v WHERE t.tcol2 = v.vcol1; 在这一示例中,视图定义中的 SELECT 语句得以执行,而且结果被存储在临时表中,然后在选择查询中引用该临时表。 分段消除 可基于分布模式跨数据库空间(dbspace)对表及其索引进行分段。分段使引擎能支持查询的并行执行。分段分布模式包括: , 按轮循分段 , 按表达式分段 , 按散列分段 按多种模式的混合分段 根据分段策略和查询的选择标准,有可能确定从查询执行中可以消除哪些分段。以下示例演示了如何创建按表达式分段的表以及从该表进行选择: CREATE TABLE tab1 (col1 INT , col2 INT) FRAGMENT BY EXPRESSION col1 < 100 IN dbspace1, col1 >= 100 IN dbspace2, col1 >= 200 IN dbspace3; SELECT * FROM tab1 WHERE col1 > 150; 在上面的示例中,只需要扫描 dbspace2 和 dbspace3 中表的分段。消除了 dbspace1 分段,因为它不符合选择查询标准。此外,如果启用了 PDQ,那么可通过多个扫描线程来并行完成分段的扫描。 关于分段表和索引的成本计算基于以下三点假设: , 如果设置了 PDQ,则并行(即使用并行线程)扫描表或索引的所有分段。如果没有 设置 PDQ,则按顺序扫描所有必需的分段,并且将象处理非分段表那样处理分段表 (除了可以在分段表中进行分段消除以外)。 , 成本主要由扫描最大分段的成本来决定;也就是说,扫描分段表或索引所用时间就 是扫描最大分段所用时间。换句话说,扫描的调用者必须等待,直到对最大分段扫 描的完成。 , 实际的分段模式(轮循或表达式)无关紧要。一旦完成分段消除并确定了最大分段 的大小,优化器就会忽略分段模式以便进行成本估计。
本文档为【informix系统管理维护手册】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_003124
暂无简介~
格式:doc
大小:257KB
软件:Word
页数:111
分类:企业经营
上传时间:2017-09-03
浏览量:39