Changes between Version 1 and Version 2 of InnoDB


Ignore:
Timestamp:
09/20/26 01:15:17 (7 days ago)
Author:
222004
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • InnoDB

    v1 v2  
    11= Crash Recovery – Опоравување на трансакции по пад на MySQL сервер =
     2
     3== 1. Вовед и цел ==
     4
     5Во оваа фаза се испитува што се случува со трансакциите кога MySQL серверот неочекувано ќе престане да работи. Практичниот пример е поврзан со претходната фаза за трансакции во AirportDB: резервација на седиште и промена на седиште.
     6
     7Целта е да се провери дали по повторното стартување:
     8 * веќе потврдените промени (`COMMIT`) остануваат зачувани;
     9 * промените од незавршена трансакција се поништуваат;
     10 * повеќе промени во една трансакција не остануваат делумно запишани.
     11
     12Ова е '''Crash Recovery''', а не враќање од резервна копија (Backup). Во експериментите повторно се стартува истиот MySQL сервер со истиот податочен волумен; не се користат `mysqldump` или враќање на база од backup.
     13
     14== 2. Како работи опоравувањето ==
     15
     16Во InnoDB, трансакцијата се потврдува со `COMMIT`. При ненадеен прекин не е доволно само повторно да се стартува апликацијата: базата треба да утврди кои промени биле потврдени, а кои не биле довршени.
     17
     18|| '''Поим''' || '''Едноставно објаснување''' ||
     19|| '''Redo log''' || Чува информации потребни за повторно применување на промени што треба да се сочуваат. При стартување InnoDB го проверува логот и, кога е потребно, ги применува промените што не биле запишани во податочните страници. ||
     20|| '''Undo log''' || Овозможува поништување на промените од незавршените трансакции. ||
     21|| '''Write-ahead logging (WAL)''' || Потребните информации за опоравување се запишуваат во логот пред соодветните податочни страници да се запишат на диск. ||
     22|| '''Checkpoint''' || Забележува до која точка податочните страници се запишани, со што се намалува обемот на работа при опоравување. ||
     23
     24Во оваа демонстрација се проверуваат две ACID својства: '''трајност (Durability)''' – потврдената резервација останува по падот; и '''атомичност (Atomicity)''' – незавршените операции се поништуваат како целина.
     25
     26== 3. Тест околина ==
     27
     28За да не се менува оригиналната база, користена е посебна Docker околина во WSL2 Ubuntu. MySQL Workbench на Windows се поврзува со портата `33307`.
     29
     30|| '''Поставка''' || '''Вредност''' ||
     31|| MySQL || `8.0.43` ||
     32|| Docker контејнер || `airportdb_recovery_lab_mysql` ||
     33|| Постојан Docker волумен || `airportdb_recovery_lab_data` ||
     34|| База || `airportdb_recovery_lab` ||
     35|| Host / порта за Workbench || `127.0.0.1:33307` ||
     36|| Внатрешна MySQL порта || `3306` ||
     37|| `innodb_flush_log_at_trx_commit` || `1` ||
     38|| `sync_binlog` || `1` ||
     39|| `log_bin` || `1` ||
     40
     41Поставката `innodb_flush_log_at_trx_commit=1` е користена за тестот на трајноста. Binary log е вклучен како дел од конфигурацијата, но '''не се користи за враќање на податоци''' во оваа фаза; за InnoDB crash recovery се важни неговите сопствени redo/undo информации.
     42
     43Проверката во Workbench е направена со:
     44
     45{{{
     46
     47SELECT @@version AS mysql_version,
     48       DATABASE() AS selected_database,
     49       @@port AS mysql_port,
     50       @@innodb_flush_log_at_trx_commit AS innodb_flush_log_at_trx_commit,
     51       @@sync_binlog AS sync_binlog,
     52       @@log_bin AS binary_logging;
     53
     54}}}
     55
     56'''Слика 1 – Верзија, база и поставки за трајност.''' [[BR]]
     57[[Image(01_environment_configuration.png, width=850)]]
     58
     59Користен е издвоен, минимален модел од AirportDB со табелите `airplane`, `flight` и `booking`, како и помошната `recovery_operation_event` за тестот на атомарност. Логиката за резервација е клонирана од претходната фаза; оригиналните процедури и бази не се менувани.
     60
     61Почетната состојба на двата лета е:
     62
     63|| '''Лет''' || '''Авион''' || '''Капацитет''' || '''Број резервации''' ||
     64|| 100 || 1 || 2 || 0 ||
     65|| 200 || 2 || 1 || 0 ||
     66
     67{{{
     68
     69SELECT f.flight_id, f.airplane_id, a.capacity,
     70       COUNT(b.booking_id) AS booking_count
     71FROM flight AS f
     72JOIN airplane AS a ON a.airplane_id = f.airplane_id
     73LEFT JOIN booking AS b ON b.flight_id = f.flight_id
     74GROUP BY f.flight_id, f.airplane_id, a.capacity
     75ORDER BY f.flight_id;
     76
     77}}}
     78
     79'''Слика 2 – Почетна состојба на летовите и резервациите.''' [[BR]]
     80[[Image(02_initial_booking_state.png, width=850)]]
     81
     82== 4. Експеримент 1 – Опоравување на потврдена резервација ==
     83
     84'''Цел:''' да се провери дали резервацијата останува зачувана по прекин што се случува '''по `COMMIT`'''.
     85
     86Во Workbench беше повикана постојната клонирана процедура за резервација:
     87
     88{{{#!sql
     89CALL sp_reserve_seat(100, '1A', 9101, 100.00);
     90
     91SELECT booking_id, flight_id, passenger_id, seat, price
     92FROM booking
     93WHERE passenger_id = 9101;
     94}}}
     95
     96Процедурата успешно ја заврши резервацијата. Во рачниот тест беше добиена резервација со `booking_id = 31`, за патник `9101`, лет `100` и седиште `1A`.
     97
     98'''Слика 3 – Резервацијата пред прекинот.''' [[BR]]
     99[[Image(03_committed_booking_before_crash.png, width=850)]]
     100
     101Потоа, во WSL терминалот серверот беше насилно прекинат со `SIGKILL` и беше стартуван повторно со истиот податочен волумен:
     102
     103{{{
     104
     105docker kill --signal=KILL airportdb_recovery_lab_mysql
     106docker start airportdb_recovery_lab_mysql
     107
     108}}}
     109
     110'''Слика 4 – Прекин и повторно стартување на Docker контејнерот.''' [[BR]]
     111[[Image(03_committed_booking_before_crash_docker.png, width=850)]]
     112
     113По повторното поврзување беше извршена проверка:
     114
     115{{{#!sql
     116SELECT booking_id, flight_id, passenger_id, seat, price
     117FROM booking
     118WHERE passenger_id = 9101 AND flight_id = 100 AND seat = '1A';
     119}}}
     120
     121'''Резултат:''' резервацијата со `booking_id = 31` остана присутна со истите податоци. Со тоа тестот ја потврди трајноста на потврдената трансакција во испитаните услови.
     122
     123'''Слика 5 – Истата резервација по опоравувањето.''' [[BR]]
     124[[Image(04_committed_booking_after_recovery.png, width=850)]]
     125
     126== 5. Контролиран прекин пред COMMIT ==
     127
     128За следните два експеримента прекинот мора да се случи '''пред''' `COMMIT`. Затоа се користени три независни Workbench сесии:
     129
     130|| '''Сесија''' || '''Улога''' ||
     131|| A – Target || Ги извршува измените и чека пред `COMMIT`. ||
     132|| B – Controller || Претходно го зазема именуваниот lock со `GET_LOCK(...)`. ||
     133|| C – Monitor || Проверува кој го поседува lock-от и дали Target чека. ||
     134
     135Именуваниот lock служи само за '''точно одредување на моментот за пад'''. Тој не претставува замена за транскацискиот механизам на InnoDB. Target мора да остане блокиран; доколку `GET_LOCK` врати `1` и се изврши `COMMIT`, тестот не е валиден.
     136
     137== 6. Експеримент 2 – Поништување на незавршена резервација ==
     138
     139'''Цел:''' да остане претходно потврдената резервација, а да се поништи новата резервација што не стигнала до `COMMIT`.
     140
     141Прво е поставена потврдена почетна резервација: патник `9101`, лет `100`, седиште `1A`. Во овој рачен тест почетната резервација имаше `booking_id = 35`.
     142
     143Во Controller е заземен lock-от:
     144
     145{{{#!sql
     146SELECT GET_LOCK('recovery_gate_exp2', 600) AS lock_acquired;
     147}}}
     148
     149Потоа во Target е започната незавршената трансакција:
     150
     151{{{#!sql
     152START TRANSACTION;
     153INSERT INTO booking (flight_id, passenger_id, seat, price)
     154VALUES (100, 9102, '1B', 100.00);
     155SELECT GET_LOCK('recovery_gate_exp2', 600) AS target_gate_acquired;
     156COMMIT;
     157}}}
     158
     159`COMMIT` е намерно поставен по чекањето. Додека Controller го држи lock-от, Target останува блокиран пред таа наредба.
     160
     161'''Слика 6 – Target чека пред COMMIT; INSERT е извршен во незавршената трансакција.''' [[BR]]
     162[[Image(05_connection_A_test.PNG, width=850)]]
     163
     164Во рачната проверка Monitor потврди дека Controller со ID `10` го поседува lock-от и Target со ID `8` е во состојба `User lock`. ID-вредностите се менуваат при ново поврзување.
     165
     166{{{
     167
     168SELECT IS_USED_LOCK('recovery_gate_exp2') AS lock_owner;
     169
     170SELECT ID, DB, TIME, STATE, INFO
     171FROM information_schema.processlist
     172WHERE DB = 'airportdb_recovery_lab'
     173  AND STATE = 'User lock'
     174  AND INFO LIKE '%recovery_gate_exp2%';
     175
     176}}}
     177
     178Без ослободување на lock-от, контејнерот е прекинат со `docker kill --signal=KILL` и повторно стартуван. По поврзувањето беше проверено:
     179
     180{{{#!sql
     181SELECT 'Потврдена 9101/1A' AS check_name, COUNT(*) AS actual_rows
     182FROM booking
     183WHERE passenger_id = 9101 AND flight_id = 100 AND seat = '1A'
     184UNION ALL
     185SELECT 'Незавршена 9102/1B', COUNT(*)
     186FROM booking
     187WHERE passenger_id = 9102 OR (flight_id = 100 AND seat = '1B');
     188}}}
     189
     190|| '''Проверка''' || '''Очекувано''' || '''Добиено''' ||
     191|| Потврдена резервација `9101/1A` || 1 || 1 ||
     192|| Незавршена резервација `9102/1B` || 0 || 0 ||
     193
     194'''Резултат:''' потврдената резервација остана, а незавршената беше поништена. При пребарување на табелата `booking` остана само резервацијата на патник `9101`.
     195
     196'''Слика 7 – Состојба по опоравувањето: зачувана е само потврдената резервација.''' [[BR]]
     197[[Image(06_uncommitted_after_recovery.png, width=850)]]
     198
     199== 7. Експеримент 3 – Атомарност на две поврзани промени ==
     200
     201'''Цел:''' да се провери дека по падот не останува делумна промена: промена на седиште без соодветниот помошен запис, или обратно.
     202
     203За почетна состојба е креирана потврдена резервација за патник `9201`, лет `100`, седиште `1A` (`booking_id = 37`). Табелата `recovery_operation_event` нема записи за `exp3`.
     204
     205'''Слика 8 – Почетна резервација на седиште 1A.''' [[BR]]
     206[[Image(07_atomicity_initial_state.png, width=850)]]
     207
     208Controller го зазема `recovery_gate_exp3`. Во Target се извршени две измени во истата трансакција:
     209
     210{{{#!sql
     211SELECT booking_id INTO @exp3_booking_id
     212FROM booking
     213WHERE passenger_id = 9201 AND flight_id = 100 AND seat = '1A';
     214
     215START TRANSACTION;
     216UPDATE booking SET seat = '1C'
     217WHERE booking_id = @exp3_booking_id;
     218
     219INSERT INTO recovery_operation_event
     220    (experiment, booking_id, before_seat, after_seat)
     221VALUES ('exp3', @exp3_booking_id, '1A', '1C');
     222
     223SELECT GET_LOCK('recovery_gate_exp3', 600) AS target_gate_acquired;
     224COMMIT;
     225}}}
     226
     227Мониторингот покажа дека Target со ID `19` чека во `User lock`, додека Controller со ID `21` го поседува lock-от. Затоа `COMMIT` сѐ уште не е извршен.
     228
     229'''Слика 9 – Контролирано чекање пред COMMIT во експериментот за атомарност.''' [[BR]]
     230[[Image(08_atomicity_precommit_gate.png, width=850)]]
     231
     232Потоа следеше истиот насилен прекин (`SIGKILL`) и повторно стартување. По опоравувањето беа проверени трите исходи:
     233
     234{{{#!sql
     235SELECT 'Оригинално 1A' AS check_name, COUNT(*) AS actual_rows
     236FROM booking WHERE passenger_id = 9201 AND flight_id = 100 AND seat = '1A'
     237UNION ALL
     238SELECT 'Незавршено 1C', COUNT(*)
     239FROM booking WHERE passenger_id = 9201 AND seat = '1C'
     240UNION ALL
     241SELECT 'Незавршен exp3 запис', COUNT(*)
     242FROM recovery_operation_event WHERE experiment = 'exp3';
     243}}}
     244
     245|| '''Проверка''' || '''Очекувано''' || '''Добиено''' ||
     246|| Оригиналното седиште `1A` постои || 1 || 1 ||
     247|| Промената на `1C` не постои || 0 || 0 ||
     248|| Новиот `exp3` запис не постои || 0 || 0 ||
     249
     250'''Резултат:''' двете незавршени измени се поништени. Патникот `9201` повторно е на `1A`, а во помошната табела нема `exp3` запис. Ова ја потврдува атомарноста на трансакцијата во експериментот.
     251
     252'''Слика 10 – Проверка по опоравувањето: резултати 1, 0 и 0.''' [[BR]]
     253[[Image(08_atomicity_after_recovery.png, width=850)]]
     254
     255== 8. Логови и checkpoint по опоравувањето ==
     256
     257Логовите на Docker контејнерот се прегледани со:
     258
     259{{{
     260
     261docker logs airportdb_recovery_lab_mysql 2>&1 | grep -Ei \
     262'not shutdown normally|Starting crash recovery|Doing recovery|Applying a batch|resurrected|must be rolled back|Rolling back|Rollback of non-prepared|ready for connections'
     263
     264}}}
     265
     266Во зачуваната снимка се гледаат повеќе стартувања. При едно од претходните стартувања, во `10:41 UTC`, InnoDB пријави примена на серија од '''12 redo log records''', откри '''една незавршена трансакција''' и пријави '''1 row to undo'''. Поновите стартувања прикажуваат и `Applying a batch of 0 redo log records`. Ова не значи дека redo секогаш применува промени; обемот на работа зависи од состојбата при конкретниот пад.
     267
     268'''Важно:''' овие лог-пораки се за лабораторијата низ повеќе извршувања. Пораките од `10:41 UTC` не се означуваат како лог исклучиво од рачниот експеримент 3.
     269
     270'''Слика 11 – InnoDB логи: неочекуван прекин, redo проверка и rollback.''' [[BR]]
     271[[Image(11_innodb_recovery_log.png, width=850)]]
     272
     273По рачните тестови, со `SHOW ENGINE INNODB STATUS;` беше проверен делот `LOG`. Во моментот на проверката се добиени следните вредности:
     274
     275|| '''Показател''' || '''Добиена вредност''' ||
     276|| Log sequence number || 30.912.187 ||
     277|| Log written up to || 30.912.187 ||
     278|| Log flushed up to || 30.912.187 ||
     279|| Pages flushed up to || 30.912.187 ||
     280|| Last checkpoint at || 30.912.187 ||
     281|| Modified db pages || 0 ||
     282|| History list length || 0 ||
     283
     284Еднаквите позиции на логот и checkpoint, заедно со `Modified db pages = 0`, покажуваат дека во моментот на оваа проверка немало модифицирани страници што чекале запис. Ова е '''снимка на состојбата по опоравувањето'''; само по себе не е доказ дека при тој конкретен старт биле применети redo записи. Доказот за активноста при recovery доаѓа од startup логовите и SQL-проверките по прекинот.
     285
     286== 9. Сумаризација на тестирањето ==
     287
     288|| '''Експеримент''' || '''Начин на проверка''' || '''Исход''' ||
     289|| 1. Потврдена резервација || Резервацијата постои пред и по `SIGKILL`. || PASS – рачно проверено ||
     290|| 2. Незавршена резервација || Потврдената останува, незавршената отсуствува (`1, 0`). || PASS – рачно проверено ||
     291|| 3. Две измени во иста трансакција || Старото седиште останува, двете нови промени отсуствуваат (`1, 0, 0`). || PASS – рачно проверено ||
     292|| 4. Потврдена и незавршена паралелно || Според зачуваниот извештај од автоматското тестирање `results/run-20260919T124053/summary.tsv`. || PASS – пријавено од автоматскиот тест; не е повторено рачно за оваа wiki страница ||
     293
     294Експериментите намерно го прекинуваат '''MySQL процесот/контејнерот''', а не целиот оперативен систем или физичкото напојување. Затоа резултатите не треба да се претставуваат како тест на физички прекин на струја. Податоците се зачувани во истиот Docker волумен, без враќање од backup.
     295
     296== 10. Заклучок ==
     297
     298Рачните тестови покажаа дека InnoDB ја зачувува потврдената резервација, ги поништува незавршените промени и не дозволува делумно зачувување на двете измени од експериментот за атомарност. Логовите ја дополнуваат SQL-проверката со увид во механизмите за опоравување. Оваа фаза се надоврзува на претходната фаза за трансакции, но проверува што се случува '''по ненадеен прекин на серверот''', наместо само како трансакциите работат додека серверот е активен.