使用 Query Explain 了解查询性能
使用 Query Explain,您可以将 Firestore 查询提交到后端,这样就可以收到有关后端查询执行的详细性能统计信息。其功能类似于许多关系型数据库系统中 EXPLAIN [ANALYZE]
操作。
您可以使用 Firestore 服务器客户端库发送 Query Explain 请求。
“Query Explain”结果可帮助您了解如何执行查询,向您展示效率低下的问题以及可能存在的服务器端瓶颈的位置。
查询说明:
- 提供有关查询规划阶段的数据分析,以便您调整查询索引并提高效率。
- 使用分析选项,可以帮助您了解每个查询的费用和性能,并且让您可以快速迭代不同的查询格式,以优化其使用方式。
了解 Query Explain 选项:默认和分析
您可以使用 default 选项或 analyze 选项执行 Query Explain 操作。
使用默认选项时,Query Explain 会规划查询,但会跳过执行阶段。这样将返回规划师阶段信息。您可以使用此参数来检查查询是否具有必要的索引,并了解使用了哪些索引。这可以帮助您验证某些情况,例如特定查询是否使用了复合索引,而不是不得与许多不同的索引相交。
借助 analytics 选项,Query Explain 同时执行这两个计划并执行查询。这将返回前面提到的所有规划工具信息,以及来自查询执行运行时的统计信息。其中包括查询的结算信息以及有关查询执行的系统级数据分析。您可以使用此工具测试各种查询和索引配置,以优化其费用和延迟时间。
Query Explain 如何收费?
将 Query Explain 与默认选项搭配使用时,系统不会执行任何索引或读取操作。无论查询复杂程度如何,一次读取操作都会产生费用。
当您将 Query Explain 与 analyze 选项搭配使用时,系统会执行索引和读取操作,因此,您需要照常为查询付费。分析活动不会产生额外费用,仅按所执行的查询的常规费用收费。
将“Query Explain”与默认选项搭配使用
您可以使用客户端库提交默认选项请求。
请注意,请求通过 IAM 进行身份验证,并使用相同的权限执行常规查询操作。Firebase Authentication 等其他身份验证技术会被忽略。如需了解详情,请参阅有关适用于服务器客户端库的 IAM 的指南。
Java(管理员)
Query q = db.collection("col").whereGreaterThan("a", 1);
ExplainOptions options = ExplainOptions.builder().build();
ExplainResults<QuerySnapshot> explainResults = q.explain(options).get();
ExplainMetrics metrics = explainResults.getMetrics();
PlanSummary planSummary = metrics.getPlanSummary();
节点(管理员)
const q = db.collection('col').where('country', '=', 'USA');
const options = { analyze : 'false' };
const explainResults = await q.explain(options);
const metrics = explainResults.metrics;
const plan = metrics.planSummary;
响应的确切格式取决于执行环境。返回的结果可转换为 JSON。例如:
{ "indexes_used": [ {"query_scope": "Collection", "properties": "(category ASC, __name__ ASC)"}, {"query_scope": "Collection", "properties": "(country ASC, __name__ ASC)"}, ] }
如需了解详情,请参阅“查询说明”报告参考。
将 Query Explain 与 analyze 选项搭配使用
您可以使用客户端库来提交分析选项请求。
请注意,请求通过 IAM 进行身份验证,并使用相同的权限执行常规查询操作。Firebase Authentication 等其他身份验证技术会被忽略。如需了解详情,请参阅有关适用于服务器客户端库的 IAM 的指南。
Java(管理员)
Query q = db.collection("col").whereGreaterThan("a", 1);
ExplainOptions options = ExplainOptions.builder().setAnalyze(true).build();
ExplainResults<QuerySnapshot> explainResults = q.explain(options).get();
ExplainMetrics metrics = explainResults.getMetrics();
PlanSummary planSummary = metrics.getPlanSummary();
List<Map<String, Object>> indexesUsed = planSummary.getIndexesUsed();
ExecutionStats stats = metrics.getExecutionStats();
节点(管理员)
const q = db.collection('col').where('country', '=', 'USA');
const options = { analyze : 'true' };
const explainResults = await q.explain(options);
const metrics = explainResults.metrics;
const plan = metrics.planSummary;
const indexesUsed = plan.indexesUsed;
const stats = metrics.executionStats;
以下示例除了 planInfo
之外,还显示了返回的 stats
对象。响应的确切格式取决于执行环境。示例响应采用 JSON 格式。
{ "resultsReturned": "5", "executionDuration": "0.100718s", "readOperations": "5", "debugStats": { "index_entries_scanned": "95000", "documents_scanned": "5" "billing_details": { "documents_billable": "5", "index_entries_billable": "0", "small_ops": "0", "min_query_cost": "0", } } }
如需了解详情,请参阅“查询说明”报告参考。
解读结果并进行调整
让我们看一个示例场景,在该场景中,我们按类型和制作国家/地区查询电影。
为便于说明,我们假设此 SQL 查询等效于此查询。
SELECT * FROM /movies WHERE category = 'Romantic' AND country = 'USA';
如果我们使用 analyze 选项,返回的指标会显示查询在两个单字段索引((category ASC, __name__ ASC)
和 (country ASC, __name__ ASC)
)上运行。它会扫描 16,500 个索引条目,但仅返回 1,200 个文档。
// Output query planning info { "indexes_used": [ {"query_scope": "Collection", "properties": "(category ASC, __name__ ASC)"}, {"query_scope": "Collection", "properties": "(country ASC, __name__ ASC)"}, ] } // Output query status { "resultsReturned": "1200", "executionDuration": "0.118882s", "readOperations": "1200", "debugStats": { "index_entries_scanned": "16500", "documents_scanned": "1200" "billing_details": { "documents_billable": "1200", "index_entries_billable": "0", "small_ops": "0", "min_query_cost": "0", } } }
如需优化执行查询的性能,您可以创建完全覆盖的复合索引 (category ASC, country ASC, __name__ ASC)
。
如果再次使用分析选项运行查询,我们可以看到为此查询选择了新建的索引,并且该查询的运行速度更快、效率更高。
// Output query planning info { "indexes_used": [ {"query_scope": "Collection", "properties": "(category ASC, country ASC, __name__ ASC)"} ] } // Output query stats { "resultsReturned": "1200", "executionDuration": "0.026139s", "readOperations": "1200", "debugStats": { "index_entries_scanned": "1200", "documents_scanned": "1200" "billing_details": { "documents_billable": "1200", "index_entries_billable": "0", "small_ops": "0", "min_query_cost": "0", } } }