DB2 UDB 内存模型

Sylvia Qi (sylviaq@ca.ibm.com), DB2 UDB 支持专家, IBM
Michael Dang (dangm@ca.ibm.com), DB2 数据库管理员, IBM

2004 年 8 月 01 日

本文将向您传授关于 DB2 如何使用内存的基础知识。而且,本文还将详细讨论 32 位内存体系结构的一些限制。对于 AIX、Solaris、HP-UX、Linux 和 Windows 这些受支持的每个平台,我们都给出了最常见的关于内存分配问题的例子。

获取本文中用到的产品和工具

请到 IBM 评估版软件下载 下载和试用 IBM 软件。


简介

理解 DB2 如何使用内存,可以防止过度分配内存,并有助于对内存的使用进行调优,从而获得更好的性能。

本文将向您传授 DB2 内存使用的基础,以及共享内存和私有内存的概念。这些内容同时适用于 32 位和 64 位的系统。虽然对于 64 位系统有一些限制,但是在未来的一段时间内还不大可能触及这些限制。因此,我们将焦点放在影响 32 位系统的内存限制,并对之进行详细的讨论。

我们首先讨论一般情况下 DB2 如何使用内存,接着讨论内存管理如何随着平台(AIX、Sun、HP、Linux 和 Windows)的不同而变化,以及它们对 DB2 的影响。最后,我们将给出一些实际生活中客户处境/问题以及他们的解决方案的有意义的例子。

本文的内容适用于 DB2 version 8。


DB2 内存结构概述

图 1中说明了 DB2 内存结构。这种内存结构在所有平台上都是一致的。

注意:在多分区环境中,下面的图适用于多分区实例中的每个分区。

图 1 - DB2 内存结构
image

DB2 在 4 种不同的内存集(memory set)内拆分和管理内存。这 4 种内存集分别是:

  • 实例共享内存(instance shared memory)
  • 数据库共享内存(database shared memory)
  • 应用程序组共享内存(application group shared memory)
  • 代理私有内存(agent private memory)

每种内存集由各种不同的内存池(亦称堆)组成。图 1 也给出了各内存池的名称。例如, locklist是属于数据库共享内存集的一个内存池。 sortheap是属于代理私有内存集的一个内存池。

我们将详细讨论每一种内存集。

实例共享内存

每个 DB2 实例都有一个实例共享内存。实例共享内存是在数据库管理器启动(db2start)时分配的,并随着数据库管理器的停止(db2stop)而释放。这种内存集用于实例级的任务,例如监控、审计和节点间通信。下面的数据库管理器配置(dbm cfg)参数控制着对实例共享内存以及其中个别内存池的限制:

  • 实例内存( instance_memory)。
  • 监视器堆( mon_heap_sz):用于监控。
  • Audit Buffer( audit_buf_sz):用于 db2audit 实用程序。
  • Fast Communication buffers ( fcm_num_buffers):用于分区之间的节点间通信。仅适用于分区的实例。

instance_memory参数指定为实例管理预留的内存数量。默认值是 AUTOMATIC。这意味着 DB2 将根据监视器堆、审计缓冲区和 FCM 缓冲区的大小计算当前配置所需的实例内存数量。此外,DB2 还将分配一些额外的内存,作为溢出缓冲区。每当某个堆超出了其配置的大小时,便可以使用溢出缓冲区来满足实例共享内存区内任何堆的峰值需求。在这种情况下,个别堆的设置是 限制的,它们可以在内存使用的峰值期间进一步增长。

如果 instance_memory被设置为某一个数字,则采用 instance_memorymon_heap_szaudit_buf_szfcm_num_buffers的和之间的较大者。这时,对实例内存就施加了一个硬性的限制,而不是软限制。当达到这个限制时,就会收到内存分配错误。出于这个原因,建议将 instance_memory的设置保留为 AUTOMATIC

如果 instance_memory被设为 AUTOMATIC,则可以使用下面的命令来确定它的值:

  • db2 attach to instance_name(其中 instance_name是实例的名称)
  • db2 get dbm cfg show detail

下面的输出表明有 42 MB 的内存被预留给实例共享内存集(10313 页 * 4096 字节/页):

  • Size of instance shared memory (4KB) (INSTANCE_MEMORY) = AUTOMATIC(10313) AUTOMATIC(10313)

instance_memory参数只是设置了实例共享内存的限制。它并没有说出当前使用了多少内存。要查明一个实例的内存使用情况,可以使用 DB2 内存跟踪器工具 db2mtrk。例如,

  • db2start
  • db2mtrk -i -v
  • Memory for instance
  • FCMBP Heap is of size 17432576 bytes
  • Database Monitor Heap is of size 180224 bytes
  • Other Memory is of size 3686400 bytes
  • Total: 21299200 bytes

上面的例子表明,虽然预留给实例共享内存集的内存有 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))。这时,应该连接到数据库,并减少主缓冲池的大小。

