Dahil sa aking linya ng trabaho, kailangan kong harapin ang mga sitwasyon kapag ang isang developer ay sumulat ng isang kahilingan at iniisip na "Ang base ay matalino, kaya nitong hawakan ang lahat ng bagay mismo!Β«
Sa ilang mga kaso (bahagi mula sa kamangmangan ng mga kakayahan ng database, bahagyang mula sa napaaga na pag-optimize), ang diskarte na ito ay humahantong sa hitsura ng "Frankensteins".
Una, magbibigay ako ng halimbawa ng naturang kahilingan:
-- Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΊΠ»ΡΡΠ΅Π²ΠΎΠΉ ΠΏΠ°ΡΡ Π½Π°Ρ
ΠΎΠ΄ΠΈΠΌ Π°ΡΡΠΎΡΠΈΠΈΡΠΎΠ²Π°Π½Π½ΡΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΠΏΠΎΠ»Π΅ΠΉ
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;
Upang lubos na masuri ang kalidad ng isang kahilingan, gumawa tayo ng ilang arbitrary na set ng 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);
Lumalabas na ang pagbabasa ng data ay tumagal nang wala pang isang-kapat ng oras pagpapatupad ng query:
Paghiwalayin ito nang pira-piraso
Tingnan natin ang kahilingan at magtaka:
- Bakit nandito ang WITH RECURSIVE kung walang recursive CTEs?
- Bakit igrupo ang min/max na halaga sa isang hiwalay na CTE kung ang mga ito ay nakatali pa rin sa orihinal na sample?
+ 25% na oras - Bakit gagamit ng walang kondisyong 'PUMILI * MULA' sa dulo upang ulitin ang nakaraang CTE?
+ 14% na oras
Sa kasong ito, napakaswerte namin na Hash Join ang napili para sa koneksyon, at hindi Nested Loop, dahil hindi lang isang CTE Scan pass ang natanggap namin, kundi 10K!
kaunti tungkol sa CTE ScanDito natin dapat tandaan iyon Ang CTE Scan ay katulad ng Seq Scan - iyon ay, walang pag-index, ngunit isang kumpletong paghahanap lamang, na mangangailangan 10K x 0.3ms = 3000ms para sa mga cycle ng cte_max o 1K x 1.5ms = 1500ms kapag nag-loop sa pamamagitan ng cte_bind!
Sa totoo lang, ano ang gusto mong makuha bilang resulta? Oo, kadalasan ito ang tanong na lumalabas sa isang lugar sa ika-5 minuto ng pagsusuri ng "tatlong kuwento" na mga query.
Nais naming mag-output para sa bawat natatanging key pair min/max mula sa pangkat ayon sa key_a.
Kaya't gamitin natin ito para dito
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);
Dahil ang pagbabasa ng data sa parehong mga opsyon ay tumatagal ng parehong humigit-kumulang 4-5ms, pagkatapos ay ang lahat ng aming oras ay nadagdag -32% - ito ay nasa pinakadalisay nitong anyo inalis ang load mula sa base CPU, kung ang naturang kahilingan ay madalas na naisakatuparan.
Sa pangkalahatan, hindi mo dapat pilitin ang base na "dalhin ang bilog, igulong ang parisukat."
Pinagmulan: www.habr.com