注册
浅谈达梦数据库(DM8)的内存与线程架构
专栏/技术分享/ 文章详情 /

浅谈达梦数据库(DM8)的内存与线程架构

科技不收敛 2026/09/30 327 1 0
摘要

浅谈达梦数据库(DM8)的内存与线程架构

一、初始

还记得第一份实习工作,那也是我第一次真正接触 DBA。当时导师告诉我,学习数据库可以先从体系结构入手。于是刚开始,我就对着《Oracle 编程艺术》和 MySQL InnoDB 相关资料硬啃内存结构。那时候其实没什么概念,很多东西看完也不太明白,只能反复看、反复记。后来接触得多了,对数据库的内存、进程、存储这些模块逐渐有了整体认识,也慢慢形成了自己的学习习惯:每接触一个新的数据库,我都会先从它的体系结构看起,先弄清楚它是怎么组织和运行的,再往具体的功能和机制深入。
最近开始学习 DM 数据库,我也还是沿用了这个习惯,从它的体系结构入手。

二、DM8 内存架构

DMServer 采用单进程、多线程架构。实例中的线程共享数据缓冲区、字典缓冲区、SQL 缓冲区等公共资源;会话和 SQL 算子在执行期间还会申请运行时内存。达梦将这些内存结构划分为内存池、缓冲区、排序区和哈希区。
博客.png

2.1 共享内存池与运行时内存池

共享内存池是 DMServer 在启动时从操作系统申请的一大片内存。

在 DMServer 的运行期间,经常会申请与释放小片内存,而直接向操作系统申请和释放内存时需要发出系统调用,此时可能会引起线程切换,降低系统运行效率。于是 DM 采用共享内存池的方式:一次向操作系统申请一片较大内存,作为共享内存池。当系统在运行过程中需要申请小片内存时,可在共享内存池内进行申请,当用完该内存时,再释放掉,即归还给共享内存池。

举个例子:可以把它想象成一家餐厅。

如果餐厅每来一位顾客,才临时去外面买一套餐具;顾客吃完饭,又把这套餐具处理掉。那么下一位顾客来了,还得重新出去购买,显然效率很低。

正常的做法是,餐厅提前准备一批餐具放在店里。顾客来了,就直接从现有餐具中拿一套使用;顾客离开后,餐具经过清洗重新放回去,下一位顾客还可以继续使用。只有现有餐具不够时,餐厅才需要再去采购。

对应到数据库中,操作系统就像餐具供应商,DMServer 的内存池就像餐厅提前准备好的餐具库,数据库内部各个模块则像不断到来的顾客。

DMServer 先向操作系统申请一批内存,之后数据库内部需要小块内存时,优先从内存池中分配;使用结束后再回收到池中,供后续请求重复使用。

核心思想就是:提前准备、按需分配、用完回收、重复利用,避免每次都直接找操作系统申请内存。
DMServer 的配置文件(dm.ini)中MEMORY_POOL 可以设置共享内存池的初始大小。空间不足时,内存池按照 MEMORY_EXTENT_SIZE 指定的步长扩展;扩展后的空闲内存超过一定规模时,系统会参考 MEMORY_TARGET 进行收缩。

除了共享内存池,DMServer 的一些功能模块在运行时还会使用自己的运行时内存池。这些运行时内存池是从操作系统申请一片内存作为本功能模块的内存池来使用,如会话内存池、虚拟机内存池等。

2.2 数据缓冲区

数据缓冲区是 DMServer 处理数据页的主要场所。查询需要的页面不在内存时,I/O 线程从数据文件读取页面并放入缓冲区;更新后的页面成为脏页,随后由 I/O 线程写回数据文件。

缓冲页由自由链、LRU 链和脏链管理:自由链保存尚未使用的缓冲页,LRU 链参与页面淘汰,脏链记录已经修改但还未写盘的页面。

举个例子,假设数据缓冲区是一家酒店,缓冲区中的每一个内存数据页就是一间房间。

  • 自由链(Free List):记录目前还没有被使用的房间。比如酒店有 100 个房间,其中 30 个空着,这 30 个房间就相当于挂在自由链上。数据库需要把一个新的磁盘数据页读入内存时,就可以从自由链中找一个空闲的数据页来使用。

  • LRU 链:记录已经被使用的房间,同时根据最近的使用情况进行管理。比如剩下的 70 个房间都有客人住过或者正在使用,就相当于这些内存数据页已经缓存了实际的数据。数据库会通过 LRU 机制判断哪些数据页最近经常使用,哪些已经很久没有使用。空间不足时,可以优先考虑淘汰长期没有使用的数据页。

  • 脏链(Dirty List):专门记录那些“内容已经发生变化,但还没有写回磁盘”的数据页。可以把它想象成酒店里已经退房、但房间状态发生了变化,还需要保洁处理的房间清单。数据库修改了某个内存数据页后,这个页就成为脏页,需要被记录下来,之后由相关机制将修改后的内容写回磁盘。
    例如有一个数据页 A:

磁盘中的 A 页 → 读取到数据缓冲区 → 进入 LRU 链 → UPDATE 修改 A → A 仍然在 LRU 链,同时因为被修改了,也进入脏链 → 后续写回磁盘 → 从脏链移除,但仍然可以继续留在 LRU 链中。

简单记成:
自由链管“有没有被占用”,LRU 链管“缓存的数据页怎么使用和淘汰”,脏链管“哪些修改过的数据页还需要写回磁盘”。

DMServer 中有三种类型的数据缓冲区,分别是 NORMAL、FAST 和 RECYCLE。NORMAL 缓冲区也称通用数据缓冲区,主要提供给系统处理的一些数据页;RECYCLE 缓冲区供临时表空间使用;FAST 缓冲区根据用户指定的 FAST_POOL_PAGES 大小由系统自动进行管理。

DMServer 提供了可以更改这些缓冲区大小的参数,用户可以根据自己应用需求情况,指定 dm.ini 文件中 BUFFER、RECYCLE、FAST_POOL_PAGES 值,这些值分别对应是 NORMAL 缓冲区大小、RECYCLE 缓冲区大小、FAST 缓冲区数据页总数。

把整个 DM 数据缓冲区想象成一家公司的三个仓库。
NORMAL = 主仓库
公司平时绝大多数正常货物放这里。
RECYCLE = 临时仓库
临时周转的货物放这里,用完很快就没有继续保存的价值,避免挤占主仓库。
FAST = 特殊快速周转区
专门划出一定数量的货位,由系统自己管理。

2.3 日志缓冲区与 Redo 文件

INSERT、UPDATE、DELETE 等修改会产生 Redo。Redo 先进入日志缓冲区,事务提交或执行检查点时,再由日志 FLUSH 线程写入联机 Redo 日志文件。RLOG_BUF_SIZE 控制日志缓冲区大小。

数据页写盘前,对应的 Redo 必须先落盘。发生异常重启时,数据库依靠已经持久化的 Redo 重做尚未完整写入数据文件的修改。配置本地归档后,归档线程还会把日志保存到归档文件中,用于恢复和日志追赶。联机 Redo 负责当前运行和故障恢复,归档文件负责保存历史日志,两者承担的职责不同。

2.4 字典缓冲区、SQL 缓冲区和执行内存

字典缓冲区主要存储一些数据字典信息,如模式信息、表信息、列信息、触发器信息等。每次对数据库的操作都会涉及到数据字典信息,访问数据字典信息的效率直接影响到相应的操作效率,如进行查询语句,就需要相应的表信息、列信息等,这些字典信息如果都在缓冲区里,则直接从缓冲区中获取即可,否则需要 I/O 才能读取到这些信息。该缓冲区配置参数为 DICT_BUF_SIZE。

SQL 缓冲区提供在执行 SQL 语句过程中所需要的内存,包括计划、SQL 语句和结果集缓存。很多应用当中都存在反复执行相同 SQL 语句的情况,此时可以使用缓冲区保存这些语句和它们的执行计划,这就是计划重用。这样带来的好处是加快了 SQL 语句执行效率,但同时给内存也增加了压力。dm.ini中 CACHE_POOL_SIZE 控制缓冲区大小,USE_PLN_POOL 决定是否启用执行计划重用。计划重用能够减少重复解析,但缓存内容越多,占用的内存也越多。

排序缓冲区提供数据排序所需要的内存空间。当用户执行 SQL 语句时,常常需要进行排序,所使用的内存就是排序缓冲区提供的。在每次排序过程中,系统都会先申请内存,排序结束后再释放内存。
排序、哈希连接和聚合操作在执行时申请工作内存。它们的消耗与并发数、数据量和执行计划直接相关,并不是启动时固定占用的一整块空间。单条 SQL 看起来消耗不大,在高并发下也可能累积成明显的内存压力。

三、DM8 线程架构

DM8 使用对称服务器架构,一个 DMServer 进程内运行多种操作系统线程。连接接收、SQL 执行、数据页 I/O、日志刷盘和定时任务分别由不同线程负责,各线程通过任务队列和同步机制协作。DM 进程中主要包括监听线程、IO 线程、工作线程、调度线程、日志线程等。
与 Oracle 常见的多进程架构有所不同。Oracle 将数据库的核心后台任务拆分给 DBWn、LGWR、CKPT、SMON、PMON、ARCn 等后台进程完成,而 DM 则主要通过同一个 DMServer 进程内部的不同线程协同完成类似工作。
比如,DM 的 IO 线程负责数据页的读入以及脏页写盘,在功能上可以类比 Oracle 的 DBWn;DM 的 日志 FLUSH 线程负责将 REDO 日志从日志缓冲区刷入磁盘,可以类比 Oracle 的 LGWR;DM 的调度线程负责自动检查点、缓存清理、会话超时检测等周期性任务,其中检查点相关功能与 Oracle 的 CKPT 有一定对应关系;DM 的日志归档线程负责异步归档任务,可以类比 Oracle 的 ARCn。此外,DM 还设置了监听线程、工作线程、定时器线程、日志 APPLY 线程以及 MAL 通信相关线程,分别承担客户端连接、SQL/事务任务执行、定时任务、备库 REDO 重做以及实例间通信等工作。

客户端连接 / SQL 请求
          │
          ▼
    监听线程 ──► 会话任务队列 ──► 会话工作线程(或线程池)
                                      │
                      ┌───────────────┼────────────────┐
                      ▼               ▼                ▼
                 数据缓冲区       日志缓冲区       排序 / 哈希内存
                      │               │
                      ▼               ▼
                   I/O 线程       日志 FLUSH 线程
                      │               │
                      ▼               ▼
                   数据文件        联机 Redo 文件

调度线程 ──► 定时检查、检查点等任务 ──► 任务工作线程
归档线程 ──► 按归档配置处理日志

3.1 监听线程与工作线程

监听线程在服务器端口等待客户端连接。收到连接请求后,它生成会话申请任务并放入任务队列,由工作线程继续处理。

工作线程分为任务工作线程和会话工作线程。任务工作线程处理调度线程生成的缓存清理、异步任务等内部工作,其初始数量由 TASK_THREADS 控制。会话工作线程负责执行客户端请求,运行方式由 STHD_FLAG 决定:

  • 非线程池模式下,一个会话对应一个会话工作线程,该会话的请求由固定线程处理。
  • 线程池模式下,会话请求进入任务队列,由线程池中的工作线程依次取出执行。

因此,估算线程资源时需要先确认线程池模式,不能简单地把连接数直接当成工作线程数。

3.2 I/O、日志与调度线程

I/O 线程负责把缺失的数据页读入缓冲区,并在缓冲区回收、检查点或数据库关闭时写出脏页。日志 FLUSH 线程专门处理 Redo 刷盘,使连续写日志和分散写数据页两条 I/O 路径能够分别运行。

调度线程每秒检查定时任务,处理检查点、缓存清理、会话超时检测和缓冲区动态调整等工作。启用归档、数据守护或其他集群功能后,进程中还会出现归档线程、MAL 监听与收发线程、备库 APPLY 线程等。具体线程数量由版本、实例配置和已启用功能决定。

四、总结

可以看出,DMServer 整体采用单进程、多线程的运行方式。内存方面,通过内存池减少频繁向操作系统申请内存的开销,通过数据缓冲区、日志缓冲区、字典缓冲区和 SQL 缓冲区等结构分别管理不同类型的数据,并为排序、哈希等 SQL 执行过程提供运行时内存。

线程方面,DMServer 将不同任务交给不同线程处理,例如监听线程负责接收连接,工作线程负责执行任务和 SQL 请求,I/O 线程负责数据页读写,日志 FLUSH 线程负责 Redo 刷盘,调度线程负责检查点和各种定时任务。

把整个体系结构串起来理解,其实就是:内存结构解决“数据和运行信息放在哪里”,线程结构解决“这些工作由谁来完成”。 两者共同构成了 DMServer 运行的基础。

参考资料

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服