wiki:InnoDB

Crash Recovery – Опоравување на трансакции по пад на MySQL сервер

1. Вовед и цел

Во оваа фаза се испитува што се случува со трансакциите кога MySQL серверот неочекувано ќе престане да работи. Практичниот пример е поврзан со претходната фаза за трансакции во AirportDB: резервација на седиште и промена на седиште.

Целта е да се провери дали по повторното стартување:

  • веќе потврдените промени (COMMIT) остануваат зачувани;
  • промените од незавршена трансакција се поништуваат;
  • повеќе промени во една трансакција не остануваат делумно запишани.

Ова е Crash Recovery, а не враќање од резервна копија (Backup). Во експериментите повторно се стартува истиот MySQL сервер со истиот податочен волумен; не се користат mysqldump или враќање на база од backup.

2. Како работи опоравувањето

Во InnoDB, трансакцијата се потврдува со COMMIT. При ненадеен прекин не е доволно само повторно да се стартува апликацијата: базата треба да утврди кои промени биле потврдени, а кои не биле довршени.

Поим Едноставно објаснување
Redo log Чува информации потребни за повторно применување на промени што треба да се сочуваат. При стартување InnoDB го проверува логот и, кога е потребно, ги применува промените што не биле запишани во податочните страници.
Undo log Овозможува поништување на промените од незавршените трансакции.
Write-ahead logging (WAL) Потребните информации за опоравување се запишуваат во логот пред соодветните податочни страници да се запишат на диск.
Checkpoint Забележува до која точка податочните страници се запишани, со што се намалува обемот на работа при опоравување.

Во оваа демонстрација се проверуваат две ACID својства: трајност (Durability) – потврдената резервација останува по падот; и атомичност (Atomicity) – незавршените операции се поништуваат како целина.

3. Тест околина

За да не се менува оригиналната база, користена е посебна Docker околина во WSL2 Ubuntu. MySQL Workbench на Windows се поврзува со портата 33307.

Поставка Вредност
MySQL 8.0.43
Docker контејнер airportdb_recovery_lab_mysql
Постојан Docker волумен airportdb_recovery_lab_data
База airportdb_recovery_lab
Host / порта за Workbench 127.0.0.1:33307
Внатрешна MySQL порта 3306
innodb_flush_log_at_trx_commit 1
sync_binlog 1
log_bin 1

Поставката innodb_flush_log_at_trx_commit=1 е користена за тестот на трајноста. Binary log е вклучен како дел од конфигурацијата, но не се користи за враќање на податоци во оваа фаза; за InnoDB crash recovery се важни неговите сопствени redo/undo информации.

Проверката во Workbench е направена со:

SELECT @@version AS mysql_version,
       DATABASE() AS selected_database,
       @@port AS mysql_port,
       @@innodb_flush_log_at_trx_commit AS innodb_flush_log_at_trx_commit,
       @@sync_binlog AS sync_binlog,
       @@log_bin AS binary_logging;

Слика 1 – Верзија, база и поставки за трајност.

Користен е издвоен, минимален модел од AirportDB со табелите airplane, flight и booking, како и помошната recovery_operation_event за тестот на атомарност. Логиката за резервација е клонирана од претходната фаза; оригиналните процедури и бази не се менувани.

Почетната состојба на двата лета е:

Лет Авион Капацитет Број резервации
100 1 2 0
200 2 1 0
SELECT f.flight_id, f.airplane_id, a.capacity,
       COUNT(b.booking_id) AS booking_count
FROM flight AS f
JOIN airplane AS a ON a.airplane_id = f.airplane_id
LEFT JOIN booking AS b ON b.flight_id = f.flight_id
GROUP BY f.flight_id, f.airplane_id, a.capacity
ORDER BY f.flight_id;

Слика 2 – Почетна состојба на летовите и резервациите.

4. Експеримент 1 – Опоравување на потврдена резервација

Цел: да се провери дали резервацијата останува зачувана по прекин што се случува по COMMIT.

Во Workbench беше повикана постојната клонирана процедура за резервација:

CALL sp_reserve_seat(100, '1A', 9101, 100.00);

SELECT booking_id, flight_id, passenger_id, seat, price
FROM booking
WHERE passenger_id = 9101;

Процедурата успешно ја заврши резервацијата. Во рачниот тест беше добиена резервација со booking_id = 31, за патник 9101, лет 100 и седиште 1A.

Слика 3 – Резервацијата пред прекинот.

Потоа, во WSL терминалот серверот беше насилно прекинат со SIGKILL и беше стартуван повторно со истиот податочен волумен:

docker kill --signal=KILL airportdb_recovery_lab_mysql
docker start airportdb_recovery_lab_mysql

Слика 4 – Прекин и повторно стартување на Docker контејнерот.

По повторното поврзување беше извршена проверка:

SELECT booking_id, flight_id, passenger_id, seat, price
FROM booking
WHERE passenger_id = 9101 AND flight_id = 100 AND seat = '1A';

Резултат: резервацијата со booking_id = 31 остана присутна со истите податоци. Со тоа тестот ја потврди трајноста на потврдената трансакција во испитаните услови.

Слика 5 – Истата резервација по опоравувањето.

5. Контролиран прекин пред COMMIT

За следните два експеримента прекинот мора да се случи пред COMMIT. Затоа се користени три независни Workbench сесии:

Сесија Улога
A – Target Ги извршува измените и чека пред COMMIT.
B – Controller Претходно го зазема именуваниот lock со GET_LOCK(...).
C – Monitor Проверува кој го поседува lock-от и дали Target чека.

Именуваниот lock служи само за точно одредување на моментот за пад. Тој не претставува замена за транскацискиот механизам на InnoDB. Target мора да остане блокиран; доколку GET_LOCK врати 1 и се изврши COMMIT, тестот не е валиден.

6. Експеримент 2 – Поништување на незавршена резервација

Цел: да остане претходно потврдената резервација, а да се поништи новата резервација што не стигнала до COMMIT.

Прво е поставена потврдена почетна резервација: патник 9101, лет 100, седиште 1A. Во овој рачен тест почетната резервација имаше booking_id = 35.

Во Controller е заземен lock-от:

SELECT GET_LOCK('recovery_gate_exp2', 600) AS lock_acquired;

Потоа во Target е започната незавршената трансакција:

START TRANSACTION;
INSERT INTO booking (flight_id, passenger_id, seat, price)
VALUES (100, 9102, '1B', 100.00);
SELECT GET_LOCK('recovery_gate_exp2', 600) AS target_gate_acquired;
COMMIT;

COMMIT е намерно поставен по чекањето. Додека Controller го држи lock-от, Target останува блокиран пред таа наредба.

Слика 6 – Target чека пред COMMIT; INSERT е извршен во незавршената трансакција.

Во рачната проверка Monitor потврди дека Controller со ID 10 го поседува lock-от и Target со ID 8 е во состојба User lock. ID-вредностите се менуваат при ново поврзување.

SELECT IS_USED_LOCK('recovery_gate_exp2') AS lock_owner;

SELECT ID, DB, TIME, STATE, INFO
FROM information_schema.processlist
WHERE DB = 'airportdb_recovery_lab'
  AND STATE = 'User lock'
  AND INFO LIKE '%recovery_gate_exp2%';

Без ослободување на lock-от, контејнерот е прекинат со docker kill --signal=KILL и повторно стартуван. По поврзувањето беше проверено:

SELECT 'Потврдена 9101/1A' AS check_name, COUNT(*) AS actual_rows
FROM booking
WHERE passenger_id = 9101 AND flight_id = 100 AND seat = '1A'
UNION ALL
SELECT 'Незавршена 9102/1B', COUNT(*)
FROM booking
WHERE passenger_id = 9102 OR (flight_id = 100 AND seat = '1B');
Проверка Очекувано Добиено
Потврдена резервација 9101/1A 1 1
Незавршена резервација 9102/1B 0 0

Резултат: потврдената резервација остана, а незавршената беше поништена. При пребарување на табелата booking остана само резервацијата на патник 9101.

Слика 7 – Состојба по опоравувањето: зачувана е само потврдената резервација.

7. Експеримент 3 – Атомичност на две поврзани промени

Цел: да се провери дека по падот не останува делумна промена: промена на седиште без соодветниот помошен запис, или обратно.

За почетна состојба е креирана потврдена резервација за патник 9201, лет 100, седиште 1A (booking_id = 37). Табелата recovery_operation_event нема записи за exp3.

Слика 8 – Почетна резервација на седиште 1A.

Controller го зазема recovery_gate_exp3. Во Target се извршени две измени во истата трансакција:

SELECT booking_id INTO @exp3_booking_id
FROM booking
WHERE passenger_id = 9201 AND flight_id = 100 AND seat = '1A';

START TRANSACTION;
UPDATE booking SET seat = '1C'
WHERE booking_id = @exp3_booking_id;

INSERT INTO recovery_operation_event
    (experiment, booking_id, before_seat, after_seat)
VALUES ('exp3', @exp3_booking_id, '1A', '1C');

