Diepgaande Analyse van de Prestatieafname van MySQL 8.0

Gebruikers merken vaak gemakkelijker een daling in de prestaties bij lage gelijktijdigheid, terwijl verbeteringen in de prestaties bij hoge gelijktijdigheid vaak moeilijker te perceiveren zijn. Daarom is het handhaven van de prestaties bij lage gelijktijdigheid cruciaal, aangezien dit een directe invloed heeft op de gebruikerservaring en de bereidheid om te upgraden [1].

Volgens uitgebreide gebruikersfeedback hebben gebruikers over het algemeen een daling in de prestaties waargenomen na de upgrade naar MySQL 8.0, met name bij batch-insert- en join-operaties. Deze neerwaartse trend is duidelijker zichtbaar geworden in hogere versies van MySQL. Bovendien hebben enkele MySQL-enthousiastelingen en testers prestatieverlies gerapporteerd in meerdere sysbench-tests na de upgrade.

Kunnen deze prestatieproblemen worden vermeden? Of, specifieker, hoe moeten we de voortdurende trend van prestatieverlies wetenschappelijk beoordelen? Dit zijn belangrijke vragen om te overwegen.

Hoewel het officiële team blijft optimaliseren, kan de geleidelijke verslechtering van de prestaties niet worden genegeerd. In bepaalde scenario’s lijken er verbeteringen te zijn, maar dit betekent niet dat de prestaties in alle scenario’s even goed zijn geoptimaliseerd. Bovendien is het ook gemakkelijk om de prestaties voor specifieke scenario’s te optimaliseren ten koste van het verminderen van de prestaties in andere gebieden.

De Oorzaken van de Daling in MySQL-prestaties

Over het algemeen, naarmate er meer functies worden toegevoegd, groeit de codebase, en met de continue uitbreiding van functionaliteit wordt het steeds moeilijker om de prestaties te beheersen.

MySQL-ontwikkelaars merken vaak niet de afname in prestaties op, aangezien elke toevoeging aan de codebase slechts een zeer kleine afname in prestaties tot gevolg heeft. Na verloop van tijd stapelen deze kleine afnames zich op, resulterend in een significante cumulatieve effect, waardoor gebruikers een merkbare prestatievermindering waarnemen in nieuwere versies van MySQL.

Als voorbeeld toont de volgende figuur de prestaties van een eenvoudige enkele join-operatie, waarbij MySQL 8.0.40 een prestatievermindering laat zien in vergelijking met MySQL 8.0.27:

Figure 1. Significant decline in join performance in MySQL 8.0.40.

De volgende figuur toont de prestatietest van batchinvoegingen onder enkele gelijktijdigheid, met de prestatievermindering van MySQL 8.0.40 in vergelijking met versie 5.7.44:

Figure 2. Significant decline in bulk insert performance in MySQL 8.0.40.

Uit de twee bovenstaande grafieken blijkt dat de prestaties van versie 8.0.40 niet goed zijn.

Laten we nu de oorzaak van de prestatievermindering in MySQL analyseren vanuit het code-niveau. Hieronder staat de functie PT_insert_values_list::contextualize in MySQL 8.0:

C++

 

De overeenkomstige functie PT_insert_values_list::contextualize in MySQL 5.7 is als volgt:

C++

 

Uit de codevergelijking lijkt MySQL 8.0 over elegantere code te beschikken en lijkt het vooruitgang te boeken.

Helaas leiden de motivaties achter deze codeverbeteringen vaak juist tot prestatievermindering. Het MySQL-officiële team heeft de vorige List-gegevensstructuur vervangen door een deque, wat een van de hoofdoorzaken is van de geleidelijke prestatievermindering. Laten we eens kijken naar de documentatie van deque:

Markdown

 

Zoals in de bovenstaande beschrijving is aangetoond, vereist het behoud van een enkel element in extreme gevallen het toewijzen van de gehele array, wat resulteert in een zeer lage geheugenefficiëntie. Bijvoorbeeld, bij bulkinvoegen, waar een groot aantal records moet worden ingevoegd, slaat de officiële implementatie elk record op in een aparte deque. Zelfs als de inhoud van het record minimaal is, moet er toch een deque worden toegewezen. De MySQL deque-implementatie wijst 1KB geheugen toe voor elke deque om snelle opvragingen te ondersteunen.

