| 22 | | со што на ниво на база се спречува две резервации да завршат со исто седиште на ист лет. |
| 23 | | |
| 24 | | === Реализација |
| 25 | | |
| 26 | | За операциите се користат четири stored procedures: |
| 27 | | |
| 28 | | || '''Процедура''' || '''Намена''' || |
| 29 | | || `sp_reserve_seat(flight_id, seat, passenger_id, price)` || Резервира седиште и проверува дали седиштето е зафатено и дали летот има слободен капацитет. || |
| 30 | | || `sp_change_seat(booking_id, new_seat)` || Атомски го менува седиштето и ја одбива промената ако новото седиште е веќе зафатено. || |
| 31 | | || `sp_cancel_booking(booking_id)` || Безбедно ја откажува резервацијата. || |
| 32 | | || `sp_change_flight_airplane(flight_id, new_airplane_id)` || Го менува авионот само ако постоечките резервации се вклопуваат во новиот капацитет. || |
| 33 | | |
| 34 | | Процедурите користат `START TRANSACTION`, `FOR UPDATE`, `COMMIT` и `ROLLBACK`. Редот од `flight` се користи како заедничка точка за locking на операциите што работат над истиот лет. |
| 35 | | |
| 36 | | При SQL грешка се извршува: |
| 37 | | |
| 38 | | {{{ |
| 39 | | ROLLBACK; |
| 40 | | RESIGNAL; |
| 41 | | }}} |
| 42 | | |
| 43 | | со што промените од неуспешната трансакција не остануваат запишани. |
| 44 | | |
| 45 | | `@txn_lab_pause` се користеше само при тестирање за намерно да се задржи една трансакција неколку секунди и полесно да се репродуцира паралелно извршување. Не претставува дел од бизнис логиката. |
| | 38 | Ова ограничување претставува дополнителна заштита на ниво на база: две резервации не можат да имаат ист `flight_id` и `seat`. |
| | 39 | |
| | 40 | Сепак, UNIQUE ограничувањето само по себе не е доволно за проверка на вкупниот капацитет. Две паралелни резервации можат да изберат различни седишта, а сепак заедно да го надминат капацитетот. Затоа е потребна и трансакциска логика. |
| | 41 | |
| | 42 | === Реални сценарија |
| | 43 | |
| | 44 | Во системот можат да се појават неколку типични конкурентни ситуации. |
| | 45 | |
| | 46 | 1. Два корисници во ист момент се обидуваат да го резервираат истото седиште на ист лет. \\ |
| | 47 | Ако двете операции ја прочитаат состојбата пред било која да запише промена, двете можат да сметаат дека седиштето е слободно. |
| | 48 | |
| | 49 | 2. Два корисници резервираат различни седишта на лет кој има само едно преостанато место. \\ |
| | 50 | UNIQUE ограничувањето нема да помогне бидејќи седиштата се различни, па мора посебно да се контролира capacity. |
| | 51 | |
| | 52 | 3. Корисник сака да го промени своето седиште, а новото седиште во меѓувреме го резервира друг корисник. \\ |
| | 53 | Промената мора да биде атомска: или целосно успева, или старата резервација останува непроменета. |
| | 54 | |
| | 55 | 4. Една операција менува седиште, а друга истовремено ја откажува резервацијата. \\ |
| | 56 | Операциите мора да се серијализираат за да не работат врз неконзистентна состојба. |
| | 57 | |
| | 58 | 5. Оператор сака да го промени авионот на летот со авион со помал капацитет. \\ |
| | 59 | Промената мора да се одбие ако веќе постојат повеќе резервации од капацитетот на новиот авион. |
| | 60 | |
| | 61 | Затоа процедурите користат: |
| | 62 | * `START TRANSACTION` |
| | 63 | * `SELECT ... FOR UPDATE` |
| | 64 | * `COMMIT` |
| | 65 | * `ROLLBACK` |
| | 66 | * `RESIGNAL` |
| | 67 | |
| | 68 | Редот од `flight` се користи како заедничка точка за locking на операциите што работат над истиот лет. Со тоа проверките и промените не се извршуваат истовремено врз различна верзија на состојбата. |
| | 69 | |
| | 70 | ===== Procedure Reserve Seat - резервација и контрола на капацитет |
| | 71 | |
| | 72 | Кај резервирањето имаме типична "check-then-write" логика: прво се проверува дали седиштето е слободно и дали летот има капацитет, а потоа се прави INSERT. |
| | 73 | |
| | 74 | Ако две сесии ја направат проверката паралелно без locking, и двете можат да видат стара состојба и да продолжат со внесување. |
| | 75 | |
| | 76 | {{{ |
| | 77 | DELIMITER $$ |
| | 78 | |
| | 79 | CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_reserve_seat`( |
| | 80 | IN p_flight_id INT, |
| | 81 | IN p_seat VARCHAR(10), |
| | 82 | IN p_passenger_id INT, |
| | 83 | IN p_price DECIMAL(10,2) |
| | 84 | ) |
| | 85 | BEGIN |
| | 86 | DECLARE v_airplane_id INT DEFAULT NULL; |
| | 87 | DECLARE v_capacity INT DEFAULT NULL; |
| | 88 | DECLARE v_booked INT DEFAULT 0; |
| | 89 | DECLARE v_occupied INT DEFAULT 0; |
| | 90 | DECLARE v_booking_id INT DEFAULT NULL; |
| | 91 | |
| | 92 | DECLARE EXIT HANDLER FOR SQLEXCEPTION |
| | 93 | BEGIN |
| | 94 | ROLLBACK; |
| | 95 | RESIGNAL; |
| | 96 | END; |
| | 97 | |
| | 98 | START TRANSACTION; |
| | 99 | |
| | 100 | IF p_flight_id IS NULL OR p_flight_id <= 0 THEN |
| | 101 | SIGNAL SQLSTATE '45000' |
| | 102 | SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'invalid flight_id'; |
| | 103 | END IF; |
| | 104 | |
| | 105 | IF p_passenger_id IS NULL OR p_passenger_id <= 0 THEN |
| | 106 | SIGNAL SQLSTATE '45000' |
| | 107 | SET MYSQL_ERRNO = 30002, MESSAGE_TEXT = 'invalid passenger_id'; |
| | 108 | END IF; |
| | 109 | |
| | 110 | IF p_price IS NULL OR p_price < 0 THEN |
| | 111 | SIGNAL SQLSTATE '45000' |
| | 112 | SET MYSQL_ERRNO = 30009, MESSAGE_TEXT = 'invalid price'; |
| | 113 | END IF; |
| | 114 | |
| | 115 | IF p_seat IS NULL |
| | 116 | OR CHAR_LENGTH(TRIM(p_seat)) = 0 |
| | 117 | OR CHAR_LENGTH(TRIM(p_seat)) > 10 THEN |
| | 118 | SIGNAL SQLSTATE '45000' |
| | 119 | SET MYSQL_ERRNO = 30003, MESSAGE_TEXT = 'invalid seat'; |
| | 120 | END IF; |
| | 121 | |
| | 122 | SET p_seat = TRIM(p_seat); |
| | 123 | |
| | 124 | SELECT airplane_id |
| | 125 | INTO v_airplane_id |
| | 126 | FROM flight |
| | 127 | WHERE flight_id = p_flight_id |
| | 128 | FOR UPDATE; |
| | 129 | |
| | 130 | IF v_airplane_id IS NULL THEN |
| | 131 | SIGNAL SQLSTATE '45000' |
| | 132 | SET MYSQL_ERRNO = 30004, MESSAGE_TEXT = 'flight not found'; |
| | 133 | END IF; |
| | 134 | |
| | 135 | IF COALESCE(@txn_lab_pause, 0) > 0 THEN |
| | 136 | DO SLEEP(@txn_lab_pause); |
| | 137 | END IF; |
| | 138 | |
| | 139 | SELECT capacity |
| | 140 | INTO v_capacity |
| | 141 | FROM airplane |
| | 142 | WHERE airplane_id = v_airplane_id; |
| | 143 | |
| | 144 | IF v_capacity IS NULL THEN |
| | 145 | SIGNAL SQLSTATE '45000' |
| | 146 | SET MYSQL_ERRNO = 30005, MESSAGE_TEXT = 'airplane not found'; |
| | 147 | END IF; |
| | 148 | |
| | 149 | SELECT COUNT(*) |
| | 150 | INTO v_occupied |
| | 151 | FROM booking |
| | 152 | WHERE flight_id = p_flight_id |
| | 153 | AND seat = p_seat; |
| | 154 | |
| | 155 | IF v_occupied > 0 THEN |
| | 156 | SIGNAL SQLSTATE '45000' |
| | 157 | SET MYSQL_ERRNO = 30006, MESSAGE_TEXT = 'seat already occupied'; |
| | 158 | END IF; |
| | 159 | |
| | 160 | SELECT COUNT(*) |
| | 161 | INTO v_booked |
| | 162 | FROM booking |
| | 163 | WHERE flight_id = p_flight_id; |
| | 164 | |
| | 165 | IF v_booked >= v_capacity THEN |
| | 166 | SIGNAL SQLSTATE '45000' |
| | 167 | SET MYSQL_ERRNO = 30007, MESSAGE_TEXT = 'flight is full'; |
| | 168 | END IF; |
| | 169 | |
| | 170 | INSERT INTO booking (flight_id, passenger_id, seat, price) |
| | 171 | VALUES (p_flight_id, p_passenger_id, p_seat, p_price); |
| | 172 | |
| | 173 | SET v_booking_id = LAST_INSERT_ID(); |
| | 174 | |
| | 175 | COMMIT; |
| | 176 | |
| | 177 | SELECT 'RESERVED' AS status, |
| | 178 | v_booking_id AS booking_id, |
| | 179 | p_flight_id AS flight_id, |
| | 180 | p_passenger_id AS passenger_id, |
| | 181 | p_seat AS seat, |
| | 182 | p_price AS price; |
| | 183 | END$$ |
| | 184 | |
| | 185 | DELIMITER ; |
| | 186 | }}} |
| | 187 | |
| | 188 | '''Што прави процедурата?''' |
| | 189 | |
| | 190 | `sp_reserve_seat` прво ги валидира влезните параметри, а потоа го заклучува соодветниот `flight` ред со `FOR UPDATE`. |
| | 191 | |
| | 192 | Додека тој lock е активен, друга процедура што работи над истиот flight не може паралелно да ја помине критичната проверка. |
| | 193 | |
| | 194 | Потоа: |
| | 195 | 1. се чита капацитетот на авионот; |
| | 196 | 2. се проверува дали избраното седиште е веќе зафатено; |
| | 197 | 3. се брои бројот на постоечки резервации; |
| | 198 | 4. ако има капацитет, се прави INSERT; |
| | 199 | 5. при успех се прави `COMMIT`. |
| | 200 | |
| | 201 | Ако седиштето е зафатено се враќа `30006`, а ако летот е полн се враќа `30007`. |
| | 202 | |
| | 203 | `EXIT HANDLER` прави `ROLLBACK` и `RESIGNAL`, така што при грешка не останува делумно запишана трансакција. |
| | 204 | |
| | 205 | `@txn_lab_pause` не е дел од бизнис логиката. Се користи само за тестирање, за намерно да се задржи lock-от неколку секунди и полесно да се репродуцира конкурентно извршување. |
| | 206 | |
| | 207 | ===== Procedure Change Seat - атомична промена на седиште |
| | 208 | |
| | 209 | При промена на седиште е важно старата позиција да не се изгуби ако новото седиште е веќе зафатено. |
| | 210 | |
| | 211 | {{{ |
| | 212 | DELIMITER $$ |
| | 213 | |
| | 214 | CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_change_seat`( |
| | 215 | IN p_booking_id INT, |
| | 216 | IN p_new_seat VARCHAR(10) |
| | 217 | ) |
| | 218 | BEGIN |
| | 219 | DECLARE v_flight_id INT DEFAULT NULL; |
| | 220 | DECLARE v_airplane_id INT DEFAULT NULL; |
| | 221 | DECLARE v_capacity INT DEFAULT NULL; |
| | 222 | DECLARE v_old_seat VARCHAR(10) DEFAULT NULL; |
| | 223 | DECLARE v_occupied INT DEFAULT 0; |
| | 224 | |
| | 225 | DECLARE EXIT HANDLER FOR SQLEXCEPTION |
| | 226 | BEGIN |
| | 227 | ROLLBACK; |
| | 228 | RESIGNAL; |
| | 229 | END; |
| | 230 | |
| | 231 | START TRANSACTION; |
| | 232 | |
| | 233 | IF p_booking_id IS NULL OR p_booking_id <= 0 THEN |
| | 234 | SIGNAL SQLSTATE '45000' |
| | 235 | SET MYSQL_ERRNO = 30011, MESSAGE_TEXT = 'invalid booking_id'; |
| | 236 | END IF; |
| | 237 | |
| | 238 | IF p_new_seat IS NULL |
| | 239 | OR CHAR_LENGTH(TRIM(p_new_seat)) = 0 |
| | 240 | OR CHAR_LENGTH(TRIM(p_new_seat)) > 10 THEN |
| | 241 | SIGNAL SQLSTATE '45000' |
| | 242 | SET MYSQL_ERRNO = 30012, MESSAGE_TEXT = 'invalid seat'; |
| | 243 | END IF; |
| | 244 | |
| | 245 | SET p_new_seat = TRIM(p_new_seat); |
| | 246 | |
| | 247 | SELECT flight_id |
| | 248 | INTO v_flight_id |
| | 249 | FROM booking |
| | 250 | WHERE booking_id = p_booking_id; |
| | 251 | |
| | 252 | IF v_flight_id IS NULL THEN |
| | 253 | SIGNAL SQLSTATE '45000' |
| | 254 | SET MYSQL_ERRNO = 30013, MESSAGE_TEXT = 'booking not found'; |
| | 255 | END IF; |
| | 256 | |
| | 257 | SELECT airplane_id |
| | 258 | INTO v_airplane_id |
| | 259 | FROM flight |
| | 260 | WHERE flight_id = v_flight_id |
| | 261 | FOR UPDATE; |
| | 262 | |
| | 263 | IF v_airplane_id IS NULL THEN |
| | 264 | SIGNAL SQLSTATE '45000' |
| | 265 | SET MYSQL_ERRNO = 30014, MESSAGE_TEXT = 'flight not found'; |
| | 266 | END IF; |
| | 267 | |
| | 268 | IF COALESCE(@txn_lab_pause, 0) > 0 THEN |
| | 269 | DO SLEEP(@txn_lab_pause); |
| | 270 | END IF; |
| | 271 | |
| | 272 | SET v_old_seat = NULL; |
| | 273 | |
| | 274 | SELECT seat |
| | 275 | INTO v_old_seat |
| | 276 | FROM booking |
| | 277 | WHERE booking_id = p_booking_id |
| | 278 | AND flight_id = v_flight_id |
| | 279 | FOR UPDATE; |
| | 280 | |
| | 281 | IF v_old_seat IS NULL THEN |
| | 282 | SIGNAL SQLSTATE '45000' |
| | 283 | SET MYSQL_ERRNO = 30013, MESSAGE_TEXT = 'booking not found'; |
| | 284 | END IF; |
| | 285 | |
| | 286 | SELECT capacity |
| | 287 | INTO v_capacity |
| | 288 | FROM airplane |
| | 289 | WHERE airplane_id = v_airplane_id; |
| | 290 | |
| | 291 | IF v_capacity IS NULL THEN |
| | 292 | SIGNAL SQLSTATE '45000' |
| | 293 | SET MYSQL_ERRNO = 30015, MESSAGE_TEXT = 'airplane not found'; |
| | 294 | END IF; |
| | 295 | |
| | 296 | IF p_new_seat = v_old_seat THEN |
| | 297 | COMMIT; |
| | 298 | |
| | 299 | SELECT 'UNCHANGED' AS status, |
| | 300 | p_booking_id AS booking_id, |
| | 301 | v_flight_id AS flight_id, |
| | 302 | v_old_seat AS old_seat, |
| | 303 | v_old_seat AS new_seat; |
| | 304 | ELSE |
| | 305 | SELECT COUNT(*) |
| | 306 | INTO v_occupied |
| | 307 | FROM booking |
| | 308 | WHERE flight_id = v_flight_id |
| | 309 | AND seat = p_new_seat |
| | 310 | AND booking_id <> p_booking_id; |
| | 311 | |
| | 312 | IF v_occupied > 0 THEN |
| | 313 | SIGNAL SQLSTATE '45000' |
| | 314 | SET MYSQL_ERRNO = 30016, |
| | 315 | MESSAGE_TEXT = 'target seat already occupied'; |
| | 316 | END IF; |
| | 317 | |
| | 318 | UPDATE booking |
| | 319 | SET seat = p_new_seat |
| | 320 | WHERE booking_id = p_booking_id; |
| | 321 | |
| | 322 | COMMIT; |
| | 323 | |
| | 324 | SELECT 'CHANGED' AS status, |
| | 325 | p_booking_id AS booking_id, |
| | 326 | v_flight_id AS flight_id, |
| | 327 | v_old_seat AS old_seat, |
| | 328 | p_new_seat AS new_seat; |
| | 329 | END IF; |
| | 330 | END$$ |
| | 331 | |
| | 332 | DELIMITER ; |
| | 333 | }}} |
| | 334 | |
| | 335 | '''Што прави процедурата?''' |
| | 336 | |
| | 337 | Најпрво се пронаоѓа flight-от на резервацијата, а потоа неговиот ред се заклучува со `FOR UPDATE`. |
| | 338 | |
| | 339 | Потоа повторно се чита и се заклучува конкретниот booking ред. Ова е важно затоа што booking-от може да биде променет или избришан додека се чека flight lock-от. |
| | 340 | |
| | 341 | Ако новото седиште е исто со старото, нема потреба од UPDATE. |
| | 342 | |
| | 343 | Во спротивно се проверува дали друго booking веќе го користи новото седиште. Ако е зафатено, се враќа: |
| | 344 | |
| | 345 | {{{ |
| | 346 | 30016: target seat already occupied |
| | 347 | }}} |
| | 348 | |
| | 349 | и целиот transaction се враќа назад. |
| | 350 | |
| | 351 | Со ова старата позиција на патникот останува непроменета ако промената не може успешно да се заврши. |
| | 352 | |
| | 353 | ===== Procedure Cancel Booking - безбедно откажување |
| | 354 | |
| | 355 | Cancel може да се судри со seat change или друга операција врз истата резервација. Затоа и оваа процедура го користи истиот locking редослед. |
| | 356 | |
| | 357 | {{{ |
| | 358 | DELIMITER $$ |
| | 359 | |
| | 360 | CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_cancel_booking`( |
| | 361 | IN p_booking_id INT |
| | 362 | ) |
| | 363 | BEGIN |
| | 364 | DECLARE v_flight_id INT DEFAULT NULL; |
| | 365 | DECLARE v_airplane_id INT DEFAULT NULL; |
| | 366 | DECLARE v_old_seat VARCHAR(10) DEFAULT NULL; |
| | 367 | |
| | 368 | DECLARE EXIT HANDLER FOR SQLEXCEPTION |
| | 369 | BEGIN |
| | 370 | ROLLBACK; |
| | 371 | RESIGNAL; |
| | 372 | END; |
| | 373 | |
| | 374 | START TRANSACTION; |
| | 375 | |
| | 376 | IF p_booking_id IS NULL OR p_booking_id <= 0 THEN |
| | 377 | SIGNAL SQLSTATE '45000' |
| | 378 | SET MYSQL_ERRNO = 30021, MESSAGE_TEXT = 'invalid booking_id'; |
| | 379 | END IF; |
| | 380 | |
| | 381 | SELECT flight_id |
| | 382 | INTO v_flight_id |
| | 383 | FROM booking |
| | 384 | WHERE booking_id = p_booking_id; |
| | 385 | |
| | 386 | IF v_flight_id IS NULL THEN |
| | 387 | SIGNAL SQLSTATE '45000' |
| | 388 | SET MYSQL_ERRNO = 30022, MESSAGE_TEXT = 'booking not found'; |
| | 389 | END IF; |
| | 390 | |
| | 391 | SELECT airplane_id |
| | 392 | INTO v_airplane_id |
| | 393 | FROM flight |
| | 394 | WHERE flight_id = v_flight_id |
| | 395 | FOR UPDATE; |
| | 396 | |
| | 397 | IF v_airplane_id IS NULL THEN |
| | 398 | SIGNAL SQLSTATE '45000' |
| | 399 | SET MYSQL_ERRNO = 30023, MESSAGE_TEXT = 'flight not found'; |
| | 400 | END IF; |
| | 401 | |
| | 402 | IF COALESCE(@txn_lab_pause, 0) > 0 THEN |
| | 403 | DO SLEEP(@txn_lab_pause); |
| | 404 | END IF; |
| | 405 | |
| | 406 | SET v_old_seat = NULL; |
| | 407 | |
| | 408 | SELECT seat |
| | 409 | INTO v_old_seat |
| | 410 | FROM booking |
| | 411 | WHERE booking_id = p_booking_id |
| | 412 | AND flight_id = v_flight_id |
| | 413 | FOR UPDATE; |
| | 414 | |
| | 415 | IF v_old_seat IS NULL THEN |
| | 416 | SIGNAL SQLSTATE '45000' |
| | 417 | SET MYSQL_ERRNO = 30022, MESSAGE_TEXT = 'booking not found'; |
| | 418 | END IF; |
| | 419 | |
| | 420 | DELETE FROM booking |
| | 421 | WHERE booking_id = p_booking_id; |
| | 422 | |
| | 423 | COMMIT; |
| | 424 | |
| | 425 | SELECT 'CANCELLED' AS status, |
| | 426 | p_booking_id AS booking_id, |
| | 427 | v_flight_id AS flight_id, |
| | 428 | v_old_seat AS seat; |
| | 429 | END$$ |
| | 430 | |
| | 431 | DELIMITER ; |
| | 432 | }}} |
| | 433 | |
| | 434 | '''Што прави процедурата?''' |
| | 435 | |
| | 436 | `sp_cancel_booking` прво го наоѓа flight-от на booking-от, потоа го заклучува истиот flight ред кој го користат и другите процедури. |
| | 437 | |
| | 438 | После тоа booking-от повторно се чита со `FOR UPDATE`. Ова спречува да се избрише запис кој во меѓувреме бил променет или веќе откажан. |
| | 439 | |
| | 440 | Ако booking-от постои, се извршува `DELETE` и `COMMIT`. |
| | 441 | |
| | 442 | Ако друга трансакција истовремено врши seat change или друга промена на истиот лет, едната операција ќе почека додека другата го ослободи lock-от. |
| | 443 | |
| | 444 | ===== Procedure Change Flight Airplane - промена на авион |
| | 445 | |
| | 446 | Ова е административна операција каде flight се префрла на друг airplane. |
| | 447 | |
| | 448 | Најважната проверка е дали новиот авион има доволен капацитет за сите веќе постоечки резервации. |
| | 449 | |
| | 450 | {{{ |
| | 451 | DELIMITER $$ |
| | 452 | |
| | 453 | CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_change_flight_airplane`( |
| | 454 | IN p_flight_id INT, |
| | 455 | IN p_new_airplane_id INT |
| | 456 | ) |
| | 457 | BEGIN |
| | 458 | DECLARE v_old_airplane_id INT DEFAULT NULL; |
| | 459 | DECLARE v_new_capacity INT DEFAULT NULL; |
| | 460 | DECLARE v_booked INT DEFAULT 0; |
| | 461 | |
| | 462 | DECLARE EXIT HANDLER FOR SQLEXCEPTION |
| | 463 | BEGIN |
| | 464 | ROLLBACK; |
| | 465 | RESIGNAL; |
| | 466 | END; |
| | 467 | |
| | 468 | START TRANSACTION; |
| | 469 | |
| | 470 | IF p_flight_id IS NULL OR p_flight_id <= 0 THEN |
| | 471 | SIGNAL SQLSTATE '45000' |
| | 472 | SET MYSQL_ERRNO = 30031, MESSAGE_TEXT = 'invalid flight_id'; |
| | 473 | END IF; |
| | 474 | |
| | 475 | IF p_new_airplane_id IS NULL OR p_new_airplane_id <= 0 THEN |
| | 476 | SIGNAL SQLSTATE '45000' |
| | 477 | SET MYSQL_ERRNO = 30032, |
| | 478 | MESSAGE_TEXT = 'invalid new_airplane_id'; |
| | 479 | END IF; |
| | 480 | |
| | 481 | SELECT airplane_id |
| | 482 | INTO v_old_airplane_id |
| | 483 | FROM flight |
| | 484 | WHERE flight_id = p_flight_id |
| | 485 | FOR UPDATE; |
| | 486 | |
| | 487 | IF v_old_airplane_id IS NULL THEN |
| | 488 | SIGNAL SQLSTATE '45000' |
| | 489 | SET MYSQL_ERRNO = 30033, MESSAGE_TEXT = 'flight not found'; |
| | 490 | END IF; |
| | 491 | |
| | 492 | IF COALESCE(@txn_lab_pause, 0) > 0 THEN |
| | 493 | DO SLEEP(@txn_lab_pause); |
| | 494 | END IF; |
| | 495 | |
| | 496 | SELECT capacity |
| | 497 | INTO v_new_capacity |
| | 498 | FROM airplane |
| | 499 | WHERE airplane_id = p_new_airplane_id; |
| | 500 | |
| | 501 | IF v_new_capacity IS NULL THEN |
| | 502 | SIGNAL SQLSTATE '45000' |
| | 503 | SET MYSQL_ERRNO = 30034, MESSAGE_TEXT = 'airplane not found'; |
| | 504 | END IF; |
| | 505 | |
| | 506 | SELECT COUNT(*) |
| | 507 | INTO v_booked |
| | 508 | FROM booking |
| | 509 | WHERE flight_id = p_flight_id; |
| | 510 | |
| | 511 | IF v_booked > v_new_capacity THEN |
| | 512 | SIGNAL SQLSTATE '45000' |
| | 513 | SET MYSQL_ERRNO = 30035, |
| | 514 | MESSAGE_TEXT = 'new airplane capacity is too small'; |
| | 515 | END IF; |
| | 516 | |
| | 517 | UPDATE flight |
| | 518 | SET airplane_id = p_new_airplane_id |
| | 519 | WHERE flight_id = p_flight_id; |
| | 520 | |
| | 521 | COMMIT; |
| | 522 | |
| | 523 | SELECT 'AIRPLANE_CHANGED' AS status, |
| | 524 | p_flight_id AS flight_id, |
| | 525 | v_old_airplane_id AS old_airplane_id, |
| | 526 | p_new_airplane_id AS new_airplane_id, |
| | 527 | v_booked AS booking_count, |
| | 528 | v_new_capacity AS new_capacity; |
| | 529 | END$$ |
| | 530 | |
| | 531 | DELIMITER ; |
| | 532 | }}} |
| | 533 | |
| | 534 | '''Што прави процедурата?''' |
| | 535 | |
| | 536 | Процедурата прво го заклучува flight редот со `FOR UPDATE`. |
| | 537 | |
| | 538 | Додека flight-от е заклучен: |
| | 539 | 1. се чита капацитетот на новиот авион; |
| | 540 | 2. се брои бројот на постоечки booking записи; |
| | 541 | 3. се проверува дали сите можат да се сместат во новиот авион. |
| | 542 | |
| | 543 | Ако: |
| | 544 | |
| | 545 | {{{ |
| | 546 | bookings > new_capacity |
| | 547 | }}} |
| | 548 | |
| | 549 | процедурата враќа: |
| | 550 | |
| | 551 | {{{ |
| | 552 | 30035: new airplane capacity is too small |
| | 553 | }}} |
| | 554 | |
| | 555 | и transaction-от се враќа назад. |
| | 556 | |
| | 557 | Ако капацитетот е доволен, `flight.airplane_id` се ажурира и се прави `COMMIT`. |
| | 558 | |
| | 559 | Со тоа не може преку оваа процедура да се создаде flight кој веќе има повеќе резервации од капацитетот на новиот авион. |