实测稀疏索引与部分索引:稀疏索引收录字段存在的文档(显式 null 算存在),用 hint 或排序时会漏掉缺失的;部分索引只收录满足过滤条件的文档,体积小得多(24,576 对 155,648 字节),查询必须蕴含过滤条件才能用;软删除场景的唯一部分索引;过滤条件的限制($ne 不行,$or 可以)。
实测唯一索引遇到 null 和缺失字段:缺失按 null 算,第二个缺失或 null 的文档就报 E11000;唯一加 sparse 允许任意多个缺失但显式 null 仍然冲突;唯一加部分索引(字符串 email)缺失和 null 都不限;复合索引逐字段按 null;已有重复数据时建索引失败及找重复的办法。
实测候选计划的试跑:每个候选计划最多取 101 条结果或 10000 次 works,同样的 works 里谁返回得多谁赢(b_1 返回 101 条,a_1 只返回 1 条);参数调成 10 后试跑缩短;SORT 阻塞,没有阻塞排序的计划更占便宜;结果很少时几次 works 就结束;最后验证选中的 b_1 在完整执行里确实最快。
实测有界队列的背压:2000 个 100 KB 条目、消费者慢时,无界队列生产者 42 ms 发完、峰值内存 242 MiB,maxsize=10 只要 26 MiB,而全部处理完的时间都在 11.2–11.4 秒;三段流水线里积压总出现在最慢一段的前面;忘了 task_done 会让 join() 永远等。
实测计划缓存:第一次执行产生 inactive 条目、第二次确认后变 active;同形状不同常量共用一个条目;换常量让缓存计划变慢(11 works 变成至少 110 works)时 profiler 记录 replanned 并重选;创建或隐藏索引会清空缓存;条目里的字段与手动清理。
实测 hint:优化器自己选的 user_1 只要 0.2 ms,hint 到 status_1 要 29 ms、全表 46 ms;键模式与索引名都可以,不存在的索引报错;hint 的计划不进计划缓存;用错 hint 让按 user 的查询慢 563 倍;aggregate、count、update 里的 hint 写法。
实测 asyncio 的取消与超时:wait_for 和 asyncio.timeout 都在约 200 ms 抛 TimeoutError;协程吞掉 CancelledError 时超时失效(1202 ms 后正常返回);shield 让任务在超时后继续跑完;gather 一个任务失败后其它任务仍在后台运行,TaskGroup 会取消它们;被取消的 aiohttp 请求会让连接关闭。
实测内存排序的 100 MiB 限制:9.0.2 默认 allowDiskUseByDefault 为 true,关掉之后 147.5 MB 的无索引排序报 code 292;allowDiskUse 时 SORT 阶段溢出 2 次共 155,400,000 字节仍成功;sort + limit 是 top-k 不会超限;有索引则没有 SORT 阶段。
实测 $or 如何用索引:每个分支都有索引时各走各的并由 OR 阶段去重;一个分支没有索引整个 $or 全表扫描(240 对 200,000 个文档);外层条件要有对应的复合索引才分配进每个分支;$or 加 sort 在每个分支都能有序时用 SORT_MERGE;同一字段的 $or 与 $in 计划相同。
在本机 10 核上量 lxml 各操作的多线程加速比(8 线程):HTML 解析 0.68–1.15 倍(不能并行,哪怕直接用 C 层的 etree.HTML),XML 解析约 2 倍,xpath 约 1 倍,序列化 tostring 约 4.1 倍;对照 sha256 5.61 倍、zlib 5.63 倍、纯 Python 与正则约 1 倍。
实测正则能否用上前缀索引:/^abc/ 收窄成 ["abc", "abd") 只检查 17 个键;不锚定的 /abc/、/^abc/i、/^.*abc/、带分支的 /^(abc|abd)/ 扫整个索引 300,006 个键(约 100 ms);小写副本字段、$in 里分开写正则是可行办法;正则不使用 collation。
实测文本索引:英文词干把 jumped、foxes 归一到 jump、fox;停用词(the)不进索引;大小写与重音默认不敏感;多词按 OR、引号是短语、减号排除;textScore 打分与 title 权重 10 的效果(3.55 升到 15.55);一个集合只能有一个文本索引。