注册
达梦数据库内存管理
培训园地/ 文章详情 /

达梦数据库内存管理

🌌 2026/08/21 204 0 0

达梦内存管理:内存池、缓冲区与排序区

1. 达梦自行管理内存

达梦数据库服务器(DMServer)在运行过程中需要频繁申请和释放小块内存。

若每次都调用操作系统的 malloc/free,会引发以下问题:
1)每次内存操作都要陷入操作系统内核,产生系统调用开销;
2)长期频繁分配释放容易在堆上形成内存碎片,导致总量充足却无法分配连续空间;
3)数据库无法追踪每块内存被哪个会话或哪条SQL占用,不利于问题定位。

因此,达梦在进程启动时预先向操作系统申请若干大块内存,由数据库自身负责内部划分和回收。绝大多数小块内存分配都在这些预申请的空间内完成,不再与操作系统交互。操作系统看到的仅是 dmserver 进程占用的常驻内存(RSS)。

关系结构如下:

操作系统物理内存 └── dmserver 进程 ├── 数据缓冲区 BUFFER / KEEP / RECYCLE / FAST │ (数据页;自由链 / LRU 链 / 脏链) ├── 共享内存池 MEMORY_POOL (实例启动时向 OS 申请) │ └── 排序块、哈希块等短生命周期小片内存 └── 运行时内存池 ├── SESSION 池 (会话建立时创建,断开时释放) └── VM 池 (语句执行期)
区域 典型参数 存什么 生命周期
数据缓冲区 BUFFER / KEEP / RECYCLE / FAST_POOL_PAGES 数据页、索引页 实例级,启动时格式化成分页
共享内存池 MEMORY_POOL / MEMORY_TARGET / MEMORY_EXTENT_SIZE 字典、SQL 计划缓存、日志缓冲、排序/哈希等短生命周期块 实例启动时向 OS 申请,可扩展/收缩
运行时内存池 会话池、虚拟机(VM)池 本会话的解析、执行器私有结构 会话建立时创建,会话结束时销毁

2. 内存区域的划分与功能

2.1 数据缓冲区(Buffer)

数据缓冲区存放从磁盘读入的数据页和索引页。查询读取数据时优先在此查找,未命中则从磁盘读取并缓存。修改数据时页标记为脏页,由检查点或淘汰机制刷回磁盘。

数据缓冲区的生命周期为实例级,随数据库启动分配,持续至实例关闭。管理方式是按固定页大小划分,通过自由链、LRU链、脏链进行管理。典型参数包括 BUFFER、KEEP、RECYCLE、FAST_POOL_PAGES。

2.2 共享内存池(Shared Memory Pool)

共享内存池存储数据字典、SQL执行计划缓存、日志缓冲区,以及排序和哈希等操作的工作内存。其生命周期为实例启动时向操作系统申请初始空间,运行中可按需扩展或收缩。

典型参数包括 MEMORY_POOL(初始大小)、MEMORY_TARGET(收缩上限)、MEMORY_EXTENT_SIZE(单次扩展步长)。

共享内存池是供所有会话共享的工作区。当需要小块内存(如排序或哈希操作)时,从该池中切分。使用完毕后归还池中,供其他模块复用。池中空间不足时,按 MEMORY_EXTENT_SIZE 逐次扩展。当占用超过 MEMORY_TARGET 时,空闲部分会缩回目标值。

2.3 运行时内存池(Runtime Memory Pool)

运行时内存池存放会话私有数据结构、执行器工作区、表达式计算临时空间。其生命周期为会话建立时创建,会话断开时释放。典型参数包括会话池和虚拟机池,由数据库自动管理。

运行时内存池属于会话私有资源。每个客户端连接建立后,数据库为该会话创建独立的运行时池,用于存放事务控制块、执行计划上下文等。SQL语句执行期间还会创建虚拟机池,语句结束后即释放。

2.4 三块区域的区别

数据缓冲区在实例启动时一次性向操作系统申请,实例关闭时归还。共享内存池在实例启动时申请初始块,运行中按需扩展,达到上限后空闲部分可收缩,但极少归还操作系统。运行时内存池在会话建立时向操作系统申请,会话断开后归还。

需要注意:BUFFER 不属于内存池,它是按固定页大小组织的数据缓存,与处理小块分配的共享内存池是两条独立的存储路径。

2.5 一条 SQL 实际会碰几块池

客户端连接 └─ 创建 SESSION 运行时池 ← 会话级,连接在就在 收到 SQL ├─ 解析 / 绑定 / 计划 │ └─ 字典缓冲、SQL 缓冲(共享池侧) └─ 执行 ├─ 创建 / 使用 VM 池 ← 本语句执行期 ├─ 扫表:数据页走 BUFFER ├─ ORDER BY / DISTINCT:排序区(短生命周期,多从共享池切) └─ HASH JOIN / HASH GROUP:哈希区(同上,超限则外存哈希) 会话断开 └─ SESSION / VM 池归还

