bigywater.tech
MongoDB · 43

建索引期间读写会被阻塞吗

建索引会阻塞读写吗?这篇在 800 万个文档的集合上建索引,同时用一个线程不停读写,对比建索引前后的延迟,再看进度、并发写入、唯一索引冲突和 drop 的结果。

0.43 / 0.19 ms
建索引期间并发读 / 写的 p99 延迟(建之前 0.39 / 0.17 ms)
0 个
建索引的 7.9 s 里并发线程遇到的错误数
50 次
唯一索引构建期间并发插入重复值成功的次数,构建最终失败

不阻塞

实测 · 本机执行输出
  建索引耗时 7.9 s

800 万个文档上建索引 7.9 s,并发线程的读写延迟几乎没有变化(p99 读 0.39 → 0.43 ms,写 0.17 → 0.19 ms),没有任何错误。

进度

实测 · 本机执行输出
  读  建之前  7,252 次,中位数 0.18 ms,p99 0.39 ms,最大 1.8 ms
  读  建期间 29,390 次,中位数 0.17 ms,p99 0.43 ms,最大 5.1 ms
  读  建之后  7,287 次,中位数 0.17 ms,p99 0.31 ms,最大 9.7 ms
  写  建之前  7,251 次,中位数 0.09 ms,p99 0.17 ms,最大 4.7 ms
  写  建期间 29,390 次,中位数 0.09 ms,p99 0.19 ms,最大 14.5 ms
  写  建之后  7,288 次,中位数 0.11 ms,p99 0.20 ms,最大 4.8 ms
  并发线程遇到的错误数: 0 []

currentOp 显示两个阶段:scanning collection 扫描集合,inserting keys from external sorter 把排好序的键写进索引,都带 done / total。

并发写入

实测 · 本机执行输出
    0.5s  Index Build: scanning collection Index Build: scanning collection: 1337622/8009093 16%  {"done": 1337622, "total": 8009093}
    1.0s  Index Build: scanning collection Index Build: scanning collection: 2623216/8010870 32%  {"done": 2623216, "total": 8010870}
    1.5s  Index Build: scanning collection Index Build: scanning collection: 4025756/8012752 50%  {"done": 4025756, "total": 8012752}
    2.0s  Index Build: scanning collection Index Build: scanning collection: 4857640/8013960 60%  {"done": 4857640, "total": 8013960}
    3.5s  Index Build: scanning collection Index Build: scanning collection: 5550659/8020329 69%  {"done": 5550659, "total": 8020329}
    4.0s  Index Build: scanning collection Index Build: scanning collection: 6954793/8022184 86%  {"done": 6954793, "total": 8022184}
    4.5s  Index Build: scanning collection  null
    5.5s  Index Build: inserting keys from external sorter into index Index Build: inserting keys from external sorter into index: 1059877/8023600 13%  {"done": 1059877, "total": 8023600}

构建期间插入的文档也会进入索引,最终索引条目数等于文档数。

唯一索引

实测 · 本机执行输出
  文档数 8,043,929;索引条目 {'_id_': 8043929, 'k_1': 8043929}
  test> db.t.find({k: {$gte: 0, $lt: 100000}})
  {
    plan: 'FETCH > IXSCAN(k_1)',
    nReturned: 799,
    keysExamined: 799,
    docsExamined: 799,
    ms: 39
  }

构建期间对重复值的并发插入不会被拒绝;构建结束时发现重复而失败(E11000),索引没有建出来,重复的文档还在。

drop

实测 · 本机执行输出
  建唯一索引耗时 5.3 s;并发线程插入 u=123 的重复文档(原来已有一个 u=123)成功了 50 次
  建唯一索引的结果: Index build failed: cd764f0b-11a1-4211-8655-ed48d2c5e00d: Collection test.t ( 779cb36c-4a3e-471a-ba26-639234e32930 ) :: caused by :: E11000 duplicate key error collection: test.t index: u_1 dup key: { u: 123 }, full error: {'ok': 
  索引列表: ['_id_', 'k_1'];u=123 的文档数 51

在构建期间 drop 集合会立即成功(0.05 s),并中止构建,构建一侧收到失败的错误。

适用范围

实测 · 本机执行输出
  建 z 索引 1.5 s 后在另一个连接上 drop 整个集合:drop ok,耗时 0.05 s;建索引的线程: Index build failed: 12875bcd-15ac-4c69-92e3-f0751a9ddca6: Collection test.t ( 779cb36c-4a3e-471a-ba26-639234e32930 ) :: caused by :: Collection test.t(779cb36c-,1.53 s

这是单节点、1 GB 数据、SSD 上的结果。副本集上的 commitQuorum、构建在 secondary 上的行为没有测。

分步演示

建索引与阻塞
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。集合 800 万个文档、缓存 0.5 GB;并发线程用 pymongo 持续读写;进度每 0.5 秒取样 currentOp
    • 没有验证:副本集上的 commitQuorum 与 secondary 构建;建索引的起止瞬间的短暂排他锁(采样间隔下没有看到,但不代表没有);更大数据集(缓存放不下)时对前台的影响;多个索引同时构建;内存限制(maxIndexBuildMemoryUsageMegabytes)与磁盘溢出;collMod 和其它 DDL 与建索引的冲突
    京ICP备2026067502号-1京公网安备11010502063487号