Gdy widać, że serwer bazodanowy zaczyna zwalniać, obciążenie procesora jest co raz większe, miło by było aby móc zlokalizować przyczynę takiego stanu rzeczy. W przypadku, gdy stosujemy standardową dystrybucję MySQL a użytkowników w bazie jest więcej niż kilku (i analizowanie bieżących wyników mytop’a czy SHOW PROCESSLIST; już nie wystarcza), w zasadzie jedynym rozwiązaniem jest włączenie pełnego logowania zapytań do slowlogów (long_query_time=0) i a potem wykorzystać magię awk’a/sed’a/perl’a aby posumować czas wykonywania zapytań dla każdego użytkownika. Problem w tym, że po pierwsze, tego typu statystyki są nieprecyzyjne. Jeśli chwilowo serwer został przeciążony, wydłuży się czas wykonywania wszystkich zapytań – nie tylko tych, które przeciążenie spowodowały. Po drugie, logowanie wszystkiego do slowlogu wpływa negatywnie na wydajność serwera – w końcu trzeba te wszystkie logi zapisać na dysku. Po trzecie, jeśli na serwerze jest spory ruch, to i logi będą długie – czas potrzebny na ich obróbkę, a także obciążenie przez ten proces generowane, może być znaczące i w praktyce uniemożliwić wykonywanie tego typu operacji na żądanie – da się je wykonać tylko w zaplanowanym okienku, w nocy, gdy dodatkowe obciążenie serwera nie będzie takim problemem. Czy jest jakieś inne rozwiązanie?

Kilka lat temu Google uszczęśliwiło administratorów baz danych MySQL na całym świecie, szczególnie tych, którzy pracowali w firmach hostingowych – tam problem był najbardziej widoczny. Wypuściło jeden wielki patch, który stosowany był na serwerach MySQL w Google, a który między innymi zawierał dodatkowe statystyki użycia serwera na poziomie poszczególnych użytkowników. Przez pewien czas problemem było to, że patch ten (który był przez lata rozbudowywany i zyskał ostatnio czwartą wersję) jest przygotowany pod konkretną wersję MySQL – 5.0.37. Jeśli chciałeś stosować inną wersję, trzeba było samodzielnie patch przepisać. Szczególnie problematyczne było to w przypadku MySQL 5.1. Na szczęście pojawił się sponsor i programiści Percony zabrali się za tą sprawę. W tym momencie każdy użytkownik Percona Server może się cieszyć ładnymi statystykami obciążenia generowanego przez poszczególnych użytkowników. Statystyki te są przechowywane w następujących tabelach w information_schema:

user_statistics
table_statistics
index_statistics
client_statistics

Dostępne są przez polecenie SHOW tabela;:

mysql> SHOW CLIENT_STATISTICS\G
*************************** 1. row ***************************
Client: localhost
Total_connections: 32287
Concurrent_connections: 0
Connected_time: 12043
Busy_time: 1355
Cpu_time: 26
Bytes_received: 40539695
Bytes_sent: 303389731
Binlog_bytes_written: 5285746
Rows_fetched: 910821
Rows_updated: 4161
Table_rows_read: 739281
Select_commands: 44171
Update_commands: 4372
Other_commands: 59284
Commit_transactions: 1
Rollback_transactions: 0
Denied_connections: 0
Lost_connections: 0
Access_denied: 0
Empty_queries: 11163
1 row in set (0,00 sec)

Można także wykorzystać składnię SELECT:

mysql> select * from information_schema.user_statistics\G
*************************** 1. row ***************************
USER: a
TOTAL_CONNECTIONS: 6670
CONCURRENT_CONNECTIONS: 0
CONNECTED_TIME: 206
BUSY_TIME: 10
CPU_TIME: 3
BYTES_RECEIVED: 21626469
BYTES_SENT: 50249340
BINLOG_BYTES_WRITTEN: 7409
ROWS_FETCHED: 7706
ROWS_UPDATED: 24
TABLE_ROWS_READ: 2628
SELECT_COMMANDS: 162
UPDATE_COMMANDS: 52
OTHER_COMMANDS: 13340
COMMIT_TRANSACTIONS: 0
ROLLBACK_TRANSACTIONS: 0
DENIED_CONNECTIONS: 0
LOST_CONNECTIONS: 0
ACCESS_DENIED: 0
EMPTY_QUERIES: 53
*************************** 2. row ***************************
USER: b
TOTAL_CONNECTIONS: 20651
CONCURRENT_CONNECTIONS: 0
CONNECTED_TIME: 3637
BUSY_TIME: 444
CPU_TIME: 12
BYTES_RECEIVED: 2101074
BYTES_SENT: 0
BINLOG_BYTES_WRITTEN: 0
ROWS_FETCHED: 491856
ROWS_UPDATED: 0
TABLE_ROWS_READ: 0
SELECT_COMMANDS: 20699
UPDATE_COMMANDS: 0
OTHER_COMMANDS: 40971
COMMIT_TRANSACTIONS: 0
ROLLBACK_TRANSACTIONS: 0
DENIED_CONNECTIONS: 0
LOST_CONNECTIONS: 0
ACCESS_DENIED: 0
EMPTY_QUERIES: 102
*************************** 3. row ***************************
USER: c
TOTAL_CONNECTIONS: 4949
CONCURRENT_CONNECTIONS: 0
CONNECTED_TIME: 6785
BUSY_TIME: 899
CPU_TIME: 10
BYTES_RECEIVED: 16794937
BYTES_SENT: 252909650
BINLOG_BYTES_WRITTEN: 5276178
ROWS_FETCHED: 410260
ROWS_UPDATED: 4133
TABLE_ROWS_READ: 735797
SELECT_COMMANDS: 23261
UPDATE_COMMANDS: 4316
OTHER_COMMANDS: 4941
COMMIT_TRANSACTIONS: 1
ROLLBACK_TRANSACTIONS: 0
DENIED_CONNECTIONS: 0
LOST_CONNECTIONS: 0
ACCESS_DENIED: 0
EMPTY_QUERIES: 10988
3 rows in set (0,00 sec)

Jak widać powyżej, w prosty sposób możemy wyciągnąć informacje na temat ruchu generowanego przez poszczególnych użytkowników. Wartość busy_time to łączny czas w sekundach, przez który dany użytkownik był połączony z serwerem MySQL i wykonywał jakieś zapytania – to jest tak z grubsza odpowiednik podsumowania czasu trwania wszystkich zapytań ze slowlogów. Wartość cpu_time to z kolei czas procesora (także w sekundach), który został poświęcony na wykonywanie zapytań danego użytkownika. Wartość ta jest znacznie niższa, bo na czas wykonania zapytania składa się nie tylko czas procesora, ale też czas oczekiwania na locki, oczekiwania na dysk, oczekiwania na zwolnienie mutexów i tak dalej. Pozostałe kolumny z tej tabeli są dosyć jasno opisane, nie będę się więc o nich rozpisywać.

Dane te są zbierane na okrągło i są dostępne w czasie rzeczywistym. Liczniki kasują się w trakcie restartu serwera albo przez wykonanie zapytania FLUSH tabela; Można sobie podefiniować zadania do crona i np. raz dziennie zrzucać sobie dane z całego dnia do archiwum, a potem wyczyścić statystyki na kolejny dzień. Daje to ciekawy przegląd obciążenia serwera MySQL w pewnym przedziale czasowym.