7.2 mmap 与三库内存架构


7.2 mmap、内存上限与三库内存架构对照

本节摘要:mmap 让数据库文件直接映射进进程地址空间,读页免掉一次内存拷贝;但它的收益有明确边界——写路径完全不走 mmap,且映射受地址空间限制。本节拆解 mmap_size 的机制与陷阱,介绍 SQLite 的软堆上限与内存统计接口,最后把三个引擎的内存架构摆成一张对照图,给出嵌入式内存预算的推导公式。

mmap_size:免拷贝读页

传统读页路径是"系统调用读进内核缓冲区,再拷贝到页缓存"。设了 mmap 之后:

PRAGMA mmap_size = 268435456; -- 允许最多映射 256MB 数据库内容 PRAGMA mmap_size; -- 查询当前生效值,连接级设置

映射范围内的页访问由操作系统页表直接完成:命中 CPU 缓存是纳秒级,命中操作系统页缓存不再有内核态切换与拷贝,真正缺页时由操作系统的缺页中断处理。收益边界要划清:只有纯读路径享受 mmap;写路径(脏页、日志、检查点)照旧走页缓存与 VFS。而且 mmap 页不走 SQLite 自己的 LRU 淘汰——由操作系统决定哪些映射页留在物理内存。带来的三个实际影响:

  1. 读密集负载的 CPU 占用下降——省掉每页一次的拷贝,吞吐敏感的只读查询受益最明显。
  2. 内存计数变"虚"——mmap 占的是地址空间与操作系统页缓存,sqlite3_memory_used 看不到它,监控要连操作系统层面一起看。
  3. 32 位平台要克制——地址空间只有 3GB 左右,映射过多会挤压应用自身的堆;64 位桌面与服务器通常可以放心给几百 MB。

⚠️ 数据库文件在 NFS 等网络文件系统上时不要开 mmap——映射页的一致性语义在这些文件系统上不可靠。桌面本地图盘、嵌入式 eMMC/UFS 才是 mmap 的主场。

内存上限与统计:与应用合租的纪律

SQLite 提供一套"自报家门"的内存治理接口,这在服务端数据库里没有对应物——因为它们不与人合租:

/* 全局软上限:超限时大分配开始失败,逼应用优雅降级 */ sqlite3_soft_heap_limit64(64 * 1024 * 1024); /* 64MB */ /* 状态查询:当前用量、峰值、页缓存命中与未命中次数 */ sqlite3_int64 used = 0, peak = 0; sqlite3_status(SQLITE_STATUS_MEMORY_USED, &used, &peak, 0);

软上限不硬抢内存,而是让超过阈值的分配请求返回失败——数据库宁可拒绝操作也不把宿主应用挤到 OOM。配合 PRAGMA cache_spill 与 temp_store 的取舍(临时表与排序落盘还是驻留),嵌入式应用可以精确控制"数据库最多借多少"。移动端系统甚至会在内存告警时要求应用主动收缩:sqlite3_db_release_memory() 让页缓存把能还的页还给堆,这是进程内引擎独有的"文明让路"机制。

图:三库内存架构对照

图:三库内存架构对照

三种内存观的根源

三种架构的分歧再次回到全册主线。SQLite 与应用合租:引擎嵌在别人的进程里,没有资格独占资源,所以一切内存都可协商、可统计、可释放;代价是没有共享池——一百个连接各持一份热点副本,内存效率随连接数下降。MySQL 独占进程:缓冲池想要多大给多大,后台线程精心刷脏,内存效率最高;代价是整个机器要为它划出专属领地。PostgreSQL 双层结构:共享缓冲区通常故意只给四分之一内存,剩下的信任操作系统页缓存——因为多进程架构下自己的池管理成本高,把二级缓存外包给内核是务实之选。

这个对照给 SQLite 用户的行动指引很直接:控制连接数比堆缓存更划算。服务端加连接不加内存(共享池摊薄),SQLite 加连接就是加内存(各持一份)——WAL 模式下"单写连接加少量只读连接"的模式之所以是官方推荐,内存账是重要原因之一。

参数对照与迁移速查

从服务端迁移到 SQLite 时,内存参数没有一一映射,按下表对号入座能避免多数误配:

服务端参数 量级习惯 SQLite 对应物 关键差异
innodb_buffer_pool_size 机器内存的一半以上 cache_size 按连接预算 全局一份对每连接一份
shared_buffers 机器内存的四分之一 无直接对应,部分由 mmap 承担 PG 信任系统页缓存做二级
work_mem 或 sort_buffer 每排序操作的额度 temp_store 三档 SQLite 无每操作精调
query_cache(已废除) 无对应 SQLite 靠语句缓存加页缓存
max_connections 数百到数千 建议个位数到几十 连接即内存副本,宁少勿多

表里最后一行是迁移中最容易踩的坑:服务端应用默认池里常驻几十个连接,直接搬到 SQLite 等于把缓存预算乘上几十。正确姿势是读连接随用随开(进程内开销近零)、写连接全局唯一,7.3 节的部署纪律与此呼应。

常见问题速答

**32 位平台怎么配?**地址空间只剩 2 到 3GB,mmap 给到 256MB 以上就会挤压应用自身的映射。建议:mmap_size 保持 0 或 64MB 以内,缓存预算整体压到 64MB 级,优先靠"缩热点"(更精的索引)而不是"加预算"。64 位桌面与服务器没有此约束,几百 MB 的 mmap 与缓存都是安全区。

**应用内存吃紧时,数据库能"还"多少回来?**调 sqlite3_db_release_memory() 后,页缓存里干净页会尽量归还,脏页因日志义务无法立即归还;WAL 模式下再做一次 TRUNCATE 检查点可以连带清掉 WAL 文件占的空间。归还后若还要继续压缩,靠软堆上限逼引擎自身节流——两层机制配合,通常能释放总占用的一半左右,剩下的是语句与结构这类骨架内存。

本节要点回顾

  • mmap 只加速读路径:免拷贝、免系统调用,写路径照旧;网络文件系统上禁用。
  • 软堆上限与内存状态接口是进程内引擎的独有纪律:可自约束、可统计、可主动让路。
  • 三种内存观:SQLite 合租堆、MySQL 独占池、PostgreSQL 双层信任操作系统——架构决定配置哲学。
  • 嵌入式预算公式:各连接缓存之和加临时存储峰值加结构缓存;控制连接数优先于加大缓存。

**mmap 与页缓存会重复缓存同一页吗?**会,两层各存一份的场景确实存在:mmap 页由操作系统页缓存托底,页缓存又持有自己那份拷贝。这听起来浪费,实际影响有限——mmap 服务读者、页缓存服务写者与未映射区域,热点读页的重复成本换来的是写路径的隔离。内存极致紧张的设备上,把 mmap_size 降为零即可回到单一缓存模型。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U