Debido á miña liña de traballo, teño que facer fronte a situacións nas que un programador escribe unha solicitude e pensa "A base é intelixente, pode xestionar todo por si mesma!«
Nalgúns casos (en parte por descoñecemento das capacidades da base de datos, en parte por optimizacións prematuras), este enfoque leva á aparición de "Frankensteins".
En primeiro lugar, vou dar un exemplo de tal petición:
-- для каждой ключевой пары находим ассоциированные значения полей
WITH RECURSIVE cte_bind AS (
SELECT DISTINCT ON (key_a, key_b)
key_a a
, key_b b
, fld1 bind_fld1
, fld2 bind_fld2
FROM
tbl
)
-- находим min/max значений для каждого первого ключа
, cte_max AS (
SELECT
a
, max(bind_fld1) bind_fld1
, min(bind_fld2) bind_fld2
FROM
cte_bind
GROUP BY
a
)
-- связываем по первому ключу ключевые пары и min/max-значения
, cte_a_bind AS (
SELECT
cte_bind.a
, cte_bind.b
, cte_max.bind_fld1
, cte_max.bind_fld2
FROM
cte_bind
INNER JOIN
cte_max
ON cte_max.a = cte_bind.a
)
SELECT * FROM cte_a_bind;
Para avaliar substancialmente a calidade dunha solicitude, creemos un conxunto de datos arbitrario:
CREATE TABLE tbl AS
SELECT
(random() * 1000)::integer key_a
, (random() * 1000)::integer key_b
, (random() * 10000)::integer fld1
, (random() * 10000)::integer fld2
FROM
generate_series(1, 10000);
CREATE INDEX ON tbl(key_a, key_b);
Resulta que a lectura dos datos levou menos dunha cuarta parte do tempo execución de consulta:
Desmontándoo peza a peza
Vexamos máis de cerca a solicitude e desconcertamos:
- Por que está WITH RECURSIVE aquí se non hai CTE recursivos?
- Por que agrupar os valores mínimos/máximos nun CTE separado se de todos os xeitos están ligados á mostra orixinal?
+25% de tempo - Por que usar un "SELECT * FROM" incondicional ao final para repetir o CTE anterior?
+14% de tempo
Neste caso, tivemos a gran sorte de que se escolleu Hash Join para a conexión, e non Nested Loop, porque entón teriamos recibido non só un pase de exploración CTE, senón 10K.
un pouco sobre CTE ScanAquí hai que lembralo CTE Scan é semellante ao Seq Scan - é dicir, sen indexación, senón só unha busca completa, que requiriría 10K x 0.3 ms = 3000ms para ciclos por cte_max ou 1K x 1.5 ms = 1500ms ao realizar un bucle por cte_bind!
En realidade, que querías conseguir como resultado? Si, normalmente esta é a pregunta que aparece nalgún lugar no minuto 5 de analizar as consultas de "tres pisos".
Queriamos emitir para cada par de claves único min/max do grupo por key_a.
Entón imos usalo para iso
SELECT DISTINCT ON(key_a, key_b)
key_a a
, key_b b
, max(fld1) OVER(w) bind_fld1
, min(fld2) OVER(w) bind_fld2
FROM
tbl
WINDOW
w AS (PARTITION BY key_a);
Dado que a lectura de datos en ambas opcións leva o mesmo aproximadamente 4-5 ms, entón todo o noso tempo gaña -32% - isto está na súa forma máis pura Carga eliminada da CPU base, se tal solicitude se executa con suficiente frecuencia.
En xeral, non debes forzar a base a "levar a redonda, rodar a cadrada".
Fonte: www.habr.com