Plain Text

 

De officiële implementatie gebruikt 1KB geheugen om indexinformatie op te slaan, en zelfs als de recordlengte niet groot is maar er veel records zijn, kunnen de geheugentoegang adressen niet aaneengeschakeld raken, wat leidt tot een slechte cachevriendelijkheid. Dit ontwerp was bedoeld om de cachevriendelijkheid te verbeteren, maar het is niet volledig effectief gebleken.

Het is vermeldenswaard dat de oorspronkelijke implementatie een List-gegevensstructuur gebruikte, waarbij geheugen werd toegewezen via een geheugenpool, wat een bepaald niveau van cachevriendelijkheid bood. Hoewel willekeurige toegang minder efficiënt is, verbetert het optimaliseren voor sequentiële toegang tot List-elementen de prestaties aanzienlijk.

Tijdens de upgrade naar MySQL 8.0 observeerden gebruikers een significante achteruitgang in de prestaties van batchinvoegen, en een van de belangrijkste oorzaken was de substantiële wijziging in de onderliggende gegevensstructuren.

Daarnaast, terwijl het officiële team het redo-logmechanisme verbeterde, leidde dit ook tot een afname van de efficiëntie van de MTR-commitoperatie. In vergelijking met MySQL 5.7 vermindert de toegevoegde code de prestaties van individuele commits aanzienlijk, ook al is de algehele schrijfsnelheid sterk verbeterd.

Laten we de kernoperatie execute van MTR-commit in MySQL 5.7.44 bekijken:

C++

 

Laten we de kern execute operatie van MTR commit in MySQL 8.0.40 onderzoeken:

C++

 

In vergelijking is het duidelijk dat in MySQL 8.0.40 de execute operatie in MTR commit veel complexer is geworden, met meer stappen. Deze complexiteit is een van de belangrijkste oorzaken van de afname in prestaties bij schrijfoperaties met lage gelijktijdigheid.

Met name de operaties m_impl->m_log.for_each_block(write_log) en log_wait_for_space_in_log_recent_closed(*log_sys, handle.start_lsn) hebben aanzienlijke overhead. Deze veranderingen werden doorgevoerd om de prestaties bij hoge gelijktijdigheid te verbeteren, maar dit ging ten koste van de prestaties bij lage gelijktijdigheid.

De prioritering van de redo log voor hoge gelijktijdigheid zorgt voor slechte prestaties bij werkbelastingen met lage gelijktijdigheid. Hoewel de introductie van innodb_log_writer_threads bedoeld was om prestatieproblemen bij lage gelijktijdigheid te verminderen, heeft dit geen invloed op de uitvoering van de bovenstaande functies. Aangezien deze operaties complexer zijn geworden en frequente MTR commits vereisen, is de prestatie aanzienlijk gedaald.

Laten we eens kijken naar de impact van de directe toevoeg/verwijder-functie op de prestaties. Hieronder staat de functie rec_init_offsets_comp_ordinary in MySQL 5.7:

C++

 

De functie rec_init_offsets_comp_ordinary in MySQL 8.0.40 is als volgt:

C++

 

Uit de bovenstaande code blijkt dat met de introductie van de instant add/drop kolomfunctie, de rec_init_offsets_comp_ordinary functie merkbaar complexer is geworden, met meer functie-aanroepen en een switch-statement dat een ernstige impact heeft op de cache-optimalisatie. Aangezien deze functie vaak wordt aangeroepen, heeft dit directe gevolgen voor de prestaties van de update-index, batch-inserts en joins, wat resulteert in een aanzienlijke prestatiedaling.

Bovendien is de prestatieafname in MySQL 8.0 niet beperkt tot het bovenstaande; er zijn veel andere gebieden die bijdragen aan de algehele prestatieafname, vooral de impact op de uitbreiding van inline functies. Bijvoorbeeld, de volgende code beïnvloedt de uitbreiding van inline functies:

C++

 

Volgens onze tests verstoort de ib::fatal verklaring de inline optimalisatie ernstig. Voor vaak geraadpleegde functies is het raadzaam om uitspraken te vermijden die de inline optimalisatie verstoren.

Laten we nu een vergelijkbaar probleem bekijken. De row_sel_store_mysql_field functie wordt vaak aangeroepen, waarbij row_sel_field_store_in_mysql_format een hotspotfunctie binnenin is. De specifieke code is als volgt:

