瞬时高并发写入不触发索引预热,真正卡住写入的是索引构建/更新引发的WiredTiger cache压力、journal刷盘竞争及IX锁排队;低基数字段建索引、写密集字段单建索引、keysExamined远大于nreturned的无效索引均显著加剧IO瓶颈。

瞬时高并发写入本身不会直接触发索引“预热”,MongoDB 没有类似数据库 buffer pool 预热的索引加载机制;真正卡住写入的是索引构建/更新过程中的内存与磁盘 IO 竞争,尤其是 WiredTiger cache 压力和 journal 刷盘延迟。
为什么 createIndex 会阻塞高并发写入
在 MongoDB 5.0(WiredTiger 引擎)中,后台建索引(background: true)仍需在内存中构建 B-tree 结构,并频繁写入 journal 和 cache。当瞬时写入流量突增时:
- WiredTiger cache 快速填满,触发更激进的 dirty page 刷盘,与 journal 刷盘争抢磁盘带宽
- 索引构建期间对集合加
intent exclusive (IX)锁,虽不阻塞读,但会排队写操作(尤其update/upsert) - 若索引字段基数低(如
{status: 1}),WiredTiger 压缩效率差,导致索引页分裂频繁、写放大加重 - 错误地在写密集字段(如时间戳、计数器)上建单字段索引,会使每次写都触发多处索引更新,IO 压力翻倍
用 collMod + indexBuildRetry 降低建索引风险
避免在业务高峰执行 createIndex。改用可中断、可重试的建索引方式:
- 先用
db.runCommand({ createIndexes: ..., commitQuorum: "votingMembers" })显式指定写关注,防止主节点写完但多数节点未确认就返回成功 - 建索引失败后,不要删索引重来 —— 执行
db.runCommand({ collMod: "mycol", indexBuildRetry: true })让 MongoDB 自动续建未完成的索引 - 对已有集合加复合索引前,用
explain("executionStats")验证查询是否真走新索引路径,避免“建了不用”白耗 IO - 禁用
directoryPerDB: false(默认),改设为true,让每个库独立目录,减少索引文件与数据文件的 inode 竞争
用 compact 清理碎片 + 控制索引内存驻留
高频写入后索引页易碎片化,WiredTiger 不会自动合并小页。手动 compact 可降低后续写入的随机 IO:
- 仅在低峰期运行:
db.runCommand({ compact: "mycol", force: true });注意该命令会短暂阻塞读写 - 设置
wiredTiger.cacheSizeGB至少为物理内存的 60%,确保索引节点尽量驻留内存,减少磁盘寻址 - 监控
wt:cache: bytes currently in the cache和wt:cache: maximum bytes configured,若前者长期 >90% of 后者,说明 cache 不足,索引被迫换出 - 删除
keysExamined ≈ docsExamined且nreturned极小的索引(比如慢日志里反复出现的IXSCAN{alxxxId:1,itemTagList:1}扫 1498 行只返 3 行),这类索引纯属 IO 负担
真正的“预热”不是提前加载索引,而是让 WiredTiger cache 在流量到来前自然填充热点索引页——这依赖稳定写入节奏。瞬时洪峰下,优先保 journal 和 oplog 的 IO 路径隔离,比强行预热索引更有效。索引字段顺序、选择性、是否覆盖查询,远比“预热”动作本身影响更大。


















