目录
[1.1.1.监听线程(Listener Thread)](#1.1.1.监听线程(Listener Thread))
[1.1.2.工作线程 (Worker Threads)](#1.1.2.工作线程 (Worker Threads))
[1.1.4.调度线程(Schedule Thread)](#1.1.4.调度线程(Schedule Thread))
[1.1.5.日志FLUSH线程(Log Flush Thread)](#1.1.5.日志FLUSH线程(Log Flush Thread))
[1.1.6.日志归档线程(Log Archive Thread)](#1.1.6.日志归档线程(Log Archive Thread))
[1.1.7.检查点线程(Checkpoint Thread)](#1.1.7.检查点线程(Checkpoint Thread))
一、达梦数据库的核心进程及线程(理论学习)
官网对其进程及线程体系的概述为:
DM 服务器使用"对称服务器构架"的单进程、多线程结构。这种对称服务器构架在有效地利用了系统资源的同时,又提供了较高的可伸缩性能,这里所指的线程即为操作系统的线程。服务器在运行时由各种内存数据结构和一系列的线程组成,线程分为多种类型,不同类型的线程完成不同的任务。线程通过一定的同步机制对数据结构进行并发访问和处理,以完成客户提交的各种任务。DM 数据库服务器是共享的服务器,允许多个用户连接到同一个服务器上,服务器进程称为共享服务器进程。
1.1.达梦数据库中不可或缺的核心线程
DM的核心进程是dmserver,该进程中又包括了监听线程、IO 线程、工作线程、调度线程、日志线程等。
一个正常运行的达梦数据库,这些"元老级"线程缺一不可:

1.1.1.监听线程(Listener Thread)
数据库的"前台" 。它主要的任务是在服务器指定端口(默认5236)循环监听 并接收来自客户端的连接请求。一旦有新连接,它会唤醒并为该会话分配一个"会话工作线程"来接手(会话申请任务)。加入工作线程的任务队列,等待工作线程来进行处理。
它在系统启动完成后才启动,并且在系统关闭时首先被关闭。为了保证在处理大量客户连接时系统具有较短的响应时间,监听线程比普通线程优先级更高。
DM 服务器 所有配置端口的范围为 1024-65534 。当客户端工具发起连接时,由操作系统为客户端工具自动分配一个端口用于与 DM 服务器进行通信。对于数据守护、DMDSC、DM MPP 和 DMDPC 等分布式数据库中各实例节点之间的通信,发起连接的节点也由操作系统自动分配端口,所以配置主备/DMDSC/DM MPP/DMDPC 时,除了各实例指定的端口外,发起连接的实例也会有操作系统自动分配的用于和其他实例进行通信的端口。
1.1.2.工作线程 (Worker Threads)
数据库的"业务员",真正的劳动主力。负责服务器各种类型任务的处理及所有实际的数据相关操作(SQL解析、执行以及数据操作等)。为了提高效率,达梦采用线程池技术,其初始数量由 WORKER_THREADS 参数控制。当连接数超过预设值时,后续请求会进入队列排队等待。
工作线程包括任务工作线程和会话工作线程等:
1)任务工作线程
负责处理服务器内部生成并加入对应任务队列的各项任务,如调度线程生成的清理缓存任务、异步执行的任务等,其初始个数由 INI 参数 TASK_THREADS 指定。任务工作线程空闲时从任务队列依次摘取任务并进行处理,当任务队列中的任务堆积时,系统会增加任务工作线程数量以减少任务堆积。
2)会话工作线程
负责处理会话的请求任务,分为非线程池模式 和线程池模式 ,由 INI 参数 STHD_FLAG 控制。非线程池模式下每一个会话对应一个会话工作线程,一个会话上的任务全部由同一个工作线程完成, 这样减少了线程切换的代价,提高了系统效率。线程池模式 初始工作线程个数由 INI 参数 WORKER_THREADS*STHD_THREAD_NUM 指定,由会话轮询线程接收所有用户请求,加入会话任务队列,会话工作线程从会话任务队列依次摘取请求任务进行处理。
1.1.3.IO线程(IO Threads)
在数据库活动中,IO操作历来是最为耗时的操作之一。IO线程正是解决磁盘I/O瓶颈的异步处理这方面专家。为了避免工作线程因等待缓慢的磁盘读写而被阻塞,IO线程专门负责从磁盘读取数据页到缓冲区,或将脏数据页写回磁盘。
**啥时候触发IO线程:**IO线程在启动后,通常都处于睡眠状态,当系统需要进行IO时,只需要发出一个IO请求,此时线程被唤醒以处理该请求,在完成该IO操作后进入睡眠状态。
如何控制IO线程的个数:可以通过设置dm.ini文件中IO_THR_GROUPS参数来设置控制IO线程组的数量,默认情况下,每个组通常包含2个线程。

通常情况下,DMServer需要进行IO操作的时机主要是以下三种:
1)需要处理的数据页不在缓冲区中,此时需要将相关数据页读入缓冲区;
2)缓冲区满或系统关闭时,此时需要将部分脏数据页写入磁盘;
3)检查点到来时,需要将所有脏数据页写入磁盘。
1.1.4.调度线程(Schedule Thread)
数据库的"管家",负责接管系统中所有需要定时调度的后台任务。它就像一个"闹钟",每秒轮询一次,负责触发检查点(Checkpoint)、清理SQL缓存、动态调整缓冲区大小、检测会话超时等内务工作。
1)检查系统级的时间触发器,如果满足触发条件则生成任务加到任务工作线程的任务队列由任务工作线程执行;
2)清理 SQL 缓存、计划缓存中失效的项,或者超出缓存限制后淘汰不常用的缓存项;
3)执行动态缓冲区检查。根据需要动态扩展或动态收缩系统缓冲池;
4)自动执行检查点。为了保证日志的及时刷盘,减少系统故障时恢复时间,根据 INI 参数设置的自动检查点执行间隔定期执行检查点操作;
5)会话超时检测。当客户连接设置了连接超时时,定期检测是否超时,如果超时则自动断开连接;
6)必要时执行数据更新页刷盘;
7)唤醒等待的工作线程。
1.1.5.日志FLUSH线程(Log Flush Thread)
**保证事务持久性的"记录官"。任何数据修改都会先产生REDO日志。**为了保障数据故障恢复的一致性,REDO日志的刷盘必须在数据页刷盘之前进行。事务运行时,会把生成的REDO日志保留再日志缓冲区中,当事务提交或则执行检查点时,,会通知FLUSH线程进行日志刷盘,严格遵守"日志先行"(WAL)原则。
**注意:**实际在生产的过程中,由于日志具备顺序写入的特点,所以它要比数据页的IO写入效率更高,并且考虑的日志写满溢出的情况,还需对日志信息进行归档处理。
1.1.6. 日志归档线程(Log Archive Thread)
数据安全的"档案管理员"。当数据库处于归档模式,且联机日志文件写满需要切换时,此线程负责将写满的日志文件复制到指定的归档目录下,为数据备份和恢复提供基础。
日志归档线程包含异步归档线程,负责远程异步归档任务。如果配置了非实时归档,由日志 FLUSH 线程产生的任务会分别加入日志归档线程,日志归档线程负责从任务队列中取出任务,按照归档类型做相应归档处理。
将日志 FLUSH 线程和日志归档线程分开的目的是为了减少不必要的效率损失,除了远程实时归档外,本地归档、远程异步归档都可以脱离 FLUSH 线程来做,如果放在 FLUSH 线程中一起做会严重影响系统性能。
1.1.7.检查点线程(Checkpoint Thread)
这个线程负责执行检查点(Checkpoint)操作,即将内存中的脏数据页统一刷回磁盘,并更新数据库的恢复起点,这对于缩短数据库崩溃后的恢复时间至关重要。
1.1.8.Purge线程
称之为回滚段清理PURGE线程,主要负责清理不再需要的Undo数据(回滚记录),有助于回收存储空间并维持数据库读一致性的高效运转。
1.1.9.MAL系统线程
MAL 系统是DM 内部高速通信系统 ,在集群、数据守护等分布式或高可用架构中负责实例间的内部高速通信。**基于 TCP/IP 协议实现。**服务器的很多重要功能都是通过 MAL 系统实现通信的,例如数据守护、MPP、远程日志归档等。MAL 系统内部包含一系列线程,有 MAL 监听线程、MAL 发送工作线程、MAL 接收工作线程等。
DM线程相关的动态视图:
| 名称 | 说明 |
|---|---|
| V$THREADS | 记录当前系统中活动线程的信息 |
| V$PROCESS | 记录服务器进程信息 |

