Cum găsesc interogările lente în MySQL 8.0 fără să omor producția

这篇内容暂时只有 Română 版本,以下是原文。

Am avut un incident acum câteva luni pe un MySQL 8.0.32, 16 vCPU, 64 GB RAM, InnoDB, cu vreo 400 GB de date și un vârf de 2.500 conexiuni active. Simptomul: la fiecare ~40 de minute, SHOW PROCESSLIST arăta 30–50 de query-uri în Sending data pe același tabel, iar replica de citire rămânea în urmă cu 90 de secunde. Nimeni nu putea spune care query era vinovatul, pentru că slow_query_log era pornit cu long_query_time = 2, iar agonia era făcută din zeci de query-uri de 300–600 ms care se suprapuneau.

Primul pas a fost să nu pornesc general_log. Pe un server cu 2.500 conexiuni, general_log scrie fiecare statement pe disc și transformă I/O-ul într-un bottleneck. În schimb, am folosit performance_schema, care era deja activat (performance_schema=ON, performance_schema_consumer_events_statements_history_long=ON). Am confirmat că istoricul lung ține 10.000 de evenimente pe thread:

SELECT NAME, ENABLED FROM performance_schema.setup_consumers
WHERE NAME LIKE 'events_statements%';

Apoi am extras top-ul după timp total, nu după timp mediu:

SELECT DIGEST_TEXT,
       COUNT_STAR AS execs,
       ROUND(SUM_TIMER_WAIT/1e12, 2) AS total_sec,
       ROUND(AVG_TIMER_WAIT/1e9, 1) AS avg_ms,
       SUM_ROWS_EXAMINED AS rows_examined
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME = 'app'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

Rezultatul a fost clar: un SELECT ... WHERE status = ? AND created_at > ? ORDER BY id DESC LIMIT 50 cu 180.000 de execuții pe oră și rows_examined mediu de 12.000. Indexul exista pe (status, created_at), dar ORDER BY id DESC forța un filesort pentru că id nu era în index. Am adăugat un index covering (status, created_at, id) și am rescris query-ul să filtreze pe created_at cu o fereastră explicită, nu cu NOW() - INTERVAL. Timpul mediu a scăzut de la 340 ms la 4 ms, iar lag-ul replicii a dispărut.

De ce am ales calea asta: performance_schema consumă memorie (buffer-ele de istoric), dar nu atinge discul per statement și îți dă timp agregat pe digest, exact ce lipsește din slow log când ai multe query-uri mici. Costul e că events_statements_history_long se pierde la restart și că nu vezi parametrii concreți ai fiecărei execuții — pentru asta am folosit events_statements_history per thread, dar doar pe sesiuni țintă.

Unde se strică: dacă performance_schema e dezactivat la boot, nu-l poți activa complet din runtime fără restart; dacă ai DIGEST_TEXT trunchiat la 1024 caractere, query-urile lungi se amestecă între ele; iar pe MariaDB, events_statements_summary_by_digest nu are aceleași coloane. Am pățit și să confund SUM_TIMER_WAIT (picosecunde) cu microsecunde și să raportez de un milion de ori mai mult.

Voi cum separați query-urile „mici dar dese” de cele „rare dar grele” când slow log-ul nu ajută?

0
赞同度 100% · 0 条评论 · 5 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。