Couchbase N1QL es un moderno motor de procesamiento de consultas diseñado para proporcionar SQL agregado para JSON por índice sobre datos distribuidos con un modelo de datos flexible. Las bases de datos modernas se despliegan en clusters masivos. El uso de JSON proporciona un modo de datos flexible. N1QL soporta SQL agregado por índice para JSON para facilitar el procesamiento de consultas.
Las aplicaciones y los controladores de bases de datos envían la consulta N1QL a uno de los nodos de consulta disponibles en un clúster. El nodo de consulta analiza la consulta y utiliza los metadatos de los objetos subyacentes para determinar el plan de ejecución óptimo, que luego ejecuta. Durante la ejecución, en función de la consulta, utilizando los índices aplicables, el nodo de consulta trabaja con los nodos de índices y datos para recuperar los datos y realizar las operaciones previstas. Dado que Couchbase es una base de datos modular en clúster, puedes escalar los servicios de datos, índices y consultas para adaptarlos a tus objetivos de rendimiento y disponibilidad.
Antes de Couchbase 5.5, incluso cuando una consulta con GROUP BY y/o agregados estaba cubierta por un índice, la consulta obtenía todos los datos relevantes del indexador y realizaba la agrupación/agregación de los datos dentro del motor de consultas.

En Couchbase 5.5 se ha mejorado el planificador de consultas para que solicite de forma inteligente al indexador que realice agrupaciones y agregaciones además del escaneo de rangos. para el índice de cobertura. El indexador se ha mejorado para realizar agrupaciones, COUNT(), SUM(), MIN(), MAX(), AVG() y operaciones relacionadas sobre la marcha.

Esto no requiere cambios en la consulta del usuario, pero sí un buen diseño del índice para cubrir la consulta y ordenar las claves del índice. No todas las consultas se beneficiarán de esta optimización, y no todos los índices pueden acelerar todas las operaciones de agrupación y agregación. Comprender los patrones adecuados te ayudará a diseñar tus índices y consultas. La agrupación y agregación de índices en el índice secundario global es compatible con ambos motores de almacenamiento: GSI estándar y GSI optimizado para memoria (MOI). La agrupación y agregación de índices sólo se admite en Enterprise Edition.
Este paso de reducción de realizar el GROUP BY y la Agregación por el indexador reduce la cantidad de transferencia de datos y E/S de disco, resultando en:
- Mejora del tiempo de respuesta a las consultas
- Mejor utilización de los recursos
- Baja latencia
- Gran escalabilidad
- Bajo coste total de propiedad
Rendimiento
La agrupación y agregación de índices puede mejorar el rendimiento de las consultas en órdenes de magnitud y reducir drásticamente las latencias. La siguiente tabla muestra algunos ejemplos de mediciones de latencia de consulta.
Índice :
|
1 |
CREATE INDEX idx_ts_type_country_city ON `travel-sample` (type, country, city); |
| Consulta | Descripción | 5.0 Latencias | 5.5 Latencias |
SELECT t.type, COUNT(type) AS cnt DESDE viaje-muestra AS t WHERE t.type IS NOT NULL GROUP BY t.type; |
|
230 ms | 13ms |
SELECT t.type, COUNT(1) AS cnt, COUNT(DISTINCT ciudad) AS cntdciudad DESDE viaje-muestra AS t WHERE t.type IN ["hotel", "aeropuerto"]. GROUP BY t.type, t.country; |
|
40ms | 7ms |
SELECT t.país, COUNT(ciudad) AS cnt DESDE viaje-muestra AS t DONDE t.type = "aeropuerto" GROUP BY t.country; |
|
25ms | 3ms |
SELECT t.ciudad, cnt DESDE viaje-muestra AS t WHERE t.type IS NOT NULL GROUP BY t.ciudad LETTING cnt = COUNT(ciudad) HAVING cnt > 0 ; |
|
300 ms | 160 ms |
Visión general de la agrupación y agregación de índices

La figura anterior muestra todas las posibles fases por las que pasa una consulta SELECT para devolver los resultados. El proceso de filtrado toma el espacio de claves inicial y produce un subconjunto óptimo de los documentos que interesan a la consulta. Para producir el subconjunto más pequeño posible, se utilizan índices para aplicar tantos predicados como sea posible. El predicado de la consulta indica el subconjunto de datos que interesa. Durante la fase de planificación de la consulta, seleccionamos los índices que se van a utilizar. A continuación, para cada índice, decidimos los predicados que debe aplicar cada índice. Los predicados de consulta se traducen en escaneos de rango en el plan de consulta y se pasan al indexador.
Si la consulta no tiene JOINs y está cubierta por un índice, se pueden eliminar las fases Fetch y Join.