二、动手设计实验深入学习
理论学习之后,更重要的是动手实践。这里为你设计了一套由浅入深的实验方案,帮助你直观地观察和验证这些线程的工作原理
实验环境准备
**软件:**安装一个达梦数据库(建议DM8),并准备好一个客户端工具(如disql)。
**工具:**确认操作系统自带的 ps、grep、top 等命令可用;可选装 gdb 调试工具用于进阶实验。
**思路:**达梦是单进程多线程架构,所有线程都"隐藏"在 dmserver 进程里,我们需要借助系统工具来让它们"现形"。
2.1.实验一:基础观察与验证(重点)
目标:直观验证"单进程多线程"架构,并掌握查看线程详情的方法。
步骤:
1)在服务器上执行ps -ef | grep dmserver,找到dmserver主进程的PID;

2)使用ps -T -p <PID>命令,列出该进程下所有的线程。观察线程名(如dm_lsnr_thd,dm_io_thd)和数量,并与达梦数据库中的v$THREADS视图中查询到的结果进行对比。

bash
[dmdba@localhost bin]$ ps -ef |grep dmserver
dmdba 1296 1 0 17:48 ? 00:00:07 /home/dmdba/dmdbms/bin/dmserver path=/dmdata2/data/IMPORT_DB/dm.ini -noconsole
dmdba 1298 1 0 17:48 ? 00:00:07 /home/dmdba/dmdbms/bin/dmserver path=/dmdata/data/DMTEST/dm.ini -noconsole
dmdba 47356 36289 0 19:48 pts/0 00:00:00 grep --color=auto dmserver
[dmdba@localhost bin]$ ps -T -p 1296
PID SPID TTY TIME CMD
1296 1296 ? 00:00:01 dmserver
1296 1465 ? 00:00:00 dm_sqllog_thd
1296 1467 ? 00:00:00 dm_pthd_thd
1296 1468 ? 00:00:00 dm_pthd_thd
1296 1476 ? 00:00:00 dm_pthd_thd
1296 1477 ? 00:00:00 dm_pthd_thd
1296 1478 ? 00:00:00 dm_pthd_thd
1296 1479 ? 00:00:00 dm_pthd_thd
1296 1480 ? 00:00:00 dm_pthd_thd
1296 1481 ? 00:00:00 dm_pthd_thd
1296 1482 ? 00:00:00 dm_pthd_thd
1296 1483 ? 00:00:00 dm_pthd_thd
1296 1484 ? 00:00:00 dm_pthd_thd
1296 1485 ? 00:00:00 dm_pthd_thd
1296 1486 ? 00:00:00 dm_pthd_thd
1296 1487 ? 00:00:00 dm_pthd_thd
1296 1488 ? 00:00:00 dm_pthd_thd
1296 1489 ? 00:00:00 dm_pthd_thd
1296 1490 ? 00:00:00 dm_tskwrk_thd
1296 1491 ? 00:00:00 dm_tskwrk_thd
1296 1492 ? 00:00:00 dm_tskwrk_thd
1296 1493 ? 00:00:00 dm_tskwrk_thd
1296 1494 ? 00:00:00 dm_tskwrk_thd
1296 1495 ? 00:00:00 dm_tskwrk_thd
1296 1496 ? 00:00:00 dm_tskwrk_thd
1296 1497 ? 00:00:00 dm_tskwrk_thd
1296 1498 ? 00:00:00 dm_tskwrk_thd
1296 1499 ? 00:00:00 dm_tskwrk_thd
1296 1500 ? 00:00:00 dm_tskwrk_thd
1296 1502 ? 00:00:00 dm_tskwrk_thd
1296 1503 ? 00:00:00 dm_tskwrk_thd
1296 1504 ? 00:00:00 dm_tskwrk_thd
1296 1505 ? 00:00:00 dm_tskwrk_thd
1296 1506 ? 00:00:00 dm_tskwrk_thd
1296 1507 ? 00:00:00 dmserver
1296 1508 ? 00:00:00 dmserver
1296 1509 ? 00:00:00 dmserver
1296 1519 ? 00:00:00 dmserver
1296 1520 ? 00:00:00 dmserver
1296 1521 ? 00:00:00 dmserver
1296 1522 ? 00:00:00 dmserver
1296 1523 ? 00:00:00 dmserver
1296 1524 ? 00:00:00 dmserver
1296 1525 ? 00:00:00 dmserver
1296 1526 ? 00:00:00 dmserver
1296 1527 ? 00:00:00 dmserver
1296 1528 ? 00:00:00 dmserver
1296 1529 ? 00:00:00 dmserver
1296 1530 ? 00:00:00 dmserver
1296 1545 ? 00:00:00 dmserver
1296 1546 ? 00:00:00 dmserver
1296 1547 ? 00:00:00 dmserver
1296 1867 ? 00:00:00 dm_quit_thd
1296 1869 ? 00:00:00 dm_osio_thd
1296 1870 ? 00:00:00 dm_osio_thd
1296 1871 ? 00:00:00 dm_osio_thd
1296 1872 ? 00:00:00 dm_osio_thd
1296 1873 ? 00:00:00 dm_osio_thd
1296 1874 ? 00:00:00 dm_osio_thd
1296 1875 ? 00:00:00 dm_osio_thd
1296 1876 ? 00:00:00 dm_osio_thd
1296 1877 ? 00:00:00 dm_osio_thd
1296 1878 ? 00:00:00 dm_osio_thd
1296 1879 ? 00:00:00 dm_osio_thd
1296 1880 ? 00:00:00 dm_osio_thd
1296 1881 ? 00:00:00 dm_osio_thd
1296 1882 ? 00:00:00 dm_osio_thd
1296 1883 ? 00:00:00 dm_osio_thd
1296 1884 ? 00:00:00 dm_osio_thd
1296 1902 ? 00:00:00 dm_pwr_thd
1296 1923 ? 00:00:00 dm_rsyswrk_thd
1296 1924 ? 00:00:00 dm_rsyswrk_thd
1296 1938 ? 00:00:00 dm_hio_thd
1296 1939 ? 00:00:00 dm_hio_thd
1296 1940 ? 00:00:00 dm_hio_thd
1296 1941 ? 00:00:00 dm_hio_thd
1296 1991 ? 00:00:00 dm_chkpnt_thd
1296 1992 ? 00:00:00 dm_redolog_thd
1296 2003 ? 00:00:00 dm_sqllog_thd
1296 2008 ? 00:00:00 dm_sql_aux_thd
1296 2010 ? 00:00:00 dm_purge_thd
1296 2022 ? 00:00:00 dm_lpq_thd
1296 2023 ? 00:00:00 dm_lpq_thd
1296 2024 ? 00:00:00 dm_lpq_thd
1296 2025 ? 00:00:00 dm_lpq_thd
1296 2026 ? 00:00:00 dm_lpq_thd
1296 2027 ? 00:00:00 dm_lpq_thd
1296 2028 ? 00:00:00 dm_lpq_thd
1296 2029 ? 00:00:00 dm_lpq_thd
1296 2030 ? 00:00:00 dm_lpq_thd
1296 2031 ? 00:00:00 dm_lpq_thd
1296 2032 ? 00:00:00 dm_trxbro_thd
1296 2050 ? 00:00:00 dm_trctsk_thd
1296 2051 ? 00:00:00 dm_wrkgrp_thd
1296 2052 ? 00:00:00 dm_wrkgrp_thd
1296 2053 ? 00:00:00 dm_wrkgrp_thd
1296 2054 ? 00:00:00 dm_wrkgrp_thd
1296 2055 ? 00:00:00 dm_wrkgrp_thd
1296 2056 ? 00:00:00 dm_wrkgrp_thd
1296 2057 ? 00:00:00 dm_wrkgrp_thd
1296 2058 ? 00:00:00 dm_wrkgrp_thd
1296 2059 ? 00:00:00 dm_wrkgrp_thd
1296 2060 ? 00:00:00 dm_wrkgrp_thd
1296 2061 ? 00:00:00 dm_wrkgrp_thd
1296 2062 ? 00:00:00 dm_wrkgrp_thd
1296 2063 ? 00:00:00 dm_wrkgrp_thd
1296 2064 ? 00:00:00 dm_wrkgrp_thd
1296 2065 ? 00:00:00 dm_wrkgrp_thd
1296 2066 ? 00:00:00 dm_wrkgrp_thd
1296 2069 ? 00:00:00 dm_audit_thd
1296 2070 ? 00:00:00 dm_audit_thd
1296 2079 ? 00:00:00 nlgn_task_threa
1296 2080 ? 00:00:02 dm_sched_thd
1296 2081 ? 00:00:00 dm_lsnr_thd
[dmdba@localhost bin]$
预期结果:你将亲眼看到成百上千的线程都归属于同一个PID。
2.2.实验二:核心线程功能验证(思路)
目标:过模拟特定操作,观察对应线程的活动。
步骤:

1)验证监听线程:在一个窗口用top -H -p <PID>实时监控线程列表。在另一个窗口,尝试用disql连接数据库。观察dm_lsnr_thd的CPU或状态变化。
这个:经测试,无明显的状态现象。
2)验证工作线程:在客户端执行一个复杂查询,如select count(*) from 一个大表;观察dm_wrkgrp_thd或dm_tskwrk_thd的活动。
3)验证IO线程:执行一个全表扫描,同时用iostat -x 1观察磁盘I/O。可以对比执行前后的IO线程状态。
4)验证日志FLUSH线程:在一个事务中执行INSERT/UPDATE操作并COMMIT。在提交瞬间,观察dm_redolog_thd是否又活动。
预期结果:你会清晰地看到:有连接进来时,监听线程"忙"一下;执行查询时,工作线程和IO线程"忙起来";提交事务时,日志FLUSH线程开始工作。
整体实验结果快,很多现象可能会观察不到。
2.3.实验三:参数调优实验(调优)
目标:通过修改线程相关参数,感受配置对性能的影响。
步骤:
1)修改前:使用性能测试工具(如dts自带的benchmark)对数据库进行压力测试,并记录下TPS/QPS及线程上下文切换次数(可用vmstat观察)。
2)修改参数:在dm.ini文件中,修改WORK_THTEADS(工作线程数)或IO_THR_GROUPS(I/O线程组数)。
3)重启生效:重启数据库使参数生效。
4)修改后:再次运行同样的压力测试。对比两次的测试结果和系统开销。
预期结果:你会发现,**线程数不是越多越好。配置过多反而会增加上下文切换的开销,降低性能。**这个实验能帮你直观理解参数配置的"黄金点"。
2.4.高级故障模拟(谨慎!)
目的:了解在极端情况下,如何定位问题线程
步骤
1)制造一个"长事务"或"锁等待"的场景。
2)通过v$threads找到疑似"卡住"的线程ID(TID)。
3)在操作系统层面,用top -H -p <PID>确认该线程是否处于D(不可中断睡眠)或R(运行)状态。
4)进阶:如果有GDB调试经验,可以gdb -p <PID>,然后thread <TID>切换到该线程,再用bt命令打印其堆栈信息,看它卡在哪个函数上。

预期结果:这个实验能让你初步掌握在生产环境中,当一个线程"假死"时,应该如何去定位和初步分析问题。