background image

Wydawnictwo Helion
ul. Koœciuszki 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

background image

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

background image

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

background image

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

background image

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.

background image

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.

background image

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.

background image

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.

background image

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.

background image

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);

background image

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.

background image

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.

background image

Czytaj dalej...

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

Ŝ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.