Changes between Initial Version and Version 1 of Phase2_Transactions_Concurrency


Ignore:
Timestamp:
03/12/26 23:23:13 (9 days ago)
Author:
226052
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Phase2_Transactions_Concurrency

    v1 v1  
     1= Трансакции, конкурентно извршување и заклучување на ресурсите
     2
     3== Трансакции
     4
     5Трансакцијата претставува логичка единица на работа составена од една или повеќе SQL операции кои мора да се извршат како целина. Тоа значи дека или сите операции во трансакцијата ќе се извршат успешно, или во случај на грешка сите направени промени ќе бидат поништени. На овој начин се избегнуваат ситуации во кои базата на податоци би останала во неконзистентна состојба. Во PostgreSQL, трансакцијата се отвора со BEGIN и се затвора со COMMIT или ROLLBACK. Покрај тоа, PostgreSQL поддржува и SAVEPOINT точка внатре во трансакцијата до која може да се врати со ROLLBACK TO SAVEPOINT, без да се откаже целата трансакција.
     6Во практични системи, трансакциите се користат секогаш кога повеќе операции се логички поврзани и треба да се третираат како една целина.
     7
     8Во системот Film Rental, на пример, процесот на изнајмување филм од страна на клиент не се состои од само една операција. При изнајмување потребно е да се креира нов запис во табелата rental, да се евидентира плаќањето во табелата payment, како и да се ажурира состојбата на копијата на филмот во табелата inventory. Доколку некоја од овие операции не успее, целиот процес треба да се поништи за да се избегнат неконзистентни податоци, како на пример да постои плаќање без регистрирано изнајмување. Поради ова, сите овие операции се извршуваат во рамките на една трансакција.
     9
     10
     11{{{
     12BEGIN;
     13INSERT INTO rental (rental_date, inventory_id, customer_id, staff_id)
     14VALUES (now(), 42, 101, 1);
     15SAVEPOINT after_rental;
     16INSERT INTO payment (customer_id, staff_id, rental_id, amount, payment_date)
     17VALUES (101, 1, (SELECT rental_id FROM rental WHERE customer_id = 101 ORDER BY rental_date DESC LIMIT 1), 4.99, now());
     18-- Ако уплатата не е точна се враќаме само до savepoint-от
     19-- изнајмувањето останува само уплатата се откажува
     20ROLLBACK TO SAVEPOINT after_rental;
     21-- Се внесува исправна уплата
     22INSERT INTO payment (customer_id, staff_id, rental_id, amount, payment_date)
     23VALUES (101, 1, (SELECT rental_id FROM rental WHERE customer_id = 101 ORDER BY rental_date DESC LIMIT 1), 2.99, now());
     24COMMIT;
     25}}}
     26
     27
     28Системите за управување со бази на податоци како PostgreSQL обезбедуваат сигурно извршување на трансакциите преку таканаречените ACID својства атомичност, конзистентност, изолација и трајност. Овие својства гарантираат дека трансакциите се извршуваат безбедно, дури и во услови кога повеќе корисници истовремено работат со истите податоци или кога се појавуваат системски грешки.
     29
     30== Аномалии при конкурентно извршување
     31
     32Кога повеќе трансакции се извршуваат истовремено врз истите податоци, може да се појават различни проблеми кои доведуваат до неконзистентни или неочекувани резултати. Овие проблеми се познати како аномалии при конкурентно извршување. Аномалиите се јавуваат кога трансакциите читаат или модифицираат податоци кои во исто време се менуваат од други трансакции.
     33
     34=== Dirty Read
     35Dirty Read се појавува кога една трансакција чита податоци кои се изменети од друга трансакција, но тие промени сè уште не се потврдени (COMMIT). Доколку втората трансакција подоцна изврши ROLLBACK, првата трансакција ќе има прочитано податоци кои всушност никогаш не требало да постојат во базата.
     36
     37=== Non-repeatable Read
     38Non-repeatable Read се појавува кога една трансакција чита одреден ред, а друга трансакција го изменува истиот ред и ја потврдува промената. Ако првата трансакција повторно го прочита истиот ред, ќе добие различна вредност, иако самата не направила никаква промена.
     39
     40=== Phantom Read
     41Phantom Read се појавува кога една трансакција извршува пребарување кое враќа одреден број редови, а друга трансакција во меѓувреме додава нов ред кој ги исполнува условите на пребарувањето.
     42
     43=== Serialization Anomaly
     44Serialization anomaly се појавува кога повеќе трансакции се извршуваат паралелно и нивниот комбиниран ефект доведува до резултат кој не би бил можен доколку трансакциите се извршеле една по една (сериски).
     45
     46За да се контролира ова однесување, системите за управување со бази на податоци користат различни изолациски нивоа кои одредуваат колку трансакциите се изолирани една од друга.
     47
     48== Изолациски нивоа
     49
     50Изолациските нивоа одредуваат како една трансакција ги гледа промените направени од други трансакции кои се извршуваат паралелно. Со користење на различни изолациски нивоа се контролира кои аномалии можат да се појават при конкурентно извршување.
     51PostgreSQL ги поддржува следните изолациски нивоа:
     52* Read Committed
     53* Repeatable Read
     54* Serializable
     55Секое од овие нивоа обезбедува различен степен на изолација помеѓу трансакциите.
     56
     57Read Committed
     58Read Committed е стандардното изолациско ниво во PostgreSQL. Кај ова ниво, една трансакција може да чита само податоци кои се веќе потврдени (COMMIT) од други трансакции.
     59Секоја SELECT наредба гледа најнова верзија на податоците во моментот кога се извршува таа наредба.
     60Поради ова, во Read Committed може да се појави '''Non-repeatable Read'''.
     61
     62'''Сценарио''': Вработен ја проверува уплатата за одредено изнајмување, но во меѓувреме друг вработен ја коригира вредноста.
     63
     64{{{
     65-- Трансакција 1 (Вработен 1 проверува уплата)
     66BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
     67SELECT amount
     68FROM payment
     69WHERE payment_id = 1;
     70-- Враќа 4.99
     71
     72-- Трансакција 2 (Вработен 2 ја коригира уплатата)
     73BEGIN;
     74UPDATE payment
     75SET amount = 9.99
     76WHERE payment_id = 1;
     77COMMIT;
     78
     79
     80-- Трансакција 1 повторно го чита истиот ред
     81SELECT amount
     82FROM payment
     83WHERE payment_id = 1;
     84-- Сега враќа 9.99 -> Non-repeatable Read
     85COMMIT;
     86}}}
     87
     88
     89== Repeatable Read
     90Кај Repeatable Read, трансакцијата работи со snapshot на базата од моментот кога започнала. Тоа значи дека сите SELECT операции ќе ги гледаат истите вредности, дури и ако други трансакции направат промени и COMMIT.
     91
     92'''Сценарио''': Се генерира месечен извештај за сите изнајмувања на клиент каде што е потребен конзистентен поглед и да не се вклучат нови изнајмувања додека извештајот се пишува.
     93
     94{{{
     95-- Трансакција 1
     96BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
     97SELECT COUNT(*) FROM rental WHERE customer_id = 101;
     98-- Враќа 5
     99
     100-- Трансакција 2 (додава ново изнајмување)
     101BEGIN;
     102INSERT INTO rental (rental_date, inventory_id, customer_id, staff_id)
     103VALUES (now(), 55, 101, 2);
     104COMMIT;
     105--Трансакција 1 повторно брои
     106SELECT COUNT(*) FROM rental WHERE customer_id = 101;
     107-- Сè уште враќа 5 снимката е заштитена
     108COMMIT;
     109}}}
     110
     111== Serializable
     112Serializable е највисокото изолациско ниво. Кај ова ниво PostgreSQL гарантира дека резултатот од паралелното извршување на трансакциите ќе биде ист како тие да се извршени една по една (сериски).
     113
     114'''Сценарио''': Системот автоматски доделува бесплатно изнајмување на секој клиент кој ќе достигне 10 изнајмувања во месецот. Двајца вработени истовремено внесуваат изнајмување за ист клиент кој има 9 и двајца гледаат 9, и двајца одлучуваат да додадат бонус.
     115{{{
     116-- Трансакција 1 (Вработен 1)
     117BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
     118SELECT COUNT(*) FROM rental
     119WHERE customer_id = 101
     120AND rental_date >= date_trunc('month', now());
     121
     122-- Враќа 9 го внесува изнајмувањето и додава бесплатен бонус
     123
     124WITH new_rental AS (
     125    INSERT INTO rental (rental_date, inventory_id, customer_id, staff_id)
     126    VALUES (now(), 42, 101, 1)
     127    RETURNING rental_id
     128)
     129INSERT INTO payment (customer_id, staff_id, rental_id, amount, payment_date)
     130SELECT 101, 1, rental_id, 0.00, now()
     131FROM new_rental;
     132
     133
     134-- Трансакција 2 (Вработен 2 истовремено)
     135BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
     136SELECT COUNT(*) FROM rental
     137WHERE customer_id = 101
     138AND rental_date >= date_trunc('month', now());
     139
     140-- Исто така враќа 9 исто одлучува да додаде бесплатен бонус
     141
     142WITH new_rental AS (
     143    INSERT INTO rental (rental_date, inventory_id, customer_id, staff_id)
     144    VALUES (now(), 55, 101, 2)
     145    RETURNING rental_id
     146)
     147INSERT INTO payment (customer_id, staff_id, rental_id, amount, payment_date)
     148SELECT 101, 2, rental_id, 0.00, now()
     149FROM new_rental;
     150
     151-- Трансакција 2 се потврдува
     152COMMIT;
     153
     154
     155-- Трансакција 1 се обидува да потврди
     156COMMIT;
     157-- ERROR:  could not serialize access due to read/write dependencies among transactions
     158-- DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
     159-- HINT:  The transaction might succeed if retried.
     160-- PostgreSQL ја прекинува Трансакција 1 за да спречи двоен бесплатен бонус
     161}}}
     162
     163== Заклучување на ресурси
     164При конкурентно извршување на повеќе трансакции, може да се случи повеќе корисници истовремено да се обидат да ги изменат истите податоци. За да се зачува конзистентноста на базата и да се спречат конфликти помеѓу трансакциите, PostgreSQL користи механизам на заклучување на ресурси (locking).
     165Заклучувањето може да биде на ниво на табела '''(Tabel level locks)''' или на ниво на ред '''(Row level locks)'''.
     166
     167=== Table-Level Locks
     168* '''ACCESS SHARE (!AccessShareLock)''' е најслабото заклучување на табела. Се добива при извршување на SELECT операции кога трансакцијата само чита податоци. Овој lock дозволува повеќе трансакции истовремено да читаат од истата табела и не ги блокира операциите за измена на податоците. Единствено конфликтува со ACCESS EXCLUSIVE заклучување.
     169
     170* '''ROW SHARE (!RowShareLock)''' се користи кога трансакцијата избира редови со намера подоцна да ги измени, најчесто при SELECT ... FOR UPDATE или SELECT ... FOR SHARE. Овој тип на заклучување означува дека одредени редови се резервирани за понатамошна обработка и спречува некои посилни операции над табелата.
     171
     172* '''ROW EXCLUSIVE (!RowExclusiveLock)''' се добива при операции кои ги менуваат податоците во табелата, како INSERT, UPDATE или DELETE. Овој lock дозволува повеќе трансакции истовремено да менуваат различни редови, но блокира одредени структурни операции врз табелата.
     173
     174* '''SHARE UPDATE EXCLUSIVE (!ShareUpdateExclusiveLock)''' се користи при операции за одржување на базата на податоци, како VACUUM, ANALYZE или CREATE INDEX CONCURRENTLY. Овој тип на заклучување овозможува читање на табелата, но спречува други операции кои би можеле да влијаат врз структурата или интегритетот на податоците додека се извршува одржувањето.
     175
     176* '''SHARE (!ShareLock)''' се користи кога е потребно табелата да може да се чита, но не и да се менуваат податоците. Овој lock најчесто се добива при извршување на CREATE INDEX. Во овој случај SELECT операциите се дозволени, но INSERT, UPDATE и DELETE се блокирани.
     177
     178* '''SHARE ROW EXCLUSIVE (!ShareRowExclusiveLock)''' е посилно заклучување кое се користи при одредени структурни операции како CREATE TRIGGER. Овој lock спречува повеќе вакви операции да се извршуваат истовремено и обезбедува конзистентност на структурата на табелата.
     179
     180* '''EXCLUSIVE (!ExclusiveLock)''' дозволува само операции за читање (SELECT), но блокира повеќето други операции кои би можеле да ја изменат табелата. Овој тип на заклучување се користи релативно ретко и служи за операции кои бараат висок степен на изолација.
     181
     182* '''ACCESS EXCLUSIVE (!AccessExclusiveLock)''' е најсилното заклучување на табела. Се добива при операции како ALTER TABLE, DROP TABLE или TRUNCATE. Додека овој lock е активен, сите други операции врз табелата се блокирани, вклучувајќи и читање и измена на податоците.
     183
     184== Row-Level Locks
     185
     186* '''FOR KEY SHARE''' Најслабиот row-level заклучок. Дозволува паралелно читање и ажурирање на редот, но блокира бришење и промена на клучните колони. Се користи при проверка на надворешни клучеви кога друга табела го референцира овој ред и сака да се осигура дека нема да биде избришан.
     187
     188* '''FOR SHARE''' Дозволува паралелно читање, но блокира било какво ажурирање или бришење на редот. Корисно кога сакаме да читаме ред и да бидеме сигурни дека нема да се промени додека трае трансакцијата.
     189
     190* '''FOR NO KEY UPDATE''' Слично на FOR UPDATE но послаб ги заклучува редовите за ажурирање, но дозволува паралелен FOR KEY SHARE. Се применува автоматски при UPDATE кој не ги менува клучните колони.
     191
     192* '''FOR UPDATE''' Најстрогиот row-level заклучок. Целосно го заклучува редот и ги блокира сите останати нивоа на заклучување. Се користи кога сакаме да читаме ред и потоа да го ажурираме, а во меѓувреме никој друг не смее да го допре.
     193
     194
     195Во продолжение се прикажани неколку практични сценарија во кои се користат механизми за заклучување на ресурси со цел да се обезбеди конзистентност на податоците при истовремено извршување на повеќе трансакции.
     196
     197'''Сценарио''': Продавницата одлучува да ги зголеми цените на сите филмови од категоријата "Action" за 20%. За тоа време не сакаме друг вработен да внесува нови изнајмувања или уплати бидејќи цените се уште не се финализирани.
     198
     199{{{
     200BEGIN;
     201LOCK TABLE film IN SHARE MODE;
     202UPDATE film
     203SET rental_rate = rental_rate * 1.20
     204WHERE film_id IN (
     205    SELECT fc.film_id FROM film_category fc
     206    JOIN category c ON fc.category_id = c.category_id
     207    WHERE c.name = 'Action'
     208);
     209COMMIT;
     210}}}
     211
     212
     213'''Сценарио''': На крај на месецот се пресметува вкупниот приход по продавница. Сакаме да читаме конзистентни податоци но да не блокираме обични SELECT операции.
     214
     215{{{
     216BEGIN;
     217LOCK TABLE payment IN EXCLUSIVE MODE;
     218LOCK TABLE rental IN EXCLUSIVE MODE;
     219
     220SELECT
     221    s.store_id,
     222    date_trunc('month', p.payment_date),
     223    SUM(p.amount),
     224    COUNT(r.rental_id)
     225FROM payment p
     226JOIN rental r ON p.rental_id = r.rental_id
     227JOIN staff s ON r.staff_id = s.staff_id
     228WHERE date_trunc('month', p.payment_date) = date_trunc('month', now() - INTERVAL '1 month')
     229GROUP BY s.store_id, date_trunc('month', p.payment_date);
     230
     231COMMIT;
     232}}}
     233
     234'''Сценарио''': Корисник се обидува да изнајми филм, системот прво ја проверува достапноста на копијата и го заклучува редот за да спречи други трансакции да ја изменат истата копија во меѓувреме.
     235
     236{{{
     237BEGIN;
     238
     239SELECT i.inventory_id
     240FROM inventory i
     241LEFT JOIN rental r
     242ON i.inventory_id = r.inventory_id
     243AND r.return_date IS NULL
     244WHERE i.inventory_id = 42
     245FOR UPDATE;
     246
     247INSERT INTO rental (rental_date, inventory_id, customer_id, staff_id)
     248VALUES (NOW(), 42, 15, 1);
     249
     250COMMIT;
     251}}}
     252
     253
     254== DEADLOCKS
     255Deadlock претставува ситуација во која две или повеќе трансакции меѓусебно се блокираат бидејќи секоја од нив држи заклучување врз ресурс што е потребен на другата трансакција. Во таква ситуација ниту една од трансакциите не може да продолжи со извршување.
     256Во PostgreSQL deadlock може да настане и при table-level locks и при row-level locks. На пример, ако една трансакција прво заклучи табела A, а потоа се обиде да ја заклучи табела B, додека друга трансакција веќе ја има заклучено табела B и се обидува да ја заклучи табела A, двете трансакции ќе чекаат една на друга и ќе настане deadlock.
     257PostgreSQL автоматски ги детектира ваквите ситуации. Кога системот ќе открие deadlock, една од трансакциите се прекинува со грешка, со што се ослободуваат заклучувањата и другата трансакција може да продолжи. Кoja трансакција ќе биде прекината не може однапред да се предвиди.
     258
     259'''Сценарио''': Двајца вработени истовремено ажурираат цени на два филма. Вработен 1 прво го ажурира филм 100 па потоа филм 200, а Вработен 2 почнува со филм 200 па потоа го ажурира филм 100.
     260
     261{{{
     262Трансакција 1
     263BEGIN;
     264
     265UPDATE film
     266SET rental_rate = rental_rate * 1.10
     267WHERE film_id = 100;
     268
     269Трансакција 2
     270BEGIN;
     271UPDATE film
     272SET rental_rate = rental_rate  * 1.10
     273WHERE film_id = 200;
     274
     275
     276Трансакција 1
     277UPDATE film
     278SET rental_rate = rental_rate * 1.10
     279WHERE film_id = 200;
     280
     281Трансакција 2
     282UPDATE film
     283SET rental_rate = rental_rate * 1.10
     284WHERE film_id = 100;
     285}}}
     286
     287Во овој момент трансакција 2 се обидува да го заклучи редот film_id = 100, кој веќе е заклучен од трансакција 1. Така што трансакција 1 чека ред што го држи трансакција 2 а трансакција 2 чека ред што го држи трансакција 1.
     288
     289Поради ова PostgreSQL детектира deadlock и прекинува една од трансакциите со грешка:
     290
     291{{{
     292ERROR:  deadlock detected
     293DETAIL:  Process 20003 waits for ShareLock on transaction 840; blocked by process 19996.
     294Process 19996 waits for ShareLock on transaction 841; blocked by process 20003.
     295}}}
     296
     297Се препорачува трансакциите кои работат со повеќе ресурси секогаш да ги заклучуваат тие ресурси во ист редослед. Исто така, првото заклучување во трансакцијата треба да биде во најрестриктивниот режим што ќе биде потребен за тој ресурс. Доколку не е можно однапред да се гарантира ова, апликацијата треба да биде подготвена да ја повтори трансакцијата доколку таа биде прекината поради deadlock.
     298
     299
     300
     301
     302
     303
     304
     305
     306