查询性能调优(二)
- [1.SQL LIMIT 的三个层次](#1.SQL LIMIT 的三个层次)
- [2.Elasticsearch 各参数的实际作用](#2.Elasticsearch 各参数的实际作用)
-
- [📊 2.1 terminate_after: 10000](#📊 2.1 terminate_after: 10000)
-
- [2.1.1 工作流程](#2.1.1 工作流程)
- [2.1.2 实际行为](#2.1.2 实际行为)
- [2.1.3 与 SQL LIMIT 的相似性](#2.1.3 与 SQL LIMIT 的相似性)
- [📈 2.2 track_total_hits: 10000](#📈 2.2 track_total_hits: 10000)
-
- [2.2.1 工作流程](#2.2.1 工作流程)
- [2.2.2 实际行为](#2.2.2 实际行为)
- [🎯 2.3 size: 100 的对比](#🎯 2.3 size: 100 的对比)
- 3.三者的完整对比表格
- 4.实际场景测试
-
- [4.1 测试数据准备](#4.1 测试数据准备)
- [4.2 场景 1:单独使用 size](#4.2 场景 1:单独使用 size)
- [4.3 场景 2:size + terminate_after](#4.3 场景 2:size + terminate_after)
- [4.4 场景 3:size + track_total_hits](#4.4 场景 3:size + track_total_hits)
- [5.正确类比 SQL](#5.正确类比 SQL)
-
- [5.1 最接近的类比](#5.1 最接近的类比)
- [5.2 不同的概念](#5.2 不同的概念)
- 6.实际应用建议
-
- [6.1 分页场景的最佳实践](#6.1 分页场景的最佳实践)
- [6.2 导出数据的场景](#6.2 导出数据的场景)
- [6.3 仪表板统计场景](#6.3 仪表板统计场景)
- 7.重要区别总结
概览:本文详细分析了 Elasticsearch 中三种关键查询参数的区别与联系。
terminate_after:真正的 LIMIT 机制,达到指定数量立即停止扫描,显著提升查询速度但可能返回不足量结果。track_total_hits:仅限制精确计数值,不影响结果返回,适合大数据集近似统计。size:传统分页参数,需要完整扫描和排序,内存消耗大。通过工作流程代码示例、实际行为对比和 SQL 类比,文章揭示了各参数的本质差异,并提供了场景测试数据和应用建议。最佳实践推荐在分页场景使用
search_after+ 近似计数的组合方案。
1.SQL LIMIT 的三个层次
sql
-- SQL 的 LIMIT 实际上包含三个功能:
SELECT * FROM table
WHERE condition
ORDER BY column
LIMIT 10000; -- 实际包含:
-- 1. 最多返回 10000 行结果
-- 2. 只处理到能返回 10000 行为止
-- 3. 总行数统计可能受影响(取决于数据库实现)
2.Elasticsearch 各参数的实际作用
📊 2.1 terminate_after: 10000
真正的 SQL LIMIT 停止机制。
json
{
"terminate_after": 10000
}
2.1.1 工作流程
python
def search_with_terminate_after(query, terminate_after):
results = []
for doc in all_documents:
if matches_query(doc, query):
results.append(doc)
# 关键点:达到数量立即停止!
if len(results) >= terminate_after:
break # 立即终止扫描
# 排序结果(如果有排序要求)
sorted_results = sort(results)
# 返回结果(可能少于请求的 size)
return sorted_results[:min(size, len(results))]
2.1.2 实际行为
json
// 查询示例
GET /logs/_search
{
"size": 100, // 想返回100个结果
"terminate_after": 10, // 但只收集10个文档就停止
"query": {
"match": { "level": "ERROR" }
}
}
// 可能的返回结果
{
"hits": {
"total": {
"value": 10, // 只找到10个(因为提前停止了)
"relation": "eq"
},
"hits": [ ... ] // 最多10个文档,不是100个!
},
"terminated_early": true # 明确标记提前终止
}
2.1.3 与 SQL LIMIT 的相似性
sql
-- MySQL 中的 LIMIT 优化
EXPLAIN SELECT * FROM logs WHERE level = 'ERROR' LIMIT 10;
-- 执行计划可能显示:
-- "Using where; Using index; Using limit"
-- 意思是:找到10行就停止扫描
📈 2.2 track_total_hits: 10000
只是计数限制,不影响结果返回。
json
{
"track_total_hits": 10000
}
2.2.1 工作流程
python
def search_with_track_total_hits(query, track_limit):
total_hits = 0
results_buffer = []
for doc in all_documents:
if matches_query(doc, query):
results_buffer.append(doc)
total_hits += 1
# 继续收集文档,但停止精确计数
if total_hits >= track_limit:
# 标记为"至少这么多",但不停止扫描
total_relation = "gte" # greater than or equal
# 继续扫描剩余文档...
# 完整的排序和分页
sorted_results = sort(results_buffer)
final_results = sorted_results[from:from+size]
return {
"total": {
"value": min(total_hits, track_limit),
"relation": total_relation
},
"hits": final_results
}
2.2.2 实际行为
json
// 查询示例
GET /logs/_search
{
"size": 100,
"from": 0,
"track_total_hits": 10000, // 只精确计数到10000
"query": {
"match": { "level": "ERROR" }
}
}
// 可能的返回结果
{
"hits": {
"total": {
"value": 10000, // 只精确计算了前10000个
"relation": "gte" // 实际可能更多!
},
"hits": [ ... ] # 正常返回100个文档(如果有)
}
}
// 即使实际有50000个匹配,也继续扫描所有文档
// 只是不精确计数超过10000的部分
🎯 2.3 size: 100 的对比
json
{
"size": 100,
"from": 0
}
工作流程
python
def search_with_size(query, size, from_val):
all_matching_docs = []
# 1. 收集所有匹配的文档
for doc in all_documents:
if matches_query(doc, query):
all_matching_docs.append(doc)
# 2. 全部排序(内存消耗大!)
sorted_docs = sort(all_matching_docs)
# 3. 分页返回
return sorted_docs[from_val:from_val+size]
3.三者的完整对比表格
| 参数 | 作用 | 是否停止扫描 | 是否影响返回结果 | 内存使用 | 类似 SQL 概念 |
|---|---|---|---|---|---|
size |
返回结果数量 | ❌ 不停止 | ✅ 直接影响 | 高 | SELECT ... LIMIT 10 的返回部分 |
terminate_after |
最大收集文档数 | ✅ 立即停止 | ✅ 直接影响(可能返回更少) | 低 | SELECT ... LIMIT 10 的执行优化 |
track_total_hits |
精确计数限制 | ❌ 不停止 | ❌ 不影响返回结果 | 中 | COUNT(*) 的近似处理 |
4.实际场景测试
4.1 测试数据准备
json
// 假设索引有 50,000 个文档
// 查询匹配其中 20,000 个文档
4.2 场景 1:单独使用 size
json
GET /test/_search
{
"size": 100,
"query": { "match_all": {} }
}
// 结果:扫描全部50,000文档 → 排序 → 返回前100个
// 内存:需要存储所有匹配文档进行排序
// 时间:较慢
4.3 场景 2:size + terminate_after
json
GET /test/_search
{
"size": 100,
"terminate_after": 1000,
"query": { "match_all": {} }
}
// 结果:扫描到第1000个匹配文档就停止 → 排序 → 返回前100个
// 内存:只存储1000个文档
// 时间:很快
// 注意:如果实际匹配很少,可能返回不足100个
4.4 场景 3:size + track_total_hits
json
GET /test/_search
{
"size": 100,
"track_total_hits": 1000,
"query": { "match_all": {} }
}
// 结果:扫描全部50,000文档 → 排序 → 返回前100个
// 计数:精确计数到1000,之后标记为"gte"
// 内存:需要存储所有匹配文档进行排序
// 时间:与单独使用size相同
5.正确类比 SQL
5.1 最接近的类比
sql
-- Elasticsearch 的 terminate_after
SELECT * FROM table
WHERE condition
ORDER BY column
LIMIT 10000; -- 数据库优化:找到10000行就停止
-- 对应 Elasticsearch:
{
"size": 10000,
"terminate_after": 10000
}
5.2 不同的概念
sql
-- SQL 的 COUNT(*) 与 Elasticsearch track_total_hits
SELECT COUNT(*) FROM table WHERE condition;
-- 如果表很大,数据库可能:
-- 1. 使用近似计数(如 PostgreSQL 的估算)
-- 2. 扫描全表但只计数
-- 对应 Elasticsearch 的近似计数:
{
"track_total_hits": false -- 返回近似值
}
-- 对应精确计数但有上限:
{
"track_total_hits": 10000 -- 精确计数到10000
}
6.实际应用建议
6.1 分页场景的最佳实践
json
// 推荐组合:search_after + track_total_hits(近似)
GET /logs/_search
{
"size": 1000,
"sort": [{"timestamp": "desc"}, {"_id": "asc"}],
"track_total_hits": false, // 不要精确计数
"query": {...}
}
// 后续分页
GET /logs/_search
{
"size": 1000,
"sort": [{"timestamp": "desc"}, {"_id": "asc"}],
"search_after": [last_timestamp, last_id],
"query": {...}
}
6.2 导出数据的场景
json
// 使用 terminate_after 防止数据量过大
GET /data/_search
{
"size": 10000,
"terminate_after": 50000, // 最多处理5万个文档
"sort": [{"_doc": "asc"}], // 使用最有效的排序
"_source": ["field1", "field2"],
"query": {
"range": {
"date": {
"gte": "2024-01-01",
"lte": "2024-01-31"
}
}
}
}
6.3 仪表板统计场景
json
// 使用 track_total_hits 获取近似总数
GET /metrics/_search
{
"size": 0,
"track_total_hits": 100000, // 精确到10万
"aggs": {
"daily_stats": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "day"
}
}
}
}
// 如果超过10万匹配,返回 "relation": "gte"
7.重要区别总结
- 1️⃣
terminate_after影响执行过程:达到限制立即停止扫描。 - 2️⃣
track_total_hits只影响计数:达到限制后继续扫描但不精确计数。 - 3️⃣
size影响返回结果:返回指定数量的文档。
记忆技巧
terminate_after= "找到这么多就停手"track_total_hits= "数到这么多就不精确数了"size= "只给我这么多结果"
terminate_after 更接近 SQL LIMIT 的 执行优化特性,而不仅仅是结果限制。