Sick SQL kontsultak egiteko errezetak

Orain dela hilabete batzuk iragarri genuen azaldu.tentsorea.ru - publiko kontsulta-planak analizatzeko eta ikusteko zerbitzua PostgreSQL-ra.

Dagoeneko 6000 aldiz baino gehiago erabili duzu, baina oharkabean pasa daitekeen funtzio erabilgarri bat da egiturazko arrastoak, hau bezalako itxura dutenak:

Sick SQL kontsultak egiteko errezetak

Entzun itzazu, eta zure eskaerak "leun eta zetatsu bihurtuko dira". πŸ™‚

Baina serioski, eskaera bat motel eta baliabide gose egiten duten egoera asko tipikoak dira eta planaren egituraren eta datuen arabera antzeman daitezke.

Kasu honetan, garatzaile bakoitzak ez du bere kabuz optimizazio-aukerarik bilatu behar, bere esperientzian soilik oinarrituz - hemen gertatzen ari dena esan dezakegu, zein izan daitekeen arrazoia eta konponbide bati nola heldu. Horixe egin genuen.

Sick SQL kontsultak egiteko errezetak

Ikus ditzagun kasu hauek hurbilagotik: nola definitzen diren eta zer gomendiotara eramaten duten.

Gaian hobeto murgiltzeko, lehenik eta behin dagokion blokea entzun dezakezu Nire txostena PGConf.Russia 2020-n, eta gero bakarrik adibide bakoitzaren azterketa zehatza egitera pasa:

#1: indizea "gutxietsia"

Sortzen denean

Erakutsi "LLC Kolokolchik" bezeroaren azken faktura.

Nola identifikatu

-> Limit
   -> Sort
      -> Index [Only] Scan [Backward] | Bitmap Heap Scan

Gomendioak

Erabilitako indizea zabaldu ordena-eremuekin.

Adibidea:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk  -- 100K "Ρ„Π°ΠΊΡ‚ΠΎΠ²"
, (random() * 1000)::integer fk_cli; -- 1K Ρ€Π°Π·Π½Ρ‹Ρ… Π²Π½Π΅ΡˆΠ½ΠΈΡ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ

CREATE INDEX ON tbl(fk_cli); -- индСкс для foreign key

SELECT
  *
FROM
  tbl
WHERE
  fk_cli = 1 -- ΠΎΡ‚Π±ΠΎΡ€ ΠΏΠΎ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΉ связи
ORDER BY
  pk DESC -- Ρ…ΠΎΡ‚ΠΈΠΌ всСго ΠΎΠ΄Π½Ρƒ "послСднюю" запись
LIMIT 1;

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Berehala ohar zaitezke indizeari 100 erregistro baino gehiago kendu zitzaizkiola, gero guztiak ordenatu zirela, eta gero bakarra geratu zela.

Zuzentzen:

DROP INDEX tbl_fk_cli_idx;
CREATE INDEX ON tbl(fk_cli, pk DESC); -- Π΄ΠΎΠ±Π°Π²ΠΈΠ»ΠΈ ΠΊΠ»ΡŽΡ‡ сортировки

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Halako lagin primitibo batean ere - 8.5 aldiz azkarragoa eta 33 aldiz irakurketa gutxiago. Zenbat eta "gertaera" gehiago izan balio bakoitzeko, orduan eta nabariagoa izango da eragina fk.

Kontuan izan dut indize horrek "aurrizkia" indize gisa funtzionatuko duela lehen baino okerrago beste kontsultetarako fk, non ordenatu pk ez zegoen eta ez dago (honi buruz gehiago irakur dezakezu Eraginkortasunik gabeko indizeak aurkitzeari buruzko nire artikuluan). Barne, normal emango du atzerriko gakoen laguntza esplizitua eremu honetan.

#2: indize-ebakidura (BitmapAnd)

Sortzen denean

