Рівні ізоляції транзакцій з прикладами на PostgreSQL

Вступ
В стандарті SQL описується чотири рівні ізоляції транзакцій — Read uncommited (Читання незафіксованих даних), Read committed (Читання зафіксованих даних), Repeatable read (Повторюване читання) і Serializable (Сериализуемость). У даній статті буде розглянуто життєвий цикл чотирьох паралельно виконуваних транзакцій з рівнями ізоляції Read committed і Serializable.
Для рівня ізоляції Read committed допустимі наступні особливі умови читання даних:
Неповторне читання — транзакція повторно читає ті ж дані, що і раніше, і виявляє, що вони були змінені інший транзакцією (яка завершилася після першого читання).
Фантомне читання — транзакція повторно виконує запит, який повертає набір рядків для деякої умови, і виявляє, що набір рядків, які відповідають умові, змінився з-за транзакції, що завершився за цей час.
Що ж стосується Serializable, то даний рівень ізоляції самий суворий, і не має феноменів читання даних.
ACID або 4 властивості транзакцій
Перш ніж приступимо до розгляду рівнів ізоляції транзакції в парі слів згадаємо про основні вимоги до транзакційної системи.
Atomicity (атомарність) — виражається в тому, що транзакція повинна бути виконана в цілому або не виконано зовсім.
Consistency (узгодженість) — гарантує, що по мірі виконання транзакцій, дані переходять з одного узгодженого стану в інший, тобто транзакція не може зруйнувати взаємної узгодженості даних.
Isolation (ізольованість) — локалізація користувальницьких процесів означає, що конкурують за доступ до БД транзакції фізично обробляються послідовно, ізольовано один від руга, але для користувачів, це виглядає, як ніби вони виконуються паралельно.
Durability (довговічність) — стійкість до помилок — якщо транзакція успішно завершене, то ті зміни у даних, що були нею зроблені, не можуть бути втрачені ні за яких обставин.
Рівень ізоляції Read Committed
За замовчуванням в PostgreSQL рівень ізоляції Read Committed. Такий рівень ізоляції завжди дозволяє бачити зміни внесені успішно завершеними операціями у решти паралельно відкритих транзакції. В транзакції, що працює на цьому рівні, запит SELECT (без пропозиції FOR UPDATE/SHARE) бачить тільки ті дані, які були зафіксовані до початку запиту; він ніколи не побачить незафіксованих даних або змін, внесених у процесі виконання запиту паралельними транзакціями. По суті запит SELECT бачить знімок бази даних в момент початку виконання запиту. Однак SELECT бачить результати змін, внесених раніше до цієї транзакції, навіть якщо вони ще не зафіксовані. Також зауважте, що два послідовних оператора SELECT можуть бачити різні дані навіть в рамках однієї транзакції, якщо якісь інші транзакції зафіксують зміни після виконання першого SELECT.
Суть рівня ізоляції Read Committed показана на діаграмі 1.
Примітка: В таблиці вже знаходиться запис з першою версією даних (v1). Прошу сприймати команди SELECT v1; — як команду повертає дані версії v1, а UPDATE v1 to v2; — як команду оновлення даних з першої до другої версії.
Створимо до бази даних 4 підключення і відкриємо в кожному з підключень по транзакції з рівнем ізоляції Read CommittedRead Committed
Read Committed
Крок 1. У початковий момент часу до будь-яких змін даних всіх транзакціях доступна початкова версія даних (v1);Read Committed
Крок 2. В ході роботи Першої транзакції дані без будь-яких блокувань успішно оновлюються до «другої версії» (v2);Read Committed
Крок 3. Зміни зроблені в Першій транзакції будуть видні тільки їй самій (SELECT повертає v2), і не будуть доступні іншим транзакціям (SELECT запит у Другій і Четвертій транзакції повертає v1);Read Committed
Крок 4. Закриття Першої транзакції. Всі зміни зроблені в ході її роботи успішно фіксуються;Read Committed
Крок 5. Після закриття Першої транзакції (попередній крок), зміни зроблені в ході її виконання над даними (оновлення з v1 до v2) були поширені на інші транзакції, SELECT запит у решти 3 відкритих транзакції повертає v2 («Неповторне читання», відрізните рівня ізоляції Read Committed від Serializable);Read Committed
Крок 6. Запит на оновлення даних у Другій транзакції до «третьої версії» успішно виконується, але запити на оновлення даних блокують змінювані рядка на подальше їх зміна, до завершення Другої транзакції;Read Committed
Крок 7. З-за блокування накладеної на дані, Третя транзакція переходить в режим очікування із запитом на видалення даних. Очікування Третьої транзакції буде відбуватися до закриття Другої транзакції;Read Committed
Крок 8. Незважаючи на те, що Третя транзакція очікує закриття Другої, а Друга так і Четверта транзакції без будь-яких проблем продовжують свою роботу, повертаючи дані згідно своїм версіями. Друга повертає v3, Четверта повертає v2;Read Committed
Крок 9. Закриття Другий транзакції призводить до розблокування даних для зміни. Рівень ізоляції Read Committed дозволяє продовжити роботу Третьої транзакції без виклику помилки. Отримавши доступ на зміну нової версії даних (v3) Третя транзакція УСПІШНО тут же їх «видаляє» (відмінність Read Committed від Serializable);Read Committed
Крок 10. До закриття Третьої транзакції, дані будуть віддаленими тільки всередині Третьої транзакції. Четвертою транзакції до закриття Третьої дані доступні (SELECT запит у Четвертій транзакції повертає v3);Read Committed
Крок 11. Закриття Третьої транзакції. Всі зміни зроблені в ході її роботи успішно фіксуються;Read Committed
Крок 12. Запит на отримання даних у Четвертій транзакції нічого не повертає («Фантомне читання», SELECT запит повертає 0 записів).Read Committed
Примітка. На діаграмі показано дію запиту INSERT. В рамках даного рівня ізоляції, додані рядки, наприклад у кроці 3, Першої транзакції були б ВИДНІ іншим транзакцій після завершення Першої транзакції.
Часткова ізоляція транзакцій, що забезпечується в режимі Read Committed, прийнятна для безлічі додатків. Цей режим швидкий і простий у використанні, проте він підходить не для всіх випадків. Додаткам, які виконують складні запити і зміни, можуть знадобитися більш суворо узгоджене представлення даних, наприклад Serializable.
Рівень ізоляції Serializable
Ізоляція рівня Serializable забезпечує безперешкодний доступ до бази даних транзакцій з SELECT запитами. Але для транзакцій з запитами UPDATE і DELETE, рівень ізоляції Serializable не допускає модифікації однієї і тієї ж рядки в рамках різних транзакцій. При ізоляції такого рівня всі транзакції обробляються так, як ніби вони всі запущені послідовно (одна за одною). Якщо дві одночасні транзакції спробують оновити одну і тугіше рядок, то це буде не можливо. У такому разі PostgreSQL примусить транзакцію, другу, так і всі наступні, що намагалися змінити рядок до скасування (відкоту — ROLLBACK).
Суть рівня ізоляції Serializable показана на діаграмі 2.
Створимо до бази даних 4 підключення і відкриємо в кожному з підключень по транзакції з рівнем ізоляції SerializableSerializable
Serializable
Крок 1. Всіх транзакціях доступна початкова версія даних (v1);Serializable
Крок 2. В ході роботи Першої транзакції дані без будь-яких блокувань успішно оновлюються до «другої версії» (v2);Serializable
Крок 3. Зміни зроблені в Першій транзакції будуть видні тільки їй самій (SELECT повертає v2), і не будуть доступні іншим транзакціям (SELECT запит у Другій і Четвертій транзакції повертає v1);Serializable
Крок 4. Запит на оновлення даних в першій транзакції (крок 2), блокує оновлювані рядка, і переводить в режим очікування Другу транзакцію із запитом на видалення даних. Блокування транзакцій на оновлювані даних буде відбуватися до закриття Першої транзакції;Serializable
Крок 5. Незважаючи на те, що Друга транзакція очікує закриття Першої, як Третя так і Четверта транзакції без будь-яких проблем продовжують свою роботу, повертаючи дані згідно своїм версіями;Serializable
Крок 6. Завершення Першої трансакції знімає блокування з оновлюваних даних, але в рамках рівня ізоляції Serializable повторне оновлення даних в паралельних транзакцій заборонено, і тому в ході виконання Другої транзакції виникає помилка (відмінність Serializable від Read Committed);Serializable
Крок 7. Запит SELECT у Другій транзакції стає не можливим, так як помилка виникла на попередньому кроці скасовує («блокує») транзакцію. Запит SELECT Третьої та Четвертої транзакції повертають первісну версію даних (v1). Незважаючи на те, що Перша транзакція була завершена успішно, зміни не стали видні іншим відкритим транзакціям (відмінність Serializable від Read Committed). Відкриття П'ятої транзакції в лівому верхньому вікні;Serializable
Крок 8. Закриття Другий транзакції. Всі зміни зроблені даною транзакцією будуть скасовані, через що виникла помилки в ході її роботи;Serializable
Крок 9. Запит SELECT в П'ятій транзакції повертає нову версію даних (v2). Запит SELECT Третьої та Четвертої транзакції повертають первісну версію даних (v1);Serializable
Крок 10. Рівень ізоляції Serializable все також не дає оновлювати дані, запит UPDATE Третьої транзакції завершується не вдало, з відповідними наслідками для перебігу всієї транзакції (незважаючи на те, що Перша транзакція вже вдало завершилася, і всі внесені їй зміни збережені в базі даних). А от запит UPDATE П'ятої транзакції завершується успішно, так як вона відкрита після завершення Першої транзакції, і працює з новою версією даних;Serializable
Крок 11. Закриття Третьої транзакції. Всі зміни зроблені даною транзакцією будуть скасовані, через що виникла помилки в ході її роботи;Serializable
Крок 12. Транзакція Чотири все також показує, що у транзакцій з SELECT запитами немає жодних проблем, а П'ята транзакція отримує вже оновлені само собою дані (v5).Serializable
Примітка. На діаграмі показано дію запиту INSERT. В рамках даного рівня ізоляції, додані рядки, наприклад у кроці 3, Першої транзакції були б НЕ ДОСТУПНІ Другої, Третьої та Четвертої транзакцій після завершення Першої транзакції. Також на схемі не показаний результат ROLLBACK (Кроки 8 і 11). У разі якщо б Друга і Третя транзакції робили якісь зміни над заблокованими даними, то всі ці зміни не були б зафіксовані, так як транзакції завершуються невдало (суть властивості — Atomicity).
Рівень ізоляції Serializable гарантує, що всі порушені в транзакції дані не будуть змінені іншими транзакціями. На цьому рівні поява "фантомів" виключається, тому стають можливими складні конкурентні операції. На практиці такий рівень ізоляції потрібно в облікових системах.
Для транзакцій, що містять тільки SELECT запити, використання рівня ізоляції Serializable виправдовує себе тоді, коли ви не хочете бачити внесені зміни паралельно завершеними операціями в ході роботи поточної транзакції.
Аномалія серіалізації (Втрачений оновлення)
Ще один феномен читання даних, описується тим, що результат успішної фіксації групи транзакцій виявляється неузгодженим при різноманітних варіантах виконання цих транзакцій по черзі.
Зорієнтуйте, будь ласка, мене в коментарях, якщо я помиляюся щодо того, що аномалія серіалізації та втрачене оновлення пов'язані між собою феномени.
Документація на сайті PostgreSQL PRO пише, що Read Committed допускає «Serialization Anomaly». Вітчизняна Wikipedia, не наполягаючи на те, що таблиця відноситься саме до PostgreSQL, пише, що Read Commited запобігає аномалію серіалізації. Англійська Вікіпедію про такий феномен читання даних умалчивает. Але німецька Вікіпедія наводить у своїй версії таблиці феномен «Lost Updates», вказуючи на те, що Read Committed може бути не схильний до втрати оновлень з додатковим захистом через курсор (Cursor Stability). Українська Вікіпедія підтримує російськомовну версію статті, іспанська Вікіпедія підтримує англійську версію статті. Англомовна документація по PostgreSQL не відрізняється від документації з сайту PostgreSQL PRO.
Cursor Stability розширює блокувальний поведінка рівня READ COMMITED для SQL-курсорів, додаючи нову операцію читання (Fetch) по курсору rc (означає read cursor, тобто читання по курсору) і вимагаючи, щоб блокування встановлювалася на поточному елементі курсору. Блокування утримується до тих пір, поки курсор не буде переміщений (поки не змінитися його поточний елемент) або закритий, можливо, операцією фіксації. Природно, транзакція, що читає по курсору, може змінити поточний рядок (wc – запис за курсору), і в цьому випадку блокування запису цього рядка буде зберігатися до тих пір, поки транзакція не зафіксується, навіть після пересування курсору з наступною вибіркою наступного рядка.
Ось такий результат вийшов в PostgreSQL 9.6Serialization Anomaly, Lost Updates
Підсумок: 149.
Висновок
Розуміння рівнів ізоляції транзакцій є важливим аспектом при обробці даних в будь багатокористувацької СУБД. Рівні ізоляції мають чітко визначеними характеристиками і поведінкою. Більш високі рівні ізоляції зменшують можливості паралельної обробки даних і підвищують ризик взаємного блокування процесів. Тому коректне використання рівнів в залежності від завдань додатків завжди є вибором розробника залежно від вимог до забезпечення логічної цілісності даних, до швидкості і до можливості паралельної пакетної обробки.
Література
» MVCC Unmasked
» 13.2. Ізоляція транзакцій
» Критика рівнів ізольованості
» Рівні ізоляції транзакцій SQL. Шпаргалка
» Рівень ізольованості транзакцій
» … the lost update phenomena

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.