PostgreSQL Antipatterns: CTE x CTE

По Ρ€ΠΎΠ΄Ρƒ Π΄Π΅ΡΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ приходится ΡΡ‚Π°Π»ΠΊΠΈΠ²Π°Ρ‚ΡŒΡΡ с ситуациями, ΠΊΠΎΠ³Π΄Π° Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΏΠΈΡˆΠ΅Ρ‚ запрос ΠΈ Π΄ΡƒΠΌΠ°Π΅Ρ‚ «Π±Π°Π·Π° умная, сама со всСм справится!«

Π’ Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… случаях (частично ΠΎΡ‚ нСзнания возмоТностСй Π‘Π”, частично ΠΎΡ‚ ΠΏΡ€Π΅ΠΆΠ΄Π΅Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΉ) Ρ‚Π°ΠΊΠΎΠΉ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ ΠΊ появлСнию Β«Ρ„Ρ€Π°Π½ΠΊΠ΅Π½ΡˆΡ‚Π΅ΠΉΠ½ΠΎΠ²Β».

Π‘Π½Π°Ρ‡Π°Π»Π° ΠΏΡ€ΠΈΠ²Π΅Π΄Ρƒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Ρ‚Π°ΠΊΠΎΠ³ΠΎ запроса:

-- для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΊΠ»ΡŽΡ‡Π΅Π²ΠΎΠΉ ΠΏΠ°Ρ€Ρ‹ Π½Π°Ρ…ΠΎΠ΄ΠΈΠΌ ассоциированныС значСния ΠΏΠΎΠ»Π΅ΠΉ
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;

Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΡ€Π΅Π΄ΠΌΠ΅Ρ‚Π½ΠΎ ΠΎΡ†Π΅Π½ΠΈΠ²Π°Ρ‚ΡŒ качСство запроса, Π΄Π°Π²Π°ΠΉΡ‚Π΅ создадим Π½Π΅ΠΊΠΈΠΉ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ»ΡŒΠ½Ρ‹ΠΉ Π½Π°Π±ΠΎΡ€ Π΄Π°Π½Π½Ρ‹Ρ…:

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

ΠžΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ΡΡ, само Ρ‡Ρ‚Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… заняло мСньшС Ρ‡Π΅Ρ‚Π²Π΅Ρ€Ρ‚ΠΈ всСго Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ выполнСния запроса:

PostgreSQL Antipatterns: CTE x CTE[ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π½Π° explain.tensor.ru]

Π Π°Π·Π±ΠΈΡ€Π°Π΅ΠΌ ΠΏΠΎ косточкам

ΠŸΡ€ΠΈΡΡ‚Π°Π»ΡŒΠ½ΠΎ посмотрим Π½Π° запрос, ΠΈ озадачимся:

  1. Π—Π°Ρ‡Π΅ΠΌ Ρ‚ΡƒΡ‚ WITH RECURSIVE, Ссли Π½ΠΈΠΊΠ°ΠΊΠΈΡ… рСкурсивных CTE β€” Π½Π΅Ρ‚Ρƒ?
  2. Π—Π°Ρ‡Π΅ΠΌ Π³Ρ€ΡƒΠΏΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ min/max-значСния Π² ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ CTE, Ссли ΠΏΠΎΡ‚ΠΎΠΌ ΠΎΠ½ΠΈ всС Ρ€Π°Π²Π½ΠΎ ΠΏΡ€ΠΈΠ²ΡΠ·Ρ‹Π²Π°ΡŽΡ‚ΡΡ ΠΊ ΠΎΡ€ΠΈΠ³ΠΈΠ½Π°Π»ΡŒΠ½ΠΎΠΉ Π²Ρ‹Π±ΠΎΡ€ΠΊΠ΅?
    +25% Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ
  3. Π—Π°Ρ‡Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π² ΠΊΠΎΠ½Ρ†Π΅ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΡƒΡŽ Π½Π°Ρ‡ΠΈΡ‚ΠΊΡƒ ΠΈΠ· ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅ΠΉ CTE Ρ‡Π΅Ρ€Π΅Π· бСзусловный ‘SELECT * FROM’?
    +14% Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ

Π’ Π΄Π°Π½Π½ΠΎΠΌ случаС Π½Π°ΠΌ Π΅Ρ‰Π΅ сильно ΠΏΠΎΠ²Π΅Π·Π»ΠΎ, Ρ‡Ρ‚ΠΎ для соСдинСния Π±Ρ‹Π» Π²Ρ‹Π±Ρ€Π°Π½ Hash Join, Π° Π½Π΅ Nested Loop, ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ Ρ‚ΠΎΠ³Π΄Π° ΠΌΡ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ»ΠΈ Π±Ρ‹ Π½Π΅ ΠΎΠ΄ΠΈΠ½-СдинствСнный ΠΏΡ€ΠΎΡ…ΠΎΠ΄ CTE Scan, Π° 10K!

Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΎ CTE ScanΠ’ΡƒΡ‚ Π½Π°Π΄ΠΎ Π²ΡΠΏΠΎΠΌΠ½ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ CTE Scan являСтся Π°Π½Π°Π»ΠΎΠ³ΠΎΠΌ Seq Scan β€” Ρ‚ΠΎ Π΅ΡΡ‚ΡŒ Π½ΠΈΠΊΠ°ΠΊΠΎΠΉ индСксации, Π° Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎΠ»Π½Ρ‹ΠΉ ΠΏΠ΅Ρ€Π΅Π±ΠΎΡ€, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΎΠ²Π°Π» Π±Ρ‹ 10K x 0.3ms = 3000ms ΠΏΡ€ΠΈ Ρ†ΠΈΠΊΠ»Π°Ρ… ΠΏΠΎ cte_max ΠΈΠ»ΠΈ 1K x 1.5ms = 1500ms ΠΏΡ€ΠΈ Ρ†ΠΈΠΊΠ»Π°Ρ… ΠΏΠΎ cte_bind!
БобствСнно, Π° Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ‚Π΅Π»ΠΈ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ-Ρ‚ΠΎ Π² Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π΅? Ага, ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ ΠΈΠΌΠ΅Π½Π½ΠΎ Ρ‚Π°ΠΊΠΎΠΉ вопрос ΠΈ посСщаСт Π³Π΄Π΅-Ρ‚ΠΎ Π½Π° 5ΠΉ ΠΌΠΈΠ½ΡƒΡ‚Π΅ Ρ€Π°Π·Π±ΠΎΡ€Π° «трСхэтаТных» запросов.

ΠœΡ‹ Ρ…ΠΎΡ‚Π΅Π»ΠΈ для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎΠΉ ΠΊΠ»ΡŽΡ‡Π΅Π²ΠΎΠΉ ΠΏΠ°Ρ€Ρ‹ вывСсти min/max ΠΈΠ· Π³Ρ€ΡƒΠΏΠΏΡ‹ ΠΏΠΎ key_a.
Π’Π°ΠΊ Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡΡ ΠΆΠ΅ для этого ΠΎΠΊΠΎΠ½Π½Ρ‹ΠΌΠΈ функциями:

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 Antipatterns: CTE x CTE
[ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π½Π° explain.tensor.ru]

ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ Ρ‡Ρ‚Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… Π² ΠΎΠ±ΠΎΠΈΡ… Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π°Ρ… Π·Π°Π½ΠΈΠΌΠ°Π΅Ρ‚ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²ΠΎ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ 4-5ms, Ρ‚ΠΎ вСсь наш Π²Ρ‹ΠΈΠ³Ρ€Ρ‹Ρˆ ΠΏΠΎ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ -32% β€” это Π² чистом Π²ΠΈΠ΄Π΅ Π½Π°Π³Ρ€ΡƒΠ·ΠΊΠ°, убранная с CPU Π±Π°Π·Ρ‹, Ссли Ρ‚Π°ΠΊΠΎΠΉ запрос выполняСтся достаточно часто.

Π’ ΠΎΠ±Ρ‰Π΅ΠΌ, Π½Π΅ стоит Π±Π°Π·Ρƒ Π·Π°ΡΡ‚Π°Π²Π»ΡΡ‚ΡŒ Β«ΠΊΡ€ΡƒΠ³Π»ΠΎΠ΅ β€” Π½ΠΎΡΠΈΡ‚ΡŒ, ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚Π½ΠΎΠ΅ β€” ΠΊΠ°Ρ‚Π°Ρ‚ΡŒΒ».

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com