Erakutsi "LLC Kolokolchik" bezeroarentzako akordio guztiak, "NAO Buttercup"-en izenean egindakoak.

Nola identifikatu

-> BitmapAnd
   -> Bitmap Index Scan
   -> Bitmap Index Scan

Gomendioak

sortzen indize konposatua jatorrizko bi eremuen arabera edo lehendik daudenetako bat zabaldu bigarreneko eremuekin.

Adibidea:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk      -- 100K "Ρ„Π°ΠΊΡ‚ΠΎΠ²"
, (random() *  100)::integer fk_org  -- 100 Ρ€Π°Π·Π½Ρ‹Ρ… Π²Π½Π΅ΡˆΠ½ΠΈΡ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ
, (random() * 1000)::integer fk_cli; -- 1K Ρ€Π°Π·Π½Ρ‹Ρ… Π²Π½Π΅ΡˆΠ½ΠΈΡ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ

CREATE INDEX ON tbl(fk_org); -- индСкс для foreign key
CREATE INDEX ON tbl(fk_cli); -- индСкс для foreign key

SELECT
  *
FROM
  tbl
WHERE
  (fk_org, fk_cli) = (1, 999); -- ΠΎΡ‚Π±ΠΎΡ€ ΠΏΠΎ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΉ ΠΏΠ°Ρ€Π΅

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Zuzentzen:

DROP INDEX tbl_fk_org_idx;
CREATE INDEX ON tbl(fk_org, fk_cli);

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Hemen ordaina txikiagoa da, Bitmap Heap Scan nahiko eraginkorra baita. Baina hala ere 7 aldiz azkarragoa eta 2.5 aldiz irakurketa gutxiago.

#3: Batu indizeak (BitmapOr)

Sortzen denean

Erakutsi "gu" zaharrenak edo esleitu gabeko lehen 20 eskaera prozesatzeko, zureak lehentasunez.

Nola identifikatu

-> BitmapOr
   -> Bitmap Index Scan
   -> Bitmap Index Scan

Gomendioak

erabiltzea BATASUNA [GUZTIAK] baldintza-or-bloke bakoitzeko azpikontsultak konbinatzeko.

Adibidea:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk  -- 100K "Ρ„Π°ΠΊΡ‚ΠΎΠ²"
, CASE
    WHEN random() < 1::real/16 THEN NULL -- с Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒΡŽ 1:16 запись "Π½ΠΈΡ‡ΡŒΡ"
    ELSE (random() * 100)::integer -- 100 Ρ€Π°Π·Π½Ρ‹Ρ… Π²Π½Π΅ΡˆΠ½ΠΈΡ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ
  END fk_own;

CREATE INDEX ON tbl(fk_own, pk); -- индСкс с "Π²Ρ€ΠΎΠ΄Π΅ ΠΊΠ°ΠΊ подходящСй" сортировкой

SELECT
  *
FROM
  tbl
WHERE
  fk_own = 1 OR -- свои
  fk_own IS NULL -- ... ΠΈΠ»ΠΈ "Π½ΠΈΡ‡ΡŒΠΈ"
ORDER BY
  pk
, (fk_own = 1) DESC -- сначала "свои"
LIMIT 20;

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Zuzentzen:

(
  SELECT
    *
  FROM
    tbl
  WHERE
    fk_own = 1 -- сначала "свои" 20
  ORDER BY
    pk
  LIMIT 20
)
UNION ALL
(
  SELECT
    *
  FROM
    tbl
  WHERE
    fk_own IS NULL -- ΠΏΠΎΡ‚ΠΎΠΌ "Π½ΠΈΡ‡ΡŒΠΈ" 20
  ORDER BY
    pk
  LIMIT 20
)
LIMIT 20; -- но всСго - 20, большС и нС надо

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Beharrezko 20 erregistro guztiak lehen blokean berehala jaso zirela aprobetxatu genuen, beraz, bigarrena, Bitmap Heap Scan "garestiago"arekin, ez zen exekutatu ere egin - azkenean. 22 aldiz azkarragoa, 44 aldiz irakurketa gutxiago!

