A causa de la meva línia de treball, he de fer front a situacions en què un desenvolupador escriu una sol·licitud i pensa "La base és intel·ligent, pot gestionar-ho tot!«
En alguns casos (en part per desconeixement de les capacitats de la base de dades, en part per optimitzacions prematures), aquest enfocament porta a l'aparició de "Frankensteins".
En primer lloc, posaré un exemple d'aquesta petició:
-- для каждой ключевой пары находим ассоциированные значения полей
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;
Per avaluar de manera substancial la qualitat d'una sol·licitud, creem un conjunt de dades arbitrari:
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 llegir les dades va trigar menys d'una quarta part del temps execució de la consulta:
Desmuntant-lo peça per peça
Fem una ullada més de prop a la sol·licitud i estiguem desconcertats:
- Per què hi ha WITH RECURSIVE aquí si no hi ha CTE recursius?
- Per què agrupar els valors mínims/màxims en un CTE separat si de totes maneres estan lligats a la mostra original?
+25% de temps - Per què utilitzar un "SELECT * FROM" incondicional al final per repetir el CTE anterior?
+14% de temps
En aquest cas, vam tenir molta sort que s'hagués escollit Hash Join per a la connexió, i no Need Loop, perquè llavors hauríem rebut no només una passada d'escaneig CTE, sinó 10K!
una mica sobre CTE ScanAquí ho hem de recordar CTE Scan és similar a Seq Scan - és a dir, sense indexació, sinó només una cerca completa, que requeriria 10K x 0.3 ms = 3000ms per a cicles per cte_max o 1K x 1.5 ms = 1500ms quan es fa un bucle per cte_bind!
De fet, què volies obtenir com a resultat? Sí, normalment aquesta és la pregunta que apareix en algun moment del minut 5 d'anàlisi de consultes de "tres pisos".
Volíem sortir per a cada parell de claus únic min/max del grup per key_a.
Així que utilitzem-lo per a això
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);
Com que la lectura de dades en ambdues opcions triga el mateix aproximadament 4-5 ms, tot el nostre temps guanya -32% - això està en la seva forma més pura càrrega eliminada de la CPU base, si aquesta sol·licitud s'executa amb prou freqüència.
En general, no hauríeu de forçar la base a "portar la rodona, fer rodar la quadrada".
Font: www.habr.com