SELECT GET_LOCK('recovery_gate_exp3', 600) AS target_gate_acquired;
COMMIT;

Мониторингот покажа дека Target со ID 19 чека во User lock, додека Controller со ID 21 го поседува lock-от. Затоа COMMIT сѐ уште не е извршен.

Слика 9 – Контролирано чекање пред COMMIT во експериментот за атомичност.

Потоа следеше истиот насилен прекин (SIGKILL) и повторно стартување. По опоравувањето беа проверени трите исходи:

SELECT 'Оригинално 1A' AS check_name, COUNT(*) AS actual_rows
FROM booking WHERE passenger_id = 9201 AND flight_id = 100 AND seat = '1A'
UNION ALL
SELECT 'Незавршено 1C', COUNT(*)
FROM booking WHERE passenger_id = 9201 AND seat = '1C'
UNION ALL
SELECT 'Незавршен exp3 запис', COUNT(*)
FROM recovery_operation_event WHERE experiment = 'exp3';
Проверка Очекувано Добиено
Оригиналното седиште 1A постои 1 1
Промената на 1C не постои 0 0
Новиот exp3 запис не постои 0 0

Резултат: двете незавршени измени се поништени. Патникот 9201 повторно е на 1A, а во помошната табела нема exp3 запис. Ова ја потврдува атомарноста на трансакцијата во експериментот.

Слика 10 – Проверка по опоравувањето: резултати 1, 0 и 0.

8. Логови и checkpoint по опоравувањето

Логовите на Docker контејнерот се прегледани со:

docker logs airportdb_recovery_lab_mysql 2>&1 | grep -Ei \
'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'

Во зачуваната снимка се гледаат повеќе стартувања. При едно од претходните стартувања, во 10:41 UTC, InnoDB пријави примена на серија од 12 redo log records, откри една незавршена трансакција и пријави 1 row to undo. Поновите стартувања прикажуваат и Applying a batch of 0 redo log records. Ова не значи дека redo секогаш применува промени; обемот на работа зависи од состојбата при конкретниот пад.

Важно: овие лог-пораки се за лабораторијата низ повеќе извршувања. Пораките од 10:41 UTC не се означуваат како лог исклучиво од рачниот експеримент 3.

Слика 11 – InnoDB логи: неочекуван прекин, redo проверка и rollback.

По рачните тестови, со SHOW ENGINE INNODB STATUS; беше проверен делот LOG. Во моментот на проверката се добиени следните вредности:

Показател Добиена вредност
Log sequence number 30.912.187
Log written up to 30.912.187
Log flushed up to 30.912.187
Pages flushed up to 30.912.187
Last checkpoint at 30.912.187
Modified db pages 0
History list length 0

Еднаквите позиции на логот и checkpoint, заедно со Modified db pages = 0, покажуваат дека во моментот на оваа проверка немало модифицирани страници што чекале запис. Ова е снимка на состојбата по опоравувањето; само по себе не е доказ дека при тој конкретен старт биле применети redo записи. Доказот за активноста при recovery доаѓа од startup логовите и SQL-проверките по прекинот.

9. Сумаризација на тестирањето

Експеримент Начин на проверка Исход
1. Потврдена резервација Резервацијата постои пред и по SIGKILL. PASS – рачно проверено
2. Незавршена резервација Потврдената останува, незавршената отсуствува (1, 0). PASS – рачно проверено
3. Две измени во иста трансакција Старото седиште останува, двете нови промени отсуствуваат (1, 0, 0). PASS – рачно проверено
4. Потврдена и незавршена паралелно Според зачуваниот извештај од автоматското тестирање results/run-20260919T124053/summary.tsv. PASS – пријавено од автоматскиот тест; не е повторено рачно за оваа wiki страница

Експериментите намерно го прекинуваат MySQL процесот/контејнерот, а не целиот оперативен систем или физичкото напојување. Затоа резултатите не треба да се претставуваат како тест на физички прекин на струја. Податоците се зачувани во истиот Docker волумен, без враќање од backup.

10. Заклучок

Рачните тестови покажаа дека InnoDB ја зачувува потврдената резервација, ги поништува незавршените промени и не дозволува делумно зачувување на двете измени од експериментот за атомарност. Логовите ја дополнуваат SQL-проверката со увид во механизмите за опоравување. Оваа фаза се надоврзува на претходната фаза за трансакции, но проверува што се случува по ненадеен прекин на серверот, наместо само како трансакциите работат додека серверот е активен.

Last modified 7 days ago Last modified on 09/20/26 01:19:57

Attachments (14)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.