Vai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Par to, kā man bija jāoptimizē PostgreSQL vaicājums un kas no tā visa iznāca.
Kāpēc tev vajadzēja? Jā, jo iepriekŔējos 4 gadus viss strādāja klusi, mierÄ«gi, kā pulkstenis tikŔķēja.
Kā epigrāfs.

Vai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Balstīts uz patiesiem notikumiem.
Visi nosaukumi ir mainīti, sakritības ir nejauŔas.

Sasniedzot noteiktu rezultātu, vienmēr ir interesanti atcerēties, kas bija stimuls sākumam, kur tas viss sākās.

Tātad, kas notika rezultātā, ir Ä«si aprakstÄ«ts rakstā "Sintēze kā viena no metodēm PostgreSQL veiktspējas uzlaboÅ”anai'.

DroÅ”i vien bÅ«s interesanti atjaunot iepriekŔējo notikumu ķēdi.
Vēsture saglabāja precīzu sākuma datumu - 2018-09-10 18:02:48.
Arī stāstā ir lūgums, no kura viss sākās:
Problēmas pieprasījumsSELECT
p.ā€œPARAMETER_IDā€ kā parametra_id,
pd."PD_NAME" AS pd_name,
pd."CUSTOMER_PARTNUMBER" AS klienta_daļas numurs,
ar ā€œLRMā€ AS LRM,
w. "LOTID" AS lotid,
w.ā€œRTD_VALUEā€ AS RTD_vērtÄ«ba,
w.ā€œLOWER_SPEC_LIMITā€ AS zemāks_spec_limits,
w.ā€œUPPER_SPEC_LIMITā€ AS augŔējais_spec_limits,
p"TYPE_CALCUL" AS type_calcul,
s"SPENT_NAME" AS iztērētais_nosaukums,
s.ā€œSPENT_DATEā€ AS iztērēts_datums,
izraksts(gads no "SPENT_DATE") AS gads,
izraksts (mēnesis no "SPENT_DATE") kā mēnesis,
s."REPORT_NAME" AS pārskata_nosaukums,
p."STPM_NAME" AS stpm_name,
p.ā€œCUSTOMERPARAM_NAMEā€ AS klienta parametra_nosaukums
NO wdata w,
pavadīja s,
pmtr p,
pavadīja_pd sp,
pd pd
WHERE s.ā€œIZLĒDTAIS_IDā€ = w.ā€œIZLĒDTS_IDā€
UN p."PARAMETER_ID" = w."PARAMETER_ID"
UN s.ā€œIZLĒDTS_IDā€ = sp.ā€œIZDOTĀ_IDā€
UN pd."PD_ID" = sp."PD_ID"
UN s.ā€œSPENT_DATEā€ >= '2018-07-01' UN s.ā€œSPENT_DATEā€ <= '2018-09-30'
un s.ā€œSPENT_DATEā€ = (ATLASÄŖT MAX(s2.ā€œSPENT_DATEā€)
NO iztērētā s2,
wdata w2
WHERE s2.ā€œIZLĒTOTĀS_IDā€ = w2.ā€œIZLĒTOTAS_IDā€
UN w2.ā€œLRMā€ = w.ā€œLRMā€);


Problēmas apraksts ir paredzami standarta - ā€œViss ir slikti. Pastāsti man, kāda ir problēma."
Uzreiz atcerējos anekdoti no 3 ar pusi collu piedziņas laikiem:

Lamer nāk pie hakera.
-Man nekas nelīdz, pasaki kur ir problēma.
- DNS...

Bet, protams, tas nav veids, kā atrisināt darbības traucējumus. "Viņi var mūs nesaprast" (Ar). Mums tas ir jāizdomā.
Nu raksim. Varbūt rezultātā kaut kas sakrāsies.

Vai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Sākta izmeklÄ“Å”ana

Tātad, ko var redzēt uzreiz ar neapbruņotu aci, pat neķeroties pie IZSKAIDROT.
1) JOIN netiek izmantoti. Tas ir slikti, it īpaŔi, ja savienojumu skaits ir vairāk nekā viens.
2) Bet vēl sliktāk ir saistÄ«ti apakÅ”vaicājumi, turklāt ar apkopoÅ”anu. Tas ir ļoti slikti.
Tas, protams, ir slikti. Bet tas ir tikai no vienas puses. No otras puses, tas ir ļoti labi, jo problēmai viennozīmīgi ir risinājums un lūgums, ko var uzlabot.
Neej pie zīlnieces (C).
Vaicājumu plāns nav tik sarežģīts, taču tas ir diezgan orientējoÅ”s:
Izpildes plānsVai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Interesantākais un noderīgākais, kā parasti, ir sākumā un beigās.
Ligzdota cilpa (maksa = 935.84..479763226.18 rindas = 3322 platums = 135) (faktiskais laiks = 31.536..8220420.295 rindas = 8111656 cilpas = 1)
PlānoŔanas laiks: 3.807 ms
Izpildes laiks: 8222351.640 ms
Izpildes laiks ir vairāk nekā 2 stundas.

