Dem bi dem, peywira lêgerîna daneyên têkildar bi karanîna komek kilîtan derdikeve holê. heta ku em hejmara giştî ya tomarên pêwîst bistînin.
Mînaka herî "jiyana rast" nîşan e 20 pirsgirêkên herî kevn, di lîsteyê de li ser lîsteya karmendan (mînak, di nav yek dabeşkirinê de). Ji bo "dashboard"ên rêveberiyê yên cihêreng ên bi kurtasî yên qadên xebatê, mijarek wekhev pir caran hewce ye.
Di vê gotarê de em ê li bicîhkirina PostgreSQL-ya çareseriyek "naîf" a pirsgirêkek wusa, algorîtmayek "aqilmend" û pir tevlihev binêrin. "loop" di SQL de bi şertek derketinê ji daneyên ku hatine dîtin, ku dikare hem ji bo pêşkeftina gelemperî û hem jî ji bo karanîna di rewşên din ên bi vî rengî de bikêr be.
Werin em komek daneya testê ji xwe bistînin
CREATE INDEX ON task(owner_id, task_date, id);
-- а старый - удалим
DROP INDEX task_owner_id_task_date_idx;
Çawa tê bihîstin, wisa jî hatiye nivîsandin
Pêşîn, bila em guhertoya herî hêsan a daxwazê xêz bikin, ku nasnameyên performeran derbas bikin
SELECT
*
FROM
task
WHERE
owner_id = ANY('{1,2,4,8,16,32,64,128,256,512}'::integer[])
ORDER BY
task_date, id
LIMIT 20;
Piçek xemgîn - me tenê 20 tomar ferman kir, lê Index Scan ew li me vegerand 960 xetên, ku paşê jî diviyabû bihata rêzkirin... Em hewl bidin ku kêm bixwînin.
unnest + ARRAY
Nîqaşa yekem ku dê alîkariya me bike ev e ku ger hewce be tenê 20 veqetandî tomar, paşê tenê bixwînin ne bêtir ji 20 bi heman rêzê ji bo her yek qûfle. Baş, index minasib (xwedî_id, erk_date, id) me hene.
Werin em heman mekanîzmayê ji bo derxistin û "belavkirina nav stûnan" bikar bînin tomar sifrê entegre, wek di ARRAY()
:
WITH T AS (
SELECT
unnest(ARRAY(
SELECT
t
FROM
task t
WHERE
owner_id = unnest
ORDER BY
task_date, id
LIMIT 20 -- ограничиваем тут...
)) r
FROM
unnest('{1,2,4,8,16,32,64,128,256,512}'::integer[])
)
SELECT
(r).*
FROM
T
ORDER BY
(r).task_date, (r).id
LIMIT 20; -- ... и тут - тоже
Oh, jixwe pir çêtir! 40% zûtir û 4.5 carî daneyên kêmtir Diviya bû ez bixwînim.
Materyalîzekirina tomarên tabloyê bi CTEBila bala we bikşînim ser wê yekê ku di hin rewşan de Hewldanek ku meriv tavilê bi zeviyên tomarê re bixebite piştî ku li wê di binpirsekê de bigere, bêyî ku wê di CTE de "pêç bike", dikare bibe sedema InitPlan "pircar bikin". li gorî hejmara van heman zeviyan:
SELECT
((
SELECT
t
FROM
task t
WHERE
owner_id = 1
ORDER BY
task_date, id
LIMIT 1
).*);
Result (cost=4.77..4.78 rows=1 width=16) (actual time=0.063..0.063 rows=1 loops=1)
Buffers: shared hit=16
InitPlan 1 (returns $0)
-> Limit (cost=0.42..1.19 rows=1 width=48) (actual time=0.031..0.032 rows=1 loops=1)
Buffers: shared hit=4
-> Index Scan using task_owner_id_task_date_id_idx on task t (cost=0.42..387.57 rows=500 width=48) (actual time=0.030..0.030 rows=1 loops=1)
Index Cond: (owner_id = 1)
Buffers: shared hit=4
InitPlan 2 (returns $1)
-> Limit (cost=0.42..1.19 rows=1 width=48) (actual time=0.008..0.009 rows=1 loops=1)
Buffers: shared hit=4
-> Index Scan using task_owner_id_task_date_id_idx on task t_1 (cost=0.42..387.57 rows=500 width=48) (actual time=0.008..0.008 rows=1 loops=1)
Index Cond: (owner_id = 1)
Buffers: shared hit=4
InitPlan 3 (returns $2)
-> Limit (cost=0.42..1.19 rows=1 width=48) (actual time=0.008..0.008 rows=1 loops=1)
Buffers: shared hit=4
-> Index Scan using task_owner_id_task_date_id_idx on task t_2 (cost=0.42..387.57 rows=500 width=48) (actual time=0.008..0.008 rows=1 loops=1)
Index Cond: (owner_id = 1)
Buffers: shared hit=4"
InitPlan 4 (returns $3)
-> Limit (cost=0.42..1.19 rows=1 width=48) (actual time=0.009..0.009 rows=1 loops=1)
Buffers: shared hit=4
-> Index Scan using task_owner_id_task_date_id_idx on task t_3 (cost=0.42..387.57 rows=500 width=48) (actual time=0.009..0.009 rows=1 loops=1)
Index Cond: (owner_id = 1)
Buffers: shared hit=4
Heman tomar 4 caran "nihêrî" bû... Heya PostgreSQL 11, ev tevger bi rêkûpêk çêdibe, û çareserî ew e ku ew di CTE de "pêçandin" e, ku ji bo optimîzatorê di van versiyonan de sînorek bêkêmasî ye.
Acumulatorê vegerî
Di guhertoya berê de, bi tevahî em dixwînin 200 xetên ji bo xatirê 20. Ne 960, lê hê kêmtir jî - ev gengaz e?
Werin em hewl bidin ku zanîna ku em hewce ne bikar bînin 20 tomar dike. Ango, em ê xwendina daneyê tenê dubare bikin heya ku em bigihîjin mîqdara ku em hewce ne.
Gav 1: Lîsteya Destpêkê
Eşkere ye, navnîşa meya "armanc" a ji 20 tomaran divê bi tomarên "yekemîn" ji bo yek ji bişkojkên meya xwediyê_id dest pê bike. Ji ber vê yekê, pêşî em ê weha bibînin "pir yekem" ji bo her yek ji keys û wê li navnîşê zêde bikin, li gorî rêza ku em dixwazin - (task_date, id) rêz bikin.
Gav 2: Têketinên "paşê" bibînin
Naha heke em navnîşa yekem ji navnîşa xwe bistînin û dest pê bikin "gavek" bêtir li ser index parastina mifteya xwediyê_id-ê, wê hingê hemî tomarên hatine dîtin tam yên din ên di hilbijartina encam de ne. Bê guman, tenê heta ku em kilîla qûnê derbas bikin navnîşa duyemîn di lîsteyê de.
Ger derkeve holê ku me qeyda duyemîn "derbas kir", wê hingê têketina dawî ya xwendinê li şûna ya yekem divê li navnîşê were zêdekirin (bi heman xwediyê_id), piştî ku em navnîşê ji nû ve rêz bikin.
Ango, em her gav werdigirin ku navnîş ji bo her bişkojkê ji yek têketinê zêdetir nîne (heke têketin bi dawî bibin û em "xaç nekin", wê hingê têketina yekem ji navnîşê dê bi hêsanî winda bibe û tiştek nayê zêdekirin. ), û ew her tim rêz kirin bi rêza hilkişîna mifteya serîlêdanê (task_date, id).
Gava 3: Parzûna û "berfireh" tomar
Di hin rêzikên hilbijartina meya vegerî de, hin tomar hene rv
têne dubare kirin - pêşî em wekî "derbaskirina sînorê navnîşa 2-emîn a navnîşê" dibînin, û dûv re wê wekî 1-emîn ji navnîşê biguhezînin. Ji ber vê yekê bûyera yekem pêdivî ye ku were fîlter kirin.
Pirsa dawî ya tirsnak
WITH RECURSIVE T AS (
-- #1 : заносим в список "первые" записи по каждому из ключей набора
WITH wrap AS ( -- "материализуем" record'ы, чтобы обращение к полям не вызывало умножения InitPlan/SubPlan
WITH T AS (
SELECT
(
SELECT
r
FROM
task r
WHERE
owner_id = unnest
ORDER BY
task_date, id
LIMIT 1
) r
FROM
unnest('{1,2,4,8,16,32,64,128,256,512}'::integer[])
)
SELECT
array_agg(r ORDER BY (r).task_date, (r).id) list -- сортируем список в нужном порядке
FROM
T
)
SELECT
list
, list[1] rv
, FALSE not_cross
, 0 size
FROM
wrap
UNION ALL
-- #2 : вычитываем записи 1-го по порядку ключа, пока не перешагнем через запись 2-го
SELECT
CASE
-- если ничего не найдено для ключа 1-й записи
WHEN X._r IS NOT DISTINCT FROM NULL THEN
T.list[2:] -- убираем ее из списка
-- если мы НЕ пересекли прикладной ключ 2-й записи
WHEN X.not_cross THEN
T.list -- просто протягиваем тот же список без модификаций
-- если в списке уже нет 2-й записи
WHEN T.list[2] IS NULL THEN
-- просто возвращаем пустой список
'{}'
-- пересортировываем словарь, убирая 1-ю запись и добавляя последнюю из найденных
ELSE (
SELECT
coalesce(T.list[2] || array_agg(r ORDER BY (r).task_date, (r).id), '{}')
FROM
unnest(T.list[3:] || X._r) r
)
END
, X._r
, X.not_cross
, T.size + X.not_cross::integer
FROM
T
, LATERAL(
WITH wrap AS ( -- "материализуем" record
SELECT
CASE
-- если все-таки "перешагнули" через 2-ю запись
WHEN NOT T.not_cross
-- то нужная запись - первая из спписка
THEN T.list[1]
ELSE ( -- если не пересекли, то ключ остался как в предыдущей записи - отталкиваемся от нее
SELECT
_r
FROM
task _r
WHERE
owner_id = (rv).owner_id AND
(task_date, id) > ((rv).task_date, (rv).id)
ORDER BY
task_date, id
LIMIT 1
)
END _r
)
SELECT
_r
, CASE
-- если 2-й записи уже нет в списке, но мы хоть что-то нашли
WHEN list[2] IS NULL AND _r IS DISTINCT FROM NULL THEN
TRUE
ELSE -- ничего не нашли или "перешагнули"
coalesce(((_r).task_date, (_r).id) < ((list[2]).task_date, (list[2]).id), FALSE)
END not_cross
FROM
wrap
) X
WHERE
T.size < 20 AND -- ограничиваем тут количество
T.list IS DISTINCT FROM '{}' -- или пока список не кончился
)
-- #3 : "разворачиваем" записи - порядок гарантирован по построению
SELECT
(rv).*
FROM
T
WHERE
not_cross; -- берем только "непересекающие" записи
Bi vî awayî, em 50% ji daneyên xwendinê ji bo 20% ji dema darvekirinê bazirganî kir. Ango, heke sedemên we hebin ku hûn bawer bikin ku xwendin dibe ku demek dirêj bigire (mînak, dane bi gelemperî ne di cache de ne, û hûn neçar in ku ji bo wê biçin dîskê), wê hingê hûn dikarin bi vî rengî kêmtir bi xwendinê ve girêdayî bin. .
Di her rewşê de, dema darvekirinê ji vebijarka yekem a "naîf" çêtir derket. Lê kîjan ji van 3 vebijarkên ku hûn bikar bînin li ser we ye.
Source: www.habr.com