排序堆的阈值( sheapthressheapthres_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

虽然大多数内存池的大小是由它们的配置参数预先确定的,但下面两种内存池的大小在默认情况下却是动态的:

  • 包缓存: pckcachesz = maxappls * 8
  • 编目缓存: catalogcache_sz = maxappls * 4
  • 活动应用程序的最大数量: maxappls = AUTOMATIC

maxappls设为 AUTOMATIC的效果是,允许任意数量的连接数据库的应用程序。DB2 将动态地分配所需资源,以支持新的应用程序。因此,包缓存和编目的大小可以随着 maxappls的值而变化。

除了上述参数以外,还有一个参数也会影响数据库共享内存的数量。这个参数就是 database_memory。该参数的缺省值是 AUTOMATIC。这意味着 DB2 将根据以上列出的各内存池的大小来计算当前配置所需的数据库内存量。此外,DB2 还将为溢出缓冲区分配一些额外的内存。每当某个堆超出了其配置的大小时,便可以使用溢出缓冲区来满足实例共享内存区内任何堆的峰值需求。

如果 database_memory被设为某个数字,则采用 database_memory与各内存池之和这两者之间的较大者。

如果 database_memory被设为 AUTOMATIC,则可以使用以下命令来显示它的值:

  • db2 connect to dbnameuser useridusing pwd
  • db2 get db cfg for dbnameshow detail

使用 db2mtrk 工具显示当前使用的内存量: db2mtrk -i -d -v (在 Windows 中,-i 必须指定。在 UNIX 中,-i 是可选的。)

	Memory for database: SAMPLE
Backup/Restore/Util Heap is of size 16384 bytes
Package Cache is of size 81920 bytes
Catalog Cache Heap is of size 65536 bytes
Buffer Pool Heap is of size 4341760 bytes
Buffer Pool Heap is of size 655360 bytes
Buffer Pool Heap is of size 393216 bytes
Buffer Pool Heap is of size 262144 bytes
Buffer Pool Heap is of size 196608 bytes
Lock Manager Heap is of size 491520 bytes
Database Heap is of size 3637248 bytes
Other Memory is of size 16384 bytes
Application Control Heap is of size 327680 bytes
Application Group Shared Heap is of size 57344000 bytes
Total: 67829760 bytes

应用程序组共享内存

这种共享内存集仅适用于以下环境。(对于其他环境,这种内存集不存在。)

  • 多分区(multi-partitioned)数据库。
  • 启用了内部并行(intra-parallel)处理的未分区(non-partitioned)数据库。
  • 支持连接集中器的数据库。

注意:max_connections的值大于 max_coordagents的值时,连接集中器便被启用。这两个参数可以在数据库管理器配置中找到。(使用 GET DBM CFG 显示数据库管理器配置。)

在以上环境中,应用程序通常需要不止一个的代理来执行其任务。允许这些代理之间能够彼此通信(相互发送/接收数据)很有必要。为了实现这一点,我们将这些代理放入到一个称作 应用程序组的组中。属于相同应用程序组的所有 DB2 代理都使用 应用程序组共享内存进行通信。

应用程序组内存集是从数据库共享内存集中分配的。其大小由 appgroup_mem_sz数据库配置参数决定。

多个应用程序可以指派给同一个应用程序组。一个应用程序组内可以容纳的应用程序数可以这样计算: appgroup_mem_sz / app_ctl_heap_sz

在应用程序组内,每个应用程序都有其自己的 应用程序控制堆。此外,应用程序组共享内存中有一部分要预留给应用程序组共享堆。如下图所示:

图 3 - DB2 应用程序组共享内存

例 1
考虑以下数据库配置:

  • 最大应用程序内存集大小 (4KB) (APPGROUP_MEM_SZ) = 40000
  • 最大应用程序控制堆大小 (4KB) (APP_CTL_HEAP_SZ) = 512
  • 用于应用程序组堆的内存所占百分比 (GROUPHEAP_RATIO) = 70

可以计算出下面的值:

  • 应用程序组共享内存集是: 40000 页 * 4K/页 = 160 MB
  • 应用程序组共享堆的大小是: 40000 * 70% = 28000 4K 页 = 114MB
  • 该应用程序组内可容纳的应用程序数为: 40000/512 = 78
  • 用于每个应用程序的应用程序控制堆为: (100-70)% * 512 = 153 4K 页 = 0.6MB

不要被 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 代理分配代理私有内存。

代理的数量受下面两者中的较低者限制:

  • 所有活动数据库的数据库配置参数 maxappls 的总和,这指定了允许的活动应用程序的最大数量。
  • 数据库管理器配置参数 maxagents 的值,这指定了允许的最大代理数。

代理私有内存集由以下内存池组成。这些内存池的大小由括号中的数据库配置参数指定:

  • Application Heap ( applheapsz
  • Sort Heap ( sortheap
  • Statement Heap ( stmtheap
  • Statistics Heap ( stat_heap_sz
  • Query Heap ( query_heap_sz
  • Java Interpreter Heap ( java_heap_sz
  • Agent Stack Size ( agent_stack_sz) (仅适用于 Windows)

我们曾提到,私有内存是在一个 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 实例
image


共享内存与私有内存

至此,我们已经讨论了实例共享内存、数据库共享内存和应用程序组共享内存以及代理私有内存。但是共享内存和私有内存的意义是什么呢?

为了理解共享内存与私有内存之间的不同之处,首先让我们通过快速阅读 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 内存集。假设:

  • 数据库 A 属于实例 db2inst1。
  • 数据库 A 为应用程序组 1 启用了 intra-parallel。
  • 代理 x 和 代理 y 都属于应用程序组 1。

图 5 - DB2 代理进程内存地址空间

图 5 展示了在 RAM 中分配的以下内存集:

  • 用于实例 db2inst1 的实例共享内存集。
  • 用于 数据库 A 的数据库共享内存集。
  • 用于 应用程序组 1 的应用程序组共享内存。
  • 用于代理 x 的代理私有内存集。
  • 用于代理 y 的代理私有内存集。
  • 为内核和库之类的东西预留的内存。

代理 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 的可寻址空间?
  • 应该如何配置 图 1中列出的每个参数,以最有效地利用可用的内存?

虽然 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 位内存地址空间
image

段 0 - 预留给 AIX 内核。

段 1 - 预留给 db2sysc 进程。

段 2 - 预留给代理私有内存。

段 3 - 预留给实例共享内存。

段 4 到段 B - 数据库共享内存始于段 4,这些段必须紧挨在一起。所有这 8 个段(2GB)可能都被用于数据库共享内存。但是,下面的每种配置都会从数据库共享内存中拿出一个段(256MB)。

注意: 对于下面的每种配置,DB2 将从数据库共享内存中拿出一个段,这个段始于段 B。

  • 如果数据库是分区的,或者启用了 intra-parallel 或连接集中器,那么数据库共享内存中有一个段被预留给应用程序组共享内存。
  • Fast Communication Manager (FCM):FCM 用于系统物理节点上不同分区之间的通信。默认情况下,这种通信是通过 UNIX socket 进行的。如果 DB2_FORCE_FCM_BP 被设为 YES,那么 FCM 通信发生在共享内存内。这意味着数据库共享内存中有一个段被预留给 FCM 通信。虽然 FCM 通信变得更快,但是它也令数据库共享内存减少了一个段。
  • fenced UDF 和存储过程:如果数据库上运行着一个 fenced 函数或过程,那么数据库共享内存中有一个段要预留给 fenced 模式的通信。
  • 如果数据库允许任何本地连接,那么数据库共享内存中有一个段要预留给代理/本地应用程序通信。如果将所有本地连接配置为 loopback 连接,那么就可以为这些连接使用 TCP/IP,而不需要共享内存(即使数据库就在服务器本地)。这样就有效地为数据库共享内存空出一个段来。要了解关于 loopback 连接的更多信息,请参考 DB2 technote

然而,如果您不想使用 loopback 解决方案,还有一种方法可以迫使 DB2 选择段 E 来用于代理/本地应用程序通信,这样数据库共享内存就不受影响(即不会减少)。请参阅后面的解释。

  • 如果启用了 ESTORE,那么还要从数据库共享内存中拿出另一个段。因此,如果启用 ESTORE,则应确保它至少是 256MB,否则就不起作用,因为要从数据库共享内存中拿出一个 256 MB 的段来仅用于管理这个 ESTORE。建议将 estore 段的大小( estore_seg_sz)设为 256MB,然后根据可用的内存更改段的数目( num_estore_segs)。

段 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。

需要从这种结构中了解到的最重要的事情是:

  • 对于禁用了 intra-parallel 的单分区系统,我们可以得到至多 2GB 的空间用于数据库共享内存(段 4 到段 B)。
  • 下面每种配置都将数据库共享内存减少了一个段(256MB):带 fenced UDF 或存储过程的数据库、带本地连接的数据库、DB2_FORCE_FCM_BP=YES 情况下的数据库、支持 intra_parallel、或支持集中器以及分区的数据库,以及启用了 ESTORE 的数据库。
  • 如果允许与数据库进行本地连接,那么应该将 DB2_MMAP_READ 和 DB2_MMAP_WRITE 都设置成 NO,以便使用段 E。否则,任何本地连接都要从数据库共享内存中拿走一个段。

这些限制规定了我们该如何配置数据库共享内存集中的每个内存池。可以使用前面给出的公式来计算数据库共享内存。得到的总和不能超过这个限制。

注意: 内存可以分配,释放,也可以当数据库正在运行时在不同区域之间交换。例如,您可以减少 locklist 而增加相同数量的任何一个给定的缓冲池。

使用 svmon 监控 AIX 上的内存使用情况

在 AIX 上,除了 db2mtrk 工具外,还可以使用 svmon 工具来监控 DB2 代理进程的内存消耗情况(需要 root 权限)。这个命令是: "svmon -P PID",其中 PID是 DB2 代理(db2agent 或 db2agentp)的进程 ID。

图 7 展示了 svmon 对于一个名为 db2agent 的进程的示例输出。与这个 db2agent 进程相关的数据库有如下特征:

  • 数据库名是 TEST。
  • INTRAP_PARALLEL = YES (get dbm cfg)
  • DB2_FORCE_FCM_BP = YES (db2set -all)
  • DB2_MMAP_READ=NO, DB2_MMAP_WRITE=NO (db2set -all)

图 7 - svmon 对于一个 DB2 代理进程 (svmon -P 11649046) 的输出

image

图 7 中观察到的一些情况:

  • 进程 ID 是 11649046,进程名为 db2agent(左上角)。这个代理被连接到数据库 TEST(数据库的名称在 svmon 输出中已经被截断,只显示了 'TES')。
  • Esid 列显示已经分配的内存段:
  • 段 4 (绿色)被分配给数据库共享内存。
  • 段 2 (橙色)被分配给代理私有内存。
  • 段 3 (蓝色)被分配给实例共享内存。
  • 段 B (紫色)被分配给应用程序组共享内存,因为数据库支持 intra_parallel。
  • 段 A (粉红色)被分配给 FCM,因为 DB2_FORCE_FCM_BP=YES。
  • 本地连接段被移到段 E(红色)。这是因为 DB2_MMAP_READ 和 DB2_MMAP_WRITE 都被设为 NO。否则,就必须将段 8 用于代理和本地连接通信,因为段 A 和段 B 都已经被占用,而段 9 被预留给 fenced 模式的通信。

观察到的这些情况与 图 6中说明的相匹配。

设置 32 位 AIX 系统上数据和堆栈的 ulimit

我们早先说过, 图 6中的段 #2 是用于代理私有内存的。实际上,并非完全如此。

确切地说,段 #2 被预留给数据和堆栈。数据包含用户数据(即代理私有内存)。而堆栈则包含要执行的指令。在这个 256M 的段中,数据是从地址 0x20000000 向下增长的。而堆栈是从地址 0x2FFFFFFF 向上增长的。为了不让数据和堆栈有冲突,设置它们的限制就十分重要。如果数据和堆栈真的有冲突,那么实例就会崩溃,并产生信号 4 或信号 11。

图 8 - 数据段和堆栈段

数据和堆栈的限制是在 ulimits(/etc/security/limits)中设置的。使用 SMIT 将它们设置成如下值:
Data = 491519, Stack = 32767 (512 字节)

上述值以 512 字节为单位,其中对于数据是 240MB,对于堆栈是 16MB。当使用 "ulimit -a" 显示这两个限制时,显示的值以 1K 字节为单位,而不是以 512 字节为单位。因此这两个值变为:
Data = 245760, Stack = 16384

注意: /etc/security/limits 包含了以 512 字节为单位的限制,而不是以 1 KB 为单位的限制。

32 位 AIX 上与分配数据库共享内存有关的常见问题

初始化数据库共享内存失败可能产生如下问题:

  • 在数据库启动时(激活数据库或第一次连接数据库) - SQL1478W, SQL0987C, SQL1084C。
  • 在运行时 - SQL10003N, SQL1042C。

下面的例子展示了会导致问题的不恰当配置。

例 1

考虑以下配置:(所有页的大小为 4KB)

  • 单分区,禁用集中器,INTRA_PARALLEL OFF,DB2_MMAP_READ=NO,DB2_MMAP_WRITE=NO,无 fenced 函数或过程
  • IBMDEFAULTBP 450,000 页
  • UTILHEAP 17,500 页
  • DBHEAP 10,000 页
  • LOCKLIST 1000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页

限制:在此配置中,对于数据库共享内存的限制是 2GB 或 8 个段。

计算:使用公式计算数据库共享内存,我们得到:
数据库共享内存 = (486,000 页 x 4KB/页的总数) x 1.1 (考虑到 10% 的开销) = ~2.1GB = 9 个段

注意: 我们会将 4 个隐藏缓冲池排除在计算之外,因为它们太小了,不会产生明显的不同。

问题: 这超出了 2GB 的限制。当激活数据库或第一次连接到数据库时,您将收到如下警告:
SQL1478W The defined buffer pools could not be started. Instead, one small buffer pool for each page size supported by DB2 has been started. SQLSTATE=01626

在 db2diag.log 中,您将看到有消息说 DB2 将利用隐藏缓冲池启动。要解决这个问题,可以减少主缓冲池的大小。

例 2

考虑如下配置: (所有页的大小都是 4K)

  • 单分区,无 fenced 函数或过程,INTRA_PARALLEL=ON
  • IBMDEFAULTBP 300,000 页
  • UTILHEAP 17,500 页
  • DBHEAP 10,000 页
  • SHEAPTHRES_SHR 50,000 页
  • LOCKLIST 1000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页

限制: 1.5GB (6 个段)。一个段用于应用程序组内存,因为 INTRA_PARALLEL 是 ON。另一个段用于本地连接,因为 DB2_MMAP_READ 和 DB2_MMAP_WRITE 都被设为 YES (该值是默认值)。

计算:
数据库共享内存 = (386,000 页 x 4KB/页的总数) x 1.1 = ~1.67GB = 7 个段

问题: 这超出了 1.5GB 的限制。当尝试激活数据库或第一次连接到数据库时,您将得到以下错误消息:
SQL1042C An unexpected system error occurred. SQLSTATE=58004

要解决这一问题:

  • 使用 db2set 将 DB2_MMAP_READ 和 DB2_MMAP_WRITE 设为 NO。这样便迫使 DB2 将段 E 用于本地连接,从而为数据库共享内存空出一个段。

例 3

考虑如下配置: (所有页的大小都为 4K)

  • IBMDEFAULTBP 250,000 页
  • INTRA_PARALLEL=ON
  • UTILHEAP 17,500 页
  • DBHEAP 10,000 页
  • SHEAPTHRES_SHR 20,000 页
  • LOCKLIST 1000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页
  • 存在 Fenced UDF

限制: 以下各占一个段:Intra-parallel ON,本地连接,以及 fenced UDFB。这样还剩下 5 个段,或者 1.25G 给数据库共享内存。

计算:
数据库共享内存 = (306,000 页 x 4KB/页的总数) x 1.1% = ~1.35GB = 6 个段

问题: 引用了 fenced UDF 的查询就会失败,并返回错误 sql10003C。要解决这个问题,可以在 unfenced 模式下运行 UDF,或者将数据库共享内存减少至不多于 5 个段。


 

32-位 Sun Solaris Version 2.6 及及其更高版本中的 DB2 内存配置

在 AIX 中,每个段的大小都是 256MB,而在 Solaris 中则有所不同,其内存段的大小不是固定的。32 位 Solaris 可寻址内存结构如 图 9所示。

图 9 - 32 位 Sun SolarisDB2 中的 32 位内存地址空间

从 0x0 到 0x00010000 之间的地址不能使用。第一个段始于 0x00010000,这个段被预留给 db2sysc 可执行程序和代理私有内存。在缺省情况下,这个段结束于 0x10000000,因而这个段总共是 256MB。(我们可以通过设置 DB2DBMSADDR DB2 注册表变量使这个段更大一些,见后面)。在这个段内,db2sysc 可执行程序只占用很小的一片内存,剩下的用于代理私有内存(200MB+)。

在默认情况下,实例共享内存的起始地址固定于 0x10000000。不过,也可以将其改为一个更高的地址,以便有更多的空间留给代理私有内存。例如,如果实例共享内存始于 0x12000000,而不是 0x10000000,那么就有 0x12000000 减去 0x10000000 即 32MB 的额外内存被用于代理私有内存。要做到这一点,可以将 DB2DBMSADDR DB2 注册表变量设为如下值:
db2set DB2DBMSADDR=0x12000000

注意: DB2DBMSADDR 值的范围是从 0x10000000 到 0x10FFFFFF,每次递增 0x10000。

实例共享内存的结束地址是不固定的。这意味着实例共享内存可能会很大。紧接着实例共享内存的是数据库共享内存。因为实例共享内存的结束地址不固定,所以数据库共享内存的起始地址也是不固定的。

在数据库共享内存之后的是用于 DB2 跟踪、共享库和堆栈(即将执行的指令)的内存。在 AIX 中,堆栈和代理私有内存共享相同的内存段,而在 Solaris 中则有所不同,其中代理私有内存和堆栈分别使用不同的内存段。因此,这里不存在两者之间发生冲突的风险。

由于这些内存段的大小不是固定的,我们只能估计实例共享内存和数据库共享内存的大小为:
4GB - 代理私有内存 - DB2 跟踪 - DB2 共享库 - 堆栈

注意这个数量同时还包括实例共享内存和数据库共享内存,并且它们的内存段必须是邻接的。

为了看一看实际中如何使用内存,可以对 db2sysc 或 db2agent 进程的 ID 运行 /usr/proc/bin/pmap 命令(以 root 的身份)。

注意: 在 Solaris 中,当使用 "pe -ef | grep instance_name" 命令显示 DB2 进程时,所有进程都作为 db2sysc 进程显示。可以使用 db2ptree命令来以“真实”名称显示 DB2 进程。

图 9展示了 pmap 命令的一个示例输出。

图 10 - pmap 命令对于 db2sysc 进程的示例输出(/usr/proc/bin/pmap -x 15444)
image

图 10 中可以观察到下面一些情况:

  • 从 0x00010000 到 0x023F8000 这一部分被预留给 db2sysc 可执行程序(~36MB)。
  • 橙色的那部分(堆),即从 0x023F8000 到 0x10000000,被用于代理私有内存(~220MB)。
  • 实例共享内存,即绿色那部分,始于默认地址 0x10000000。这意味着没有设置 DB2DBMSADDR。
  • 所有 3 个绿色的段被用于共享内存,包括实例共享内存和数据库共享内存。pmap 输出并没有说出哪一个段是实例共享内存,哪一个段是数据库共享内存。我们只知道,数据库共享内存在 0xFE002000 结束,因为从这个地址开始的是一个用于匿名内存的段。因此,实例共享内存和数据库共享内存总共的大小是 0xFE002000 - 0x10000000 = 3,992,985,600 字节 = ~3.7 GB。
  • 从 0xFFBC0000 到 0xFFFFFFFF 是用于堆栈的内存段(~4MB)。

注意: 在 32 位 Solaris 中,我们通常将 DB2 数据库共享内存限制在大约 3.5 GB。

如果设置了 DB2DBMSADDR 注册表变量,那么实例共享内存将从该变量指定的地址开始。下面的例子展示了这一点是如何实现的。

例子

设置 DB2DBMSADDR 注册表变量:

  • db2set DB2DBMSADDR = 0x12000000
  • db2stop
  • db2start (需要重新启动实例,以使更改生效)。

获得 db2sys 进程的进程 ID

  • ps -ef | grep sylviaq ('sylviaq' 是实例名)。
  • -ef | grep sylviaq
  • sylviaq 13166 1 0 13:09:12 pts/2 0:00 /export/home/sylviaq/sqllib/bin/db2bp 13049C11221 5
  • sylviaq 13263 13256 0 13:11:02 ? 0:00 db2sysc
  • sylviaq 13265 13256 0 13:11:03 ? 0:00 db2sysc
  • sylviaq 13257 13254 0 13:10:59 pts/3 0:00 -ksh
  • sylviaq 13256 13253 0 13:10:59 ? 0:00 db2sysc
  • sylviaq 13262 13256 0 13:11:00 ? 0:00 db2sysc
  • sylviaq 13360 13049 0 13:11:41 pts/2 0:00 grep sylviaq
  • sylviaq 13264 13256 0 13:11:02 ? 0:00 db2sysc
  • sylviaq 13266 13261 0 13:11:03 ? 0:00 db2sysc

以 root 的身份 cd 到 /usr/proc/pmap,并对任何 db2sysc 进程运行 pmap:
./pmap -x 13263

pmap 输出:

  • 13263: db2sysc
  • Address Kbytes Resident Shared Private Permissions Mapped File
  • 00010000 35808 4064 1608 2456 read/exec db2sysc
  • 02316000 896 168 48 120 read/write/exec db2sysc
  • 023F6000 744 264 8 256 read/write/exec [ heap ]
  • 12000000 243472 243472 - 243472 read/write/exec/shared [shmid=0xbc3]
  • 21000000 22512 22512 - 22512 read/write/exec/shared [shmid=0xbc4]
  • FCC00000 8328 8328 - 8328 read/write/exec/shared [shmid=0xa96]
  • FE002000 8 - - - read/write/exec [ anon ]

注意,实例共享内存现在从 0x12000000 开始,而不是从默认地址 0x10000000 开始。代理私有内存的大小(由 'heap' 标出)从 220MB ( 图 10)增加到了 252 MB。(0x12000000 - 0x023F6000 = 0xFC0A000 = 264282112 (十进制) = ~252MB)

您可能注意到, 图 9中给出的 4GB 地址空间没有包括任何内核内存。这就对了,在 Solaris 中,内核有其自己的地址空间,该地址空间与进程的地址空间是分开的。这样就将更多的空间留给了其他内存集,例如数据库共享内存。

虽然与 AIX 相比,在 Solaris 中我们有更大的地址空间用于数据库共享内存(在 AIX 上是 2GB),但是在 Solaris 上所有共享内存都是固定在物理 RAM 中。如果 RAM 比较小,那么对于可以并发运行的数据库数目就有很大的影响。请参阅 “Sun Solaris 中与分配数据库共享内存有关的常见问题”一节中的例 2。

需要从这种结构中了解到的最重要的事情是:

  • 与 AIX 不同,Solaris 的内存段其大小不是固定的。我们可以通过设置 DB2DBMSADDR DB2 注册表变量,将实例共享内存移到更高的地址,从而增加代理私有内存。
  • 数据库共享内存的限制大约是 3.5GB。
  • 功能内存与 RAM 固定,因而不能交换出去。

32 位 Sun Solaris 中与分配数据库共享内存有关的常见问题

没有充分配置内核参数以及初始化数据库共享内存失败可能导致如下失败:

  • 在数据库启动时(激活数据库或第一次连接到数据库) - SQL1478W, SQL1084C, hang condition
  • 在运行时 - SQL2043N, 挂起条件

在 Solaris 系统中,DB2 提供了一个叫做 db2osconf的工具。该工具根据系统的大小对内核参数的值给出建议。对于一个给定的系统,建议的值要足够高,以便能够容纳最合理的工作负载。

最常见的未能正确设置的内核参数是 shmmax。该参数按字节指定系统中可以分配的共享内存段的最大大小。如果把 DB2 配置成创建大于这个值的数据库共享内存,那么请求就会失败。其他要知道的内核参数是 shmsegshmmni

Solaris 中另一个与分配数据库共享内存有关的常见问题是由于这样的一个事实导致的,即共享内存段的所有页都是固定在物理 RAM 中的。如果在 RAM 中没有足够的空闲页可用,或者没有足够的可以被 OS 为满足数据库段而调出的其他页,那么启动数据库的请求就会遭到失败。

下面的例子展示了会导致问题的不恰当配置。

例 1

考虑如下配置: (所有页的大小都为 4K)

服务器:

  • 服务器上的物理 RAM 16GB
  • shmsys:shminfo_shmmax = 2097152 (2GB)

数据库:

  • IBMDEFAULTBP 400,000 页
  • UTILHEAP 17,500 页
  • DBHEAP 30,000 页
  • LOCKLIST 1000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页

限制: DB2 发出的任何创建大于 shmmax 值(这里是 2GB)的数据库共享内存集的请求都将失败,并返回一个 out of memory type 错误。

计算:
数据库共享内存 = (456,000 页 x 4KB/页) x 1.1 = ~2.0GB

问题: 可能仍然可以激活数据库或者连接到数据库。但是,尝试运行一个应用程序时可能返回如下错误消息:
SQL1224N A database agent could not be started to service request, or was terminated as a result of a database system shutdown or a force command. SQLSTATE=55032

这是 DB2 经常返回的一个错误。不过,如果不是在 AIX 服务器上,而是在 UNIX 服务器上,那么这就很可能与内存资源问题有关。特别地,可能是内核参数没有进行适当的调优。

为解决这个问题,可以适当地配置内核参数。设置:
shmsys:shminfo_shmmax = 15099494 (~90% of 16GB)

例 2

考虑如下配置: (所有页的大小都为 4K)

服务器上的物理 RAM 是 1 GB。

数据库 A:

  • IBMDEFAULTBP 137,500 页
  • INTRA_PARALLEL ON
  • UTILHEAP 10,000 页
  • DBHEAP 10,000 页
  • SHEAPTHRES_SHR 20,000 页
  • LOCKLIST 1000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页
  • APPGROUP_MEM_SZ 20,000 页

数据库 B:

  • IBMDEFAULTBP 92,500 页
  • INTRA_PARALLEL ON
  • UTILHEAP 5,000 页
  • DBHEAP 10,000 页
  • SHEAPTHRES_SHR 15,000 页
  • LOCKLIST 1000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页
  • APPGROUP_MEM_SZ 20,000 页

限制: 因为共享内存是固定到物理 RAM 的,所以这种内存不会被换出。因此,我们只能分配最多 1GB (可用的物理 RAM)的共享内存给数据库使用。

计算:

  • 数据库 A 共享内存 = (186,000 页 x 4KB/页) x 1.1% = ~818MB
  • 数据库 A 应用程序组内存 = 20,000 页 x 4KB/页 = 80MB
  • 数据库 B 共享内存 = (131,000 页 x 4KB/页) x 1.1% = ~576MB
  • 数据库 B 应用程序组内存 = 20,000 页 x 4KB/页 = 80MB
  • 为了启动数据库 A,要求: 818MB + 80MB = ~898MB
  • 为了启动数据库 B,要求: 576MB + 80MB = ~656MB

问题: 假设数据库 A 是激活的。至少有 898MB 的共享内存固定在物理 RAM 中。当尝试激活数据库 B 时,就会碰到如下错误消息:
SQL1084C Shared memory segments cannot be allocated. SQLSTATE=57019

如果同时启动数据库 A 和数据库 B,我们将请求至少 1.55GB (898MB + 656MB) 的可用物理 RAM,以便同样地将共享内存固定在 RAM 中。显然,1GB 的 RAM 不够。为解决这一问题:

  • 尝试减少这两个数据库的缓冲池大小。或者
  • 尝试减少应用程序组内存。或者
  • 更可能的是,增加更多的物理 RAM。在这种情况下,您将需要至少 1.55GB 的物理 RAM,才能同时启动这两个数据库。

在这种情况下,按照上面的数据库配置参数,在任何时刻启动某一个数据库(数据库 A 或数据库 B)是可行的,但是不能同时启动两个数据库。这是必须增加更多的物理 RAM。

这个问题在 AIX 中不会出现,因为在 AIX 中共享内存没有固定在物理 RAM 中。在这种场景中,可以启动数据库 B。但是,这意味着数据库 A 的数据库内存必须调出。当一个应用程序访问数据库 A 时,又得将数据库 B 的数据库内存调出。您可以想象未来要发生的调页次数有多少。

例 3

考虑如下配置: (所有页的大小都是 4K)

服务器:

  • 服务器上的物理 RAM 16GB
  • shmsys:shminfo_shmmax = 15099494 (16GB 的 ~90%)

数据库:

  • IBMDEFAULTBP 350,000 页
  • UTILHEAP 17,500 页
  • DBHEAP 10,000 页
  • LOCKLIST 1000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页
  • ESTORE_SEG_SZ = 400000 页
  • NUM_ESTORE_SEGS = 1

计算:

  • 数据库共享内存 = (386,000 页 x 4KB/页 + 400,000 页的 estore * 100 字节) x 1.1 = ~1.66GB
  • ESTORE memory = 400000 x 4KB = 1.6GB

问题: 数据库共享内存是 1.66GB。这个数小于 3.35GB 的数据库共享内存限制。shmmax 参数设置得比较恰当。但是在启动数据库时可能返回下面的错误消息!您可以在 db2diag.log 中看到如下错误消息:

  • 2003-12-04-10.10.13.362027 Instance:db2inst1 Node:000
  • PID:18327(db2agent (SAMPLE) 0) TID:1 Appid:*LOCAL.sample.047844091013
  • oper system services sqloVLMAttachVLMSegment Probe:20 Database:SAMPLE
  • sqloVLMAttachVLMSegment - shmat failed
  • 0xFFBE833C : 0x0000000C

shmat 是一个 UNIX 函数,它将与 shmid(另一个 Solaris 函数)所标识的共享内存相关的共享内存段附加到调用进程的数据段上。shmat 失败于 0x000000C,即 ENOMEM 或可用数据空间不足以容纳共享内存段。

换句话说,不能激活数据库或连接到数据库,因为没有足够的共享内存来满足请求。

那么该怎么办呢?答案在于我们分配 ESTORE 的方式。每个 ESTORE 段必须是一个连续的块。在我们的例子中,我们试图为 ESTORE 分配很大的一块(连续的)内存(1.6GB)。即使有 16GB 的 RAM,它也可能会被分段,从而没有一块连续的 1.6GB 的内存。

为解决这个问题,在数据库配置文件中像下面这样设置 ESTORE 的值:

  • ESTORE_SEG_SZ = 40000 页
  • NUM_ESTORE_SEGS = 10

通过设置上面的 ESTORE 值,实际上我们仍然试图为 ESTORE 分配总共 1.6 GB 的内存。然而,我们将尝试为 ESTORE 分配 10 块 160MB 的内存。这样分配的连续空间就要小得多,因此最有可能解决问题。



回页首

32 位 HP-UX 中的 DB2 内存配置

在 32-位 HP-UX 平台上,默认 HP-UX 内存管理是基于象限(quadrant)的,其中每个进程都有其自己的空间。每个进程(包括 DB2 代理)可以寻址最多 4GB 的内存。4G 的可寻址内存被拆分成 4 个象限,每个象限大小为 1G,如 图 11所示。

图 11 - HU-UX 中的 DB2 32 位内存地址空间

象限 1 (1GB) 被预留给程序文本(可执行代码)。

象限 2 (1GB) 被预留给全局程序数据。

象限 1 和象限 2(减去内核内存和其他进程的内存)可一起用于私有内存。象限 1 和 2 存在了 n 次(每个进程一次)

象限 3 (1GB) 被预留给全局共享内存。

象限 4 (0.75GB) 被预留给全局共享内存。最后 0.25GB 用于 I/O 映射。

系统范围共享内存的限制是 1.75GB。也就是说,所有共享对象(不仅仅是 DB2)都被映射到象限 3 和 4 上由所有进程共享的一个单独的空间内。这个共享内存空间分为象限 3 中的 1GB 和象限 4 中的 0.75GB。但是,共享内存段不能跨象限“拆分”,而应该保证是一个连续的地址空间。根据进程的不同,DB2 分配各种不同的段。每个段只能映射到象限 3 或象限 4 中的某一个象限,但是不能同时映射到这两个象限。因此,DB2 允许分配的最大共享内存段在象限 3 中的大小为 1GB,在象限 4 中的大小为 0.75GB。实际上,很可能不会分配这么大的内存段,因为之前可能已经分配了一些小的内存段(或者是由 DB2 分配的,或者是由其他进程分配的)。这意味着数据库共享内存集实际上被限制在大约 0.75 到 1GB。这比其他 UNIX 平台上可用的任何数据库共享内存集要少得多。

注意: 缺省的 HP-UX 内存架构使用 SHARE_MAGIC 内核可执行程序。随着 HP-UX 10.20 内核的更改,可以通过编译应用程序使之使用 SHMEM_MAGIC 内核可执行程序,可以将全局共享内存的限制从 1.75G 增加到 2.75GB。不过,目前还没有计划使用 SHMEM_MAGIC 可执行程序编译 DB2 代码。

对于 HP-UX 11.0 或更新的版本,有一种方法可以以内存窗(Memory Windows)的形式绕过 1.75GB 的共享内存限制。内存窗允许每个进程从共享内存中定义最多 1GB 的惟一的全局空间。放在这个惟一空间内的共享内存段只能被相同内存窗中的进程访问。但是不同的应用程序或同一应用程序的不同实例可以放在不同的内存窗中,并消耗系统上更多的可用物理内存。

内存窗使象限 3 能够作为 /etc/services.window 中定义的一个进程组的私有共享内存空间。在内存窗的 DB2 实现中,每个实例都被映射到一个单独的内存窗。换句话说,每个 DB2 实例都可以有自己的象限 3 内存空间。象限 4 仍然用作全局共享内存区,其中任何共享进程都可以分配段。注意,如果象限 4 有可用的空间,那么共享库总是首先被映射到象限 4。于是,每个实例的可用共享内存就变为 1GB 加上象限 4 中仍然可用的空间。不过,共享内存段依然不能跨越象限边界。根据段的类型和大小,DB2 可以指定在象限 4 中创建段。数据库共享内存集仍然有大约 1GB 的限制。因此,内存窗的好处是允许我们为一个 DB2 实例分配整个象限(象限 3,大约 1GB)。然而,如果没有内存窗,则所有实例都必须共享最多 1.75GB 的系统范围的共享«内存。

提示: 如果为每个实例都创建一个数据库,就可以有效地为所有数据库分配最多 1GB 的数据库共享内存。

注意: 内存窗只是为 32 位应用程序扩展了系统范围的虚拟容量。通过扩展虚拟容量,可以使用更多的底层 RAM。在不使用内存窗的情况下,不管 RAM 有多大,最多只能消耗 1.75GB 的物理内存。

要了解有关如何为 DB2 启用内存窗的更多信息,请参阅

需要从这种结构中了解到的最重要的事情是:

32 位 HP-UX 上与分配数据库共享内存有关的常见问题

没有充分配置内核参数或者初始化数据库共享内存失败可能导致以下失败:

  • 在数据库管理器启动时 - SQL1220N
  • 在数据库启动时(激活数据库或第一次连接到数据库) - SQL1478W, SQL1084C, SQL3605C, hang condition
  • 在运行时 - SQL2043N, hang condition

最常见的未能正确设置的内核参数是 shmmax。请参考 Quick Beginnings,以了解如何根据物理 RAM 设置适当的值。SHMMAX 按字节指定系统上可以分配的最大共享内存段。如果将 UDB 配置为创建一个大于该值的数据库共享内存集,则请求将遭到失败。其他需要了解的内核参数是 shmmni单击这里获得关于修改内核参数的信息。

下面的例子展示了会导致问题的不正确的配置。

例 1

考虑如下配置: (所有页的大小都为 4K)

服务器:

  • 服务器上的物理 RAM 1GB
  • shmmax = 966 367 641 (1GB)
  • shmseg = 32

数据库:

  • IBMDEFAULTBP 100,000 页
  • UTILHEAP 5000 页
  • DBHEAP 10,000
  • LOCKLIST 17,500 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页

计算:
数据库共享内存 = (140,000 页 x 4KB/页) x 1.1% = ~616MB

问题: 可能仍然可以激活数据库或连接到数据库。但是,当尝试连接到数据库时,在 db2diag.log 中可能间歇地出现如下错误消息。
DIA3605C Memory allocation failure occurred.

注意,对数据库共享内存的计算表明,我们仍然没有超出 1GB 可用共享内存的限制。接下来我们看一看内核参数的设置。查看 "Quick Beginnings for DB2 Servers" 以获得对 32 位 HP-UX 平台上调优内核参数的建议。在查看这本书时,实际上您会发现其中没有写到 shmseg 参数。这意味着您应该使用默认设置。shmseg 的默认设置是 120。(由于 SHMSEG 内核参数被设得太低,有些共享内存段不能适当地分配。)

要解决这个问题,需适当地配置内核参数。

  • 在这种情况下,设置 shmseg = 120

例 2

考虑如下配置: (所有页的大小都为 4K)

服务器:

  • 服务器上的物理 RAM 1GB
  • shmmax = 966 367 641 (1GB)
  • shmseg = 120
  • 服务器上的交换空间 500MB

数据库 A:

  • IBMDEFAULTBP 100,000 页
  • UTILHEAP 2500 页
  • DBHEAP 10,000
  • LOCKLIST 20,000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页

数据库 B:

  • IBMDEFAULTBP 160,000 页
  • UTILHEAP 7,500 页
  • DBHEAP 20,000 页
  • LOCKLIST 10,000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页

限制: 由于物理 RAM 只有 1GB,数据库共享内存集只能映射到它在物理上可以使用的空间,即 1GB + 交换空间。

计算:

  • 数据库 A 共享内存 = (140,000 页 x 4KB/页) x 1.1% = ~616MB
  • 数据库 B 共享内存 = (205,000 页 x 4KB/页) x 1.1% = ~902MB

问题: 分别激活或连接到数据库 A 或数据库 B 是可行的,但是不能并发进行。尝试同时激活这两个数据库将产生如下错误消息:
SQL1478W The defined buffer pools could not be started. Instead, one small buffer pool for each page size supported by DB2 has been started. SQLSTATE=01626

如果同时启动数据库 A 和数据库 B,需要至少 1.52GB (616MB + 902MB) 的共享内存。将数据库 A 映射到象限 4 (~0.75GB 可用共享内存)和将数据库 B 映射到象限 3 (~1GB)应该没有问题。但是,这一次,我们会受到物理内存的限制。显然,1GB 的 RAM 不足以处理 1.52GB 的共享内存映射。此外,SWAP 空间也被设置得太低。要解决这一问题:

  • 尝试减少这两个数据库的缓冲池大小。或者
  • 尝试将交换空间增加到物理 RAM 的两倍。或者
  • 最好的解决方案是,增加更多的物理 RAM。在这种情况下,您将需要至少 2GB 的物理 RAM,才能同时启动这两个数据库。

例 3

考虑如下配置: (所有页的大小都为 4K)

服务器:

  • 服务器上的物理 RAM 6GB
  • Instance 1 (3 个数据库)

数据库 A:

  • IBMDEFAULTBP 140,000 页
  • UTILHEAP 7,500 页
  • DBHEAP 20,000 页
  • LOCKLIST 10,000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页
  • APPGROUP_MEM_SZ 20,000 页

数据库 B:

  • IBMDEFAULTBP 80,000 页
  • UTILHEAP 2500 页
  • DBHEAP 10,000 页
  • LOCKLIST 20,000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页
  • APPGROUP_MEM_SZ 20,000 页

数据库 C:

  • IBMDEFAULTBP 130,000 页
  • UTILHEAP 7,500 页
  • DBHEAP 20,000 页
  • LOCKLIST 10,000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页
  • APPGROUP_MEM_SZ 20,000 页

限制: 由于物理 RAM 只有 1GB,数据库共享内存集只能映射到它在物理上可以使用的那些空间,即 1GB + 交换空间。

计算:

  • 数据库 A 共享内存 = (185,000 页 x 4KB/页) x 1.1% = ~814MB
  • 数据库 A 应用程序组内存 = 20,000 页 x 4KB/页 = 80MB
  • 数据库 B 共享内存 = (120,000 页 x 4KB/页) x 1.1% = ~528MB
  • 数据库 B 应用程序组内存 = 20,000 页 x 4KB/页 = 80MB
  • 数据库 C 共享内存 = (175,000 页 x 4KB/页) x 1.1% = ~770MB
  • 数据库 C 应用程序组内存 = 20,000 页 x 4KB/页 = 80MB
  • 要启动数据库 A,要求: 816MB + 80MB = ~894MB
  • 要启动数据库 B,要求: 530MB + 80MB = ~608MB
  • 要启动数据库 C,要求: 772MB + 80MB = ~850MB

问题: 启动数据库 A 和数据库 B 成功。但是启动数据库 C 时失败,并返回如下错误:
SQL1478W The defined buffer pools could not be started. Instead, one small buffer pool for each page size supported by DB2 has been started. SQLSTATE=01626

在这里,物理内存不是问题,因为我们有足够多的 RAM(6GB)。进一步的测试得处了以下启动组合:

  • 启动 A + B ->成功
  • 启动 B + C ->成功
  • 启动 A + C ->不成功
  • 启动 (A + B) + C ->不成功
  • 启动 (B + C) + A ->不成功

组合 (A + B) 和组合 (B + C) 获得成功,因为它们初始化数据库所需的共享内存总量分别是 1.5GB (894 + 608) 和 1.46GB (608+850)。这低于 1.75GB 的共享内存限制,并且每个数据库共享内存段可以安全地连续映射到一个象限。而其他组合将失败于 SQL1478W,因为它们要么超出了 1.75GB 共享内存限制,要么不能在一个象限内为一个数据库分配连续的共享内存段。

要解决这一问题:

  • 实现内存窗。最好的解决方案是定义 3 个 DB2 实例。将一个数据库恢复到每个实例。并为每个实例创建一个内存窗。这样一来,每个实例都可以完全访问它自己的 1GB 内存窗共享内存空间。注意: max_mem_windows 内核参数必须设为 2 (实例数 -1)。


32-位 Linux/Intel 中的 DB2 内存配置

图 12中展示了 32 位 Linux/Intel 上的 4GB 可寻址内存。

图 12 - Linux/Intel 中的 DB2 32 位内存地址空间

从 0x08048000 到 0x0FFFFFFF 的段是预留给 db2sysc 可执行程序的。

从 0x10000000 到 0x3FFFFFFF 的段是实例共享内存,总共是 0.75GB。

在默认情况下,DB2 共享库始于 0x40000000。

在低地址装载共享库,而将更多的空间留给数据库共享内存,这是可行的。(这也意味着用于实例共享内存的空间将更少。但是由于实例内存与数据库内存相比通常比较小,因此这样可以获得好处。)例如,如果在低于 0x40000000 的地址装载共享库,那么就可以在 0x38000000 装载数据库共享内存。如果在低于 0x2a000000 的地址装载共享库,那么就可以在 0x30000000 装载数据库共享内存,这样就有超过 2GB 的空间留给数据库共享内存。这些更改要求重新编译内核,我们在本文中不会对此加以讨论。请参考 Linux 手册中内核重编译那一节,以了解更多信息。

不过,对于 Redhat Advanced Server 和 SuSE SLES 8 上的 v8.1 FP2,DB2 将尝试自动将共享库重新分配到一个较低的地址,这样就不需要重新编译内核。在这些企业发行版中,有一个叫做 /proc/ pid/mapped_base 的文件包含了一个地址,共享库将从该地址装载到内存。在默认情况下,该地址是 0x40000000,但是我们可以把它降低一些(降到 0x20000000)。当 db2sysc 启动时,我们检查 mapped_base 文件是否存在。如果存在,则使用新的值并重新执行。然后,mapped_base 值发生了改变的进程中产生的每个进程都使用这个新值。

数据库共享内存(包括缓冲池)始于 0x50000000 (默认值)。它从 0x50000000 开始朝着堆栈方向向下增长。堆栈包含要执行的指令。堆栈从 0xC0000000 开始向上增长。

使数据库共享内存和堆栈不发生冲突,这一点很重要。我建议为堆栈使用 16MB 这么大的空间。在这样的情况下,我们有 ~1.73GB 用于数据库共享内存(从 0x50000000 到 0xC0000000,减去 16MB 的堆栈)。

在使用 "ulimit -a" 或 "ulimit -s" 显示限制时,这些值是以 1K 字节为单位显示的。
Stack = 16384

设置这个限制,使堆栈和共享内存地址空间不会相互冲突,这一点很重要。如果它们之间有冲突,那么实例就会崩溃,并发出信号 4 或信号 11。

最后 1GB 的内存 被预留给 Linux 内核。

您可能已经注意到,没有用于代理私有内存的段。这就对了,在 32 位的 Linux/Intel 中,没有预留特定的段给私有内存。代理私有内存是从共享库之间的任意空闲空间中分配的。

为了看一看这里是如何使用内存的,请参阅叫作 /proc/ PID/maps 的文件,其中 PID 是 db2agent 进程的进程 ID。 图 12展示了 maps 文件的示例输出。

图 13 - db2sysc 进程的 maps 文件

图 13 中可以观察到下面一些情况:

  • 从 0x10000000 开始的绿色的段是实例共享内存。
  • 共享库从 0x40000000 开始装载。在共享库之间,橙色的段是代理私有内存。
  • 绿色是数据库共享内存,始于 0x50000000。
  • 最后一个紫色的段是堆栈。

提示: 阅读此文,了解如何设置 AWE ,以及 AWE 是干什么的。

需要从这种结构中了解到的最重要的事情是:

  • 在默认情况下,数据库共享内存的大小是 1.75GB 减去堆栈大小。
  • 将共享库装载到一个较低的地址,从而使留给实例共享内存的空间更少,留给数据库共享内存的空间更多。

32 位 Linux 中与分配数据库共享内存有关的常见问题

没有充分配置内核参数或者初始化数据库共享内存失败可能导致以下失败:

  • 在数据库管理器启动时 - SQL1220N
  • 在数据库启动时(激活数据库或第一次连接到数据库) - SQL1478W, SQL1084C, hang condition
  • 在运行时 - SQL2043N, hang condition

最常见的未能正确设置的内核参数是 shmmax。请参考 Quick Beginnings ,了解如何根据物理 RAM 设置适当的值。 shmmax按字节指定了系统中可以分配的最大共享内存段。如果将 UDB 配置成创建大于这个值的数据库共享内存集,那么该请求将遭到失败。其他要指定的内核参数是 shmmni。请参考 这个链接,以获得更多关于修改内核参数的信息。

下面的例子展示了会导致问题的不恰当配置。

例 1

服务器:

  • 服务器上的物理 RAM 2GB
  • kernel.shmmax = 32768(32KB)

数据库:

  • IBMDEFAULTBP 200,000 页
  • INTRA_PARALLEL ON
  • UTILHEAP 17,500 页
  • DBHEAP 10,000 页
  • SHEAPTHRES_SHR 50,000 页
  • LOCKLIST 1000 页
  • PCKCACHE 5000 页
  • CATALOGCACHE 2500 页
  • APPGROUP_MEM_SZ 20,000 页(v8 中的默认值)

限制: SHMMAX 被设为 32KB。DB2START 可以分配足够多的共享内存来成功地启动实例。

计算:

  • 数据库共享内存 = (286,000 页 x 4KB/页) x 1.1 = ~1.26GB
  • 数据库应用程序组内存 = 20,000 页 x 4KB/页 = 80MB

问题: 在 db2start 期间,将碰到以下错误:
SQL1220N The database manager shared memory set cannot be allocated.

要解决这个问题,可以将 SHMMAX 参数增加到一个更合理的大小,例如 2GB。

例 2

服务器:

  • DB2 v8.1.0.0
  • OS: Linux SuSE SLES-8
  • glibc version : 2.2.5
  • Kernel: 2.4.19

问题: 如 SHMMAX 被设为 2GB,即 2147483648,那么一次 db2start 就会使其降至 268435456。如果将这个值减少 1 个字节,那么 db2start 不会作出任何变化。该问题在 v8 FP2, APAR LI70159中已得到修复。

对 SuSe 8.0 不会发生问题。


32 位 Windows 中的 DB2 内存配置

在 Windows 上,DB2 UDB 有着根本不同的体系结构。所有 DB2 操作都是以进程(db2sysc.exe)内的线程的形式实现的。Windows 进程模型最多可寻址 4GB 的内存,如 图 14中所示。

图 14 - Windows 中的 DB2 32-位内存地址空间

在 Windows 中没有真正的共享内存集的概念。在 UNIX 中,所有共享内存池通常都是某个内存集(例如缓冲池,dbheap,等等)的一部分,而在 Windows 中,这些共享内存池都是根据需要从 db2sysc 的私有内存集中分配的。

在非 Advanced Server Windows 环境(NT,2000 Pro/Standard)中,4GB 地址空间被分成 2GB 的用户空间和 2GB 的内核空间。这样可以有效地将 db2sysc 进程可访问的内存总数限制到 2GB。

在 Advanced Server 环境中,将 2GB/2GB 的内存拆分重新配置为 3GB 的用户空间和 1GB 的内核空间是可行的,如 图 14中所示。

为了允许内存使用量超出这些限制,必须在 boot.ini 文件中使用 /3GB switch。这里要警告的是,/3GB switch 只在 Windows 2000 Advanced Server、Windows 2000 DataCenter、Windows2003 Enterprise Edition 和 Windows 2003 DataCenter 中受支持。您可以在 Windows2000 Pro/Standard 中设置这个 switch,但是用户空间最多只能有 2GB,而内核空间缩小到 1GB,这会导致 1GB 的内存丢失。

注意: 为了有效地使用 /3GB switch,必须在 Windows 32 位环境中安装了最少 4GB 的物理 RAM。

为了克服 2GB (或 3GB)的用户空间限制,Windows 2000 Advanced Server 和 Windows 2003 Advanced Server 提供了对更大内存的支持。它们是 Address Windowing Extensions (AWE) 和 Physical Address Extension (PAE)。

AWE 是一种机制,用于通过一个可能更小的窗口访问内存中一个很大的部分。按照这种方法,如果某个大的内存池是线性的,那么它就可以被寻址。实际上,AWE 允许创建超过 2GB 限制的缓冲池。AWE 功能是通过使用 boot.ini 文件中的 /PAE switch 来实现的。/PAE 在 Windows2000 Pro/Standard 中有效,但如果不是使用 Windows 2000 Advanced Server、Windows 2000 DataCenter、Windows2003 Enterprise Edition 和 Windows 2003 DataCenter,则 Microsoft 不会允许你的操作系统与这样的 switch 一起运行。

提示: 这篇文章很好地描述了如何设置 AWE,以及 AWE 是干什么的。

需要从这种结构中了解到的最重要的事情是:

  • 在 Windows 体系结构中没有真正的内存集概念。DB2 使用的所有内存(共享的或私有的)池都是从相同的内存段中分配的 - 这个内存段就是用户空间,在非 Advanced Servers 环境中限于 2GB。
  • 在 Advanced Servers 中使用文件中的 /3GB switch 将这一限制增加到 3GB 是可行的。
  • 通过使用 AWE 可以将缓冲池增加到超过 2GB 的限制。实际上,可以定义最大 64GB 的缓冲池。


结束语

DB2 的内存结构由 4 个内存集组成:实例共享内存、数据库共享内存、应用程序组共享内存(只有在数据库启用了 intra-parallel,或者启用了集中器,或者多分区的时候才适用)以及代理私有内存。每种内存集由各个内存池组成。例如,缓冲池和数据库堆是数据库内存集内的两个不同的内存池。

每个实例有一个实例共享内存集,由这个实例中的所有数据库共享。每个数据库有一个数据库共享内存集,由连接到该数据库的所有代理共享。每个应用程序组有一个应用程序组共享内存集,由属于该应用程序组的所有代理共享。每个代理(或进程)都有一个代理私有内存集。该内存集是由代理独占使用的。

对于 32 位内存结构,不管系统有多少物理 RAM,在任何平台上任何进程的可寻址内存都是 4GB。而且,这 4GB 中有一部分要预留给操作系统。剩下的所有内存(亦称用户空间)是由应用程序(包括 DB2)使用的。

在配置 DB2 内存的使用时应记住,所有内存池应该能够纳入可寻址用户空间。这个空间的大小随平台的不同而不同。表 1 列出了每种 DB2 内存集的限制。应用程序组共享内存集没有列出,因为它是从数据库共享内存集中分配的。

表 1 - 基于 32 位内存结构的 DB2 内存集限制

平台
实例共享内存
数据库共享内存
代理私有内存

AIX
256MB
1.25 – 2GB (1)
240MB (2)

Solaris
不固定(3)
3.5GB
~220MB (4)

HP-UX
0.75 - 1GB (5)
0.75 – 1GB
象限 1 & 2 (6)

Linux
256MB
1.75 – 2.25GB
256MB

Windows
n/a (7)
n/a (7)
n/a (7)

注意:
(0) 在多分区环境中,这些限制适用于每个分区。
(1) 当 DB2_MMAP_READ=NO 并且 DB2_MMAP_WRITE=NO 时,限制的最大值为 2GB。以下每种情况都会占用一个 256MB 的段,致使 1.25GB 的最小值出现:fenced 函数和过程;DB2_FORCE_FCM_BP=YES;启用了 intra-parallel/启用了集中器/多分区。
(2) Data (代理私有内存)和 Stack 之间共享 256MB 的段。在 ulimit 中将堆栈设为 16MB,便可以将 240MB 留给代理私有内存。
(3) 在 Solaris 中,实例共享内存可能非常大,因为共享内存段没有固定的地址。实例共享内存和数据库共享内存合起来大约是 3.7GB。
(4) 在 Solaris 中,代理私有内存与 db2sysc 可执行程序共享同一个 256MB 的段。这个可执行程序大约占 36MB。
(5) 在 HP-UX 中,所有共享内存都位于象限 3 (1GB)和象限 4 (0.75 GB)中。
(6) 象限 1 和 2(减去内核内存和其他进程内存)可以一起用作私有内存。
(7) 所有实例共享内存、数据库共享内存和代理私有内存都必须能纳入 2GB 的用户空间限制,或者在 Advanced Server 上使用了 boot.ini 文件中的 /3GB switch 之后 3GB 的限制。如果支持 AWE 则是 64GB。

对于 32 位内存结构,不管物理 RAM 有多大,实例、数据库配置都受到 4GB 可寻址空间的限制。不过,如果有足够的 RAM,只需每个实例或数据库都符合以上限制,就可以在系统中并发地运行多个实例或数据库。为了克服这一限制,应该考虑转换到 64 位的 DB2。


致谢

特别感谢 Philip Cho 和 Michael Cornish 为本文提供了技术审校。

posted on 2008-06-22 18:09  Mainz  阅读(1529)  评论(0编辑  收藏  举报

导航