Optimizazio metodo honi buruzko istorio zehatzagoa adibide zehatzak erabiliz artikuluetan irakur daiteke PostgreSQL Antipatterns: JOIN eta OR kaltegarriak ΠΈ PostgreSQL Antipatterns: bilaketaren finketa errepikakor baten istorioa, edo "Optimizazioa aurrera eta atzera".

Bertsio orokortua hainbat gakotan oinarritutako hautaketa ordenatua (eta ez bakarrik const/NULL bikotea) eztabaidatzen da artikuluan SQL HowTo: while begizta bat idaztea zuzenean kontsultan, edo "Oinarrizko hiru urrats".

#4: Alferrikako gauza asko irakurtzen ditugu

Sortzen denean

Oro har, lehendik dagoen eskaera bati "beste iragazki bat erantsi" nahi duzunean sortzen da.

Β«Eta ez duzu berdina, baina perlazko botoiekin? " "The Diamond Arm" filma

Adibidez, goiko zeregina aldatuz, erakutsi prozesatzeko lehen 20 eskaera "kritiko" zaharrenak, haien helburua edozein dela ere.

Nola identifikatu

-> Seq Scan | Bitmap Heap Scan | Index [Only] Scan [Backward]
   && 5 Γ— rows < RRbF -- ΠΎΡ‚Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΠΎΠ²Π°Π½ΠΎ >80% ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Π½Π½ΠΎΠ³ΠΎ
   && loops Γ— RRbF > 100 -- ΠΈ ΠΏΡ€ΠΈ этом большС 100 записСй суммарно

Gomendioak

Sortu [gehiago] espezializatua NON baldintza duen indizea edo gehitu eremu gehigarriak aurkibidean.

Iragazkiaren egoera "estatikoa" bada zure helburuetarako, hau da ez du hedapena suposatzen baloreen zerrenda etorkizunean - hobe da NON indizea erabiltzea. Hainbat egoera boolear/enumeratu ondo sartzen dira kategoria honetan.

Iragazte-baldintza bada esanahi desberdinak har ditzake, orduan hobe da indizea eremu hauekin zabaltzea - ​​goiko BitmapAnd-ekin gertatzen den bezala.

Adibidea:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk -- 100K "Ρ„Π°ΠΊΡ‚ΠΎΠ²"
, CASE
    WHEN random() < 1::real/16 THEN NULL
    ELSE (random() * 100)::integer -- 100 Ρ€Π°Π·Π½Ρ‹Ρ… Π²Π½Π΅ΡˆΠ½ΠΈΡ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ
  END fk_own
, (random() < 1::real/50) critical; -- 1:50, Ρ‡Ρ‚ΠΎ заявка "критичная"

CREATE INDEX ON tbl(pk);
CREATE INDEX ON tbl(fk_own, pk);

SELECT
  *
FROM
  tbl
WHERE
  critical
ORDER BY
  pk
LIMIT 20;

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Zuzentzen:

CREATE INDEX ON tbl(pk)
  WHERE critical; -- Π΄ΠΎΠ±Π°Π²ΠΈΠ»ΠΈ "статичноС" условиС Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΠΈ

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Ikusten duzunez, iragazketa erabat desagertu da planetik, eta eskaera bihurtu da 5 aldiz azkarrago.

#5: mahai eskasa

Sortzen denean

Zereginen prozesatzeko ilara propioa sortzeko hainbat saiakerak, mahai gainean dauden erregistroen eguneratze/ezabatze kopuru handi batek "hildako" erregistro kopuru handi bat eragiten duenean.

Nola identifikatu

-> Seq Scan | Bitmap Heap Scan | Index [Only] Scan [Backward]
   && loops Γ— (rows + RRbF) < (shared hit + shared read) Γ— 8
      -- ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Π½ΠΎ большС 1KB Π½Π° ΠΊΠ°ΠΆΠ΄ΡƒΡŽ запись
   && shared hit + shared read > 64

Gomendioak

Egin eskuz aldizka HUTSEAN [BETEA] edo behar bezain maiz prestakuntza lortzea autohutsean bere parametroak finkatuz, besteak beste mahai zehatz baterako.

Kasu gehienetan, horrelako arazoak kontsulta-diseinu eskasak eragiten ditu negozio-logikatik deitzean eztabaidatutakoak bezala PostgreSQL Antipatterns: "hildakoen" horden aurka borrokatzea.

Baina ulertu behar duzu VACUUM FULL-ek ere agian ez duela beti lagunduko. Horrelako kasuetarako, merezi du artikuluko algoritmoa ezagutzea DBA: HUTSAK huts egiten duenean, mahaia eskuz garbitzen dugu.

#6: aurkibidearen "erditik" irakurtzea

Sortzen denean

Badirudi pixka bat irakurri dugula, eta dena indexatu egin dela, eta ez dugu gehiegizko inor iragazi, baina hala ere nahi baino orrialde gehiago irakurtzen ditugu.

Nola identifikatu

-> Index [Only] Scan [Backward]
   && loops Γ— (rows + RRbF) < (shared hit + shared read) Γ— 8
      -- ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Π½ΠΎ большС 1KB Π½Π° ΠΊΠ°ΠΆΠ΄ΡƒΡŽ запись
   && shared hit + shared read > 64

Gomendioak

Begiratu arretaz erabilitako indizearen egitura eta kontsultan zehaztutako gako-eremuak - ziurrenik indizearen zati bat ez dago ezarrita. Ziurrenik antzeko indize bat sortu beharko duzu, baina aurrizki-eremurik gabe edo haien balioak errepikatzen ikasi.

Adibidea:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk      -- 100K "Ρ„Π°ΠΊΡ‚ΠΎΠ²"
, (random() *  100)::integer fk_org  -- 100 Ρ€Π°Π·Π½Ρ‹Ρ… Π²Π½Π΅ΡˆΠ½ΠΈΡ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ
, (random() * 1000)::integer fk_cli; -- 1K Ρ€Π°Π·Π½Ρ‹Ρ… Π²Π½Π΅ΡˆΠ½ΠΈΡ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ

CREATE INDEX ON tbl(fk_org, fk_cli); -- всС ΠΏΠΎΡ‡Ρ‚ΠΈ ΠΊΠ°ΠΊ Π² #2
-- Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π²ΠΎΡ‚ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ индСкс ΠΏΠΎ fk_cli ΠΌΡ‹ ΡƒΠΆΠ΅ посчитали лишним ΠΈ ΡƒΠ΄Π°Π»ΠΈΠ»ΠΈ

SELECT
  *
FROM
  tbl
WHERE
  fk_cli = 999 -- Π° fk_org Π½Π΅ Π·Π°Π΄Π°Π½ΠΎ, хотя стоит Π² индСксС Ρ€Π°Π½ΡŒΡˆΠ΅
LIMIT 20;

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Badirudi dena ondo dagoela, baita indizearen arabera ere, baina nolabait susmagarria da - irakurritako 20 erregistro bakoitzeko 4 datu kendu behar izan ditugu, 32 KB erregistro bakoitzeko - ez al da lodia? Eta indizearen izena tbl_fk_org_fk_cli_idx gogoeta eragitea.

Zuzentzen:

CREATE INDEX ON tbl(fk_cli);

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Bat-batean - 10 aldiz azkarrago, eta 4 aldiz gutxiago irakurtzeko!

Indizeen erabilera ez-eraginkorra duten egoeren beste adibide batzuk ikus daitezke artikuluan DBA: alferrikako indizeak aurkitzea.

#7: CTE Γ— CTE

Sortzen denean

Eskaeran CTE "koipea" lortu zuen mahai ezberdinetatik, eta gero beraien artean egitea erabaki zuten JOIN.

