Wpisy otagowane ‘MySQL’

MySQL slow_queries

wtorek, 10 Luty 2009

Już kiedyś płakałem z tego powodu – Analiza log-slow-queries (z życia wzięte…), a tu stary klient narzeka na wydajność swojego serwera. I co my tu mamy?

  • tabela 1 – 117’608 wierszy,
  • tabela 2 – 730 wierszy,
  • tabela 3 – 709 wierszy.

Niby niewiele, ale da się z tego zrobić taki wynik:

# Query_time: 11'005  Lock_time: 0  Rows_sent: 1  Rows_examined: 88'891'086

Dla porównania 10 miesięcy temu to samo zapytanie:

# Query_time: 53  Lock_time: 0  Rows_sent: 615  Rows_examined: 8'674'949

Nie mam pytań. Zdecydowanie potrzeba nowego procesora, więcej pamięci i szybszych dysków. Programisty przecież nie przekonamy…

SQL Relay

czwartek, 4 Wrzesień 2008

Zajmował się ktoś SQL Relay? Wygląda obiecująco, i te wsparcie dostawców – Oracle, MySQL, mSQL, PostgreSQL, Sybase, MS SQL Server (oba przez FreeTDS), IBM DB2, Interbase, SQLite, ODBC, MS Access (MDB Tools)…

Postanowiłem przyjrzeć się temu w połączeniu z PostgreSQL i MySQL z C/C++ i Perl API. Wersja 0.39.4.
Jest w portach dla FreeBSD, nie ma w paczkach dla Gentoo. Potrzebuje dodatkowej biblioteki Rudiments (devel/rudiments we FreeBSD, dev-cpp/rudiments w Gentoo) i jeśli chcemy jakiegoś API, to odpowiednio: Perl, PHP, Python, Ruby, TCL, Java lub Zope. GTK+ i glib da nam GUI.

Konfiguracja nietrudna – w pliku XML. W tagu instance definiujemy proces nasłuchu. Tag user to definicje użytkowników wraz z hasłami. Na koniec tag connection, który wskazuje konfigurację do konkretnej bazy.
W bardziej złożonej konfiguracji mamy jeszcze router i filter. Pierwszy tag odpowiada za kierowanie zapytań do odpowiedniej bazy, drugi – za filtrowanie zapytań (zdefiniowane nie będą w ogóle przesyłane do serwera, a klient dostanie błąd). W obu przypadkach jest możliwość dopasowania zapytań za pomocą wyrażeń regularnych.

Jeżeli uruchamiamy SQL Relay za pomocą sqlr-start, musimy pamiętać, że wszystkie programy, które wywołuje, np. sqlr-listener muszą być w ścieżce ($PATH). Baza również musi działać. Jeśli nie, pozostaje walka z czyszczeniem shared memory segment i semaphore (ipcs, ipcrm), bo SQL Relay nie sprząta po sobie.

Na tym skończyłem walkę. To znaczy próbowałem uruchomić SQL Relay zarówno z MySQL jak i PostgreSQL, ale niestety zakończyło się to niepowodzeniem (SQL Relay nasłuchiwał, przy połączeniu cisza). Mam nadzieję, że kiedyś wrócę do tego tematu.

Przepraszam, jeśli ktoś był pełen nadzieji, że czegoś się dowie z tego artykułu. Zmieniłem pracę i już nie robię tego co robiłem, więc wiele rzeczy pozostało niedokończonych…

Bezpieczne połączenia z MySQL (C API)

niedziela, 10 Sierpień 2008

Ostatnio coraz więcej kodu w C piszę z użyciem MySQL API, a że dane przy jednym z projektów są poufne i serwer nie jest lokalny, przyjrzałem się połączeniom SSL. W zasadzie po stronie kodu to nic wielkiego – wystarczy wywołać mysql_ssl_set() przed mysql_real_connect() (mysql_connect() jest deprecated). Oczywiście pod warunkiem, że klient jest skompilowany ze wsparciem OpenSSL (lub yaSSL, ale nigdy nie testowałem).

Uruchomienie samego serwera (również przy założeniu, że skompilowany ze wsparciem SSL) sprowadza się do podania dodatkowych parametrów w linii komend. Przykładowo może to wyglądać tak:

mysqld --defaults-file=/etc/mysql/my.cnf --ssl --ssl-ca=</path/to/CA.crt> --ssl-cert=</path/to/file.crt> --ssl-key=</path/to/file.key>

Ewentualnie odpowiednie parametry podać w pliku konfiguracyjnym (my.cnf):

ssl-ca = </path/to/CA.crt>
ssl-cert =</path/to/file.crt>
ssl-key =</path/to/file.key>

Pamiętać należy, że w przypadku, gdy certyfikat jest zabezpieczony hasłem, MySQL może być jedynie uruchomiony ręcznie. Korzystając ze skryptów startowych (na pewno w Gentoo Linux) czy nawet wrzucając proces w tło (&) serwer nie skorzysta z certyfikatu i nie nawiążemy szyfrowanej transmisji. Próba takiego uruchomienia zakończy się podobnym wpisem w logach:

[Warning] Failed to setup SSL

Testowe połączenie do serwera z konsoli mysql możemy wykonać podając jedynie ścieżkę do certyfikatu CA (format PEM), np.

mysql -h <host> -u <user> -p --ssl-ca=</path/to/CA.crt> <database>

Wywołując STATUS możemy sprawdzić czy bieżące połączenie ma wsparcie SSL:

SSL:            Cipher in use is DHE-RSA-AES256-SHA

czy też nie:

SSL:            Not in use

Wymuszanie szyfrowanych połączeń możliwe tylko poprzez REQUIRE SSL przy nadawaniu praw użytkownikowi (GRANT). W takim przypadku połączenie bez SSL zakończy się komunikatem podobnym jak w przypadku np. podania błędnego hasła:

ERROR 1045 (28000): Access denied for user 'exg'@'192.168.1.101' (using p assword: YES)

Więcej o używaniu SSL w MySQL można poczytać tutaj:

Wracając do samej funkcji mysql_ssl_set(), to jako parametry przyjmuje: uchwyt połączenia zwrócony przez mysql_init(), ścieżkę do pliku klucza, certyfikatu i certyfikatu CA oraz ścieżkę do katalogu z certyfikatami CA w formacie PEM, a także listę wspieranych algorytmów.
Nieużywane parametry powinny przyjmować wartość NULL.
Funkcja zawsze zwraca 0, o błędach SSL informuje mysql_real_connect().

ERROR 2026 (HY000): SSL connection error

Na koniec łyżka dziegciu – libmysql (i klient mysql) dla Windows (ten z binarnej paczki) nie jest skompilowany z obsługą SSL. Aby korzystać z bezpiecznych połączeń należy pobrać źródła i samodzielnie dokonać kompilacji. Mi się nie udało… Może przez zbyt stare Visual C++.

Analiza log-slow-queries (z życia wzięte…)

sobota, 26 Lipiec 2008

Jedna z tabel klienta wygląda tak:

CREATE TABLE `mix_stats` (
`id` int(11) NOT NULL auto_increment,
`id_produktu` decimal(20,0) NOT NULL default ’0′,
`data` date default NULL,
`count` decimal(1,0) NOT NULL default ’0′,
`session` varchar(255) NOT NULL default ”,
`ip` varchar(20) NOT NULL default ”,
`referer` varchar(255) NOT NULL default ”,
PRIMARY KEY  (`id`)
) TYPE=MyISAM

MySQL 3.23, w logach następujące wpisy:

# Time: 080726 10:40:51
# User@Host: xxx[xxx] @ localhost []
# Query_time: 48  Lock_time: 0  Rows_sent: 1  Rows_examined: 639552
SELECT count(id) FROM mix_stats WHERE id_produktu = ’84092324382e93e’ AND session = ”;

Jeżeli ktoś jest na tyle cierpliwy, by czekać 48 sekund na wynik, to musi mu bardzo zależeć…

Analizę bazy wykonywałem na MySQL 5.0.60, więc mam większe pole do popisu, np.:

mysql> SELECT count(id) FROM mix_stats WHERE id_produktu = '84092324382e93e' AND session = '';
+-----------+
| count(id) |
+-----------+
|         0 |
+-----------+
1 row in set, 65535 warnings (0.99 sec)