Vai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Nepatiesas hipotēzes, kas prasīja laiku

1. hipotēze ā€“ optimizētājs pieļauj kļūdu un izveido nepareizu plānu.

Lai vizualizētu izpildes plānu, mēs izmantosim vietni https://explain.depesz.com/. Tomēr vietne neko interesantu un noderÄ«gu neparādÄ«ja. No pirmā un otrā acu uzmetiena nekas Ä«sti nevarētu palÄ«dzēt. Vai ir iespējams, ka pilna skenÄ“Å”ana ir minimāla. Uz priekÅ”u.

2. hipotēze-Ietekme uz pamatni no autovakuuma puses, jāatbrīvojas no bremzēm.

Bet autovakuuma dēmoni uzvedas labi, nav ilgstoÅ”u procesu. Nav nopietnas slodzes. Mums jāmeklē kaut kas cits.

3. hipotēze - Statistika ir novecojusi, viss ir jāpārrēķina

Atkal, ne tas. Statistika ir atjaunināta. Kas, ņemot vērā problēmu trÅ«kumu ar autovakuumu, nav pārsteidzoÅ”i.

Sāksim optimizēt

Galvenā tabula 'wdata' noteikti nav maza, gandrīz 3 miljoni ierakstu.
Un tieŔi Ŕai tabulai seko Full Scan.

Jauktais nosacījums: ((w."SPENT_ID" = s."SPENT_ID") UN ((1. apakŔplāns) = s."SPENT_DATE"))
-> Seq Scan uz wdata w (maksa = 0.00..574151.49 rindas = 26886249 platums = 46) (faktiskais laiks = 0.005..8153.565 rindas = 26873950 cilpas = 1)
Mēs darām standarta lietu: "Nāc, izveidosim indeksu, un viss lidos."
Laukā ā€œSPENT_IDā€ tika izveidots rādÄ«tājs
Rezultātā:
Vaicājuma izpildes plāns, izmantojot indeksuVai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Nu, vai tas palīdzēja?
Bija: 8 222 351.640 ms (nedaudz vairāk par 2 stundām)
Kļuva: 6 985 431.575 ms (gandrÄ«z 2 stundas)
Vispār tie paŔi āboli, skats no malas.
Atcerēsimies klasiku:
ā€œVai jums ir tāds pats, bet bez spārniem? Meklēsies".

Vai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Principā to varētu saukt par labu rezultātu, nu, ne labu, bet pieņemamu. Sniedziet klientam vismaz lielu pārskatu, kurā aprakstīts, cik daudz ir paveikts un kāpēc paveiktais bija labs.
Tomēr galīgais lēmums vēl ir tālu. Ļoti tālu.

Un tagad pats interesantākais - turpinām optimizēt, pieslīpēsim pieprasījumu

Pirmais solis ā€” izmantojiet JOIN

Pārrakstītais pieprasījums tagad izskatās Ŕādi (nu vismaz tā ir skaistāka):
Pieprasiet, izmantojot JOINSELECT
p.ā€œPARAMETER_IDā€ kā parametra_id,
pd."PD_NAME" AS pd_name,
pd."CUSTOMER_PARTNUMBER" AS klienta_daļas numurs,
ar ā€œLRMā€ AS LRM,
w. "LOTID" AS lotid,
w.ā€œRTD_VALUEā€ AS RTD_vērtÄ«ba,
w.ā€œLOWER_SPEC_LIMITā€ AS zemāks_spec_limits,
w.ā€œUPPER_SPEC_LIMITā€ AS augŔējais_spec_limits,
p"TYPE_CALCUL" AS type_calcul,
s"SPENT_NAME" AS iztērētais_nosaukums,
s.ā€œSPENT_DATEā€ AS iztērēts_datums,
izraksts(gads no "SPENT_DATE") AS gads,
izraksts (mēnesis no "SPENT_DATE") kā mēnesis,
s."REPORT_NAME" AS pārskata_nosaukums,
p."STPM_NAME" AS stpm_name,
p.ā€œCUSTOMERPARAM_NAMEā€ AS klienta parametra_nosaukums
NO wdata w INNER JOIN iztērēts s ON w.ā€œSPENT_IDā€=s.ā€ā€œSPENT_IDā€
IEKŠĒJĀ PIEVIENOÅ ANĀS pmtr p ON p.ā€œPARAMETER_IDā€ = w.ā€œPARAMETER_IDā€
IEKŠĒJĀ PIEVIENOÅ ANĀS pavadÄ«ja_pd sp ON s.ā€œSPENT_IDā€ = sp.ā€œSPENT_IDā€
IEKŠĒJĀ JOIN pd pd ON pd.ā€œPD_IDā€ = sp.ā€œPD_IDā€
KUR
s.ā€œSPENT_DATEā€ >= '2018-07-01' UN s.ā€œSPENT_DATEā€ <= '2018-09-30'AND
s.ā€œSPENT_DATEā€ = (ATLASÄŖT MAX(s2.ā€œSPENT_DATEā€)
NO wdata w2 IEKŠĒJĀ JOIN iztērēts s2 ON w2.ā€œSPENT_IDā€=s2.ā€œSPENT_IDā€
INNER JOIN wdata w
IESLĒGTS w2.ā€œLRMā€ = w.ā€œLRMā€ );
PlānoŔanas laiks: 2.486 ms
Izpildes laiks: 1223680.326 ms