3. 排序区与哈希区的工作原理

排序区和哈希区是复杂查询中最常消耗内存的两个操作区域,两者均从共享内存池中获取工作内存,使用完毕后立即释放。

3.1 排序区(Sort Area)

排序操作不仅出现在 ORDER BY 子句中,还出现在 GROUP BY 分组(无可用索引时)、DISTINCT 去重、UNION 合并去重、MERGE JOIN 前的有序化、创建索引时的键排序以及部分窗口函数等场景。

排序区的工作方式如下。当待排序数据量不超过 SORT_BUF_SIZE 时,在内存中完成排序,结束后释放内存。当待排序数据量超过 SORT_BUF_SIZE 时,分批写入临时表空间(走 RECYCLE 缓冲),最后进行多路归并,产生磁盘I/O。

关键参数包括 SORT_BUF_SIZE(单次排序操作可用的内存上限)、SORT_BUF_GLOBAL_SIZE(整个实例内所有排序操作可占用的内存总和上限)、SORT_BLK_SIZE(排序分片大小)以及 RECYCLE(临时表空间使用的缓冲区大小,影响外排序性能)。

3.2 哈希区(Hash Area)

哈希连接用于等值连接场景,如 t1.c1 = t2.c1,当连接列缺少索引或优化器判断哈希比嵌套循环更优时选用。

哈希区的工作方式如下。数据库选择较小的一侧作为Build表,在内存中构建哈希表,再扫描另一侧(Probe表)逐行探测匹配。若Build表数据量不超过 HJ_BUF_SIZE,全程在内存完成。若Build表数据量超过 HJ_BUF_SIZE,两表按哈希函数分区写入临时存储,再逐分区执行内存哈希,称为外存哈希,产生磁盘I/O。

关键参数包括 HJ_BUF_SIZE(单次哈希连接可用的内存上限)、HJ_BUF_GLOBAL_SIZE(实例内所有哈希连接可占用的内存总和上限)、HJ_BLK_SIZE(哈希操作每次分配的块大小),以及 HAGR_BUF_SIZE(哈希分组、DISTINCT等聚合操作的内存上限)和 HAGR_BUF_GLOBAL_SIZE(实例内所有哈希聚合操作的内存总和上限)。

哈希分组(HAGR_)与哈希连接(HJ_)使用不同参数。一条SQL可能同时触发排序和哈希,例如同时包含 GROUP BY 和 ORDER BY 的查询。

3.3 复杂查询里排序区和哈希区的机制

ORDER BY / GROUP BY / DISTINCT │ ├─ 工作集 ≤ SORT_BUF_SIZE → 内存内排序 → 得到有序结果 └─ 工作集 > SORT_BUF_SIZE → 写入临时段(RECYCLE)→ 多路归并 等值 HASH JOIN │ ├─ Build 侧 ≤ HJ_BUF_SIZE → 内存中建哈希表并探测 └─ Build 侧 > HJ_BUF_SIZE → 分区写入临时段 → 逐分区再哈希

4. 参数设置过小或过大的影响

MEMORY_POOL 设置过小会导致运行中频繁向操作系统扩展,增加系统调用开销,N_EXTEND_EXCLUSIVE 计数持续增长。设置过大会导致实例启动占用过高,空闲空间浪费,并挤压其他内存区域。

BUFFER 设置过小会导致命中率低,频繁淘汰数据页,物理读增加,FREE 接近0,淘汰计数持续上升。设置过大会导致实例启动失败或触发操作系统Swap,脏页增多,检查点刷盘造成IO尖峰,同时挤压排序和哈希可用空间。

SORT_BUF_SIZE 设置过小会导致内排序转外排序,临时表空间膨胀,ORDER BY 和建索引操作显著变慢。设置过大时,多个会话并发,总消耗约为会话数乘以 SORT_BUF_SIZE,容易耗尽内存。

HJ_BUF_SIZE 设置过小会导致哈希连接转为外存哈希,等值大连接变慢。设置过大时,单条SQL占用大量内存,多并发时受全局上限限制,且与 BUFFER 争抢物理内存。

RECYCLE 设置过小会导致外排序或外哈希即使算法正确,仍受限于磁盘速度,整体缓慢。设置过大时,临时场景不占那么多空间造成浪费。

需要说明:SORT_BUF_SIZE 和 HJ_BUF_SIZE 是会话级参数,单会话设置过大尚可,但需乘以并发会话数估算实际内存消耗。SORT_BUF_GLOBAL_SIZE 和 HJ_BUF_GLOBAL_SIZE 是实例级上限,用于防止所有会话的总消耗失控。

5. 上机实验


第 1 步:看到四个参数(证明 BUFFER 和内存池不是同一个)

SELECT PARA_NAME, PARA_VALUE FROM V$DM_INI WHERE PARA_NAME IN ( 'BUFFER', 'MEMORY_POOL', 'SORT_BUF_SIZE', 'HJ_BUF_SIZE' );

