Ова страница је машински преведена. Прочитајте енглески оригинал. English

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“:

Code
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:

Code
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.:

Code
Database=mydatabase.fdb
{
  enabled = true
…
}

Pregledajmo najvažnije parametre u konfiguracionoj datoteci:

Include filter parametar

Code
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:

Code
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:

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

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

Code
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:

Code
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:

Code
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:

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

Code
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

Као што видите, у дневнику имамо записе о обе измене - успешној и неуспешној, као и поруку о грешци у вези са мртвом блокадом.

Размотримо како да анализирамо дневник:

У поруци о грешци, пронађимо идентификатор трансакције:

Code
 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

Поред записа о грешци (обично непосредно изнад њега), видећете неуспелу наредбу:

Code
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:

Code
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].

Како избећи мртве блокаде?

Сада треба да идентификујете најбоље решење за вашу апликацију да бисте избегли мртве блокаде - то може бити промена параметара трансакције, или је могуће додати додатну обраду грешака и поновити операцију или извршити додатне провере на пословном нивоу апликације.