Cuando todos los predicados se traducen exactamente a escaneos de rango, la fase de filtrado también puede eliminarse. En esa situación, el escaneo y la agregación están uno al lado del otro, y como el indexador tiene la capacidad de hacer agregación, esa fase se puede hacer en el nodo indexador. En algunos casos, las fases de ordenación, desplazamiento y límite también pueden realizarse en el nodo del indexador.

El siguiente diagrama de flujo describe cómo el planificador de consultas decide realizar la agregación de índices para cada bloque de consulta de la consulta. Si la agregación de índices no es posible, las agregaciones se realizan en el motor de consultas.

Por ejemplo, comparemos el rendimiento anterior frente al actual del uso de GROUP BY y examinemos el plan EXPLAIN de la siguiente consulta que utiliza un índice definido en Couchbase viaje-muestra cubo:
|
1 |
CREATE INDEX `def_type` ON `travel-sample`(`type`); |
Considere la consulta:
|
1 2 3 4 |
SELECT type, COUNT(type) FROM `travel-sample` WHERE type IS NOT MISSING GROUP BY type; |
Antes de la versión 5.5 de Couchbase, este motor de consultas obtenía los datos relevantes del indexador y agrupaba y agregaba los datos dentro del motor de consultas. Esta sencilla consulta tarda unos 250 ms.

Ahora, en la versión 5.5 de Couchbase, esta consulta utiliza el mismo índice def_type, pero se ejecuta en menos de 20 ms. En la explicación de abajo, puedes ver menos pasos y la falta del paso de agrupación después del escaneo del índice porque el paso de escaneo del índice también hace la agrupación y agregación.

A medida que aumente la complejidad de los datos y las consultas, también lo hará el rendimiento (tanto latencia como rendimiento).
Comprensión de EXPLAIN de la agrupación y agregación de índices
Mirando la explicación de la consulta:
|
1 |
EXPLAIN SELECT type, COUNT(type) FROM `travel-sample` WHERE type IS NOT MISSING GROUP BY type;{ |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
{ "plan": { "#operator": "Sequence", "~children": [ { "#operator": "IndexScan3", "covers": [ "cover ((`travel-sample`.`type`))", "cover ((meta(`travel-sample`).`id`))", "cover (count(cover ((`travel-sample`.`type`))))" ], "index": "def_type", "index_group_aggs": { "aggregates": [ { "aggregate": "COUNT", "depends": [ 0 ], "expr": "cover ((`travel-sample`.`type`))", "id": 2, "keypos": 0 } ], "depends": [ 0 ], "group": [ { "depends": [ 0 ], "expr": "cover ((`travel-sample`.`type`))", "id": 0, "keypos": 0 } ] }, "index_id": "b948c92b44c2739f", "index_projection": { "entry_keys": [ 0, 2 ] }, "keyspace": "travel-sample", "namespace": "default", "spans": [ { "exact": true, "range": [ { "inclusion": 1, "low": "null" } ] } ], "using": "gsi" }, { "#operator": "Parallel", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "InitialProject", "result_terms": [ { "expr": "cover ((`travel-sample`.`type`))" }, { "expr": "cover (count(cover ((`travel-sample`.`type`))))" } ] }, { "#operator": "FinalProject" } ] } } ] }, "text": "SELECT type, COUNT(type) FROM `travel-sample` WHERE type IS NOT MISSING GROUP BY type;" } |
Verá "index_group_aggs" en la sección IndexScan (es decir, "#operator": "IndexScan3"). Si "index_group_aggs" está ausente, el servicio de consulta está realizando la agrupación y agregación. Si la consulta está presente está utilizando la agrupación y agregación de índices y tiene toda la información relevante que el indexador requiere para la agrupación y agregación. La siguiente tabla describe cómo interpretar la diversa información del objeto index_group_aggs.
| Nombre del campo | Descripción | Números de línea del ejemplo | Explicar el texto en el ejemplo |
| agregados | Matriz de objetos Aggregate, y cada objeto representa un agregado. La ausencia de este elemento significa que en la consulta sólo está presente group by. | 14-24 | agregados |
| agregado | Operación agregada (MAX/MIN/SUM/COUNT/COUNTN). | 16 | CONTAR |
| distinto | El modificador agregado es DISTINTO | – | Falso(Cuando es verdadero sólo aparece) |
| depende | Lista de posiciones clave de índice (empezando por 0) de las que depende la expresión agregada. | 17-19 | 0 (porque el tipo es la clave 0 del índice def_type) |
| expr | expresión agregada | 20 | cubierta ((viaje-muestra.tipo)) |
| id | ID único dado internamente y que se utilizará en proyección_índice | 21 | 2 |
| keypos | Indicador que indica el uso de la expresión en la posición clave del índice o del campo expr.
|
22 | 0 (porque el tipo es la clave 0 del índice def_type) |
| depende | Lista de posiciones clave de índice de las que dependen las expresiones de grupos/agregados (lista consolidada) | 25-27 | 0 |
| grupo | Matriz de objetos GROUP BY, y cada objeto representa una clave de grupo. La ausencia de este elemento significa que no hay ninguna cláusula GROUP BY presente en la consulta. | 28-37 | grupo |
| depende | Lista de posiciones de las teclas de índice (empezando por 0) de las que depende la expresión de grupo. | 30-32 | 0
(porque el tipo es la clave 0 del índice clave del índice def_type) |
| expr | expresión de grupo. | 33 | cubierta ((viaje-muestra.tipo)) |
| id | ID único dado internamente y que se utilizará en proyección_índice. | 34 | 0 |
| keypos | Indicador que indica el uso de la expresión en la posición clave del índice o del campo expr.
|
35 | 0 (porque el tipo es la clave 0 del índice def_type) |
El campo "covers" es un array que contiene todas las claves de índice, claves de documento (META().id), expresiones de claves de grupo que no coinciden exactamente con las claves de índice (ordenadas por id), agregados ordenados por id. También "Index_projection" tendrá todos los ids de grupo/agregado.
|
1 2 3 4 5 |
"covers": [ "cover ((`travel-sample`.`type`))", ← Index key (0) "cover ((meta(`travel-sample`).`id`))", ← document key (1) "cover (count(cover ((`travel-sample`.`type`))))" ← aggregate (2) ] |
En el caso anterior, la expresión de grupo tipo es la misma clave de índice def_tipo. No se incluye dos veces.
Detalles Agrupación y agregación de índices
Utilizaremos ejemplos para mostrar cómo funciona la agrupación y agregación de índices. Para seguir los ejemplos cree un bucket "default" e inserte los siguientes documentos:
|
1 2 3 4 5 6 7 8 9 |
INSERT INTO default (KEY,VALUE) VALUES ("ga0001", {"c0":1, "c1":10, "c2":100, "c3":1000, "c4":10000, "a1":[{"id":1}, {"id":1}, {"id":2}, {"id":3}, {"id":4}, {"id":5}]}), VALUES ("ga0002", {"c0":1, "c1":20, "c2":200, "c3":2000, "c4":20000, "a1":[{"id":1}, {"id":1}, {"id":2}, {"id":3}, {"id":4}, {"id":5}]}), VALUES ("ga0003", {"c0":1, "c1":10, "c2":300, "c3":3000, "c4":30000, "a1":[{"id":1}, {"id":1}, {"id":2}, {"id":3}, {"id":4}, {"id":5}]}), VALUES ("ga0004", {"c0":1, "c1":20, "c2":400, "c3":4000, "c4":40000, "a1":[{"id":1}, {"id":1}, {"id":2}, {"id":3}, {"id":4}, {"id":5}]}), VALUES ("ga0005", {"c0":2, "c1":10, "c2":100, "c3":5000, "c4":50000, "a1":[{"id":1}, {"id":1}, {"id":2}, {"id":3}, {"id":4}, {"id":5}]}), VALUES ("ga0006", {"c0":2, "c1":20, "c2":200, "c3":6000, "c4":60000, "a1":[{"id":1}, {"id":1}, {"id":2}, {"id":3}, {"id":4}, {"id":5}]}), VALUES ("ga0007", {"c0":2, "c1":10, "c2":300, "c3":7000, "c4":70000, "a1":[{"id":1}, {"id":1}, {"id":2}, {"id":3}, {"id":4}, {"id":5}]}), VALUES ("ga0008", {"c0":2, "c1":20, "c2":400, "c3":8000, "c4":80000, "a1":[{"id":1}, {"id":1}, {"id":2}, {"id":3}, {"id":4}, {"id":5}]}); |
Ejemplo 1: Agrupación por claves de índice principales
Consideremos la consulta y el índice siguientes:
|
1 2 3 4 5 6 7 8 |
SELECT d.c0 AS c0, d.c1 AS c1, SUM(d.c3) AS sumc3, AVG(d.c4) AS avgc4, COUNT(DISTINCT d.c2) AS dcountc2 FROM default AS d WHERE d.c0 > 0 GROUP BY d.c0, d.c1 ORDER BY d.c0, d.c1 OFFSET 1 LIMIT 2; |
Índice requerido:
|
1 |
CREATE INDEX idx1 ON default(c0, c1, c2, c3, c4); |
La consulta tiene GROUP BY y múltiples agregados, algunos de los agregados tienen el modificador DISTINCT. La consulta se puede cubrir con el índice idx1 y el predicado (d.c0 > 0) se puede convertir en un escaneo de rango exacto y pasarlo al escaneo de índice. Por lo tanto, la combinación de índice y consulta cumple los requisitos de agrupación y agregación de índices.
Los índices se ordenan y agrupan de forma natural por el orden de definición de la clave del índice. En la consulta anterior, las claves GROUP BY (d.c0, d.c1) coinciden exactamente con las claves principales (c0, c1) del índice. Por lo tanto, el índice tiene los datos de cada grupo juntos, el indexador producirá una fila por grupo, es decir, agregación completa. Además, la consulta tiene un agregado que tiene el modificador DISTINCT y coincide exactamente con una de las claves del índice con una posición menor o igual al número de claves de grupo más una (es decir, hay 2 claves de grupo, el modificador DISTINCT puede ser cualquiera de las claves de índice en la posición 0,1,2 porque la clave de índice seguida de las claves de grupo y el modificador DISTINCT pueden aplicarse sin ordenación). Por lo tanto, la consulta anterior es adecuada para que el indexador gestione la agrupación y la agregación.
Si el grupo carece de una de las claves de índice principales y existe un predicado de igualdad, se realiza una optimización especial tratando la clave de índice implícitamente presente en las claves de grupo y determinando si la agregación completa es posible o no. Para el índice de partición, todas las claves de partición deben estar presentes en las claves de grupo para generar agregaciones completas.