屏幕截图 20260818 231946.png

第 2 步:再连一次库,证明运行时池跟着会话走

在窗口执行下列sql语句

SELECT SESS_ID, USER_NAME, STATE, THRD_ID FROM V$SESSIONS ORDER BY SESS_ID;

新连接的窗口里再执行上面那句。用户多出一行 SESS_ID

比内存池:

SELECT COUNT(*) AS POOL_CNT FROM V$MEM_POOL; SELECT NAME, COUNT(*) AS CNT FROM V$MEM_POOL GROUP BY NAME ORDER BY CNT DESC;

屏幕截图 20260818 233859.png

屏幕截图 20260818 234001.png

第 3 步:造表 + 对比排序内存

CREATE TABLE MEM_FACT ( ID INT NOT NULL, DEPT INT NOT NULL, AMOUNT DECIMAL(18,2), PAD VARCHAR(180) ); INSERT INTO MEM_FACT SELECT LEVEL, MOD(LEVEL, 50) + 1, MOD(LEVEL, 1000) * 0.37, RPAD('X', 180, 'Y') FROM DUAL CONNECT BY LEVEL <= 50000; COMMIT; SELECT COUNT(*) FROM MEM_FACT;

让服务器把行按 ID 排完再写入另一张表,避免 Manager 把行画在屏幕上。

先热身一次(耗时丢掉,只为把数据打进 BUFFER):

SELECT COUNT(*), SUM(ID) FROM MEM_FACT;

再准备结果表,同一窗口继续:

CREATE TABLE MEM_OUT (ID INT, PAD VARCHAR(180)); SP_SET_PARA_VALUE(1, 'SORT_BUF_SIZE', 1); TRUNCATE TABLE MEM_OUT; INSERT INTO MEM_OUT SELECT ID, PAD FROM MEM_FACT ORDER BY ID DESC; COMMIT;

看这条 INSERT 的耗时。同一窗口再:

SP_SET_PARA_VALUE(1, 'SORT_BUF_SIZE', 32); TRUNCATE TABLE MEM_OUT; INSERT INTO MEM_OUT SELECT ID, PAD FROM MEM_FACT ORDER BY ID DESC; COMMIT;

每档连跑两遍,只记第二遍(第一遍可能还在填缓冲)。比较的是 INSERT ... ORDER BY 的耗时。

最后把排序缓冲改回第 1 步的原值:

SP_SET_PARA_VALUE(1, 'SORT_BUF_SIZE', 2);

屏幕截图 20260818 234454.png

屏幕截图 20260818 234614.png

第 4 步:对比哈希内存(大表自己连自己)

维表很小的 JOIN 往往看不出差异,所以用自连接把 Build 侧做大。

SP_SET_PARA_VALUE(1, 'HJ_BUF_SIZE', 2); SELECT /*+ USE_HASH(A, B) */ COUNT(*) FROM MEM_FACT A, MEM_FACT B WHERE A.ID = B.ID AND A.DEPT = 1;

看耗时。同一窗口再:

SP_SET_PARA_VALUE(1, 'HJ_BUF_SIZE', 64); SELECT /*+ USE_HASH(A, B) */ COUNT(*) FROM MEM_FACT A, MEM_FACT B WHERE A.ID = B.ID AND A.DEPT = 1;

/*+ USE_HASH(A, B) */ 是提示优化器走哈希连接。两次差不多,说明本机数据量未打满 HJ_BUF,差异不明显。

做完清理:

DROP TABLE MEM_FACT;

屏幕截图 20260818 234726.png
屏幕截图 20260818 234820.png

6. 调参原则

1)先定位瓶颈类型:使用V$BUFFERPOOL查看缓冲区命中率与淘汰计数;使用V$MEM_POOL查看内存池扩展次数;使用EXPLAIN查看执行计划中的排序和哈希操作。确定问题是缓冲区不足、排序外溢还是哈希外溢,再调整对应参数。

2)数据缓冲区(BUFFER):按数据热度和物理内存总量规划,一般设为物理内存的60%~80%。若数据文件总大小小于此比例,按实际数据量设置即可。修改后需重启实例生效,用命中率验证效果。

3)排序内存(SORT_BUF_SIZE):按单条SQL的排序数据量估算,乘以最大并发会话数,再受SORT_BUF_GLOBAL_SIZE约束。建索引等操作可临时调大,完成后恢复。

4)哈希内存(HJ_BUF_SIZE):按Build表大小估算,乘以最大并发哈希连接数,再受HJ_BUF_GLOBAL_SIZE约束。仅对等值大连接有效,嵌套循环连接不受此参数影响。

5)共享内存池(MEMORY_POOL):通过N_EXTEND_EXCLUSIVE观察扩展频率,若长期大于0再考虑增大初始值。同时设置MEMORY_TARGET作为上限,防止池无限扩张。


评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服