| | 15 | Сценарио: трансфер на играч |
| | 16 | При трансфер на играч, потребно е да се ажурираат повеќе табели истовремено: буџетот на клубот, информациите за играчот и историјата на трансфери. Овие операции мора да се извршат атомично за да се избегне неконзистентност. |
| | 17 | {{{ |
| | 18 | BEGIN; |
| | 19 | |
| | 20 | SELECT budget |
| | 21 | FROM clubs |
| | 22 | WHERE club_id = 10 |
| | 23 | FOR UPDATE; |
| | 24 | |
| | 25 | UPDATE clubs |
| | 26 | SET budget = budget - 5000000 |
| | 27 | WHERE club_id = 10; |
| | 28 | |
| | 29 | UPDATE players |
| | 30 | SET club_id = 10 |
| | 31 | WHERE player_id = 65; |
| | 32 | |
| | 33 | INSERT INTO transfers(player_id, from_club, to_club, amount) |
| | 34 | VALUES (65, 5, 10, 5000000); |
| | 35 | |
| | 36 | COMMIT; |
| | 37 | }}} |
| | 38 | Прво се заклучува редот за клубот за да се спречи истовремено менување, буџетот се намалува, играчот се префрла во нов клуб, се запишува трансфер. Доколку било која операција не успее, трансакцијата ќе се поништи (ROLLBACK) |
| | 39 | {{{ |
| | 40 | BEGIN; |
| | 41 | |
| | 42 | UPDATE clubs |
| | 43 | SET budget = budget - 100000000 |
| | 44 | WHERE club_id = 10; |
| | 45 | |
| | 46 | ROLLBACK; |
| | 47 | }}} |
| | 48 | Ова спречува неконзистентност на базата. |
| | 49 | |
| | 50 | === Конкурентност |
| | 51 | |
| | 52 | Lost update проблемот се јавува кога две трансакции истовремено читаат и ажурираат ист податок. |
| | 53 | {{{ |
| | 54 | Session 1: |
| | 55 | |
| | 56 | BEGIN; |
| | 57 | SELECT budget FROM clubs WHERE club_id = 10; |
| | 58 | }}} |
| | 59 | {{{ |
| | 60 | BEGIN; |
| | 61 | SELECT budget FROM clubs WHERE club_id = 10; |
| | 62 | UPDATE clubs SET budget = 5000000 WHERE club_id = 10; |
| | 63 | COMMIT; |
| | 64 | }}} |
| | 65 | {{{ |
| | 66 | UPDATE clubs SET budget = 7000000 WHERE club_id = 10; |
| | 67 | COMMIT; |
| | 68 | }}} |
| | 69 | Вредноста од Session 2 се губи → lost update. Како да се спречи ова? Со Row-Level Locking, поточно со FOR UPDATE кој го заклучува редот. |
| | 70 | {{{ |
| | 71 | BEGIN; |
| | 72 | |
| | 73 | SELECT budget |
| | 74 | FROM clubs |
| | 75 | WHERE club_id = 10 |
| | 76 | FOR UPDATE; |
| | 77 | |
| | 78 | UPDATE clubs |
| | 79 | SET budget = budget - 5000000 |
| | 80 | WHERE club_id = 10; |
| | 81 | |
| | 82 | COMMIT; |
| | 83 | }}} |
| | 84 | |
| 44 | | WHERE player_id = 65; |
| 45 | | |
| 46 | | COMMIT; |
| 47 | | }}} |
| 48 | | |
| 49 | | Истата трансакција гледа различна вредност, ова е дозволено во READ COMMITTED |
| 50 | | |
| 51 | | {{{ |
| 52 | | BEGIN ISOLATION LEVEL SERIALIZABLE; |
| 53 | | |
| 54 | | SELECT highest_market_value_in_eur |
| 55 | | FROM players |
| 56 | | WHERE player_id = 65; |
| 57 | | |
| 58 | | UPDATE players |
| 59 | | SET highest_market_value_in_eur = 70000000 |
| 60 | | WHERE player_id = 65; |
| 61 | | |
| 62 | | COMMIT; |
| 63 | | |
| 64 | | SELECT highest_market_value_in_eur |
| 65 | | FROM players |
| 66 | | WHERE player_id = 65; |
| 67 | | |
| 68 | | COMMIT; |
| 69 | | }}} |
| 70 | | |
| 71 | | Serializable нема non-repeatable read, конзистентност на ниво на трансакција. |
| | 121 | WHERE club_id = 10 AND age < 18; |
| | 122 | |
| | 123 | -- резултат = 4 |
| | 124 | |
| | 125 | INSERT INTO players(name, age, club_id) |
| | 126 | VALUES ('Young Player 2', 17, 10); |
| | 127 | |
| | 128 | COMMIT; |
| | 129 | }}} |
| | 130 | На крај ќе имаме 6 играчи со што constraint-от е прекршен. Ова се нарекува write skew проблем, кој не може да се спречи со READ COMMITTED изолација. Двете трансакции читаат валидна состојба, но поради паралелно извршување, системот завршува во неконзистентна состојба. Решение за оваа ситуација е : |
| | 131 | {{{ |
| | 132 | SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
| | 133 | }}} |
| | 134 | SERIALIZABLE изолацијата гарантира дека ваквите аномалии нема да се појават, но по цена на намалени перформанси. |
| 105 | | === Locks и MVCC во PostgreSQL |
| 106 | | |
| 107 | | Во системи со повеќе истовремени корисници, базата на податоци мора да обезбеди конзистентност и изолација на податоците, без притоа да се жртвуваат перформансите. PostgreSQL го решава овој проблем преку комбинација на механизми за заклучување (locking) и Multi-Version Concurrency Control (MVCC). |
| 108 | | |
| 109 | | Во овој дел се прикажува разликата помеѓу table-level locking, row-level locking и MVCC, со практични примери кои покажуваат како различните пристапи влијаат врз конкурентноста, блокирањето и видливоста на податоците помеѓу трансакции. |
| 110 | | |
| 111 | | Table-level locking |
| 112 | | |
| 113 | | * целата табела е заклучена |
| 114 | | * други трансакции чекаат, дури и ако работат на друг ред |
| 115 | | * многу лоша конкурентност |
| 116 | | |
| 117 | | {{{ |
| 118 | | BEGIN; |
| 119 | | |
| 120 | | LOCK TABLE players IN ACCESS EXCLUSIVE MODE; |
| 121 | | |
| 122 | | -- табелата е целосно заклучена |
| 123 | | |
| 124 | | SELECT * FROM players; (ACCESS EXCLUSIVE е најсилен lock) |
| 125 | | }}} |
| 126 | | |
| 127 | | Row-level locking |
| 128 | | |
| 129 | | * многу подобра верзија од table-level locking |
| 130 | | * се заклучува само конкретниот ред, другите редови се слободни |
| 131 | | |
| 132 | | {{{ |
| 133 | | BEGIN; |
| 134 | | |
| 135 | | SELECT * FROM players WHERE player_id = 65 FOR UPDATE; |
| 136 | | |
| 137 | | -- само редот со id=1 е заклучен |
| 138 | | |
| 139 | | SELECT * FROM players WHERE player_id = 80; |
| 140 | | |
| 141 | | -- работи нормално |
| 142 | | |
| 143 | | UPDATE players SET highest_market_value_in_eur = highest_market_value_in_eur - 100 WHERE player_id = 65; |
| 144 | | |
| 145 | | -- чека, редот е заклучен |
| 146 | | }}} |
| 147 | | |
| 148 | | MVCC(Multi-Version Concurrency Control) |
| 149 | | |
| 150 | | * наместо locks, PostgreSQL креира нови верзии на редиците (помалку блокирања) |
| 151 | | |
| | 168 | === MVCC |
| | 169 | Во системи со повеќе истовремени корисници, класичните механизми за заклучување (locking) можат да доведат до блокирање и намалени перформанси. PostgreSQL го решава овој проблем преку Multi-Version Concurrency Control (MVCC), кој овозможува читање на податоци без блокирање, преку одржување на повеќе верзии на редиците. Наместо да ги заклучува податоците, системот создава нови верзии при секоја промена, додека старите верзии остануваат достапни за други трансакции. |
| | 170 | |
| | 171 | Сценарио 1: Read without blocking - Во класичен locking систем, кога една трансакција ажурира податок, другите трансакции мора да чекаат. Со MVCC, читањето не се блокира, што овозможува висока конкурентност. |
| 178 | | SELECT market_value_in_eur |
| 179 | | FROM players |
| 180 | | WHERE player_id = 65; |
| 181 | | -- сега Session 2 ја гледа новата вредност |
| 182 | | }}} |
| 183 | | |
| 184 | | MVCC е одличен бидејќи нема блокирање на SELECT операции, нема dirty reads и има висока конкурентност. |
| | 198 | BEGIN; |
| | 199 | UPDATE players |
| | 200 | SET market_value_in_eur = market_value_in_eur + 3000000 |
| | 201 | WHERE player_id = 80; |
| | 202 | }}} |
| | 203 | Бидејќи трансакциите работат со различни редови, тие не се блокираат меѓусебно. MVCC овозможува паралелно извршување, што значително ги подобрува перформансите во системи со голем број корисници. |
| | 204 | |
| | 205 | Сценарио 3: Update conflict - Иако MVCC дозволува паралелно читање, конфликт може да настане кога две трансакции се обидуваат да ажурираат ист ред. |
| | 206 | {{{ |
| | 207 | -- Session 1 |
| | 208 | BEGIN; |
| | 209 | UPDATE players |
| | 210 | SET market_value_in_eur = 15000000 |
| | 211 | WHERE player_id = 65; |
| | 212 | }}} |
| | 213 | {{{ |
| | 214 | -- Session 2 |
| | 215 | BEGIN; |
| | 216 | UPDATE players |
| | 217 | SET market_value_in_eur = 20000000 |
| | 218 | WHERE player_id = 65; |
| | 219 | }}} |
| | 220 | Во овој случај, една од трансакциите ќе мора да чека додека другата не заврши. MVCC не ги елиминира сите конфликти, туку овозможува баланс помеѓу конкурентност и конзистентност. |
| | 221 | |
| | 222 | MVCC vs Locking: Главната разлика помеѓу MVCC и класичното заклучување е во тоа што MVCC дозволува читање без блокирање, додека locking пристапот блокира операции. MVCC обезбедува подобра конкурентност, но бара дополнителна меморија за чување на повеќе верзии. |