El árbol de ejecución gráfico anterior muestra el escaneo de índices (IndexScan3) realizando agregaciones de escaneo y agrupación de índices. Se proyectan los resultados del escaneo de índices.
Veamos la explicación basada en el texto :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
{ "plan": { "#operator": "Sequence", "~children": [ { "#operator": "Sequence", "~children": [ { "#operator": "IndexScan3", "as": "d", "covers": [ "cover ((`d`.`c0`))", "cover ((`d`.`c1`))", "cover ((`d`.`c2`))", "cover ((`d`.`c3`))", "cover ((`d`.`c4`))", "cover ((meta(`d`).`id`))", "cover (count(distinct cover ((`d`.`c2`))))", "cover (countn(cover ((`d`.`c4`))))", "cover (sum(cover ((`d`.`c3`))))", "cover (sum(cover ((`d`.`c4`))))" ], "index": "idx1", "index_group_aggs": { "aggregates": [ { "aggregate": "COUNT", "depends": [ 2 ], "distinct": true, "expr": "cover ((`d`.`c2`))", "id": 6, "keypos": 2 }, { "aggregate": "COUNTN", "depends": [ 4 ], "expr": "cover ((`d`.`c4`))", "id": 7, "keypos": 4 }, { "aggregate": "SUM", "depends": [ 3 ], "expr": "cover ((`d`.`c3`))", "id": 8, "keypos": 3 }, { "aggregate": "SUM", "depends": [ 4 ], "expr": "cover ((`d`.`c4`))", "id": 9, "keypos": 4 } ], "depends": [ 0, 1, 2, 3, 4 ], "group": [ { "depends": [ 0 ], "expr": "cover ((`d`.`c0`))", "id": 0, "keypos": 0 }, { "depends": [ 1 ], "expr": "cover ((`d`.`c1`))", "id": 1, "keypos": 1 } ] }, "index_id": "d06df7c5d379cd5", "index_order": [ { "keypos": 0 }, { "keypos": 1 } ], "index_projection": { "entry_keys": [ 0, 1, 6, 7, 8, 9 ] }, "keyspace": "default", "limit": "2", "namespace": "default", "offset": "1", "spans": [ { "exact": true, "range": [ { "inclusion": 0, "low": "0" } ] } ], "using": "gsi" }, { "#operator": "Parallel", "maxParallelism": 1, "~child": { "#operator": "Sequence", "~children": [ { "#operator": "InitialProject", "result_terms": [ { "as": "c0", "expr": "cover ((`d`.`c0`))" }, { "as": "c1", "expr": "cover ((`d`.`c1`))" }, { "as": "sumc3", "expr": "cover (sum(cover ((`d`.`c3`))))" }, { "as": "avgc4", "expr": "(cover (sum(cover ((`d`.`c4`)))) / cover (countn(cover ((`d`.`c4`)))))" }, { "as": "dcountc2", "expr": "cover (count(distinct cover ((`d`.`c2`))))" } ] }, { "#operator": "FinalProject" } ] } } ] }, { "#operator": "Limit", "expr": "2" } ] }, "text": "SELECT d.c0 AS c0, d.c1 AS c1, SUM(d.c3) AS sumc3, AVG(d.c4) AS avgc4, COUNT(DISTINCT d.c2) AS dcountc2 FROM default AS d\nWHERE d.c0 > 0 GROUP BY d.c0, d.c1 ORDER BY d.c0, d.c1 OFFSET 1 LIMIT 2;" } |
- El "index_group_aggs" (líneas 24-89) en la sección IndexScan (es decir, "#operator": "IndexScan3") muestra la consulta utilizando la agrupación y agregación de índices.
- Si la consulta utiliza agrupación y agregación de índices, los predicados se convierten exactamente en escaneos de rangos y se pasan al escaneo de índices como parte de los rangos, por lo que no habrá ningún operador Filter en la explicación.
- Como las claves de agrupación coinciden exactamente con las claves de índice principales, el indexador producirá agregaciones completas. Por lo tanto, también eliminamos la agrupación en el servicio de consulta (no hay operadores InitialGroup, IntermediateGroup, FinalGroup en la explicación).
- El indexador proyecta "index_projection" (líneas 99-107) incluyendo todas las claves de grupo y agregados.
- La consulta ORDER BY coincide con las claves de índice principales y GROUP BY está en las claves de índice principales, por lo que podemos utilizar el orden de índice. Esto se puede encontrar en explicar (líneas 91-98) y no utilizará "#operator": "Order" entre las líneas 164-165.
- Como la consulta puede utilizar el orden del índice y no hay cláusula HAVING en la consulta, los valores "offset" y "limit" pueden pasarse al indexador.
- Esto se puede encontrar en la línea 112, 110. El "offset" se puede aplicar sólo una vez no verá "#operator": "Offset" entre las líneas 164-165, Pero volver a aplicar el "límite" es no-op. Esto se puede ver en la línea 165-168.
- La consulta que contiene AVG(x) se ha reescrito como SUM(x)/COUNTN(x). COUNTN(x) sólo cuenta cuando x es un valor numérico.
Ejemplo 2: Agrupación por claves de índice principales, LETTING, HAVING
Consideremos la consulta y el índice siguientes:
|
1 2 3 4 5 6 7 8 9 10 |
SELECT d.c0 AS c0, d.c1 AS c1, sumc3 AS sumc3, AVG(d.c4) AS avgc4, COUNT(DISTINCT d.c2) AS dcountc2 FROM default AS d WHERE d.c0 > 0 GROUP BY d.c0, d.c1 LETTING sumc3 = SUM(d.c3) HAVING sumc3 > 0 ORDER BY d.c0, d.c1 OFFSET 1 LIMIT 2; |
Índice requerido:
|
1 |
CREATE INDEX idx1 ON default(c0, c1, c2, c3, c4); |
La consulta anterior es similar a la del ejemplo 1, pero contiene las cláusulas LETTING y HAVING. El indexador no podrá manejarlas, por lo que las cláusulas LETTING y HAVING se aplican en el servicio de consulta después de la agrupación y las agregaciones. Por lo tanto, verá los operadores Let y Filter después de IndexScan3 en el árbol de ejecución. La cláusula Having es un filtro y elimina más elementos, por lo que "offset" y "limit" no pueden enviarse al indizador y deben aplicarse en el servicio de consulta, pero aún podemos utilizar el orden del índice.

