사용된 쿼리와 색인이 최적의 상태인지 확인하기 위해 Query Explain을 사용하여 쿼리를 만들고 실행 요약을 검토할 수 있습니다.
자바
...// Build the queryQuery<Entity>query=Query.newEntityQueryBuilder().setKind("employees").setFilter(CompositeFilter.and(PropertyFilter.gt("salary",100000),PropertyFilter.gt("experience",0))).setOrderBy(OrderBy("experience"),OrderBy("salary")).build();// Set the explain options to get back *only* the plan summaryQueryResults<Entity>results=datastore.run(query,ExplainOptions.newBuilder().build());// Get the explain metricsOptional<ExplainMetrics>explainMetrics=results.getExplainMetrics();if(!explainMetrics.isPresent()){thrownewException("No explain metrics returned");}// Get the plan summaryPlanSummaryplanSummary=explainMetrics.get().getPlanSummary();List<Map<String,Object>>indexesUsed=planSummary.getIndexesUsed();System.out.println("----- Indexes Used -----");indexesUsed.forEach(map->map.forEach((s,o)->System.out.println(s+": "+o)));// Get the execution statsif(!explainMetrics.getExecutionStats().isPresent()){thrownewException("No execution stats returned");}ExecutionStatsqueryStats=explainMetrics.getExecutionStats().get();Map<String,Object>debugStats=queryStats.getDebugStats();System.out.println("----- Debug Stats -----");debugStats.forEach((s,o)->System.out.println(s+": "+o));
다음 예시는 올바른 색인 순서를 사용하여 Datastore 모드의 Firestore에서 스캔하는 항목 수를 줄이는 방법을 보여줍니다.
간단한 쿼리
직원 컬렉션의 이전 예시에서 (salary, experience) 색인을 사용해 실행되는 간단한 쿼리는 다음과 같습니다.
orderBy() 절을 명시적으로 사용하여 앞의 순서로 조건자를 추가하면 Datastore 모드의 Firestore는 (salary, experience) 색인을 사용하여 쿼리를 실행합니다. 첫 번째 범위 필터의 선택이 이전 쿼리보다 우수하므로 쿼리가 더 빠르게 실행되며 비용 효율적입니다.
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["이해하기 어려움","hardToUnderstand","thumb-down"],["잘못된 정보 또는 샘플 코드","incorrectInformationOrSampleCode","thumb-down"],["필요한 정보/샘플이 없음","missingTheInformationSamplesINeed","thumb-down"],["번역 문제","translationIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-09-05(UTC)"],[[["\u003cp\u003eThis page demonstrates indexing strategies for optimizing queries that involve range and inequality filters across multiple fields.\u003c/p\u003e\n"],["\u003cp\u003eUsing Query Explain helps determine if a query and its associated indexes are optimized for performance, allowing for a review of the execution summary.\u003c/p\u003e\n"],["\u003cp\u003eProper index ordering, by setting the more selective constraints first, significantly reduces the number of scanned entries and improves query efficiency and cost-effectiveness.\u003c/p\u003e\n"],["\u003cp\u003eThe provided examples illustrate how a simple query can scan thousands of index entries only to filter them out, whereas optimized indexing reduces the number of index entries scanned to find the matching documents.\u003c/p\u003e\n"]]],[],null,["# Optimize queries with range and inequality filters on multiple properties\n\nThis page provides examples of indexing strategies that you can use for queries\nwith range and inequality filters on multiple fields to create an efficient\nquery experience.\n\nBefore you optimize your\nqueries, read about [range and inequality filters on multiple properties](/datastore/docs/multiple-range-fields) concepts .\n\nOptimize queries with Query Explain\n-----------------------------------\n\nTo determine if the query and indexes used are optimal, you can create a query\nusing [Query Explain](/datastore/docs/query-explain-analyze) and review the execution summary. \n\n### Java\n\n ...\n // Build the query\n Query\u003cEntity\u003e query =\n Query.newEntityQueryBuilder()\n .setKind(\"employees\")\n .setFilter(\n CompositeFilter.and(\n PropertyFilter.gt(\"salary\", 100000), PropertyFilter.gt(\"experience\", 0)))\n .setOrderBy(OrderBy(\"experience\"), OrderBy(\"salary\"))\n .build();\n\n // Set the explain options to get back *only* the plan summary\n QueryResults\u003cEntity\u003e results = datastore.run(query, ExplainOptions.newBuilder().build());\n\n // Get the explain metrics\n Optional\u003cExplainMetrics\u003e explainMetrics = results.getExplainMetrics();\n if (!explainMetrics.isPresent()) {\n throw new Exception(\"No explain metrics returned\");\n }\n\n // Get the plan summary\n PlanSummary planSummary = explainMetrics.get().getPlanSummary();\n List\u003cMap\u003cString, Object\u003e\u003e indexesUsed = planSummary.getIndexesUsed();\n System.out.println(\"----- Indexes Used -----\");\n indexesUsed.forEach(map -\u003e map.forEach((s, o) -\u003e System.out.println(s + \": \" + o)));\n\n // Get the execution stats\n if (!explainMetrics.getExecutionStats().isPresent()) {\n throw new Exception(\"No execution stats returned\");\n }\n\n ExecutionStats queryStats = explainMetrics.getExecutionStats().get();\n Map\u003cString, Object\u003e debugStats = queryStats.getDebugStats();\n System.out.println(\"----- Debug Stats -----\");\n debugStats.forEach((s, o) -\u003e System.out.println(s + \": \" + o));\n\nThe following example shows how the use of correct index ordering saves the\nnumber of entities that Firestore in Datastore mode scans.\n\n### Simple queries\n\nWith the [earlier example](/datastore/docs/multiple-range-fields#indexing_considerations) of a collection of employees, the simple query\nthat runs with the `(salary, experience)` index is as follows: \n\n### GQL\n\n SELECT *\n FROM /employees\n WHERE salary \u003e 100000 AND experience \u003e 0\n ORDER BY experience, salary;\n\n### Java\n\n Query\u003cEntity\u003e query =\n Query.newEntityQueryBuilder()\n .setKind(\"employees\")\n .setFilter(\n CompositeFilter.and(\n PropertyFilter.gt(\"salary\", 100000), PropertyFilter.gt(\"experience\", 0)))\n .setOrderBy(OrderBy(\"experience\"), OrderBy(\"salary\"))\n .build();\n\nThe query scans 95000 index entries only to return 5 entities. A large number\nof index entries were read but filtered out because they did not satisfy the\nquery predicate. \n\n```scilab\n// Output query planning info\n{\n \"indexesUsed\": [\n {\n \"query_scope\": \"Collection Group\",\n \"properties\": \"(experience ASC, salary ASC, __name__ ASC)\"\n }\n ]\n },\n // Output Query Execution Stats\n {\n \"resultsReturned\": \"5\",\n \"executionDuration\": \"2.5s\",\n \"readOperations\": \"100\",\n \"debugStats\": {\n \"index_entries_scanned\": \"95000\",\n \"documents_scanned\": \"5\",\n \"billing_details\": {\n \"documents_billable\": \"5\",\n \"index_entries_billable\": \"95000\",\n \"small_ops\": \"0\",\n \"min_query_cost\": \"0\"\n }\n }\n }\n```\n\nAs per the earlier example, we can infer that the `salary` constraint is more\nselective than the `experience` constraint. \n\n### GQL\n\n SELECT *\n FROM /employees\n WHERE salary \u003e 100000 AND experience \u003e 0\n ORDER BY salary, experience;\n\n### Java\n\n Query\u003cEntity\u003e query =\n Query.newEntityQueryBuilder()\n .setKind(\"employees\")\n .setFilter(\n CompositeFilter.and(\n PropertyFilter.gt(\"salary\", 100000), PropertyFilter.gt(\"experience\", 0)))\n .setOrderBy(OrderBy(\"salary\"), OrderBy(\"experience\"))\n .build();\n\nWhen you explicitly use the `orderBy()` clause to add the predicates in the\nearlier order, Firestore in Datastore mode uses the `(salary, experience)` index\nto run the query. Since the selection of the first range filter is\nbetter than the earlier query, the query runs faster and is cost-efficient. \n\n```scilab\n // Output query planning info\n{\n \"indexesUsed\": [\n {\n \"query_scope\": \"Collection Group\",\n \"properties\": \"(salary ASC, experience ASC, __name__ ASC)\"\n }\n ],\n // Output Query Execution Stats\n \"resultsReturned\": \"5\",\n \"executionDuration\": \"0.2s\",\n \"readOperations\": \"6\",\n \"debugStats\": {\n \"index_entries_scanned\": \"1000\",\n \"documents_scanned\": \"5\",\n \"billing_details\": {\n \"documents_billable\": \"5\",\n \"index_entries_billable\": \"1000\",\n \"small_ops\": \"0\",\n \"min_query_cost\": \"0\"\n }\n }\n }\n```\n\nWhat's next\n-----------\n\n- Learn about [Query Explain](/datastore/docs/query-explain-analyze)."]]