Tātad, pirmais rezultāts.
Bija: 6 985 431.575 ms (gandrÄ«z 2 stundas).
Kļuva: 1 223 680.326 ms (nedaudz vairāk nekā 20 minÅ«tes).
Labs rezultāts. Principā atkal varētu pie tā apstāties. Bet tas ir tik neinteresanti, ka jūs nevarat apstāties.
PRIEKÅ 

Vai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Otrais solis ā€“ atbrÄ«vojieties no korelētā apakÅ”vaicājuma

Mainīts pieprasījuma teksts:
Bez korelēta apakÅ”vaicājumaSELECT
p.ā€œPARAMETER_IDā€ kā parametra_id,
pd."PD_NAME" AS pd_name,
pd."CUSTOMER_PARTNUMBER" AS klienta_daļas numurs,
ar ā€œLRMā€ AS LRM,
w. "LOTID" AS lotid,
w.ā€œRTD_VALUEā€ AS RTD_vērtÄ«ba,
w.ā€œLOWER_SPEC_LIMITā€ AS zemāks_spec_limits,
w.ā€œUPPER_SPEC_LIMITā€ AS augŔējais_spec_limits,
p"TYPE_CALCUL" AS type_calcul,
s"SPENT_NAME" AS iztērētais_nosaukums,
s.ā€œSPENT_DATEā€ AS iztērēts_datums,
izraksts(gads no "SPENT_DATE") AS gads,
izraksts (mēnesis no "SPENT_DATE") kā mēnesis,
s."REPORT_NAME" AS pārskata_nosaukums,
p."STPM_NAME" AS stpm_name,
p.ā€œCUSTOMERPARAM_NAMEā€ AS klienta parametra_nosaukums
NO wdata w IEKŠĒJS JOIN iztērēts s UZ s.ā€œSPENT_IDā€ = w.ā€œSPENT_IDā€
IEKŠĒJĀ PIEVIENOÅ ANĀS pmtr p ON p.ā€œPARAMETER_IDā€ = w.ā€œPARAMETER_IDā€
IEKŠĒJĀ PIEVIENOÅ ANĀS pavadÄ«ja_pd sp ON s.ā€œSPENT_IDā€ = sp.ā€œSPENT_IDā€
IEKŠĒJĀ JOIN pd pd ON pd.ā€œPD_IDā€ = sp.ā€œPD_IDā€
IEKŠĒJĀ PIEVIENOÅ ANĀS (ATLASÄŖT w2.ā€œLRMā€, MAX(s2.ā€œSPENT_DATEā€)
NO iztērētā s2 IEKŠĒJĀ PIEVIENOÅ ANĀS wdata w2 ON s2.ā€œIZPĒRTĒTAS_IDā€ = w2.ā€œIZPĒRTĒTAS_IDā€
GROUP BY w2.ā€œLRMā€
) md uz w.ā€œLRMā€ = md.ā€œLRMā€
KUR
s."SPENT_DATE" >= '2018-07-01' UN s."SPENT_DATE" <= '2018-09-30';
PlānoŔanas laiks: 2.291 ms
Izpildes laiks: 165021.870 ms

Bija: 1 223 680.326 ms (nedaudz vairāk nekā 20 minÅ«tes).
Kļuva: 165 021.870 ms (nedaudz vairāk nekā 2 minÅ«tes).
Å is jau ir diezgan labs.
Tomēr, kā saka briti "Bet vienmēr ir bet" Pārāk labam rezultātam automātiski vajadzētu izraisīt aizdomas. Šeit kaut kas nav kārtībā.

