PostgreSQL Antimönster: CTE x CTE

I yrkesverksamhet stöter man ibland pÄ situationer nÀr utvecklaren skriver en frÄga och tÀnker "databasen Àr smart, den klarar allt sjÀlv!"«

I vissa fall (delvis pÄ grund av okunskap om databasens möjligheter, delvis pÄ grund av för tidiga optimeringar) leder den hÀr metoden till att "frankensteiner" uppstÄr.

Först ska jag ge ett exempel pÄ en sÄdan frÄga:

-- för varje nyckelpar hittar vi relaterade vÀrden för fÀlten
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
)
-- hittar min/max vÀrden för varje första nyckel
, cte_max AS (
  SELECT
    a
  , max(bind_fld1) bind_fld1
  , min(bind_fld2) bind_fld2
  FROM
    cte_bind
  GROUP BY
    a
)
-- kopplar nyckelpar och min/max-vÀrden efter första nyckeln
, 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;

För att konkret bedöma kvaliteten pÄ frÄgan, lÄt oss skapa en viss godtycklig uppsÀttning data:

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

Det visar sig att sjÀlva lÀsningen av data tog mindre Àn en fjÀrdedel av den totala tiden för frÄgeexekveringen:

PostgreSQL Antimönster: CTE x CTE[titta pÄ explain.tensor.ru]

Analyserar noggrant

LÄt oss ta en noggrann titt pÄ frÄgan och fundera:

  1. Varför finns det WITH RECURSIVE hÀr om det inte finns nÄgra rekursiva CTE:er?
  2. Varför gruppera min/max-vÀrden i en separat CTE om de ÀndÄ kopplas till den ursprungliga urvalet?
    +25% tid
  3. Varför anvĂ€nda en ny inlĂ€sning frĂ„n föregĂ„ende CTE i slutet med en obetingad ‘SELECT * FROM’?
    +14% tid

I det hÀr fallet hade vi tur att en Hash Join valdes för att förena och inte en Nested Loop, annars skulle vi fÄ mer Àn en enda genomgÄng av CTE Scan, utan vi skulle fÄtt 10K!

litet om CTE ScanHĂ€r bör man komma ihĂ„g att CTE Scan Ă€r motsvarigheten till Seq Scan — det vill sĂ€ga ingen indexering, utan bara en total genomgĂ„ng, vilket skulle krĂ€va 10K x 0.3ms = 3000ms vid cykler över cte_max eller 1K x 1.5ms = 1500ms vid cykler över cte_bind!
Egentligen, vad ville vi Ästadkomma som resultat? Aha, vanligtvis kommer just den frÄgan upp efter ungefÀr femte minuten av att analysera "tre vÄningar"-frÄgor.

Vi ville fÄ ut min/max för varje unik nyckelpar grupperat efter key_a.
SÄ lÄt oss anvÀnda fönsterfunktioner:

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

PostgreSQL Antimönster: CTE x CTE
[titta pÄ explain.tensor.ru]

Eftersom datalĂ€sning i bĂ„da fallen tar ungefĂ€r 4-5 ms, sĂ„ Ă€r hela vĂ„r tidsvinst -32% — det Ă€r helt och hĂ„llet belastning borttagen frĂ„n basens CPU, om en sĂ„dan förfrĂ„gan görs tillrĂ€ckligt ofta.

Generellt sett bör man inte tvinga databasen att "bÀra runt cirkulÀrt och rulla runt kvadratiskt".

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster