四月 23, 2026
摘要:在本教程中,您将学习如何计算 PostgreSQL 的内存使用量。
目录
PostgreSQL 架构
PostgreSQL 的架构基于三个基本部分:进程、内存和磁盘。
内存可分为两类:
- 本地内存:由每个后端进程加载,供自己用于查询处理。它分为以下子类:
- 工作内存:工作内存用于按 ORDER BY 和 DISTINCT 操作对元组进行排序,以及连接表。
- 临时缓冲区:用于存储临时表。
- 本地缓存:用于缓存执行计划、关系和系统表信息。
- 共享内存:由 PostgreSQL 服务器在启动时分配,供所有进程使用。它分为以下子类:
- 共享缓冲池:PostgreSQL 从磁盘加载包含表和索引的页面,以直接从内存工作,从而减少磁盘访问。
- WAL 缓冲区:WAL 数据是 PostgreSQL 中的事务日志,包含了在数据库中进行的更改。WAL 缓冲区是在将 WAL 数据写入磁盘到 WAL 文件之前临时存储的区域。在提交事务的时候,PostgreSQL 会刷写 WAL 数据到磁盘。这对于避免在服务器发生故障时丢失信息非常重要。
Linux 进程 smaps 文件
/proc/PID/smaps 是一个基于 maps 文件的扩展,它会显示每个进程映射的内存消耗。对于每个映射(又名虚拟内存区域或 VMA),有一系列行,如下所示:
00400000-00914000 r-xp 00000000 09:00 3545633 /usr/pgsql/bin/postgres
Size: 5200 kB
Rss: 964 kB
Pss: 214 kB
Shared_Clean: 964 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 964 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
00b13000-00b14000 r--p 00513000 09:00 3545633 /usr/pgsql/bin/postgres
Size: 4 kB
Rss: 4 kB
Pss: 0 kB
Shared_Clean: 0 kB
...
其中第一行显示的信息与 /proc/PID/maps 中显示的映射信息相同。以下几行显示了映射的大小(size);支持 VMA 时分配的每个页面的大小(KernelPageSize),通常与页表条目中的大小相同;支持 VMA 时 MMU 使用的页面大小(在大多数情况下,与 KernelPageSize 相同);当前驻留在 RAM 中的映射量(RSS);该进程在此映射中的比例份额(PSS);以及映射中干净和弄脏的共享页面和私有页面的数量。
一个进程的 “比例集大小”(PSS)是它在内存中的页面数,其中每个页除以共享它的进程数。因此,如果一个进程有 1000 个私有页面,还有 1000 个页面与另外一个进程共享,则其 PSS 将为 1500。“Private_Dirty” 是 PSS 中由脏页组成的部分。
内存使用量计算
我们先创建一个简单的存储过程,用于收集数据库进程的内存使用量:
CREATE OR REPLACE PROCEDURE gather_proc_memstat()
AS $$
DECLARE
pid integer;
BEGIN
DROP TABLE IF EXISTS proc_smaps;
-- 显式保留行,以便 COMMIT 不会清空表
CREATE TEMP TABLE proc_smaps (
pid integer,
type varchar,
size bigint,
unit varchar
) ON COMMIT PRESERVE ROWS;
COMMIT;
FOR pid IN
SELECT pg_stat_get_backend_pid(s.backendid)
FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS s
LOOP
EXECUTE format($sql$
COPY proc_smaps FROM PROGRAM
$shell$ awk '$1 ~ /:/ {print %s, $1, $2, $3}' /proc/%s/smaps $shell$
WITH (DELIMITER ' ', ON_ERROR ignore)
$sql$, pid, pid);
END LOOP;
END $$ LANGUAGE plpgsql;
要在 Linux 上计算 PostgreSQL 进程的内存使用量,请执行以下操作:
CALL gather_proc_memstat();
SELECT pid, sum(size) AS uss_in_kb, backend_type
FROM proc_smaps JOIN pg_stat_activity USING (pid)
WHERE type LIKE 'Private%'
GROUP BY pid, backend_type
ORDER BY uss_in_kb DESC;
pid | uss_in_kb | backend_type
-----+-----------+------------------------------
21 | 12760 | checkpointer
78 | 5888 | client backend
80 | 5880 | client backend
79 | 5868 | client backend
77 | 5768 | client backend
82 | 5708 | client backend
83 | 5652 | client backend
81 | 5632 | client backend
74 | 5616 | client backend
76 | 5548 | client backend
75 | 5436 | client backend
90 | 4340 | client backend
18 | 3116 | io worker
27 | 1340 | undo launcher
25 | 588 | autovacuum launcher
26 | 532 | logical replication launcher
19 | 468 | io worker
20 | 404 | io worker
24 | 284 | walwriter
22 | 276 | background writer
(20 rows)
- 首先,获取 PostgreSQL 中运行的所有进程的进程 PID
- 然后获取每个进程的相应
smaps文件(/proc/PID/smaps)的名称 - 然后从该文件中获取
Private行,Private_Clean和Private_Dirty表示进程的私有、不可共享的已清理(Clean)和已脏(Dirty)内存大小 - 最后,将数字相加