Hipotēze par vaicājuma laboÅ”anu, lai atbrÄ«votos no korelētā apakÅ”vaicājuma, ir pareiza. Bet jums tas ir nedaudz jāpielāgo, lai gala rezultāts bÅ«tu pareizs.
Rezultātā pirmais starprezultāts:
Rediģēts vaicājums bez korelēta apakÅ”vaicājumaSELECT
p.ā€œPARAMETER_IDā€ kā parametra_id,
pd."PD_NAME" AS pd_name,
pd."CUSTOMER_PARTNUMBER" AS klienta_daļas numurs,
ar ā€œLRMā€ AS LRM,
w. "LOTID" AS lotid,
w.ā€œRTD_VALUEā€ AS RTD_vērtÄ«ba,
w.ā€œLOWER_SPEC_LIMITā€ AS zemāks_spec_limits,
w.ā€œUPPER_SPEC_LIMITā€ AS augŔējais_spec_limits,
p"TYPE_CALCUL" AS type_calcul,
s"SPENT_NAME" AS iztērētais_nosaukums,
s.ā€œSPENT_DATEā€ AS iztērēts_datums,
izraksts(gads no s.ā€œSPENT_DATEā€) AS gads,
izraksts (mēnesis no s.ā€œSPENT_DATEā€) kā mēnesis,
s."REPORT_NAME" AS pārskata_nosaukums,
p."STPM_NAME" AS stpm_name,
p.ā€œCUSTOMERPARAM_NAMEā€ AS klienta parametra_nosaukums
NO wdata w IEKŠĒJS JOIN iztērēts s UZ s.ā€œSPENT_IDā€ = w.ā€œSPENT_IDā€
IEKŠĒJĀ PIEVIENOÅ ANĀS pmtr p ON p.ā€œPARAMETER_IDā€ = w.ā€œPARAMETER_IDā€
IEKŠĒJĀ PIEVIENOÅ ANĀS pavadÄ«ja_pd sp ON s.ā€œSPENT_IDā€ = sp.ā€œSPENT_IDā€
IEKŠĒJĀ JOIN pd pd ON pd.ā€œPD_IDā€ = sp.ā€œPD_IDā€
IEKŠĒJĀ PIEVIENOÅ ANĀS ( ATLASÄŖT w2.ā€œLRMā€, MAX(s2.ā€œSPENT_DATEā€) KĀ ā€œSPENT_DATEā€
NO iztērētā s2 IEKŠĒJĀ PIEVIENOÅ ANĀS wdata w2 ON s2.ā€œIZPĒRTĒTAS_IDā€ = w2.ā€œIZPĒRTĒTAS_IDā€
GROUP BY w2.ā€œLRMā€
) md ON md.ā€œIZLĒDZIS_DATUMSā€ = s.ā€œIZLĒDZIS_DATUMSā€ UN md.ā€œLRMā€ = w.ā€œLRMā€
KUR
s."SPENT_DATE" >= '2018-07-01' UN s."SPENT_DATE" <= '2018-09-30';
PlānoŔanas laiks: 3.192 ms
Izpildes laiks: 208014.134 ms

Tātad, tas, ko mēs galu galā iegūstam, ir pirmais pieņemamais rezultāts, ko nav kauns parādīt klientam:
Sākās ar: 8 222 351.640 ms (vairāk nekā 2 stundas)
Mums izdevās sasniegt: 1 223 680.326, 20 ms (nedaudz vairāk par XNUMX minūtēm).
Rezultāts (starpposms): 208 014.134 ms (nedaudz vairāk nekā 3 minÅ«tes).

Izcils rezultāts.

Vai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Kopsavilkums

Mēs varējām tur apstāties.
BETā€¦
ApetÄ«te rodas ēdot. Tas, kurÅ” iet, pārvaldÄ«s ceļu. JebkurÅ” rezultāts ir starpposms. Apstājās un nomira. utt.
Turpināsim optimizāciju.
Lieliska ideja. ÄŖpaÅ”i ņemot vērā, ka klients pat neiebilda. Un pat stipri par to.

Tātad, ir pienācis laiks pārveidot datubāzi. Pati vaicājuma struktÅ«ru vairs nevar optimizēt (lai gan, kā vēlāk izrādÄ«jās, ir iespēja nodroÅ”ināt, ka viss tieŔām neizdodas). Bet sākt optimizēt un attÄ«stÄ«t datu bāzes dizainu jau ir ļoti daudzsoloÅ”a ideja. Un pats galvenais interesanti. Atkal atcerieties savu jaunÄ«bu. Galu galā es ne uzreiz kļuvu par DBA, es uzaugu kā programmētājs (BASIC, montētājs, C, dubultā plus C, Oracle, plsql). Interesanta tēma, protams, atseviŔķam memuāram ;-).
Tomēr nenovērsīsim uzmanību.

Tātad,

Vai atceries, kā tas viss sākās. Viss bija pirmo reizi un atkal

Vai varbÅ«t sadalÄ«Å”ana mums palÄ«dzēs?
Spoileris ā€” "Jā, tas palÄ«dzēja, tostarp optimizējot veiktspēju."

Bet tas ir pavisam cits stāsts...

Turpinājums sekosā€¦

Avots: www.habr.com

Pievieno komentāru