mysql> SHOW WARNINGS LIMIT 3;
+---------+------+-----------------------------------------------------+
| Level   | Code | Message                                             |
+---------+------+-----------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '84092324382e93e' |
| Warning | 1292 | Truncated incorrect DOUBLE value: '84092324382e93e' |
| Warning | 1292 | Truncated incorrect DOUBLE value: '84092324382e93e' |
+---------+------+-----------------------------------------------------+
3 rows in set (0.00 sec)

Na początek polecam: DECIMAL. Choć pewnie skończy się przejściem do innego usługodawcy, gdzie mocniejsza maszyna bazodanowa przemieli to szybciej (ostrzeżenia i błędy i tak użytkowników nie interesują)…

MySQL vs. PostgreSQL (adresy IP)

poniedziałek, 21 Lipiec 2008

Jeżeli kiedykolwiek zastanawialiśmy się jak przechowywać i łatwo przetwarzać adresy IP w bazie danych MySQL, odpowiedź brzmi – zmienić bazę na PostgreSQL (i nie tylko w tym przypadku).

MySQL oprócz funkcji INET_ATON() i INET_NTOA() nie oferuje nic więcej, ale to i tak lepsze niż przechowywanie adresów w CHAR(15) czy VARCHAR(15). Wskazany typ kolumny to UNSIGNED INT, a konwersja przy wstawianiu rekordów czy ich pobieraniu właśnie za pomocą wspomnianych funkcji.

PostgreSQL daje nam następujące typy danych: cidr, inet i dodatkowo macaddr (do przechowywania adresów MAC). Umożliwia także łatwe zarządzanie danymi za pomocą operatorów i funkcji.

Oczywiście sposób przechowywania adresu IP na pewno nie powinien być kryterium wyboru silnika baz danych, ale może być drobnym argumentem.

Do przeanalizowania kwestia szybkości przeszukiwania, indeksowania i wielkości danych.

Do poczytania:

Sprawdzanie aktywności użytkowników MySQL

piątek, 18 Lipiec 2008

Podłoże problemu: bazy zakładane na serwerze na żądanie użytkowników / klientów nie były usuwane. Po pewnym czasie ilość baz urosła, z czego spory odsetek była nieużywana. Nie obciąża to serwera, ale zabiera miejsce na dysku i ogólnie powoduje bałagan. Trzeba to jakoś posprzątać!

Prosty sposób – REVOKE i telefon. Mało elegancki! My szukamy czegoś na poziomie ;-)

Od pewnego czasu śledzę projekt mysql-proxy. Wprawdzie już jakiś czas projekt jest nieuaktualniany (At revision 511.), ale mam nadzieję, że Jan Kneschke cały czas pracuje…

Początkowo używałem mysql-proxy jako typowego pośrednika, co było pomocne przy przenoszeniu baz (sporo klientów łączyło się przez UNIX socket). Nie sprawdzał się za dobrze (ostatnia testowana wersja nie zawsze łączyła z PHP, wcześniejsze miały duże problemy ze stabilną pracą w ogóle). Na podobny projekt – pgpool-II (dla PostgreSQL) nie narzekałem nigdy. Niemniej z racji długości życia nie ma ich co porównywać.

Dla śledzenia użytkowników z tematu napisałem skrypt (LUA), który każde połączenie zapisuje do pliku dziennika (można przerobić na zapisywanie do bazy + jakiś licznik). Informacją wystarczającą jest IP klienta, nazwa użytkownika oraz baza, do której się podłączył. Do posprzątania nic więcej nas nie interesuje.
Sposób uruchomienia mysql-proxy opisany jest w dokumentacji, więc warto tam zajrzeć.
Skrypt wygląda tak:

local log_file = ‘/tmp/mysql.log’
local fh = io.open(log_file, „a+”)

function read_auth_result(auth)
if auth.packet:byte() == proxy.MYSQLD_PACKET_OK then
fh:write(
string.format(„%s %s\t%s\t%s\n”,
os.date(‘%Y-%m-%d %H:%M:%S’),
proxy.connection.client.address,
proxy.connection.client.username,
proxy.connection.client.default_db))
end
fh:flush()
end

Ze struktury proxy.connection.client przyda nam się address (adres IP klienta), username (nazwa uzytkownika) oraz default_db (baza, do której zostało wykonane połączenie). Jest jeszcze scrambled_password, ale logowanie tego podpada pod inwigilację. Logujemy tylko uwierzytelnionych użytkowników (MYSQLD_PACKET_OK – 0, ale jest jeszcze możliwość użycia MYSQLD_PACKET_ERR – 255). I w wyniku otrzymujemy:

2008-07-18 11:52:40 127.0.0.1:43228    root    test
2008-07-18 12:00:16 127.0.0.1:62133    exg    exg
2008-07-18 12:00:32 127.0.0.1:62901    root    mysql
2008-07-18 12:07:54 10.74.0.254:11243 guzik
2008-07-18 12:08:00 10.74.0.254:16369 guzik test

Log bez bazy, to połączenie z konsoli. \u czy USE nie loguje się w ogóle.
Po długoterminowym zbieraniu informacji (do miesiąca) mamy już listę wykorzystywanych baz. Pozostaje dump i DROP :>

Pamiętajmy też, że baza widzi połączenie z mysql-proxy, więc należy dać stosowne uprawnienia dla tego hosta.

Więcej do poczytania:

MySQL: multiple tablespaces

środa, 16 Lipiec 2008

Wiadomo nie od dziś, że plik przestrzeni tabel InnoDB rośnie, ale nie maleje. Szczególnie dużym problemem jest to w momencie gdy tworzymy i usuwamy duże tabele. Sposobów radzenia sobie z tym jest kilka (nie wszystkie eleganckie, ale takie znalazłem – ja nie korzystam z żadnego z nich):

  • zmiana silnika wszystkich tabel z InnoDB na MyISAM, zatrzymanie bazy, usunięcie pliku ibdata1 i stworzenie go od nowa, zmiana silnika tabel z MyISAM na InnoDB – życzę powodzenia przy większej ilości tabel,
  • zrzut danych (dump), usunięcie pliku ibdata1 i utworzenie go od nowa, import danych.

Pewnie inne wariacje powyższych też zadziałają podobnie. Niemniej uważam, że downtime jest dość istotny, więc nie sprawdzą się w mocno obciążonym środowisku produkcyjnym.

Więcej o zmaganiach innych można poczytać tutaj:

Ja wybrałem tworzenie osobnego pliku dla każdej tabeli InnoDB. Na pierwszy rzut oka wydaje się, że jest to lek na całe zło InnoDB – usunięcie bazy / tabeli skutkuje usunięciem pliku, szybsze OPTIMIZE dla tabel, łatwiejsze tworzenie ‘zimnych kopii zapasowych’, obejście problemu systemu plików związanego z maksymalną wielkością pliku. Nic tylko używać.

Jeśli chodzi o przygotowanie serwera do tego, to sprowadza się do dodania w sekcji [mysqld] pliku my.cnf następującej linii:

[mysqld]
innodb_file_per_table

Oczywiście wymaga to restartu serwera. Wszystkie nowe tabele będą już miały osobny plik .ibd w katalogu bazy (dane i indeksy w jednym pliku w odróżnieniu od MyISAM – .MYD i MYI, plik z definicją tabel .frm tworzony jest dla obu silników). Dostęp do istniejących tabel zapisanych w ibdata1 jest po zmianie możliwy. Podobnie w sytuacji, gdybyśmy chcieli powrócić do poprzeniej konfiguracji – osobne pliki będą obsługiwane nadal.

Gdybyśmy chcieli dotychczasowe tabele utrzymywać w osobnych plikach, należy wykonać:

ALTER TABLE test ENGINE=InnoDB;

pamiętając, że nadal nie zmniejszy to głównego pliku.

Poza tym wspólna przestrzeń tabel (tablespace) dla InnoDB (plik ibdata1) zawsze będzie istnieć, nawet jak nową bazę uruchomimy z innodb_file_per_table, a wynika to z tego, że trzymane są tam np. undo logs.

Dodatkowo, wg dokumentacji MySQL, plików .ibd nie można przenosić dowolnie pomiędzy katalogami baz tak jak w przypadku MyISAM (jeśli ktoś w ogóle robi coś takiego). Powodem jest przetrzymywanie we wspólnej przestrzeni nazw InnoDB nazwy bazy. Po szczegóły zainteresowanych odsyłam do dokumentacji.

Nie znalazłem jeszcze wad tego rozwiązania. Jak tylko się znajdą, uaktualnię artykuł.

Do poczytania: