Како пратити мртве блокаде у Firebird-у
Alexey Kovyazin, 08.05.2019, (c) IBSurgeon
Konflikti ažuriranja (često nazvani „deadlock-ovi“) javljaju se u Firebird aplikacijama koje vrše intenzivna konkurentna ažuriranja podataka.
Kao što možete videti iz članka „ Transakcije u Firebird-u“, postoje 3 tipa grešaka koje sadrže ključnu reč „deadlock“:
deadlock
-update conflicts with concurrent update
-concurrent transaction number is NNN
lock time-out on wait transaction
-deadlock
-update conflicts with concurrent update
-concurrent transaction number is NNN
lock conflict on no wait transaction
-deadlock
-update conflicts with concurrent update
-concurrent transaction number is NNN
Svim 3 tipa konflikata ažuriranja zajednička je činjenica da je konflikt ažuriranja obično rezultat 2 operacije modifikacije koje pokušavaju da modifikuju isti zapis.
Moguće opcije su sledeće: UPDATE, DELETE SELECT WITH LOCK, MERGE, UPDATE OR INSERT.
Relativno je lako pratiti „žrtvovanu“ operaciju koja prima poruku o grešci - samo je stavite u try… catch i zabeležite parametre korišćene u upitu koji puca na grešci.
Međutim, ovim pristupom je teško pratiti „pobednika“ - tj. konkurentnu transakciju u kojoj je ažuriranje uspešno obavljeno, bez greške.
Da bi pomogao programerima da istraže konflikte ažuriranja, Firebird u poruke o greškama stavlja referencu na konkurentnu transakciju - tj. transakciju u kojoj konkurentno ažuriranje još nije potvrđeno. Zajedno sa Trace API-jem, to nam daje mogućnost da pratimo obe konfliktne operacije.
Razmotrimo praktične korake kako to uraditi.
Prvo, moramo konfigurisati praćenje. Za ovo, kreirajte tekstualnu datoteku (u našem slučaju to je C:\Temp\mytrace.conf) sa sledećim sadržajem:
database
{
enabled = true
include_filter="(UPDATE%)|(DELETE%)|(MERGE%)|(SELECT%FROM%WITH LOCK)|(UPDATE OR INSERT%)"
log_statement_finish = true
log_errors = true
include_gds_codes="deadlock"
log_initfini = false
time_threshold = 0
max_sql_length = 65000
}
Konfiguraciona datoteka iznad omogućava praćenje za sve baze podataka, pa ako imate više od 1 aktivne baze, bolje je navesti ime baze u konfiguracionoj datoteci, tj.:
Database=mydatabase.fdb
{
enabled = true
…
}
Pregledajmo najvažnije parametre u konfiguracionoj datoteci:
Include filter parametar
include_filter="(UPDATE%)|(DELETE%)|(MERGE%)|(SELECT%FROM%WITH LOCK)|(UPDATE OR INSERT%)"
To znači da želimo da pratimo samo UPDATE i DELETE naredbe i ignorišemo SELECT-e. Neophodno je filtrirati što više naredbi, jer u produkcijskom sistemu pod visokim opterećenjem količina zapisa u tekstualnom dnevniku može biti prevelika za analizu.
Obično znamo koja tabela učestvuje u konfliktu ažuriranja, dobra ideja je da je stavimo u filter, da smanjimo broj ažuriranja za analizu:
include_filter="(UPDATE T1%)|(DELETE FROM T1%)|(MERGE INTO T1%)|(SELECT% FROM T1%WITH lock%)|(UPDATE OR INSERT INTO T1%)"
Šta ako se ažuriranja i brisanja vrše preko uskladištenih procedura? Pa, to čini ceo proces težim: dodajte u filter sve uskladištene procedure koje mogu biti uključene u konkurentna ažuriranja, i takođe omogućite beleženje procedura:
include_filter="(UPDATE T1%)|(DELETE FROM T1%)|(MERGE INTO T1%)|(SELECT% FROM T1%with lock%)|(UPDATE OR INSERT INTO T1%)|(%SP_UPDATE_T1%)|(%SP_DEL_T1%)"
log_procedures=true
Parametri beleženja grešaka
Zatim, moramo specificirati beleženje grešaka i ograničiti ga na deadlock-ove.
log_errors = true
include_gds_codes="deadlock"
Napomena: parametar include_gds_codes se pojavio tek u Firebird 3.0.2, pa ako koristite 2.5 ili 3.0.0 ili 3.0.1, nije moguće filtrirati samo deadlock greške, pa će sve greške biti odštampane u dnevnik.
Demonstracija
Da demonstriramo ceo proces, pripremimo test bazu podataka - radi jednostavnosti, biće 1 tabela sa 1 primarnom kolonom.
C:\HQbird\Firebird30>isql
Use CONNECT or CREATE DATABASE to specify a database
SQL> create database "c:\temp\testdeadlock.fdb" user "SYSDBA" password "masterkey";
SQL> create table t1(i1 integer not null primary key);
SQL> insert into t1(i1) values(1);
SQL> commit; exit;
Nakon toga, moramo pokrenuti trace sesiju:
fbtracemgr.exe -se localhost:service_mgr -start -conf "C:\temp\mytrace.conf" -user SYSDBA -pass masterkey
U ovom slučaju, fbtracemgr štampa dnevnik na ekran, ali u produkciji, naravno, moramo ga preusmeriti u datoteku.
Nakon uspešnog pokretanja, fbtracemgr štampa nešto ovako:
Trace session ID 4 started
Zatim, pokrenimo 2 isql sesije i pokušajmo da simuliramo konflikt ažuriranja. U tabeli ispod imamo 2 kolone za 2 isql sesije. Svaki red predstavlja trenutak u vremenu kada su komande izvršene.
| Vreme | isql sesija 1 | isql sesija 2 |
| T1 | <br>isql -user SYSDBA -pass masterkey localhost:c:\temp\testdeadlock.fdb<br>Database: localhost:c:\temp\testdeadlock.fdb, User: SYSDBA<br>SQL> set transaction wait;<br>Commit current transaction (y/n)?y<br>Committing.<br>SQL> select current_transaction from rdb$database;<br> CURRENT_TRANSACTION<br>=====================<br> 15<br>SQL> select * from t1;<br> I1<br>============<br> 2<br>SQL><br> |
|
| T2 | <br>isql -user SYSDBA -pass masterkey localhost:c:\temp\testdeadlock.fdb<br>Database: localhost:c:\temp\testdeadlock.fdb, User: SYSDBA<br>SQL> set transaction wait;<br>Commit current transaction (y/n)?y<br>Committing.<br>SQL> select current_transaction from rdb$database;<br> CURRENT_TRANSACTION<br>=====================<br> 20<br>SQL> select * from t1;<br> I1<br>============<br> 2<br>SQL><br> |
|
| Dakle, na početnoj tački, vidimo da su pokrenute 2 transakcije, #15 i #20. Zatim, pokušajmo da izvršimo konkurentno ažuriranje istog zapisa: | ||
| T3 | <br>SQL> UPDATE T1 SET i1=5 where i1=2;<br> |
|
| T4 | <br>SQL> UPDATE T1 SET i1=100 where i1=2;<br> |
|
| T5 | <br>SQL> commit;<br>SQL><br> |
<br>Statement failed, SQLSTATE = 40001<br>deadlock<br>-update conflicts with concurrent update<br>-concurrent transaction number is 15<br>SQL><br> |
Pokrenuli smo UPDATE u sesiji 1 ali ga nismo potvrdili.
U sesiji 2, UPDATE se „zakačio“ - čekao je na kraj konkurentne transakcije, i kada smo potvrdili UPDATE u sesiji 1, sesija 2 je primila grešku:
Statement failed, SQLSTATE = 40001
deadlock
-update conflicts with concurrent update
-concurrent transaction number is 15
SQL>
Dakle, imamo tipičnu situaciju u slučaju konflikta ažuriranja sa wait transakcijom.
Pregledajmo trace dnevnik da pronađemo dokaze o konfliktu ažuriranja.
U ovom slučaju, trace dnevnik je veoma kratak, ali u slučaju produkcijskog sistema, može biti mnogo duži, ali pristup pronalaženju deadlock-ova je isti.
fbtracemgr.exe -se localhost:service_mgr -start -conf "C:\temp\fbtrace.conf" -user SYSDBA -pass masterkey
Trace session ID 4 started
2019-05-07T11:28:43.9850 (2692:00000000018C1940) EXECUTE_STATEMENT_FINISH
C:\TEMP\TESTDEADLOCK.FDB (ATT_12, SYSDBA:NONE, NONE, TCPv6:::1/52938)
C:\HQbird\Firebird30\isql.exe:8828
(TRA_15, CONCURRENCY | WAIT | READ_WRITE)
Statement 52:
-------------------------------------------------------------------------------
UPDATE T1 SET i1=5 where i1=2
0 records fetched
0 ms, 2 read(s), 21 fetch(es), 3 mark(s)
2019-05-07T11:29:45.0190 (2692:00000000018C0040) FAILED EXECUTE_STATEMENT_FINISH
C:\TEMP\TESTDEADLOCK.FDB (ATT_13, SYSDBA:NONE, NONE, TCPv6:::1/52968)
C:\HQbird\Firebird30\isql.exe:7032
(TRA_20, CONCURRENCY | WAIT | READ_WRITE)
Statement 55:
-------------------------------------------------------------------------------
UPDATE T1 SET i1=100 where i1=2
0 records fetched
40242 ms, 19 fetch(es), 2 mark(s)
2019-05-07T11:29:45.0190 (2692:00000000018C0040) ERROR AT JStatement::execute
C:\TEMP\TESTDEADLOCK.FDB (ATT_13, SYSDBA:NONE, NONE, TCPv6:::1/52968)
C:\HQbird\Firebird30\isql.exe:7032
335544336 : deadlock
335544451 : update conflicts with concurrent update
335544878 : concurrent transaction number is 15
Као што видите, у дневнику имамо записе о обе измене - успешној и неуспешној, као и поруку о грешци у вези са мртвом блокадом.
Размотримо како да анализирамо дневник:
У поруци о грешци, пронађимо идентификатор трансакције:
2019-05-07T11:29:45.0190 (2692:00000000018C0040) ERROR AT JStatement::execute
C:\TEMP\TESTDEADLOCK.FDB (ATT_13, SYSDBA:NONE, NONE, TCPv6:::1/52968)
C:\HQbird\Firebird30\isql.exe:7032
335544336 : deadlock
335544451 : update conflicts with concurrent update
335544878 : concurrent transaction number is 15
Поред записа о грешци (обично непосредно изнад њега), видећете неуспелу наредбу:
2019-05-07T11:29:45.0190 (2692:00000000018C0040) FAILED EXECUTE_STATEMENT_FINISH
C:\TEMP\TESTDEADLOCK.FDB (ATT_13, SYSDBA:NONE, NONE, TCPv6:::1/52968)
C:\HQbird\Firebird30\isql.exe:7032
(TRA_20, CONCURRENCY | WAIT | READ_WRITE)
Statement 55:
-------------------------------------------------------------------------------
UPDATE T1 SET i1=100 where i1=2
0 records fetched
40242 ms, 19 fetch(es), 2 mark(s)
Приметите да је време извршавања огромно (~40 секунди) - то указује на време када је операција чекала резултат конкурентне трансакције.
И, на крају, потражите број трансакције са успешном UPDATE операцијом: за то, пронађите TRA_NNN, где је NNN број трансакције. У нашем случају, то ће бити TRA_15:
2019-05-07T11:28:43.9850 (2692:00000000018C1940) EXECUTE_STATEMENT_FINISH
C:\TEMP\TESTDEADLOCK.FDB (ATT_12, SYSDBA:NONE, NONE, TCPv6:::1/52938)
C:\HQbird\Firebird30\isql.exe:8828
(TRA_15, CONCURRENCY | WAIT | READ_WRITE)
Statement 52:
-------------------------------------------------------------------------------
UPDATE T1 SET i1=5 where i1=2
0 records fetched
0 ms, 2 read(s), 21 fetch(es), 3 mark(s)
Дакле, то је то - пронашли смо обе стране конфликта приликом измене - успешну и неуспешну.
Алати за идентификацију мртвих блокада
Као што видите, може бити прилично тешко идентификовати основни узрок конфликта закључавања/мртве блокаде: пошто конфликт увек има две стране, а једна страна „побеђује“ (завршава без порука о грешци), може бити незгодно идентификовати победника. На пример, конфликт се може десити унутар EXECUTE STATEMENT, или у угњежденој ускладиштеној процедури, или унутар окидача, или може бити комбинација свих ових ствари.
Да би идентификовао мртве блокаде, IBSurgeon је развио алат „Deadlock Analyzer“, који анализира дневник праћења и креира лако разумљиве дијаграме са детаљима позива који су довели до мртвих блокада. Овај алат је доступан као део Enterprise Subscription (има пробну верзију) - региструјте се на cc.ib-aid.com.
Такође, HQbird представља проширену верзију fbtrace додатка, који се може конфигурисати да чува само измене и покушаје измена (тј. неуспеле операције и конфликте закључавања и мртве блокаде).
Контактирајте IBSurgeon подршку за више информација о томе: [email protected].
Како избећи мртве блокаде?
Сада треба да идентификујете најбоље решење за вашу апликацију да бисте избегли мртве блокаде - то може бити промена параметара трансакције, или је могуће додати додатну обраду грешака и поновити операцију или извршити додатне провере на пословном нивоу апликације.