Ejemplo 3: Agrupación por claves de índice no principales
Consideremos la consulta y el índice siguientes:
|
1 2 3 4 5 6 7 8 |
SELECT d.c1 AS c1, d.c2 AS c2, SUM(d.c3) AS sumc3, AVG(d.c4) AS avgc4, COUNT(d.c2) AS countc2 FROM default AS d WHERE d.c0 > 0 GROUP BY d.c1, d.c2 ORDER BY d.c1, d.c2 OFFSET 1 LIMIT 2; |
Índice requerido:
|
1 |
CREATE INDEX idx1 ON default(c0, c1, c2, c3, c4); |
La consulta tiene GROUP BY y múltiples agregados. La consulta se puede cubrir con el índice idx1 y el predicado (d.c0 > 0) se puede convertir en escaneo de rango exacto y pasarlo a escaneo de índice. Por lo tanto, la combinación de índice y consulta cumple los requisitos de agrupación y agregación de índices.
En la consulta anterior, las claves GROUP BY (d.c1, d.c2) NO coinciden con las claves principales (c0, c1) del índice. Los grupos están dispersos por el índice. Por lo tanto, el indizador producirá varias filas por cada grupo, es decir, agregación parcial. En caso de agregación parcial, el servicio de consulta realiza la fusión de grupos, la consulta no puede utilizar el orden del índice ni enviar "offset", "limit" al indizador. En caso de agregación parcial, si algún agregado tiene el modificador DISTINCT, la agrupación de índices y la agregación no son posibles. La consulta anterior es adecuada para que el indizador gestione la agrupación y la agregación.

