Wydawnictwo Helion
ul. Kociuszki 1c
44-100 Gliwice
tel. 032 230 98 63
e-mail: helion@helion.pl
MySQL. Mechanizmy
wewnêtrzne bazy danych
Autor: Sasha Pachev
T³umaczenie: Grzegorz Werner
ISBN: 978-83-246-1232-1
Tytu³ orygina³u:
Understanding MySQL Internals
Format: B5, stron: 240
Poznaj sekrety jednej z najpopularniejszych baz danych
Jak przechowywane s¹ dane?
Jak dodawaæ w³asne zmienne konfiguracyjne?
Jak przebiega proces replikacji?
MySQL to obecnie jedna z najpopularniejszych baz danych. Jedn¹ z jej najwiêkszych
zalet jest nieodp³atny dostêp zarówno do samego systemu, jak i do jego kodu
ród³owego. Mo¿liwoæ przegl¹dania kodu i w razie potrzeby samodzielnego
modyfikowania go mo¿e okazaæ siê przydatna programistom tworz¹cym aplikacje, które
korzystaj¹ z MySQL jako zaplecza bazodanowego. Jednak samodzielne
przegryzanie
siê
przez setki tysiêcy linii kodu i rozpracowywanie mechanizmów dzia³ania bazy
danych mo¿e zaj¹æ mnóstwo czasu.
Dziêki tej ksi¹¿ce poznasz kod ród³owy i sposób dzia³ania tego narzêdzia. Autor, przez
wiele lat pracuj¹cy w zespole tworz¹cym MySQL, przedstawia w niej tajniki systemu.
Podczas czytania poznasz architekturê i wzajemne powi¹zania pomiêdzy komponentami
MySQL, strukturê kodu ród³owego oraz metody modyfikowania go przez kompilacj¹.
Dowiesz siê tak¿e, jak przebiega komunikacja pomiêdzy klientem i serwerem bazy
danych, jak realizowane s¹ zapytania, w jaki sposób sk³adowane s¹ dane i jak
implementowane s¹ mechanizmy replikacji.
Architektura MySQL
Struktura kodu ród³owego
Komunikacja pomiêdzy klientem i serwerem
Zmienne konfiguracyjne
Obs³uga ¿¹dañ
Parser i optymalizator zapytañ
Mechanizmy sk³adowania danych
Replikacja danych
Dziêki tej ksi¹¿ce zrozumiesz budowê bazy danych MySQL i bêdziesz w stanie
samodzielnie dostosowaæ j¹ do ka¿dego zadania
Spis tre
ļci
_
5
Spis tre
ļci
Przedmowa ...............................................................................................................................9
1. Historia i architektura MySQL ..................................................................................... 15
Historia MySQL
15
Architektura MySQL
17
2. Praca z kodem
Śródĥowym MySQL ............................................................................. 31
Powäoka Uniksa
31
BitKeeper
31
Przygotowywanie systemu do budowania MySQL z drzewa BitKeepera
34
Budowanie MySQL z drzewa BitKeepera
35
Budowanie z dystrybucji Ēródäowej
37
Instalowanie MySQL w katalogu systemowym
38
Ukäad katalogów z kodem Ēródäowym
38
Przygotowywanie systemu do uruchomienia MySQL w debugerze
40
Wycieczka po kodzie Ēródäowym w towarzystwie debugera
40
Podstawy pracy z gdb
41
Wyszukiwanie definicji w kodzie Ēródäowym
44
Interesujñce punkty wstrzymania i zmienne
45
Modyfikowanie kodu Ēródäowego
45
Wskazówki dla koderów
47
Aktualizowanie repozytorium BitKeepera
50
Zgäaszanie poprawki
51
3. Podstawowe klasy, struktury, zmienne i interfejsy API ............................................53
THD
53
NET
58
TABLE
58
Field
58
6
_
Spis tre
ļci
Narzödziowe wywoäania API
65
Makra preprocesora
68
Zmienne globalne
70
4. Komunikacja mi
ýdzy klientem a serwerem ...............................................................73
Przeglñd protokoäu
73
Format pakietu
73
Relacje miödzy protokoäem MySQL a warstwñ systemu operacyjnego
74
Uzgadnianie poäñczenia
75
Pakiet polecenia
80
Odpowiedzi serwera
83
5. Zmienne konfiguracyjne .............................................................................................89
Zmienne konfiguracyjne: samouczek
89
Interesujñce aspekty konkretnych zmiennych konfiguracyjnych
96
6. W
étkowa obsĥuga żédaħ ...........................................................................................115
Wñtki kontra procesy
115
Implementacja obsäugi Ĕñdaþ
117
Problemy programowania wñtkowego
121
7. Interfejs mechanizmów sk
ĥadowania .......................................................................127
Klasa handler
127
Dodawanie wäasnego mechanizmu skäadowania do MySQL
142
8. Dost
ýp wspóĥbieżny i blokowanie ............................................................................ 163
MenedĔer blokad tabel
164
9. Parser i optymalizator ............................................................................................... 169
Parser
169
Optymalizator
172
10. Mechanizmy sk
ĥadowania ........................................................................................ 195
Wspólne cechy architektury
196
MyISAM
196
InnoDB
202
Memory (Heap)
204
MyISAM Merge
205
NDB
205
Archive
206
Federated
207
Spis tre
ļci
_
7
11. Transakcje ..................................................................................................................209
Implementowanie transakcyjnego mechanizmu skäadowania
209
Implementowanie podklasy handler
210
Definiowanie handlertona
212
Praca z pamiöciñ podröcznñ zapytaþ
214
Praca z binarnym dziennikiem replikacji
214
Unikanie zakleszczeþ
215
12. Replikacja ....................................................................................................................217
Przeglñd
217
Replikacja oparta na instrukcjach i na wierszach
218
Dwuwñtkowy serwer podrzödny
219
Konfiguracja z wieloma serwerami nadrzödnymi
219
Polecenia SQL uäatwiajñce zrozumienie replikacji
220
Format dziennika binarnego
223
Tworzenie wäasnego narzödzia do replikacji
227
Skorowidz .............................................................................................................................229
115
ROZDZIA
Ĥ 6.
W
étkowa obsĥuga żédaħ
Podczas pisania kodu serwera programista staje przed dylematem: czy obsäugiwaè Ĕñdania
za pomocñ wñtków, czy procesów? Oba podejĈcia majñ swoje zalety i wady. Od samego poczñtku
MySQL korzystaä z wñtków. W tym rozdziale uzasadnimy wñtkowñ obsäugö Ĕñdaþ w serwerze
MySQL, a takĔe omówimy jej wady i zalety oraz implementacjö.
W
étki kontra procesy
Byè moĔe najwaĔniejszñ róĔnicñ miödzy procesem a wñtkiem jest to, Ĕe wñtek potomny wspóä-
dzieli stertö (globalne dane programu) z wñtkiem macierzystym, a proces potomny — nie.
Ma to pewne konsekwencje, które trzeba uwzglödniè podczas wybierania jednego albo drugiego
modelu.
Zalety w
étków
Wñtki sñ implementowane w bibliotekach programistycznych i systemach operacyjnych z nastö-
pujñcych powodów:
x
Zmniejszone wykorzystanie pamiöci. Koszty pamiöciowe zwiñzane z tworzeniem nowego
wñtku sñ ograniczone do stosu oraz do pamiöci ewidencyjnej uĔywanej przez menedĔer
wñtków.
x
Dostöp do globalnych danych serwera bez uĔycia zaawansowanych technik. JeĈli dane mogñ
zostaè zmodyfikowane przez inny dziaäajñcy równolegle wñtek, wystarczy chroniè odpo-
wiedniñ sekcjö za pomocñ blokady ze wzajemnym wykluczaniem, zwanej muteksem (opi-
sywanej w dalszej czöĈci rozdziaäu). JeĈli nie ma takiej moĔliwoĈci, dostöp do globalnych
danych moĔna uzyskiwaè w taki sposób, jakby nie byäo Ĕadnych wñtków.
x
Tworzenie wñtku zajmuje znacznie mniej czasu niĔ tworzenie procesu, poniewaĔ nie trzeba
kopiowaè segmentu sterty, który moĔe byè bardzo duĔy.
x
Program szeregujñcy w jñdrze szybciej przeäñcza konteksty miödzy wñtkami niĔ miödzy
procesami. Dziöki temu w mocno obciñĔonym serwerze procesor ma wiöcej czasu na wyko-
nywanie rzeczywistej pracy.
116
_
Rozdzia
ĥ 6. Wétkowa obsĥuga żédaħ
Wady w
étków
Wñtki odgrywajñ waĔnñ rolö we wspóäczesnych systemach komputerowych, ale majñ równieĔ
pewne wady:
x
Pomyäki programistyczne sñ bardzo kosztowne. Awaria jednego wñtku powoduje zaäama-
nie caäego serwera. Jeden wyrodny wñtek moĔe uszkodziè globalne i zakäóciè dziaäanie
innych wñtków.
x
ãatwo popeäniè pomyäkö. Programista musi stale myĈleè o problemach, jakie moĔe spowo-
dowaè jakiĈ inny wñtek, oraz o tym, jak ich uniknñè. Niezbödna jest bardzo defensywna
postawa.
x
Wielowñtkowe serwery sñ znane z usterek synchronizacyjnych, które sñ trudne do odtwo-
rzenia podczas testów, ale ujawniajñ siö w bardzo zäym momencie w Ĉrodowiskach produk-
cyjnych. Wysokie prawdopodobieþstwo wystöpowania takich usterek jest nastöpstwem
wspóädzielenia przestrzeni adresowej, co znacznie zwiöksza stopieþ interakcji miödzy
wñtkami.
x
W pewnych okolicznoĈciach rywalizacja o blokady moĔe wymknñè siö spod kontroli. JeĈli
zbyt wiele wñtków próbuje jednoczeĈnie pozyskaè ten sam muteks, moĔe to doprowadziè
do nadmiernego przeäñczania kontekstów: procesor przez wiökszoĈè czasu zamiast uĔytecz-
nej pracy wykonuje program szeregujñcy.
x
W systemach 32-bitowych przestrzeþ adresowa procesu jest ograniczona do 4 GB. Ponie-
waĔ wszystkie wñtki wspóädzielñ tö samñ przestrzeþ adresowñ, teoretycznie caäy serwer
ma do dyspozycji 4 GB pamiöci RAM, nawet jeĈli w komputerze zainstalowano znacznie
wiöcej fizycznej pamiöci. W praktyce przestrzeþ adresowa robi siö bardzo zatäoczona przy
znacznie mniejszym limicie, gdzieĈ okoäo 1,5 GB w Linuksie x86.
x
Zatäoczona 32-bitowa przestrzeþ adresowa stwarza jeszcze jeden problem: kaĔdy wñtek
potrzebuje trochö miejsca na stos. Kiedy stos zostaje przydzielony, to nawet jeĈli wñtek ko-
rzysta z niego w minimalnym stopniu, konieczne jest zarezerwowanie czöĈci przestrzeni
adresowej serwera. KaĔdy nowy wñtek ogranicza miejsce, które moĔna przeznaczyè na
stertö. JeĈli wiöc nawet w komputerze jest duĔo fizycznej pamiöci, moĔe siö okazaè, Ĕe nie da
siö jednoczeĈnie zaalokowaè duĔych buforów, uruchomiè wiele wspóäbieĔnych wñtków
oraz zapewniè kaĔdemu z nich duĔo miejsca na stos.
Zalety rozwidlonych procesów
Wady wñtków odpowiadajñ zaletom korzystania z oddzielnych procesów:
x
Pomyäki programistyczne nie sñ tak katastrofalne. Choè niekontrolowany proces moĔe
zakäóciè dziaäanie caäego serwera, jest to znacznie mniej prawdopodobne.
x
Trudniej popeäniè pomyäkö. Przez wiökszoĈè czasu programista moĔe myĈleè tylko o jed-
nym wñtku wykonania, nie martwiñc siö o potencjalnych intruzów.
x
Pojawia siö znacznie mniej fantomowych usterek. Kiedy wystñpi jakaĈ usterka, zwykle
moĔna äatwo jñ odtworzyè. KaĔdy rozwidlony proces ma wäasnñ przestrzeþ adresowñ, wiöc
stopieþ ich wzajemnej interakcji jest znacznie mniejszy.
x
W systemach 32-bitowych ryzyko wyczerpania przestrzeni adresowej jest duĔo mniejsze.
Implementacja obs
ĥugi żédaħ
_
117
Wady rozwidlonych procesów
Aby podsumowaè nasz przeglñd, wymieniö wady rozwidlonych procesów, które sñ lustrzanym
odbiciem zalet wñtków:
x
Wykorzystanie pamiöci jest nieoptymalne. Podczas rozwidlania procesu potomnego kopio-
wane sñ duĔe segmenty pamiöci.
x
Wspóädzielenie danych miödzy procesami wymaga uĔycia specjalnych technik. Utrudnia
to dostöp do globalnych danych serwera.
x
Tworzenie procesu wiñĔe siö z wiökszymi kosztami na poziomie jñdra. KoniecznoĈè kopio-
wania segmentu danych procesu macierzystego znacznie obniĔa wydajnoĈè. Linux jednak
odrobinö tu oszukuje, stosujñc technikö zwanñ kopiowaniem przy zapisie. Rzeczywiste
kopiowanie strony procesu macierzystego zachodzi dopiero wtedy, gdy zostanie ona zmo-
dyfikowana przez proces macierzysty lub potomny. Do tego momentu oba procesy uĔywajñ
jednej strony.
x
Przeäñczanie kontekstów miödzy procesami jest bardziej czasochäonne, poniewaĔ jñdro musi
przeäñczyè strony, tabele deskryptorów plików oraz inne dodatkowe informacje kontekstowe.
Serwer ma mniej czasu na wykonywanie rzeczywistej pracy.
Ogólnie rzecz biorñc, serwer wñtkowy jest idealny wtedy, gdy programy obsäugi poäñczeþ muszñ
wspóädzieliè wiele danych, a programiĈcie nie brakuje umiejötnoĈci. Kiedy trzeba byäo wybraè
model odpowiedni dla MySQL, wybór byä prosty. Serwer baz danych musi mieè wiele wspóä-
uĔytkowanych buforów oraz innych wspóädzielonych danych.
JeĈli chodzi o umiejötnoĈci programistyczne, tych równieĔ nie brakowaäo. Podobnie jak dobry
jeĒdziec staje siö jednoĈciñ ze swoim koniem, tak Monty staä siö jednoĈciñ z komputerem.
Bolaäo go, kiedy widziaä marnotrawienie zasobów systemowych. Byä pewien, Ĕe potrafi napisaè
kod praktycznie pozbawiony usterek, poradziè sobie z problemami wspóäbieĔnoĈci powodo-
wanymi przez wñtki, a nawet pracowaè z maäym stosem. Co za ekscytujñce wyzwanie! Oczy-
wiĈcie, wybraä wñtki.
Implementacja obs
ĥugi żédaħ
Serwer oczekuje na poäñczenia w swoim gäównym wñtku. Po odebraniu poäñczenia przydziela
wñtek do jego obsäugi. W zaleĔnoĈci od konfiguracji i bieĔñcego stanu serwera, wñtek moĔe zostaè
utworzony od zera albo przydzielony z pamiöci podröcznej (puli) wñtków. Klient przesyäa
Ĕñdania, a serwer je realizuje, dopóki klient nie wyda polecenia koþczñcego sesjö (
COM_QUIT
)
albo sesja nie zostanie nieoczekiwanie przerwana. Po zakoþczeniu sesji, w zaleĔnoĈci od konfi-
guracji i bieĔñcego stanu serwera, wñtek moĔe zostaè zakoþczony albo zwrócony do puli wñt-
ków w oczekiwaniu na nastöpne poäñczenie.
Struktury, zmienne, klasy i interfejsy API
JeĈli chodzi o obsäugö wñtków, prawdopodobnie najwaĔniejsza jest klasa
THD
, która reprezen-
tuje deskryptor wñtku. Niemal kaĔda z funkcji parsera i optymalizatora przyjmuje obiekt
THD
jako argument, najczöĈciej pierwszy na liĈcie argumentów. Klasö
THD
opisano szczegóäowo
w rozdziale 3.
118
_
Rozdzia
ĥ 6. Wétkowa obsĥuga żédaħ
Podczas tworzenia wñtku deskryptor jest umieszczany na globalnej liĈcie wñtków
I_List<THD>
threads
(
I_List<>
to szablonowa klasa poäñczonej listy; zob. sql/sql_list.h oraz sql/sql_list.c).
Listy tej uĔywa siö do trzech podstawowych celów:
x
dostarczanie danych na uĔytek polecenia
SHOW PROCESSLIST
;
x
lokalizowanie docelowego wñtku podczas wykonywania polecenia
KILL
;
x
sygnalizowanie wszystkim wñtkom, aby przerwaäy pracö, kiedy serwer jest zamykany.
WaĔnñ rolö odgrywa inna lista
I_List<THD>
:
thread_cache
. Jest ona uĔywana w doĈè nieocze-
kiwany sposób: jako metoda na przekazywanie obiektu
THD
utworzonego przez wñtek gäówny
do wñtku oczekujñcego w puli, który zostaä wyznaczony do obsäugi bieĔñcego Ĕñdania. Wiöcej
informacji moĔna znaleĒè w funkcjach
create_new_thread()
,
start_cached_thread()
oraz
end_thread()
w pliku sql/mysqld.cc.
Wszystkie operacje zwiñzane z tworzeniem, koþczeniem i Ĉledzeniem wñtków sñ chronione
przez muteks
LOCK_thread_count
. Do obsäugi wñtków uĔywa siö trzech zmiennych warunku
POSIX.
CONT_thread_count
pomaga w synchronizacji podczas zamykania serwera, gwarantujñc,
Ĕe wszystkie wñtki dokoþczñ swojñ pracö przed zatrzymaniem wñtku gäównego. Warunek
COND_thread_cache
jest rozgäaszany, kiedy wñtek gäówny postanawia obudziè buforowany
wñtek i skierowaè go do obsäugi bieĔñcej sesji z klientem. Warunek
COND_flush_thread_cache
jest uĔywany przez buforowane wñtki do sygnalizowania, Ĕe zaraz zakoþczñ pracö (podczas
zamykania serwera albo przetwarzania sygnaäu
SIGHUP
).
Ponadto do obsäugi wñtków uĔywa siö kilku globalnych zmiennych stanu. Sñ one opisane
w tabeli 6.1.
Wykonywanie
żédaħ krok po kroku
Pötla realizacji standardowych Ĕñdaþ
select()/accept()
znajduje siö w funkcji
handle_con-
nections_sockets()
w pliku sql/mysqld.cc. Po doĈè skomplikowanej serii testów, które spraw-
dzajñ ewentualne bäödy wywoäania
accept()
na róĔnych platformach, docieramy do poniĔszego
fragmentu kodu:
if (!(thd= new THD))
{
(void) shutdown(new_sock,2);
VOID(closesocket(new_sock));
continue;
}
Tworzy on instancjö
THD
. Po pewnych dodatkowych operacjach na obiekcie
THD
wykonanie
przenosi siö do funkcji
create_new_thread()
w tym samym pliku sql/mysqld.cc. Po przepro-
wadzeniu kilku kolejnych testów oraz inicjalizacji dochodzimy to instrukcji warunkowej, która
ustala, jak zostanie uzyskany wñtek obsäugi Ĕñdania. Istniejñ dwie moĔliwoĈci: uĔyè buforowa-
nego wñtku albo utworzyè nowy.
Kiedy buforowanie wñtków jest wäñczone, stary wñtek po obsäuĔeniu Ĕñdania klienta nie koþ-
czy dziaäania, lecz usypia. Gdy nowy klient nawiñzuje poäñczenie, serwer nie tworzy od razu
nowego wñtku, lecz sprawdza, czy ma jakieĈ uĈpione wñtki w pamiöci podröcznej. JeĈli tak, to
budzi jeden z nich, przekazujñc mu instancjö
THD
jako argument.
Implementacja obs
ĥugi żédaħ
_
119
Tabela 6.1. Zmienne globalne zwi
ñzane z wñtkami
Definicja zmiennej
Opis
int abort_loop
Znacznik, który sygnalizuje w
étkom, że czas po sobie posprzétaë i zakoħczyë pracý. Serwer
nigdy nie wymusza przerwania w
étku, ponieważ mogĥoby to doprowadzië do poważnego
uszkodzenia danych. Ka
żdy wétek jest napisany w taki sposób, aby monitorowaĥ swoje
ļrodowisko i koħczyĥ dziaĥanie, kiedy serwer tego zażéda.
int cached_thread_count
Zmienna stanu
ļledzéca liczbý wétków, które zakoħczyĥy dziaĥanie i oczekujé na przydzielenie
do obs
ĥugi innego żédania. Można jé obejrzeë w wynikach polecenia
SHOW STATUS
pod nag
ĥówkiem
Threads_connected
.
int kill_cached_thread
Znacznik, który sygnalizuje wszystkim buforowanym w
étkom, że powinny zakoħczyë dziaĥanie.
Buforowane w
étki czekajé na warunek
COND_thread_cache
w funkcji
end_thread()
.
Przerywaj
é pracý, kiedy wykrywajé, że ten znacznik jest ustawiony.
int max_connections
Zmienna konfiguracyjna serwera okre
ļlajéca maksymalné liczbý poĥéczeħ nieadministracyjnych,
które serwer mo
że przyjéë. Po osiégniýciu tego limitu administrator bazy danych może nawiézaë
jedno dodatkowe po
ĥéczenie administracyjne, aby jakoļ rozwiézaë kryzys.
Dzi
ýki temu limitowi serwer może „wyhamowaë”, zanim sparaliżuje system przez nadmierne
wykorzystanie zasobów.
Limit ten jest kontrolowany przez zmienn
é konfiguracyjné
max_connections
o domy
ļlnej
warto
ļci 100.
int max_used_connections
Zmienna stanu
ļledzéca maksymalné liczbý jednoczesnych poĥéczeħ odnotowané od czasu
uruchomienia serwera. Jej warto
ļë można obejrzeë w wynikach polecenia
SHOW STATUS
pod nag
ĥówkiem
Max_used_connections
.
int query_id
Zmienna u
żywana do generowania unikatowych identyfikatorów zapytaħ. Każdemu zapytaniu
przes
ĥanemu do serwera przypisuje siý bieżécé wartoļë tej zmiennej, która nastýpnie
jest zwi
ýkszana o 1.
int thread_cache_size
Zmienna konfiguracyjna serwera okre
ļlajéca maksymalné liczbý wétków w pamiýci podrýcznej
w
étków.
int thread_count
Zmienna stanu
ļledzéca bieżécé liczbý wétków. Jej wartoļë można obejrzeë w wynikach
polecenia
SHOW STATUS
pod nag
ĥówkiem
Threads_cached
.
int thread_created
Zmienna stanu
ļledzéca liczbý wétków utworzonych od momentu uruchomienia serwera.
Jej warto
ļë można obejrzeë w wynikach polecenia
SHOW STATUS
pod nag
ĥówkiem
Threads_created
.
int thread_id
Zmienna u
żywana do generowania unikatowych identyfikatorów wétków. Każdemu nowo
utworzonemu w
étkowi przypisuje siý bieżécé wartoļë tej zmiennej, która nastýpnie
jest zwi
ýkszana o 1. Można jé obejrzeë w wynikach polecenia
SHOW STATUS
pod nag
ĥówkiem
Connections
.
int thread_running
Zmienna stanu
ļledzéca liczbý wétków, które obecnie odpowiadajé na zapytanie. Zwiýkszana
o 1 na pocz
étku funkcji
dispatch_command()
w pliku sql/sql_parse.cc i zmniejszana o jeden
na ko
ħcu tej funkcji. Można jé obejrzeë w wynikach polecenia
SHOW STATUS
pod nag
ĥówkiem
Threads_running
.
Choè buforowanie wñtków moĔe znacznie zwiökszyè wydajnoĈè mocno obciñĔonego systemu,
funkcjö tö pierwotnie dodano w celu rozwiñzania pewnych problemów synchronizacji w Linuksie
na platformach Alpha.
JeĈli buforowanie wñtków jest wyäñczone albo Ĕaden buforowany wñtek nie jest dostöpny, w celu
obsäuĔenia Ĕñdania trzeba utworzyè nowy wñtek.
120
_
Rozdzia
ĥ 6. Wétkowa obsĥuga żédaħ
Decyzja jest podejmowana w nastöpujñcym bloku:
if (cached_thread_count > wake_thread)
{
start_cached_thread(thd);
}
Funkcja
start_cached_thread()
z pliku sql/mysqld.cc budzi wñtek, który obecnie nie obsäuguje
Ĕñdania, jeĈli taki wñtek istnieje. Warunek
cached_thread_count > wake_thread
gwarantuje
istnienie uĈpionego wñtku, wiöc funkcja nigdy nie jest wywoäywana, jeĈli nie ma Ĕadnych bufo-
rowanych wñtków. Dotyczy to równieĔ sytuacji, w której pamiöè podröczna wñtków jest
wyäñczona.
JeĈli test dostöpnoĈci buforowanych wñtków zakoþczy siö niepowodzeniem, kod przechodzi
do bloku
else
, gdzie zadanie utworzenia nowego wñtku przypada poniĔszemu wierszowi:
if ((error=pthread_create(&thd->real_id, &connection_attrib,
handle_one_connection,
(void*) thd)))
Nowy wñtek zaczyna siö od funkcji
handle_one_connection()
w pliku sql/sql_parse.cc.
Funkcja
handle_one_connection()
po kilku testach i inicjalizacjach bierze siö do roboty:
while (!net->error && net->vio != 0 && !thd->killed)
{
if (do_command(thd))
break;
}
Polecenia sñ akceptowane i przetwarzane dopóty, dopóki nie wystñpi warunek zakoþczenia
pötli. Oto moĔliwe warunki wyjĈcia:
x
Bäñd sieciowy.
x
Wñtek zostaje usuniöty poleceniem
KILL
przez administratora bazy danych albo przez zamy-
kany serwer.
x
Klient wysyäa Ĕñdanie
COM_QUIT
, informujñc serwer, Ĕe chce zakoþczyè sesjö. W takim przy-
padku funkcja
do_command()
z pliku sql/sql_parse.cc zwraca wartoĈè niezerowñ.
x
Funkcja
do_command()
zwraca wartoĈè niezerowñ z jakiejĈ innej przyczyny. Obecnie jedynñ
innñ moĔliwoĈciñ jest to, Ĕe nadrzödny serwer replikacji postanawia przerwaè przesyäanie
strumienia aktualizacji, którego serwer podrzödny (albo klient podszywajñcy siö pod ser-
wer podrzödny) zaĔñdaä poleceniem
COM_BINLOG_DUMP
.
Nastöpnie funkcja
handle_one_connection()
przechodzi do fazy koþczenia wñtku i porzñd-
kowania. Kluczowym elementem tego segmentu kodu jest wywoäanie funkcji
end_thread()
z pliku sql/mysqld.cc.
Funkcja
end_thread()
zaczyna od pewnych dodatkowych czynnoĈci porzñdkowych, a nastöpnie
dociera do interesujñcego punktu: moĔliwoĈci umieszczenia obecnie wykonywanego wñtku
w pamiöci podröcznej. Decyzja jest podejmowana przez nastöpujñcñ instrukcjö warunkowñ:
if (put_in_cache && cached_thread_count < thread_cache_size &&
! abort_loop && !kill_cached_threads)
JeĈli funkcja
end_thread()
postanowi zbuforowaè wñtek, wykonywana jest poniĔsza pötla:
while (!abort_loop && ! wake_thread && ! kill_cached_threads)
(void) pthread_cond_wait(&COND_thread_cache, &LOCK_thread_count);
Problemy programowania w
étkowego
_
121
Pötla czeka, aĔ wñtek zostanie obudzony przez wywoäanie
start_cached_thread()
, procedurö
obsäugi sygnaäu
SIGHUP
albo procedurö zamykania serwera. JeĈli sygnaä budzenia pochodzi
od funkcji
start_cached_thread()
, parametr
wake_thread
ma wartoĈè niezerowñ. W takim
przypadku kod pobiera obiekt
THD
przekazany przez
start_cached_thread()
z listy
thread_
cache
, a nastöpnie wraca (zwróèmy uwagö na makro
DBUG_VOID_RETURN
) do funkcji
handle_one_
connection()
, aby zaczñè obsäugiwanie nowego klienta.
JeĈli wñtek nie zostanie przeniesiony do pamiöci podröcznej, ostatecznie koþczy dziaäanie przez
wywoäanie
pthread_exit()
.
Problemy programowania w
étkowego
W MySQL wystöpujñ podobne komplikacje co w innych programach, które uĔywajñ wñtków.
Wywo
ĥania standardowej biblioteki C
Podczas pisania kodu, który moĔe byè wykonywany przez kilka wñtków jednoczeĈnie, trzeba
zachowaè szczególnñ ostroĔnoĈè, jeĈli chodzi o wywoäywanie funkcji z zewnötrznych bibliotek.
Zawsze istnieje pewne prawdopodobieþstwo, Ĕe wywoäany kod uĔywa zmiennej globalnej,
pisze we wspóädzielonym deskryptorze pliku albo uĔywa jakiegoĈ innego wspólnego zasobu,
nie gwarantujñc wzajemnego wykluczania. W takim przypadku trzeba zabezpieczyè wywoäanie
za pomocñ muteksu.
Trzeba zachowaè ostroĔnoĈè, a jednoczeĈnie unikaè nadmiernej ochrony, która moĔe spowo-
dowaè spadek wydajnoĈci. Na przykäad moĔna oczekiwaè, Ĕe wywoäanie
malloc()
jest bezpieczne
dla wñtków. Inne funkcje, takie jak
gethostbyname()
, czösto majñ odpowiedniki bezpieczne dla
wñtków. Skrypty konfigurujñce kompilacjö MySQL sprawdzajñ, czy sñ one dostöpne i uĔywajñ
ich, kiedy tylko jest to moĔliwe. JeĈli odpowiednik bezpieczny dla wñtków nie zostanie wykryty,
w ostatecznoĈci wäñczany jest ochronny muteks.
Ogólnie rzecz biorñc, MySQL oszczödza sobie wielu zmartwieþ zwiñzanych z bezpieczeþstwem
wñtków, implementujñc odpowiedniki wywoäaþ standardowej biblioteki C w warstwie przeno-
ĈnoĈci w mysys oraz w bibliotece äaþcuchów w strings. Nawet jeĈli ostatecznie wywoäywana
jest biblioteka C, to w wiökszoĈci przypadków odbywa siö to za poĈrednictwem nakäadki. JeĈli
w jakimĈ systemie okazuje siö, Ĕe wywoäanie nie jest bezpieczne dla wñtków, moĔna äatwo roz-
wiñzaè problem przez dodanie muteksu do nakäadki.
Blokady z wzajemnym wykluczaniem (muteksy)
W serwerze wñtkowym kilka wñtków moĔe mieè dostöp do wspóädzielonych danych. W takim
przypadku kaĔdy wñtek musi zagwarantowaè, Ĕe bödzie miaä dostöp na wyäñcznoĈè. W tym
celu stosuje siö blokady z wzajemnym wykluczaniem, zwane teĔ muteksami.
W miarö jak zwiöksza siö zäoĔonoĈè aplikacji, trzeba zdecydowaè, ilu muteksów uĔyè i jakie
dane powinny byè chronione przez kaĔdy z nich. Jednñ skrajnoĈciñ jest utworzenie oddzielnego
muteksu dla kaĔdej zmiennej. Ma to tö zaletö, Ĕe rywalizacja o muteksy jest ograniczona do
minimum. Sñ równieĔ pewne wady: co siö stanie, jeĈli trzeba bödzie uzyskaè dostöp do grupy
zmiennych w sposób atomowy? Konieczne bödzie oddzielne pozyskanie kaĔdego muteksu.
122
_
Rozdzia
ĥ 6. Wétkowa obsĥuga żédaħ
W takim przypadku trzeba zawsze pozyskiwaè je w tej samej kolejnoĈci, aby uniknñè zaklesz-
czeþ. Czöste wywoäania funkcji
pthread_mutex_lock()
i
pthread_mutex_unlock()
doprowa-
dzñ do spadku wydajnoĈci, a programista prödzej czy póĒniej pomyli kolejnoĈè wywoäaþ i spo-
woduje zakleszczenie.
Na drugim koþcu spektrum znajduje siö jeden muteks dla wszystkich zmiennych. Upraszcza
to pracö programisty — wystarczy zaäoĔyè blokadö podczas dostöpu do zmiennej globalnej,
a póĒniej jñ zwolniè. Niestety, ma to bardzo negatywny wpäyw na wydajnoĈè. Wiele wñtków
musi niepotrzebnie czekaè, kiedy jeden z nich uzyskuje dostöp do zmiennej, która nie musi
byè chroniona przed innymi.
Rozwiñzaniem jest odpowiednie pogrupowanie zmiennych globalnych i utworzenie muteksu
dla kaĔdej grupy. WäaĈnie w ten sposób postñpili twórcy MySQL.
W tabeli 6.2 znajduje siö lista globalnych muteksów MySQL wraz z opisami grup zmiennych,
które sñ przez nie chronione.
Tabela 6.2. Globalne muteksy
Nazwa muteksu
Opis muteksu
LOCK_Acl
Inicjalizowany, ale obecnie nieu
żywany w kodzie. W przyszĥoļci może zostaë usuniýty.
LOCK_active_mi
Chroni wska
Śnik
active_mi
, który wskazuje deskryptor aktywnego podrz
ýdnego serwera
replikacji. W tym momencie ochrona jest zb
ýdna, ponieważ wartoļë
active_mi
nigdy
nie jest zmieniana wspó
ĥbieżnie. Ochrona stanie siý jednak konieczna, kiedy do serwera
zostanie dodana obs
ĥuga wielu serwerów nadrzýdnych.
LOCK_bytes_received
Chroni zmienn
é stanu
bytes_received
, która
ļledzi liczbý bajtów odebranych
od wszystkich klientów od momentu uruchomienia serwera. Nieu
żywana w wersji 5.0
i nowszych.
LOCK_bytes_sent
Chroni zmienn
é stanu
bytes_sent
, która
ļledzi liczbý bajtów wysĥanych do wszystkich
klientów od momentu uruchomienia serwera. Nieu
żywana w wersji 5.0 i nowszych.
LOCK_crypt
Chroni wywo
ĥanie uniksowej biblioteki C
crypt()
, które nie jest bezpieczne dla w
étków.
LOCK_delayed_create
Chroni zmienne i struktury zaanga
żowane w tworzenie wétku do obsĥugi opóŚnionego
wstawiania. Opó
Śnione operacje wstawiania natychmiast wracajé do klienta, nawet
je
ļli tablica jest zablokowana — w takim przypadku sé przetwarzane w tle przez wétek
opó
Śnionego wstawiania.
LOCK_delayed_insert
Chroni list
ý wétków opóŚnionego wstawiania
I_List<delayed_insert>
delayed_threads
.
LOCK_delayed_status
Chroni zmienne stanu
ļledzéce operacje opóŚnionego wstawiania.
LOCK_error_log
Chroni zapisy w dzienniku b
ĥýdów.
LOCK_gethostbyname_r
Chroni wywo
ĥanie
gethostbyname()
w funkcji
my_gethostbyname_r()
w pliku
mysys/my_gethostbyname.c w systemach, w których biblioteka C nie oferuje wywo
ĥania
gethostbyname_r()
.
LOCK_global_system_variables
Chroni operacje modyfikuj
éce globalne zmienne konfiguracyjne z poziomu wétku
klienckiego.
LOCK_localtime_r
Chroni wywo
ĥanie
localtime()
w funkcji
my_localtime_r()
w pliku
mysys/my_pthread.c w systemach, w których biblioteka C nie oferuje wywo
ĥania
localtime_r()
.
LOCK_manager
Chroni struktury danych u
żywane przez wétek menedżera, który obecnie jest odpowiedzialny
za okresowe wymuszanie zapisu tabel na dysku (je
ļli ustawienie
flush_time
jest
niezerowe) oraz za porz
édkowanie dzienników Berkeley DB.
Problemy programowania w
étkowego
_ 123
Tabela 6.2. Globalne muteksy — ci
ñg dalszy
Nazwa muteksu
Opis muteksu
LOCK_mapped_file
Chroni struktury danych i zmienne u
żywane do operacji na plikach odwzorowanych
w pami
ýci. Obecnie funkcja ta jest wewnýtrznie obsĥugiwana, ale nie jest używana
w
żadnej czýļci kodu.
LOCK_open
Chroni struktury danych i zmienne zwi
ézane z pamiýcié podrýczné tabel oraz z otwieraniem
i zamykaniem tabel.
LOCK_rpl_status
Chroni zmienn
é
rpl_status
, która mia
ĥa byë używana do bezpiecznej replikacji
z automatycznym przywracaniem danych. Obecnie jest to martwy kod.
LOCK_status
Chroni zmienne wy
ļwietlane w wynikach polecenia
SHOW STATUS
.
LOCK_thread_count
Chroni zmienne i struktury danych zaanga
żowane w tworzenie lub niszczenie wétków.
LOCK_uuid_generator
Chroni zmienne i struktury danych u
żywane przez funkcjý SQL
UUID()
.
THR_LOCK_charset
Chroni zmienne i struktury danych zwi
ézane z operacjami na zestawie znaków.
THR_LOCK_heap
Chroni zmienne i struktury danych zwi
ézane z pamiýciowym mechanizmem skĥadowania
(MEMORY).
THR_LOCK_isam
Chroni zmienne i struktury danych zwi
ézane z mechanizmem skĥadowania ISAM.
THR_LOCK_lock
Chroni zmienne i struktury danych zwi
ézane z menedżerem blokad tabel.
THR_LOCK_malloc
Chroni zmienne i struktury danych zwi
ézane z nakĥadkami na rodziný wywoĥaħ
malloc()
. U
żywany gĥównie w wersji
malloc()
przeznaczonej do debugowania
(zob. mysys/safemalloc.c).
THR_LOCK_myisam
Chroni zmienne i struktury danych zwi
ézane z mechanizmem skĥadowania MyISAM.
THR_LOCK_net
Obecnie u
żywany do ochrony wywoĥania
inet_ntoa()
w funkcji
my_inet_ntoa()
w pliku mysys/my_net.c
THR_LOCK_open
Chroni zmienne i struktury danych, które
ļledzé otwarte pliki.
Oprócz muteksów globalnych istnieje kilka muteksów osadzonych w strukturach lub klasach,
które säuĔñ do ochrony czöĈci danej struktury lub klasy. Istnieje wreszcie kilka muteksów glo-
balnych o zasiögu plikowym (
static
) w bibliotece mysys.
Blokady odczytu-zapisu
Blokada na wyäñcznoĈè nie zawsze jest najlepszym rozwiñzaniem ochrony operacji wspóäbieĔ-
nych. WyobraĒmy sobie sytuacjö, w której pewna zmienna rzadko jest modyfikowana tylko
przez jeden wñtek, natomiast czösto czytana przez wiele innych. GdybyĈmy uĔyli muteksu,
zwykle jeden wñtek czytajñcy musiaäby czekaè, aĔ inny zakoþczy czytanie, choè mogäyby one
wykonywaè siö wspóäbieĔnie.
W takich sytuacjach lepiej sprawdza siö inny typ blokady: blokada odczytu-zapisu. Blokady
odczytu mogñ byè wspóädzielone, a blokady zapisu wzajemnie siö wykluczajñ. Zatem wiele
wñtków czytajñcych moĔe dziaäaè wspóäbieĔnie, pod warunkiem Ĕe nie ma wñtku piszñcego.
Jak widaè, blokada odczytu-zapisu moĔe robiè to samo co muteks i wiöcej. Czemu wiöc nie uĔy-
waè tylko blokad odczytu-zapisu? Jak mówi przysäowie, nie ma nic za darmo. Dodatkowe
funkcje wymagajñ bardziej zäoĔonej implementacji. W rezultacie blokady odczytu-zapisu zaj-
mujñ wiöcej cykli procesora, nawet gdy blokada zostanie pozyskana natychmiast.