Folytatjuk egy sor cikket, amelyek az ismeretlen mĂłdszerek teljesĂtmĂ©nyjavĂtĂĄsĂĄra összpontosĂtanak a "lĂĄtszĂłlag egyszerƱ" PostgreSQL lekĂ©rdezĂ©sek esetĂ©ben:
Ne gondolja, hogy annyira nem szeretem a JOIN-t... đ
De sokszor a lekĂ©rdezĂ©s jelentĆsen teljesĂtmĂ©nyesebb, ha elkerĂŒljĂŒk. EzĂ©rt ma megprĂłbĂĄljuk egyĂĄltalĂĄn megszabadulni a nagy erĆforrĂĄst igĂ©nylĆ JOIN-tĂłl â egy szĂłtĂĄr segĂtsĂ©gĂ©vel.

A PostgreSQL 12-tĆl kezdĆdĆen a lenti szituĂĄciĂłk egy rĂ©sze kissĂ© mĂĄskĂ©pp jelentkezhet . Ez a viselkedĂ©s visszaĂĄllĂthatĂł a korĂĄbbi ĂĄllapotba a kulcs megadĂĄsĂĄval
MATERIALIZED.
Sok "tény" egy korlåtozott szótår miatt
NĂ©zzĂŒnk egy teljesen valĂłs alkalmazĂĄsi feladatot â ki kell listĂĄznunk vagy aktĂv feladatokat a kĂŒldĆkkel:
25.01 | Ivanov I.I. | Ăj algoritmus leĂrĂĄsĂĄnak elĆkĂ©szĂtĂ©se.
22.01 | Ivanov I.I. | Cikk ĂrĂĄsa a Habr-ra: Ă©let JOIN nĂ©lkĂŒl.
20.01 | Petrov P.P. | SegĂtsĂ©g a lekĂ©rdezĂ©s optimalizĂĄlĂĄsĂĄban.
18.01 | Ivanov I.I. | Cikk ĂrĂĄsa a Habr-ra: JOIN a dĂĄtumok eloszlĂĄsĂĄnak figyelembevĂ©telĂ©vel.
16.01 | Petrov P.P. | SegĂtsĂ©g a lekĂ©rdezĂ©s optimalizĂĄlĂĄsĂĄban.
Absztrakt vilĂĄgban a feladatok ĂrĂłinak egyenletesen kellene eloszlaniuk a szervezet összes munkatĂĄrsa között, de a valĂłsĂĄgban a feladatok ĂĄltalĂĄban egy meglehetĆsen korlĂĄtozott szĂĄmĂș embertĆl Ă©rkeznek â "a fĆnöksĂ©gtĆl" felfelĂ© a hierarchiĂĄban, vagy "a szomszĂ©dos bokor" munkatĂĄrsaitĂłl (elemzĆk, tervezĆk, marketing, âŠ).
TegyĂŒk fel, hogy a szervezetĂŒnkben 1000 ember közĂŒl csak 20 ĂrĂł (ĂĄltalĂĄban mĂ©g kevesebb) ad feladatokat minden egyes konkrĂ©t vĂ©grehajtĂłnak Ă©s , hogy felgyorsĂtsuk a "hagyomĂĄnyos" lekĂ©rdezĂ©st.
Script-generĂĄtor
-- munkatĂĄrsak
CREATE TABLE person AS
SELECT
id
, repeat(chr(ascii('a') + (id % 26)), (id % 32) + 1) "name"
, '2000-01-01'::date - (random() * 1e4)::integer birth_date
FROM
generate_series(1, 1000) id;
ALTER TABLE person ADD PRIMARY KEY(id);
-- feladatok a megadott eloszlĂĄssal
CREATE TABLE task AS
WITH aid AS (
SELECT
id
, array_agg((random() * 999)::integer + 1) aids
FROM
generate_series(1, 1000) id
, generate_series(1, 20)
GROUP BY
1
)
SELECT
*
FROM
(
SELECT
id
, '2020-01-01'::date - (random() * 1e3)::integer task_date
, (random() * 999)::integer + 1 owner_id
FROM
generate_series(1, 100000) id
) T
, LATERAL(
SELECT
aids[(random() * (array_length(aids, 1) - 1))::integer + 1] author_id
FROM
aid
WHERE
id = T.owner_id
LIMIT 1
) a;
ALTER TABLE task ADD PRIMARY KEY(id);
CREATE INDEX ON task(owner_id, task_date);
CREATE INDEX ON task(author_id);
Mutassuk meg az utolsó 100 feladatot egy adott végrehajtónak:
SELECT
task.*
, person.name
FROM
task
LEFT JOIN
person
ON person.id = task.author_id
WHERE
owner_id = 777
ORDER BY
task_date DESC
LIMIT 100; 
EzĂ©rt a 1/3 az egĂ©sz idĆbĆl Ă©s 3/4 az olvasott adatokbĂłl az adatlapok csak azĂ©rt kĂ©szĂŒltek el, hogy 100 alkalommal keressĂŒk meg a szerzĆt â minden megjelenĂtett feladathoz. De tudjuk, hogy ebbĆl a szĂĄzbĂłl csak 20 kĂŒlönbözĆ â nem lehetne ezt a tudĂĄst kihasznĂĄlni?
hstore-szĂłtĂĄr
Hasznåljuk ki kulcs-érték "szótår" generålåsåhoz:
CREATE EXTENSION hstoreA szĂłtĂĄrba elegendĆ lesz beletenni a szerzĆ ID-jĂĄt Ă©s a nevĂ©t, Ăgy meg tudjuk majd keresni ezen a kulcson:
-- a cĂ©lzott kivĂĄlasztĂĄs kialakĂtĂĄsa
WITH T AS (
SELECT
*
FROM
task
WHERE
owner_id = 777
ORDER BY
task_date DESC
LIMIT 100
)
-- a szĂłtĂĄr kialakĂtĂĄsa az egyedi Ă©rtĂ©kekhez
, dict AS (
SELECT
hstore( -- hstore(keys::text[], values::text[])
array_agg(id)::text[]
, array_agg(name)::text[]
)
FROM
person
WHERE
id = ANY(ARRAY(
SELECT DISTINCT
author_id
FROM
T
))
)
-- a szótår kapcsolt értékeinek lekérdezése
SELECT
*
, (TABLE dict) -> author_id::text -- hstore -> key
FROM
T; 
Az informĂĄciĂłk megszerzĂ©se a szemĂ©lyekrĆl kĂ©t alkalommal kevesebb idĆt Ă©s hĂ©t alkalommal kevesebb adatot igĂ©nyelt! Az "szĂłtĂĄrazĂĄs" mellett ezeket az eredmĂ©nyeket a tömeges rekordlekĂ©rdezĂ©s segĂtsĂ©gĂ©vel is elĂ©rtĂŒk egyetlen ĂĄthaladĂĄs alatt = ANY(ARRAY(...)).
A tĂĄblĂĄzat bejegyzĂ©sei: sorozatosĂtĂĄs Ă©s desorozĂĄs
De mi a teendĆ, ha nem csak egy szövegmezĆt, hanem egy teljes bejegyzĂ©st kell megĆriznĂŒnk a szĂłtĂĄrban? Ekkor PostgreSQL kĂ©pessĂ©ge segĂt, hogy a tĂĄblĂĄzat bejegyzĂ©sĂ©t egy egysĂ©ges Ă©rtĂ©kkĂ©nt kezeljĂŒk:
...
, dict AS (
SELECT
hstore(
array_agg(id)::text[]
, array_agg(p)::text[] -- mĂĄgia #1
)
FROM
person p
WHERE
...
)
SELECT
*
, (((TABLE dict) -> author_id::text)::person).* -- mĂĄgia #2
FROM
T;NĂ©zzĂŒk meg, hogy itt tulajdonkĂ©ppen mi törtĂ©nik:
- VettĂŒk p-t, mint alias a person tĂĄblĂĄzat teljes bejegyzĂ©sĂ©re Ă©s összegyƱjtöttĂŒk Ćket egy tömbbĂ©.
- Ez a tömb a rekordokat ĂĄtkonvertĂĄltuk szövegsorok tömbjĂ©vĂ© (person[]::text[]), hogy elhelyezzĂŒk Ćket egy hstore szĂłtĂĄrban Ă©rtĂ©kek tömbjekĂ©nt.
- Kapcsolt rekord nyerĂ©sekor azt kihĂșztuk a szĂłtĂĄrbĂłl a kulcs alapjĂĄn mint szövegsort.
- A szöveget tĂĄblĂĄzattĂpusĂș Ă©rtĂ©kkĂ© kell alakĂtanunk a person (minden tĂĄblĂĄzathoz automatikusan lĂ©trejön ugyanaz a nevƱ tĂpus).
- A tĂpusĂș rekordokat âkiterjesztettĂŒkâ az oszlopokra
(...).*.
json szĂłtĂĄr
De az a trĂŒkk, amit fent alkalmaztunk, nem mƱködik, ha nincs megfelelĆ tĂĄblĂĄzatos tĂpus a âdekĂłdolĂĄshozâ. Ugyanez a helyzet akkor is, ha adatforrĂĄskĂ©nt CTE sort prĂłbĂĄlunk hasznĂĄlni, nem pedig âvalĂłdiâ tĂĄblĂĄt. Ebben az esetben segĂtenek nekĂŒnk.
a json kezelĆ funkciĂłk :
Meg kell jegyeznĂŒnk, hogy a cĂ©lstruktĂșra leĂrĂĄsakor nem szĂŒksĂ©ges minden mezĆt felsorolnunk az eredeti sorbĂłl, csak azokat, amelyek valĂłban szĂŒksĂ©gesek. Ha azonban van âbennszĂŒlöttâ tĂĄblĂĄnk, akkor jobb, ha a json_populate_record funkciĂłt hasznĂĄljuk..
A szĂłtĂĄrhoz valĂł hozzĂĄfĂ©rĂ©s tovĂĄbbra is egyszeri, de a json-[de]szerializĂĄlĂĄs költsĂ©gei elĂ©ggĂ© jelentĆsek, ezĂ©rt ilyen mĂłdon Ă©rdemes csak bizonyos esetekben hasznĂĄlni, amikor a âcivilebbâ CTE Scan gyengĂ©bben teljesĂt.
A teljesĂtmĂ©ny tesztelĂ©se
TehĂĄt kĂ©t mĂłdot kaptunk az adatok szĂłtĂĄrba törtĂ©nĆ szerializĂĄlĂĄsĂĄra â hstore / json_object. Ezen kĂvĂŒl magukat a kulcsok Ă©s Ă©rtĂ©kek tömbjeit is kĂ©t mĂłdon lehet elĆĂĄllĂtani, belsĆ vagy kĂŒlsĆ szöveggĂ© alakĂtĂĄssal: array_agg(i::text) / array_agg(i)::text[].
EllenĆrizzĂŒk a kĂŒlönfĂ©le tĂpusĂș szerializĂĄciĂłk hatĂ©konysĂĄgĂĄt egy teljesen szintetikus pĂ©ldĂĄval â kĂŒlönbözĆ mennyisĂ©gƱ kulcsot szerializĂĄlva:
WITH dict AS (
SELECT
hstore(
array_agg(i::text)
, array_agg(i::text)
)
FROM
generate_series(1, ...) i
)
TABLE dict;ĂrtĂ©kelĆ script: szerializĂĄlĂĄs
A T AS (
KIVĂLASZTĂS
*
, (
KIVĂLASZTĂS
regexp_replace(ea[array_length(ea, 1)], '^VĂ©grehajtĂĄsi idĆ: (d+.d+) ms$', '1')::real et
BĆVĂTĂS
(
KIVĂLASZTĂS
array_agg(el) ea
BĆVĂTĂS
dblink('port= ' || current_setting('port') || ' dbname=' || current_database(), $$
explain analyze
A SZĂTĂRNĂL (
KIVĂLASZTĂS
hstore(
array_agg(i::text)
, array_agg(i::text)
)
BĆVĂTĂS
generate_series(1, $$ || (1 << v) || $$) i
)
TĂBLĂZAT dict
$$) T(el text)
) T
) et
BĆVĂTĂS
generate_series(0, 19) v
, LATERAL generate_series(1, 7) i
RENDSZER SZERINT
1, 2
)
KIVĂLASZTĂS
v
, avg(et)::numeric(32,3)
BĆVĂTĂS
T
GROUP BY
1
RENDSZER SZERINT
1; 
A PostgreSQL 11 körĂŒl a szĂłtĂĄr mĂ©retĂ©ig 2^12 kulcsig a json-be valĂł sorosĂtĂĄs kevesebb idĆt igĂ©nyel. A legnagyobb hatĂ©konysĂĄgot a json_object Ă©s az âbelsĆâ tĂpuskonverziĂł kombinĂĄciĂłja kĂ©pviseli array_agg(i::text).
Most prĂłbĂĄljuk meg minden kulcs Ă©rtĂ©kĂ©t nyolcszor kiolvasni â hiszen ha a szĂłtĂĄrhoz nem nyĂșlunk, akkor minek van szĂŒksĂ©g rĂĄ?
ĂrtĂ©kelĆ script: olvasĂĄs a szĂłtĂĄrbĂłl
A T AS (
KIVĂLASZTĂS
*
, (
KIVĂLASZTĂS
regexp_replace(ea[array_length(ea, 1)], '^VĂ©grehajtĂĄsi idĆ: (d+.d+) ms$', '1')::real et
BĆVĂTĂS
(
KIVĂLASZTĂS
array_agg(el) ea
BĆVĂTĂS
dblink('port= ' || current_setting('port') || ' dbname=' || current_database(), $$
explain analyze
A SZĂTĂRNĂL (
KIVĂLASZTĂS
json_object(
array_agg(i::text)
, array_agg(i::text)
)
BĆVĂTĂS
generate_series(1, $$ || (1 << v) || $$) i
)
KIVĂLASZTĂS
(TABLE dict) -> (i % ($$ || (1 << v) || $$) + 1)::text
BĆVĂTĂS
generate_series(1, $$ || (1 << (v + 3)) || $$) i
$$) T(el text)
) T
) et
BĆVĂTĂS
generate_series(0, 19) v
, LATERAL generate_series(1, 7) i
RENDSZER SZERINT
1, 2
)
KIVĂLASZTĂS
v
, avg(et)::numeric(32,3)
BĆVĂTĂS
T
GROUP BY
1
RENDSZER SZERINT
1; 
ĂS⊠mĂĄr körĂŒlbelĂŒl 2^6 kulcsnĂĄl a json-szĂłtĂĄrbĂłl valĂł olvasĂĄs több mint többszörös hĂĄtrĂĄnyba kerĂŒl az hstore-bĂłl valĂł olvasĂĄshoz, a jsonb esetĂ©ben ugyanez törtĂ©nik 2^9-kor.
VĂ©gsĆ következtetĂ©sek:
- ha szĂŒksĂ©ges JOIN többször ismĂ©tlĆdĆ rekordokkal â jobb a tĂĄblĂĄzat âszĂłtĂĄrba helyezĂ©seâ
- ha a szĂłtĂĄr vĂĄrhatĂłan kicsi, Ă©s keveset olvasol belĆle â hasznĂĄlhatsz json[b]-t
- minden mås esetben hstore + array_agg(i::text) hatékonyabb
ForrĂĄs: habr.com