El árbol de ejecución gráfico anterior muestra el escaneo de índices (IndexScan3) realizando agregaciones de escaneo y agrupación de índices. Los resultados de la exploración de índices se agrupan de nuevo y se proyectan.
Veamos la explicación basada en el texto :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
{ "plan": { "#operator": "Sequence", "~children": [ { "#operator": "Sequence", "~children": [ { "#operator": "IndexScan3", "as": "d", "covers": [ "cover ((`d`.`c0`))", "cover ((`d`.`c1`))", "cover ((`d`.`c2`))", "cover ((`d`.`c3`))", "cover ((`d`.`c4`))", "cover ((meta(`d`).`id`))", "cover (count(cover ((`d`.`c2`))))", "cover (countn(cover ((`d`.`c4`))))", "cover (sum(cover ((`d`.`c3`))))", "cover (sum(cover ((`d`.`c4`))))" ], "index": "idx1", "index_group_aggs": { "aggregates": [ { "aggregate": "COUNT", "depends": [ 2 ], "expr": "cover ((`d`.`c2`))", "id": 6, "keypos": 2 }, { "aggregate": "COUNTN", "depends": [ 4 ], "expr": "cover ((`d`.`c4`))", "id": 7, "keypos": 4 }, { "aggregate": "SUM", "depends": [ 3 ], "expr": "cover ((`d`.`c3`))", "id": 8, "keypos": 3 }, { "aggregate": "SUM", "depends": [ 4 ], "expr": "cover ((`d`.`c4`))", "id": 9, "keypos": 4 } ], "depends": [ 1, 2, 3, 4 ], "group": [ { "depends": [ 1 ], "expr": "cover ((`d`.`c1`))", "id": 1, "keypos": 1 }, { "depends": [ 2 ], "expr": "cover ((`d`.`c2`))", "id": 2, "keypos": 2 } ], "partial": true }, "index_id": "d06df7c5d379cd5", "index_projection": { "entry_keys": [ 1, 2, 6, 7, 8, 9 ] }, "keyspace": "default", "namespace": "default", "spans": [ { "exact": true, "range": [ { "inclusion": 0, "low": "0" } ] } ], "using": "gsi" }, { "#operator": "Parallel", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "InitialGroup", "aggregates": [ "sum(cover (count(cover ((`d`.`c2`)))))", "sum(cover (countn(cover ((`d`.`c4`)))))", "sum(cover (sum(cover ((`d`.`c3`)))))", "sum(cover (sum(cover ((`d`.`c4`)))))" ], "group_keys": [ "cover ((`d`.`c1`))", "cover ((`d`.`c2`))" ] } ] } }, { "#operator": "IntermediateGroup", "aggregates": [ "sum(cover (count(cover ((`d`.`c2`)))))", "sum(cover (countn(cover ((`d`.`c4`)))))", "sum(cover (sum(cover ((`d`.`c3`)))))", "sum(cover (sum(cover ((`d`.`c4`)))))" ], "group_keys": [ "cover ((`d`.`c1`))", "cover ((`d`.`c2`))" ] }, { "#operator": "FinalGroup", "aggregates": [ "sum(cover (count(cover ((`d`.`c2`)))))", "sum(cover (countn(cover ((`d`.`c4`)))))", "sum(cover (sum(cover ((`d`.`c3`)))))", "sum(cover (sum(cover ((`d`.`c4`)))))" ], "group_keys": [ "cover ((`d`.`c1`))", "cover ((`d`.`c2`))" ] }, { "#operator": "Parallel", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "InitialProject", "result_terms": [ { "as": "c1", "expr": "cover ((`d`.`c1`))" }, { "as": "c2", "expr": "cover ((`d`.`c2`))" }, { "as": "sumc3", "expr": "sum(cover (sum(cover ((`d`.`c3`)))))" }, { "as": "avgc4", "expr": "(sum(cover (sum(cover ((`d`.`c4`))))) / sum(cover (countn(cover ((`d`.`c4`))))))" }, { "as": "countc2", "expr": "sum(cover (count(cover ((`d`.`c2`)))))" } ] } ] } } ] }, { "#operator": "Order", "limit": "2", "offset": "1", "sort_terms": [ { "expr": "cover ((`d`.`c1`))" }, { "expr": "cover ((`d`.`c2`))" } ] }, { "#operator": "Offset", "expr": "1" }, { "#operator": "Limit", "expr": "2" }, { "#operator": "FinalProject" } ] }, "text": "SELECT d.c1 AS c1, d.c2 AS c2, SUM(d.c3) AS sumc3, AVG(d.c4) AS avgc4, COUNT(d.c2) AS countc2 FROM default AS d WHERE d.c0 > 0 GROUP BY d.c1, d.c2 ORDER BY d.c1, d.c2 OFFSET 1 LIMIT 2;" } |
- El "index_group_aggs" (líneas 24-88) en la sección IndexScan (es decir, "#operator": "IndexScan3") muestra la consulta utilizando la agrupación y agregación de índices.
- Si la consulta utiliza agrupación y agregación de índices, los predicados se convierten exactamente en escaneos de rangos y se pasan al escaneo de índices como parte de los rangos, por lo que no habrá ningún operador Filter en la explicación.
- Como las claves del grupo por NO coinciden con las claves principales del índice, el indexador producirá agregaciones parciales. Esto puede verse como "partial":true dentro de "index_group_aggs" en la línea 87. El servicio de consulta realiza la fusión de grupos (ver líneas 119-161)
- Proyectos de indexador "index_projection" (líneas 91-99) que contienen claves de grupo y agregados.
- Si el Indexador genera agregaciones parciales, la consulta no puede utilizar el orden del índice y requiere una ordenación explícita, y "offset", "limit" no pueden ser enviados al indexador. El plan tendrá operadores explícitos "Order", "Offset" y "Limit" (línea 197 - 217)
- La consulta contiene AVG(x) que se ha reescrito como SUM(x)/COUNTN(x). COUNTN(x) sólo cuenta cuando x es un valor numérico.
- Durante la fusión de grupos
- MIN se convierte en MIN de MIN
- MAX se convierte en MAX de MAX
- SUM se convierte en SUM de SUM
- CONTAR se convierte en SUMA de CONTAR
- CONTN se convierte en SUMA de COUNTN
- AVG se convierte en SUM de SUM dividido por SUM de COUNTN
Ejemplo 4: Agrupación y agregación con índice de matriz
Consideremos la consulta y el índice siguientes:
|
1 2 3 4 5 6 7 8 |
SELECT d.c0 AS c0, d.c1 AS c1, SUM(d.c3) AS sumc3, AVG(d.c4) AS avgc4, COUNT(DISTINCT d.c2) AS dcountc2 FROM default AS d WHERE d.c0 > 0 AND d.c1 >= 10 AND ANY v IN d.a1 SATISFIES v.id = 3 END GROUP BY d.c0, d.c1 ORDER BY d.c0, d.c1 OFFSET 1 LIMIT 2; |
Índice requerido:
|
1 |
CREATE INDEX idxad1 ON default(c0, c1, DISTINCT ARRAY v.id FOR v IN a1 END, c2, c3, c4); |
La consulta tiene GROUP BY y múltiples agregados, algunos de los agregados tienen el modificador DISTINCT. El predicado de la consulta tiene la cláusula ANY y la consulta puede cubrirse mediante el índice de matriz idxad1. El predicado (d.c0 > 0 AND d,c11 >= 10 AND ANY v IN d.a1 SATISFIES v.id = 3 END ) pueden convertirse en escaneos de rango exacto y pasarse al escaneo de índice. En el caso de los índices de matrices, el indexador mantiene un elemento independiente para cada clave de índice de matriz. Para utilizar la agrupación y agregación de índices, el predicado SATISFIES debe tener un único predicado de igualdad y la clave de índice de matriz debe tener el modificador DISTINCT. Por lo tanto, la combinación de índice y consulta es adecuada para gestionar la agrupación y agregación de índices.

