第一阶段 08 · 刷新与可见性(refresh 与 NRT)

发布时间:2026/7/31 18:08:38
第一阶段 08 · 刷新与可见性(refresh 与 NRT) 阶段第一阶段 / 核心概念目标搞懂「为什么写完立刻查不到」——ES 的近实时NRT机制以及refresh的正确用法。本篇是独立文档示例不依赖任何具体项目。承接第 07 篇写操作。1. 概念写完为什么查不到和 PostgreSQL 最大的心智差异PGINSERT提交后同一事务外立刻能SELECT到读己所写。ES写入成功 ≠ 立刻可被搜索到。文档要先经过一次refresh才会进入可搜索的段segment。这就是NRTNear Real Time近实时默认每1 秒自动 refresh 一次所以「写完马上搜」经常搜不到等约 1 秒才出现。这不是 bug是设计。写入 index/bulk ──成功──► 在内存 buffer 里还搜不到 │ refresh默认每 1s或手动触发 ▼ 进入 segment可被 search 搜到重要区分写成功index/bulk返回 200 数据已在 ES、不会丢可搜索能被_search命中 需要 refresh 之后。两者不是一回事。2. 为什么不设计成「立刻可见」因为每次 refresh 都要生成新的 Lucene segment开销不小。如果每写一条就 refresh 一次高并发写入下性能会很差。所以 ES 用「攒一批、每秒刷一次」来换吞吐——这也是为什么大批导入时反而要把自动 refresh 关掉。3. 让数据「马上可见」的三种方式3.1 立刻查不到时最省事等一下 / 靠默认 1s绝大多数业务场景等默认的 1 秒即可不需要任何额外操作。3.2 单次写入强制刷新refresh参数写操作可以带refresh让这条写入立刻可搜索代价是更慢别高频用。取值含义何时用false默认不强制等自动 refresh~1s绝大多数场景true立即refresh 整个分片写完马上可搜测试、必须读己所写的少量写wait_for不主动刷但阻塞等到下一次 refresh才返回想「可见后再返回」又不想加重负载DSLPUT orders_idx/_doc/1001?refreshtrue { region: AP, amount: 1299 } PUT orders_idx/_doc/1002?refreshwait_for { region: NA, amount: 999 }3.3 手动刷新整个索引_refresh批量写完后一次性刷新整个索引让刚写的都可见POST orders_idx/_refresh4. Spring Boot 实现4.1 单条写入带 refresh// 写完立刻可搜Refresh.Trueclient.index(i-i.index(orders_idx).id(doc.getId()).document(doc).refresh(Refresh.True));// true / False / WaitFor// 等到下一次 refresh 再返回更温和client.index(i-i.index(orders_idx).id(doc.getId()).document(doc).refresh(Refresh.WaitFor));Refresh枚举co.elastic.clients.elasticsearch._types.Refresh值为True/False/WaitFor。4.2 bulk 带 refreshclient.bulk(b-b.operations(ops).refresh(Refresh.WaitFor));// 整批写完后统一等可见4.3 手动刷新整个索引publicvoidrefreshIndex(Stringindex)throwsIOException{client.indices().refresh(r-r.index(index));}5. 大批量导入反过来「关掉」刷新导入大量数据时频繁 refresh 反而拖慢。标准套路是先关、后开、再刷一次// 1) 导入前关闭自动 refresh、去掉副本client.indices().putSettings(s-s.index(orders_idx).settings(st-st.refreshInterval(t-t.time(-1)).numberOfReplicas(0)));// 2) bulk 大批写入此时不刷、不建副本最快// ... client.bulk(...) 多批 ...// 3) 导入后恢复设置并手动刷新一次client.indices().putSettings(s-s.index(orders_idx).settings(st-st.refreshInterval(t-t.time(1s)).numberOfReplicas(1)));client.indices().refresh(r-r.index(orders_idx));对照第 07 篇 §7「大批导入临时调优」这里给出了具体代码。6. 删除 / 更新也一样受 NRT 影响update/delete/updateByQuery/deleteByQuery同样遵循 NRT改完/删完也要等 refresh 后搜索结果才反映变化。它们也支持相同的refresh参数。client.delete(d-d.index(orders_idx).id(1001).refresh(Refresh.True));特例按_id直接GETclient.get(...)是实时的不受 refresh 影响它走的是 translog/内存不依赖 segment。所以「按主键取单条」总能读到最新但「用条件_search搜」才受 NRT 限制。7. 坑与最佳实践别把「写完搜不到」当 bug那是 NRT等 ~1s 或用refresh/wait_for。refreshtrue不要高频用每次都新建 segment写多了严重拖慢仅用于测试或极少量必需场景。想「可见后再返回」用wait_for比true温和不主动增加 refresh 次数。大批导入要关自动 refreshrefresh_interval: -1导完恢复并手动刷一次。读己所写按主键如果只是要确认刚写的单条用GET _doc/{id}实时别用_search。单元测试测试里写完立刻断言记得带refreshtrue否则查不到导致假失败。下一篇进入第二阶段查询能力10-match-全文匹配.md。写入调优refresh、副本、translog的更多细节见第四阶段 30、31、40 篇。