== Трансакции === Вовед Кај систем за авионски резервации повеќе корисници можат истовремено да резервираат, менуваат или откажуваат резервации за истиот лет. Без трансакции и locking, две операции можат паралелно да ја прочитаат истата состојба и да доведат до неконзистентни податоци. Во оваа фаза се проверуваат четири правила: 1. Едно седиште не смее да биде резервирано двапати на ист лет. \\ 2. Бројот на резервации не смее да го надмине капацитетот на авионот. \\ 3. Промена или откажување на резервација мора да биде атомска операција. \\ 4. Промена на авион не смее да создаде состојба во која бројот на резервации е поголем од новиот капацитет. Имплементацијата и автоматизираната валидација се изведени во изолираната `airportdb_transactions_lab`, со три InnoDB табели: `airplane`, `flight` и `booking`. Во `booking` постои UNIQUE ограничување над: {{{ (flight_id, seat) }}} со што на ниво на база се спречува две резервации да завршат со исто седиште на ист лет. === Реализација За операциите се користат четири stored procedures: || '''Процедура''' || '''Намена''' || || `sp_reserve_seat(flight_id, seat, passenger_id, price)` || Резервира седиште и проверува дали седиштето е зафатено и дали летот има слободен капацитет. || || `sp_change_seat(booking_id, new_seat)` || Атомски го менува седиштето и ја одбива промената ако новото седиште е веќе зафатено. || || `sp_cancel_booking(booking_id)` || Безбедно ја откажува резервацијата. || || `sp_change_flight_airplane(flight_id, new_airplane_id)` || Го менува авионот само ако постоечките резервации се вклопуваат во новиот капацитет. || Процедурите користат `START TRANSACTION`, `FOR UPDATE`, `COMMIT` и `ROLLBACK`. Редот од `flight` се користи како заедничка точка за locking на операциите што работат над истиот лет. При SQL грешка се извршува: {{{ ROLLBACK; RESIGNAL; }}} со што промените од неуспешната трансакција не остануваат запишани. `@txn_lab_pause` се користеше само при тестирање за намерно да се задржи една трансакција неколку секунди и полесно да се репродуцира паралелно извршување. Не претставува дел од бизнис логиката. === Тестирање ==== 1. Паралелна резервација на исто седиште Во тестот две различни сесии се обидуваат да го резервираат истото седиште `26G` на flight `100`. Првата сесија повикува: {{{ SET @txn_lab_pause = 10; CALL sp_reserve_seat( 100, '26G', 4, 100.00 ); }}} и резервацијата успешно се креира. [[Image(F2_TXN_same_seat_A_success.PNG)]] '''Слика 1.''' Успешна резервација на `26G` за passenger `4`. Во втората сесија истовремено се прави обид да се резервира истото седиште за passenger `5`: {{{ CALL sp_reserve_seat( 100, '26G', 5, 100.00 ); }}} [[Image(F2_TXN_same_seat_B_error.PNG)]] '''Слика 2.''' Втората резервација е одбиена со `30006: seat already occupied`. Овој тест покажува дека две паралелни резервации не можат да завршат со исто `(flight_id, seat)`. Додека првата трансакција го обработува flight `100`, втората операција не може да ја наруши истата состојба. По завршувањето на првата резервација, вториот повик се одбива со `30006: seat already occupied`. ==== 2. Промена кон веќе зафатено седиште Во автоматизираниот тест на flight `100` постојат две резервации: {{{ booking 1 -> seat 1A booking 2 -> seat 1B }}} Кога првата резервација се обидува да се премести на `1B`, процедурата враќа: {{{ 30016: target seat already occupied }}} По `ROLLBACK`, првата резервација останува на `1A`, а втората на `1B`. Со ова се проверува атомичноста на `sp_change_seat`: ако промената не може целосно да се изврши, старата состојба се задржува. ==== 3. Промена и откажување на резервации При паралелниот тест една сесија го менува седиштето на една резервација од `1A` во `1C`, додека друга сесија откажува друга резервација на истиот лет. Поради locking на `flight` редот, операциите над резервациите за истиот лет се серијализираат и се извршуваат една по друга. ==== 4. Проверка на капацитет За flight `200` беше поставен авион со capacity `1`. Две сесии се обидуваат да резервираат различни седишта. Првата резервација успева, а втората добива: {{{ 30007: flight is full }}} На крај останува точно една резервација. Овој тест е различен од UNIQUE проверката: седиштата можат да бидат различни, но вкупниот број на booking записи сепак не смее да го надмине капацитетот. ==== 5. Промена кон авион со помал капацитет Flight `100` користи airplane `1` со capacity `2` и има две постоечки резервации. Се прави обид летот да се префрли на airplane `2`, кој има capacity `1`: {{{ CALL sp_change_flight_airplane(100, 2); }}} [[Image(F2_TXN_downgrade_error.PNG)]] '''Слика 3.''' Промената е одбиена со `30035: new airplane capacity is too small`. Потоа се проверува состојбата на летот: [[Image(F2_TXN_downgrade_flight_state.PNG)]] '''Слика 4.''' Flight `100` и понатаму користи airplane `1` со capacity `2`. Бројот на резервации исто така останува непроменет: [[Image(F2_TXN_downgrade_booking_count.PNG)]] '''Слика 5.''' По неуспешната промена остануваат двете резервации. Со тоа се покажува дека процедурата ја одбива промената пред да се создаде состојба `bookings > capacity`, а по грешката се задржува претходната состојба. === Резултати од валидацијата Покрај рачните Workbench проверки, сценаријата беа извршени и како одделни автоматизирани тестови. || '''Сценарио''' || '''Очекувано''' || '''Резултат''' || || Исто седиште || Само една резервација || PASS / `30006` || || Capacity = 1 || Нема overbooking || PASS / `30007`, count = 1 || || Зафатено target седиште || Старата позиција останува || PASS / `30016` || || Change vs cancel || Операциите се серијализираат || PASS || || Aircraft downgrade || Промената се одбива || PASS / `30035` || По автоматизираните проверки: {{{ duplicate_groups = 0 capacity_violations = 0 }}} со што не беше пронајдена состојба со дуплирано седиште или надминат капацитет. === Заклучок Тестовите покажуваат дека UNIQUE ограничувањето е доволно за директно спречување на две исти `(flight_id, seat)` вредности, но не е доволно за контрола на вкупниот капацитет. За контролата на капацитетот и паралелните промени е потребна трансакциска логика која ја заклучува состојбата на летот додека се вршат проверките и промените. Со `FOR UPDATE`, `COMMIT` и `ROLLBACK`, процедурите при запишување преку нив спречуваат overbooking и обезбедуваат неуспешната операција да не остави делумно изменета состојба.