Este ejemplo es similar al ejemplo 1 excepto que utiliza un índice de matriz. El árbol gráfico de ejecución anterior muestra el escaneo de índice (IndexScan3) realizando escaneo, agregaciones de agrupación de índice, orden, desplazamiento y límite. Se proyectan los resultados del escaneo de índice.
Ejemplo 5: Agrupación y agregación de la operación UNNEST
Consideremos la consulta y el índice siguientes:
|
1 2 3 4 5 |
SELECT v.id AS id, d.c0 AS c0, SUM(v.id) AS sumid, AVG(d.c1) AS avgc1 FROM default AS d UNNEST d.a1 AS v WHERE v.id > 0 GROUP BY v.id, d.c0; |
Índice requerido:
|
1 |
CREATE INDEX idxaa1 ON default(ALL ARRAY v.id FOR v IN a1 END, c0, c1); |
La consulta tiene GROUP BY y múltiples agregados. La consulta tiene UNNEST en el array d.a1 y un predicado en la clave del array (v.id > 0). El índice idxaa1 califica la consulta (para que Unnest utilice el índice de la matriz para el escaneo del índice, el índice de la matriz debe ser la clave principal y la variable de la matriz en la definición del índice debe coincidir con el alias UNNEST). El predicado (v.id > 0) puede convertirse en escaneo de rango exacto y pasarse al escaneo de índice. Por lo tanto, la combinación de índice y consulta es adecuada para gestionar agrupaciones y agregaciones de índices.