Kasua garrantzitsua da v12 azpiko bertsioetarako edo eskaerarekin WITH MATERIALIZED.

Nola identifikatu

-> CTE Scan
   && loops > 10
   && loops Γ— (rows + RRbF) > 10000
      -- слишком большоС Π΄Π΅ΠΊΠ°Ρ€Ρ‚ΠΎΠ²ΠΎ ΠΏΡ€ΠΎΠΈΠ·Π²Π΅Π΄Π΅Π½ΠΈΠ΅ CTE

Gomendioak

Arretaz aztertu eskaera - eta Hemen beharrezkoak al dira CTEak?? Bai bada, orduan aplikatu "hiztegia" hstore/json-en atalean azaldutako ereduaren arabera PostgreSQL Antipatterns: sakatu dezagun JOIN astuna hiztegi batekin.

#8: aldatu diskora (tenperatura idatzita)

Sortzen denean

Erregistro kopuru handi baten behin-behineko prozesatzea (ordenatzea edo bakartzea) ez da horretarako esleitutako memorian sartzen.

Nola identifikatu

-> *
   && temp written > 0

Gomendioak

Eragiketak erabiltzen duen memoria kopurua parametroaren zehaztutako balioa asko gainditzen ez badu lan_mem, merezi du zuzentzea. Berehala konfigurazioan sartu dezakezu guztiontzat, edo egin dezakezu SET [LOCAL] eskaera/transakzio zehatz baterako.

Adibidea:

SHOW work_mem;
-- "16MB"

SELECT
  random()
FROM
  generate_series(1, 1000000)
ORDER BY
  1;

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Zuzentzen:

SET work_mem = '128MB'; -- ΠΏΠ΅Ρ€Π΅Π΄ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ΠΌ запроса

Sick SQL kontsultak egiteko errezetak
[ikusi explain.tensor.ru helbidean]

Ageriko arrazoiengatik, memoria bakarrik erabiltzen bada eta ez diskoa, orduan kontsulta askoz azkarrago exekutatuko da. Aldi berean, HDDko kargaren zati bat ere kentzen da.

Baina ulertu behar duzu ez duzula beti memoria asko eta asko esleitu ahal izango; besterik gabe, ez da denentzat nahikoa izango.

#9: garrantzirik gabeko estatistikak

Sortzen denean

Datu-basera asko isurtzen zuten aldi berean, baina ez zuten urruntzeko astirik izan ANALYZE.

Nola identifikatu

-> Seq Scan | Bitmap Heap Scan | Index [Only] Scan [Backward]
   && ratio >> 10

Gomendioak

Egin ezazu ANALYZE.

Egoera hau zehatzago azaltzen da PostgreSQL Antipatterns: estatistikak dena dira.

#10: "zerbait gaizki joan da"

Sortzen denean

Eskaera lehiakide batek ezarritako blokeo baten itxaron bat egon zen, edo ez zegoen CPU/hipervisorearen hardware baliabide nahikorik.

Nola identifikatu

-> *
   && (shared hit / 8K) + (shared read / 1K) < time / 1000
      -- RAM hit = 64MB/s, HDD read = 8MB/s
   && time > 100ms -- Ρ‡ΠΈΡ‚Π°Π»ΠΈ ΠΌΠ°Π»ΠΎ, Π½ΠΎ слишком Π΄ΠΎΠ»Π³ΠΎ

Gomendioak

Erabili kanpokoa jarraipen-sistema baliabideak blokeatzeko edo anormalaren kontsumoa egiteko zerbitzaria. Dagoeneko hitz egin dugu ehunka zerbitzarietarako prozesu hau antolatzeko dugun bertsioari buruz Hemen ΠΈ Hemen.

Sick SQL kontsultak egiteko errezetak
Sick SQL kontsultak egiteko errezetak

Iturria: www.habr.com

Gehitu iruzkin berria