简介
理解 DB2 如何使用内存,可以防止过度分配内存,并有助于对内存的使用进行调优,从而获得更好的性能。
本文将向您传授 DB2 内存使用的基础,以及共享内存和私有内存的概念。这些内容同时适用于 32 位和 64 位的系统。虽然对于 64 位系统有一些限制,但是在未来的一段时间内还不大可能触及这些限制。因此,我们将焦点放在影响 32 位系统的内存限制,并对之进行详细的讨论。
我们首先讨论一般情况下 DB2 如何使用内存,接着讨论内存管理如何随着平台(AIX、Sun、HP、Linux 和 Windows)的不同而变化,以及它们对 DB2 的影响。最后,我们将给出一些实际生活中客户处境/问题以及他们的解决方案的有意义的例子。
本文的内容适用于 DB2 version 8。
DB2 内存结构概述
图 1中说明了 DB2 内存结构。这种内存结构在所有平台上都是一致的。
注意:在多分区环境中,下面的图适用于多分区实例中的每个分区。
图 1 - DB2 内存结构
DB2 在 4 种不同的内存集(memory set)内拆分和管理内存。这 4 种内存集分别是:
每种内存集由各种不同的内存池(亦称堆)组成。图 1 也给出了各内存池的名称。例如, locklist是属于数据库共享内存集的一个内存池。 sortheap是属于代理私有内存集的一个内存池。
我们将详细讨论每一种内存集。
实例共享内存
每个 DB2 实例都有一个实例共享内存。实例共享内存是在数据库管理器启动(db2start)时分配的,并随着数据库管理器的停止(db2stop)而释放。这种内存集用于实例级的任务,例如监控、审计和节点间通信。下面的数据库管理器配置(dbm cfg)参数控制着对实例共享内存以及其中个别内存池的限制:
instance_memory参数指定为实例管理预留的内存数量。默认值是 AUTOMATIC。这意味着 DB2 将根据监视器堆、审计缓冲区和 FCM 缓冲区的大小计算当前配置所需的实例内存数量。此外,DB2 还将分配一些额外的内存,作为溢出缓冲区。每当某个堆超出了其配置的大小时,便可以使用溢出缓冲区来满足实例共享内存区内任何堆的峰值需求。在这种情况下,个别堆的设置是 软限制的,它们可以在内存使用的峰值期间进一步增长。
如果 instance_memory被设置为某一个数字,则采用 instance_memory与 mon_heap_sz、 audit_buf_sz和 fcm_num_buffers的和之间的较大者。这时,对实例内存就施加了一个硬性的限制,而不是软限制。当达到这个限制时,就会收到内存分配错误。出于这个原因,建议将 instance_memory的设置保留为 AUTOMATIC。
如果 instance_memory被设为 AUTOMATIC,则可以使用下面的命令来确定它的值:
下面的输出表明有 42 MB 的内存被预留给实例共享内存集(10313 页 * 4096 字节/页):
instance_memory参数只是设置了实例共享内存的限制。它并没有说出当前使用了多少内存。要查明一个实例的内存使用情况,可以使用 DB2 内存跟踪器工具 db2mtrk。例如,
上面的例子表明,虽然预留给实例共享内存集的内存有 42 MB,但在 db2mtrk运行时只用到了大约 21 MB。
注意: 在某些情况下,db2mtrk 显示的大小会大于指定给配置参数的值。在这种情况下,赋予配置参数的值被作为一种软限制,内存池实际使用的内存可能会增长,从而超出配置的大小。
数据库共享内存
每个数据库有一个数据库共享内存集。数据库共享内存是在数据库被激活或者第一次被连接上的时候分配的。该内存集将在数据库处于非激活状态时释放(如果数据库先前是处于激活状态)或者最后一个连接被断开的时候释放。这种内存用于数据库级的任务,例如备份/恢复、锁定和 SQL 的执行。
图2展示了数据库共享内存集内的各种内存池。括号中显示了控制这些内存池大小的配置参数。
图 2 - DB2 数据库共享内存
完整的绿色方框意味着,在数据库启动的时候,该内存池是完全分配的,否则,就只分配部分的内存。例如,当一个数据库第一次启动时,不管 util_heap_sz的值是多少,只有大约 16 KB 的内存被分配给实用程序堆。当一个数据库实用程序(例如备份、恢复、导出、导入和装载)启动时,才会按 util_heap_sz指定的大小分配全额的内存。
主缓冲池
数据库缓冲池通常是数据库共享内存中最大的一块内存。DB2 在其中操纵所有常规数据和索引数据。一个数据库必须至少有一个缓冲池,并且可以有多个缓冲池,这要视工作负载的特征、数据库中使用的数据库页面大小等因素而定。例如,页面大小为 8KB 的表空间只能使用页面大小为 8KB 的缓冲池。
可以通过 CREATE BUFFERPOOL 语句中的 EXTENDED STORAGE 选项“扩展”缓冲池。扩展的存储(ESTORE)充当的是从缓冲池中被逐出的页的辅助缓存,这样可以减少 I/O。ESTORE 的大小由 num_estore_segs 和 estore_seg_sz 这两个数据库配置参数来控制。如果使用 ESTORE,那么就要从数据库共享内存中拿出一定的内存,用于管理 ESTORE,这意味着用于其他内存池的内存将更少。
这时您可能要问,为什么要这么麻烦去使用 ESTORE?为什么不分配一个更大的缓冲池呢?答案跟可寻址内存(而不是物理内存)的限制有关,我们在后面会加以讨论。
隐藏的缓冲池
当数据库启动时,要分配 4 个页宽分别为 4K、8K、16K 和 32K 的小型缓冲池。这些缓冲池是“隐藏”的,因为在系统编目中看不到它们(通过 SELECT * FROM SYSCAT.BUFFERPOOLS 显示不出)。
如果主缓冲池配置得太大,则可能出现主缓冲池不适合可寻址内存空间的情况。(我们在后面会谈到可寻址内存。)这意味着 DB2 无法启动数据库,因为一个数据库至少必须有一个缓冲池。如果数据库没有启动,那么就不能连接到数据库,也就不能更改缓冲池的大小。由于这个原因,DB2 预先分配了 4 个这样的小型缓冲池。这样,一旦主缓冲池无法启动,DB2 还可以使用这些小型的缓冲池来启动数据库。(在此情况下,用户将收到一条警告(SQLSTATE 01626))。这时,应该连接到数据库,并减少主缓冲池的大小。
排序堆的阈值( sheapthres, sheapthres_shr)
如果没有索引满足所取的行的要求顺序,或者优化器断定排序的代价低于索引扫描,那么就需要进行排序。DB2 中有两种排序,一种是私有排序,一种是共享排序。私有排序发生在代理的私有代理内存(在下一节讨论)中,而共享排序发生在数据库的数据库共享内存中。
对于私有排序,数据库管理器配置参数 sheapthres指定了私有排序在任何时刻可以消耗的内存总量在实例范围内的 软限制。如果一个实例总共消耗的私有排序内存达到了这一限制,那么为额外传入的私有排序请求所分配的内存将大大减少。这样就会在 db2diag.log 中看到如下消息:
"Not enough memory available for a (private) sort heap of size size of sortheap. Trying smaller size..."
如果启用了内部分区并行性(intra-partition parallelism)或者集中器(concentrator),那么当 DB2 断定共享排序比私有排序更有效时,DB2 就会选择执行共享排序。如果执行共享排序,那么就会在数据库共享内存中分配用于这种排序的排序堆。用于共享排序的最大内存量是由 sheapthres_shr数据库参数指定的。这是对共享排序在任何时刻可以消耗的内存总量在数据库范围内的 硬限制。当达到这个限制时,请求排序的应用程序将收到错误 SQL0955 (rc2)。之后,在共享内存总消耗量回落到低于由 sheapthres_shr指定的限制之前,任何共享排序内存的请求都得不到允许。
下面的公式可以计算出数据库共享内存集大致需要多少内存:数据库共享内存 = (主缓冲池 + 4 个隐藏的缓冲池 + 数据库堆 +实用程序堆 + locklist + 包缓存 + 编目缓存) + (estore 的页数 * 100 字节) + 大约 10% 的开销
对于启用了 intra_parallel 或集中器情况下的数据库,共享排序内存必须作为数据库共享内存的一部分预先分配,因而上述公式变为:数据库共享内存 = (主缓冲池 + 4 个隐藏的缓冲池 + 数据库堆 +实用程序堆 + locklist + 包缓存 + 编目缓存 + sheapthres_shr) + (estore 的页数 * 100 字节) + 大约 10% 的开销
提示: 为了发现分配给主缓冲池的内存有多少,可以发出:
SELECT * FROM SYSCAT.BUFFERPOOLS
虽然大多数内存池的大小是由它们的配置参数预先确定的,但下面两种内存池的大小在默认情况下却是动态的:
将 maxappls设为 AUTOMATIC的效果是,允许任意数量的连接数据库的应用程序。DB2 将动态地分配所需资源,以支持新的应用程序。因此,包缓存和编目的大小可以随着 maxappls的值而变化。
除了上述参数以外,还有一个参数也会影响数据库共享内存的数量。这个参数就是 database_memory。该参数的缺省值是 AUTOMATIC。这意味着 DB2 将根据以上列出的各内存池的大小来计算当前配置所需的数据库内存量。此外,DB2 还将为溢出缓冲区分配一些额外的内存。每当某个堆超出了其配置的大小时,便可以使用溢出缓冲区来满足实例共享内存区内任何堆的峰值需求。
如果 database_memory被设为某个数字,则采用 database_memory与各内存池之和这两者之间的较大者。
如果 database_memory被设为 AUTOMATIC,则可以使用以下命令来显示它的值:
使用 db2mtrk 工具显示当前使用的内存量: db2mtrk -i -d -v (在 Windows 中,-i 必须指定。在 UNIX 中,-i 是可选的。)
|
应用程序组共享内存
这种共享内存集仅适用于以下环境。(对于其他环境,这种内存集不存在。)
注意:当 max_connections的值大于 max_coordagents的值时,连接集中器便被启用。这两个参数可以在数据库管理器配置中找到。(使用 GET DBM CFG 显示数据库管理器配置。)
在以上环境中,应用程序通常需要不止一个的代理来执行其任务。允许这些代理之间能够彼此通信(相互发送/接收数据)很有必要。为了实现这一点,我们将这些代理放入到一个称作 应用程序组的组中。属于相同应用程序组的所有 DB2 代理都使用 应用程序组共享内存进行通信。
应用程序组内存集是从数据库共享内存集中分配的。其大小由 appgroup_mem_sz数据库配置参数决定。
多个应用程序可以指派给同一个应用程序组。一个应用程序组内可以容纳的应用程序数可以这样计算: appgroup_mem_sz / app_ctl_heap_sz
在应用程序组内,每个应用程序都有其自己的 应用程序控制堆。此外,应用程序组共享内存中有一部分要预留给应用程序组共享堆。如下图所示:
图 3 - DB2 应用程序组共享内存
例 1
考虑以下数据库配置:
可以计算出下面的值:
不要被 app_ctrl_heap_sz 参数迷惑。这个参数不是一个应用程序组内用于每个应用程序的各应用程序控制堆的大小。它只是在计算这个应用程序组内可容纳多少应用程序时用到的一个值。每个应用程序的实际应用程序控制堆大小都是通过 图 3中给出的公式计算的,这个公式就是 ((100 - groupheap_ratio)% * app_ctrl_heap_sz)。
因此,groupheap_ratio 越高,应用程序组共享堆就越大,从而用于每个应用程序的应用程序控制堆就越小。
例 2
假设在一天中最忙的时间里,有 200 个应用程序连接到例 1 中所描述的数据库上。由于每个应用程序组可以容纳 78 个应用程序,因此我们需要 200/78 = 3 个应用程序组来容纳总共 200 个应用程序。这里应确保系统有足够多的 RAM 来支持这一配置。否则就会发生 SQL10003N 错误。
代理私有内存
每个 DB2 代理进程都需要获得内存,以执行其任务。代理进程将代表应用程序使用内存来优化、构建和执行访问计划,执行排序,记录游标信息(例如位置和状态),收集统计信息,等等。为响应并行环境中的一个连接请求或一个新的 SQL 请求,要为一个 DB2 代理分配代理私有内存。
代理的数量受下面两者中的较低者限制:
代理私有内存集由以下内存池组成。这些内存池的大小由括号中的数据库配置参数指定:
我们曾提到,私有内存是在一个 DB2 代理被“指派”执行任务时分配给该代理的。那么,私有内存何时释放呢?答案取决于 dbm cfg 参数 num_poolagents的值。该参数的值指定任何时候可以保留的闲置代理的最大数目。如果该值为 0,那么就不允许有限制代理。只要一个代理完成了它的工作,这个代理就要被销毁,它的内存也要返回给操作系统。如果该参数被设为一个非零值,那么一个代理在完成其工作后不会被销毁。相反,它将被返回到闲置代理池,直到闲置代理的数目到达 num_poolagents指定的最大值。当传入一个新的请求时,就要调用这些闲置代理来服务该新请求。这样就减少了创建和销毁代理的开销。
当代理变成闲置代理时,它仍然保留了其代理的私有内存。这样设计是为了提高性能,因为当代理被再次调用时,它便有准备好的私有内存。如果有很多的闲置代理,并且所有这些闲置代理都保留了它们的私有内存,那么就可能导致系统耗尽内存。为了避免这种情况,DB2 使用一个注册表变量来限制每个闲置代理可以保留的内存量。这个变量就是 DB2MEMMAXFREE。它的默认值是 8 388 608 字节。这意味着每个闲置代理可以保留最多 8MB 的私有内存。如果有 100 个闲置代理,那么这些代理将保留 800MB 的内存,因此它们很快就会耗尽 RAM。您可能希望降低或增加这一限制,这取决于 RAM 的大小。
图 1展示了一个 DB2 实例的 DB2 内存结构。 图 4将展示在同一个系统上有两个实例并发运行的情况。虚拟内存包括物理 RAM 和调页空间(paging space)。共享内存“倾向于”留在 RAM 中,因为对它们的访问更频繁。如果代理闲置了较长的一段时间,则其代理私有内存将被调出。
图 4 - 并发运行的两个 DB2 实例
共享内存与私有内存
至此,我们已经讨论了实例共享内存、数据库共享内存和应用程序组共享内存以及代理私有内存。但是共享内存和私有内存的意义是什么呢?
为了理解共享内存与私有内存之间的不同之处,首先让我们通过快速阅读 DB2 进程 model来了解一下 DB2 代理进程。在 DB2 中,所有数据库请求都是由 DB2 代理或子代理来服务的。例如,当一个应用程序连接到一个数据库时,就有一个 DB2 代理指派给它。当该应用程序发出任何数据库请求(例如一个 SQL 查询)时,该代理就会出来执行完成这个查询所需的所有任务 —— 它代表该应用程序工作。(如果数据库是分区的,或者启用了 intra-parallel,那么可以分配不止一个的代理来代表应用程序工作。这些代理叫做 子代理。)
每个代理或子代理都被当作一个 DB2 进程,它获得一定数量的内存来执行工作。这种内存被称作 代理私有内存—— 它不能与其他任何代理共享。之前我们曾提到过,代理私有内存包括一些内存池,例如应用程序堆大小、排序堆大小和语句堆大小。(参见 图 1)
除了私有内存(代理在其中使用 排序堆执行“私有”任务,例如私有排序)外,代理还需要数据库级的资源,例如缓冲池、 locklist和日志缓冲区。这些资源在数据库共享内存中(参见 图 1)。 DB2 的工作方式是,数据库共享内存中的所有资源都由连接到相同数据库的所有代理或子代理共享。因此,该内存集被称作共享内存,而不是私有内存。例如,连接到数据库 A 的代理 x 使用数据库 A 的数据库共享内存中的资源。现在又有一个代理,即代理 y 也连接到数据库 A。那么代理 y 将与代理 x 共享数据库 A 的数据库内存。(当然,代理 x 和代理 y 都有其自己的代理私有内存,这些代理私有内存不是共享的。)
这样的逻辑同样适用于实例共享内存和应用程序组共享内存。
下图展示了当两个 DB2 代理(代理 x 和代理 y)连接到数据库 A 时分配的 DB2 内存集。假设:
图 5 - DB2 代理进程内存地址空间
图 5 展示了在 RAM 中分配的以下内存集:
代理 x 和代理 y 共享相同的实例内存、数据库内存和应用程序组内存,因为它们属于相同的实例、相同的数据库和相同的应用程序组。此外,它们有其自己的代理私有内存。
每个 DB2 代理进程都有其自己的内存地址空间。在内存空间中的内存地址允许代理访问物理 RAM 中的内存。我们可以把这些地址看作指向 RAM 的指针,如 图 5所示。对于任何 DB2 进程,这个地址空间必须能够容纳上述所有 4 种内存集。
前面已提到,ESTORE 是用于扩展缓冲池的大小。那么您可能要问,为什么不创建一个更大的缓冲池呢。答案是:因为地址空间受到限制,地址空间可能容不下一个更大的缓冲池!在此情况下,需要定义一个较小的地址空间能够容纳的缓冲池。如果有过量的物理内存,那么可以用该内存来配置 ESTORE。
那么,我们怎么知道一个 DB2 代理的地址空间是多大呢?地址空间的大小取决于当前的实例是 32 位的实例还是 64 位的实例。我们将在下一节对此进行解释。
32 位体系结构与 64 位体系结构中的可寻址内存
如果有一个 64 位的 DB2 实例,则意味着 DB2 使用的是 64 位的内存体系结构。在这种体系结构中,对于所有平台,每个进程的地址空间都是 2 的 64 次方,或者 18,446,744,073 GB。这是一个相当巨大的内存。将所有 DB2 内存集放入这个地址空间应该没有问题。
另一方面,如果有一个 32 位 DB2 实例,则对于所有平台,地址空间只有 2 的 32 次方,或者 4 GB(Linux/390 平台除外,在此平台下地址空间实际上只有 2 的 31 次方。不过,在本文中我们不讨论 Linux/390 中的 DB2)。所以,不管物理 RAM 有多大,要使一个 DB2 进程能够访问它所需的所有资源,包括实例共享内存、数据库共享内存、应用程序组共享内存、它自己的代理私有内存以及用于内核的内存等,所有这些资源必须能放入到 4GB 的地址空间内。
这就导致了两个非常重要的问题:
虽然 4GB 的地址空间限制适用于所有平台,但是对于上述问题的回答却与平台有关。例如,在 AIX 系统上可以分配给 DB2 数据库的最大数据库内存数就与 Solaris 系统上的数据库不同。接下来的几节将讨论不同的平台对 DB2 中的内存配置有何影响。
注意: 本文的后续部分只针对 32 位内存体系结构。我们即将讨论的问题不适用于 64 位的体系结构。
32 位 AIX 中的 DB2 内存配置
在 32 位的 AIX 上,4GB 的可寻址内存空间被拆分为 16 个段,每段 256MB。 图 6展示了用于一个 DB2 代理进程的 32位 内存地址空间。(假设 DB2_MMAP_READ 和 DB2_MMA_WRITE 这两个 DB2 注册表变量都被设为 NO。如果这两个变量没有设为 NO,则表示方法会有点不同。我们将在后面解释。)
图 6 - AIX 中的 DB2 32 位内存地址空间
段 0 - 预留给 AIX 内核。
段 1 - 预留给 db2sysc 进程。
段 2 - 预留给代理私有内存。
段 3 - 预留给实例共享内存。
段 4 到段 B - 数据库共享内存始于段 4,这些段必须紧挨在一起。所有这 8 个段(2GB)可能都被用于数据库共享内存。但是,下面的每种配置都会从数据库共享内存中拿出一个段(256MB)。
注意: 对于下面的每种配置,DB2 将从数据库共享内存中拿出一个段,这个段始于段 B。
段 C - 预留给 DB2 跟踪使用程序。
段 D 和 F- 预留给 DB2 共享库
段 E - 在默认情况下,这个段是不用的。不过,如果设置 DB2_MMAP_READ=NO 和 DB2_MMAP_WRITE=NO,那么该段用于 DB2 代理以及本地应用程序之间的通信(如 图 6所示)。这将有效地为数据库共享内存一个段。
注意: 为了最大化数据库共享内存的空间,应使用以下注册表变量设置:DB2_FORCE_FCM_BP=NO (该值是默认值),DB2_MMAP_READ=NO,DB2_MMAP_WRITE=NO。