El árbol de ejecución gráfico anterior muestra el escaneo de índices (IndexScan3) realizando escaneos, agregaciones de agrupación de índices. Los resultados del escaneo de índices se proyectan. El UNNEST es un tipo especial de JOIN entre el padre y cada elemento del array. Por lo tanto, el UNNEST repite los campos del documento padre (d.c0, d.c1) y el d.c0, dc.1 referencia tendría duplicados en comparación con el original d (Es necesario tenerlo en cuenta al utilizar SUM(), AVG()).
Reglas de agrupación y agregación de índices
La agrupación y agregación de índices se realiza por bloque de consulta, y la decisión de utilizar o no la agrupación/agregación de índices sólo se toma tras el proceso de selección de índices.
- El bloque de consulta no debe contener Joins, NEST, SUBconsultas.
- El bloque de consulta debe estar cubierto por un índice unifilar.
- El bloque de consulta no debe contener ARRAY_AGG()
- El bloque de consulta no puede correlacionarse
- Todos los predicados deben traducirse exactamente en exploraciones de rangos.
- Las expresiones GROUP BY y Aggregate no pueden hacer referencia a subconsultas, parámetros con nombre ni parámetros posicionales.
- Claves GROUP BY, las expresiones agregadas pueden ser claves de índice, claves de documento, expresiones sobre claves de índice o expresiones sobre claves de documento.
- El índice debe ser capaz de agrupar y agregar todos los agregados en el bloque de consulta, de lo contrario no habrá agregación del índice. (es decir, ALL o None)
- El agregado contiene el modificador DISTINCT
- Las claves de grupo deben coincidir exactamente con las claves de índice principales (si la consulta contiene un predicado de igualdad en la clave de índice, asume que esta clave de índice se incluye implícitamente en las claves de GRUPO si no está ya presente).
- La expresión agregada debe estar en una de las claves de índice principales n+1 (n representa el número de claves de grupo).
- En caso de índice de partición, las claves de partición deben coincidir exactamente con las claves de grupo.
Resumen
Cuando analice el plan de explicación, correlacione los predicados en la explicación con los rangos y asegúrese de que todos los predicados se traducen exactamente a escaneos de rangos y que la consulta está cubierta. Asegúrese de que la consulta utiliza agrupaciones y agregaciones de índices y, si es posible, utilice agregaciones completas del indexador ajustando las claves de índice para mejorar el rendimiento.