C++

 

De row_sel_field_store_in_mysql_format functie roept uiteindelijk row_sel_field_store_in_mysql_format_func aan.

C++

 

De row_sel_field_store_in_mysql_format_func functie kan niet inline worden uitgevoerd vanwege de aanwezigheid van de ib::fatal code.

C++

 

Vaak aangeroepen inefficiënte functies, die tienduizenden keren per seconde worden uitgevoerd, kunnen een ernstige impact hebben op de prestatie van joins.

Laten we doorgaan met het verkennen van de redenen voor prestatievermindering. De volgende officiële prestatieoptimalisatie is eigenlijk een van de hoofdoorzaken van de afname van de prestaties bij het samenvoegen. Hoewel bepaalde query’s kunnen worden verbeterd, zijn ze nog steeds een aantal van de redenen voor de prestatievermindering van gewone samenvoegingsbewerkingen.

GitHub Flavored Markdown

 

De problemen van MySQL gaan verder dan deze. Zoals blijkt uit de analyses hierboven, is de prestatievermindering in MySQL niet zonder reden. Een reeks kleine problemen, wanneer opgehoopt, kan leiden tot merkbare prestatievermindering die gebruikers ervaren. Deze problemen zijn echter vaak moeilijk te identificeren, waardoor ze nog moeilijker op te lossen zijn.

De zogenaamde ‘voortijdige optimalisatie’ is de oorzaak van alle kwaad, en dit geldt niet voor de ontwikkeling van MySQL. Databaseontwikkeling is een complex proces, en het verwaarlozen van prestaties in de loop van de tijd maakt latere prestatieverbeteringen aanzienlijk uitdagender.

Oplossingen om de prestatievermindering van MySQL te verminderen

De belangrijkste redenen voor de afname van de schrijfprestaties hebben te maken met MTR-commitproblemen, direct toevoegen/verwijderen van kolommen en enkele andere factoren. Deze zijn moeilijk te optimaliseren op traditionele manieren. Gebruikers kunnen echter de prestatiedaling compenseren door middel van PGO-optimalisatie. Met een goede strategie kan de prestatie over het algemeen stabiel worden gehouden.

Voor batch-insertieprestatievermindering vervangt onze open-source versie [2] de officiële deque door een verbeterde lijstimplementatie. Dit adresseert voornamelijk geheugenefficiëntieproblemen en kan gedeeltelijk prestatievermindering verlichten. Door PGO-optimalisatie te combineren met onze open-source versie, kan de batch-insertieprestatie vergelijkbaar worden met die van MySQL 5.7.

Figure 3. Optimized MySQL 8.0.40 with PGO performs roughly on par with version 5.7.

Gebruikers kunnen ook meerdere threads benutten voor gelijktijdige batchverwerking, waarbij volop gebruik wordt gemaakt van de verbeterde gelijktijdigheid van het redo-logboek, wat de batch-insertieprestaties aanzienlijk kan verbeteren.

Wat betreft update-indexproblemen, vanwege de onvermijdelijke toevoeging van nieuwe code, kan PGO-optimalisatie helpen dit probleem te verzachten. Onze PGO-versie [2] kan dit probleem aanzienlijk verlichten.

Voor leesprestaties, met name join-prestaties, hebben we aanzienlijke verbeteringen aangebracht, waaronder het oplossen van inlinekwesties en het doorvoeren van andere optimalisaties. Met de toevoeging van PGO kunnen join-prestaties met meer dan 30% worden verhoogd in vergelijking met de officiële versie.

Figure 4. Using PGO, along with our optimizations, can lead to significant improvements in join performance.

We zullen blijven investeren in het optimaliseren van prestaties bij lage gelijktijdigheid. Dit proces is lang maar omvat tal van gebieden die verbetering behoeven.

De open-source versie is beschikbaar voor testen en inspanningen zullen worden voortgezet om de prestaties van MySQL te verbeteren.MySQL-prestaties.

Referenties

[1] Bin Wang (2024). De Kunst van Probleemoplossing in Softwaretechniek: Hoe MySQL te Verbeteren.

[2] Verbeterd voor MySQL · GitHub

Source:
https://dzone.com/articles/mysql-80-performance-degradation-analysis