使用查询解释了解查询性能

利用查询解释功能,您可以向后端提交 Firestore 查询,并会相应收到有关该后端查询执行的详细性能统计信息。它的功能类似于许多关系型数据库系统中的 EXPLAIN [ANALYZE] 操作。

您可以使用 Firestore 服务器客户端库发送查询解释请求。

查询解释结果可以帮助您了解查询是如何执行的,向您揭示低效问题,并指明可能存在服务器端瓶颈的位置。

查询解释:

  • 在查询规划阶段提供有用的数据洞见,以便您可以调整查询索引并提高效率。
  • 使用分析选项可以帮助您了解每个查询的成本和性能,让您能够快速遍历不同的查询句式,以优化它们的使用。

了解查询解释选项:默认和分析

您可以使用“默认”选项或“分析”选项执行查询解释操作。

使用默认选项时,查询解释会规划查询,但会跳过执行阶段;并返回规划阶段的信息。您可以用该选项来检查查询是否具有必要的索引,并了解查询使用了哪些索引。例如,这可以帮助您确认特定查询是否使用了某个复合索引,而不是交叉使用多个不同的索引。

使用分析选项时,查询解释不仅会规划查询,还会执行查询。此选项会返回上文提及的所有规划信息,以及来自查询执行运行时的统计信息;包括查询的结算信息以及查询执行的系统级数据洞见。您可以使用此工具测试各种查询和索引配置,以优化其成本并缩短延迟时间。

查询解释如何收费?

如果您在使用查询解释时选择的是默认选项,系统不会执行任何索引或读取操作。无论查询复杂程度如何,都只会收取一次读取操作的费用。

如果您在使用查询解释时选择的是分析选项,系统会相应执行索引和读取操作,因此您需要照常为查询付费。分析活动不会产生额外费用,只会收取执行查询的常规费用。

使用查询解释时选择默认选项

您可以使用客户端库提交默认选项请求。

请注意,请求使用 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();

    
Node(管理员)

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)"},
    ]
}

如需了解详情,请参阅查询解释报告参考文档

使用查询解释时选择分析选项

您可以使用客户端库提交分析选项请求。

请注意,请求使用 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();

    
Node(管理员)

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';

如果我们使用分析选项,返回的指标就会显示查询在两个单字段索引((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",
               }
    }
}