До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Всім привіт. На зв'язку Владислав Родін. В даний час я керівник курсу «Архітектор високих навантажень» в OTUS, а також викладаю на курсах, присвячених архітектурі ПЗ.

Окрім викладання, як ви могли помітити, я займаюся написанням авторського матеріалу для блогу OTUS на хабрі і сьогоднішню статтю хочу приурочити до запуску курсу «PostgreSQL»на який прямо зараз відкритий набір.

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Запровадження

В Минулого разу ми з вами поговорили про те, що транзакції в базах даних служать для вирішення двох завдань: забезпечення стійкості до відмов і доступу до даних в конкурентному середовищі. Для повноцінного виконання цих завдань транзакція повинна мати властивості ACID. Сьогодні ми докладно поговоримо про букву I (isolation) у цій абревіатурі.

Ізоляція

Ізоляція вирішує завдання доступу до даних у конкурентному середовищі, фактично надаючи захист від race condition'ів. В ідеалі, ізоляція означає серіалізацію, тобто властивість, що забезпечує те, що результат виконання транзакцій паралельно такий же, як якщо вони виконувались послідовно. Основна проблема цієї властивості полягає в тому, що вона дуже важко забезпечується технічно і як наслідок сильно б'є за продуктивністю системи. Саме тому ізоляцію часто послаблюють, приймаючи ризики виникнення деяких аномалій, про які йтиметься нижче. Можливість виникнення тих чи інших аномалій якраз і характеризує рівень ізоляції транзакцій.

Найбільш відомими аномаліями є: dirty read, non-repeatable read, phantom read, але насправді їх ще 5: dirty write, cursor lost update, lost update, read skew, write skew.

Dirty write

Суть аномалії у тому, що транзакції можуть перезаписувати незакоммічені дані.

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Ця аномалія небезпечна не лише тим, що дані можуть конфліктувати після коміту обох транзакцій (як на картинці), але й тим, що порушується атомарність: тому що ми дозволимо перезаписувати незакоммічені дані, то незрозуміло, як відкотити одну транзакцію, не зачепивши при цьому іншу .

Лікується аномалія досить просто: вішаємо блокування на запис перед початком запису, забороняючи іншим транзакціям змінювати запис доти, доки блокування не буде знято.

Dirty read

Dirty read означає прочитання незакоммічених даних.

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Проблеми виникають, коли на основі вибірки необхідно здійснити якісь дії або ухвалити рішення.

Для виправлення аномалії можна повісити блокування читання, але це сильно вдарить по продуктивності. Набагато простіше сказати, що для rollback'а транзакції вихідний стан даних (до початку запису) обов'язково має бути збережений у системі. Чому б не читати звідти? Це досить недорого, тому більшість баз даних прибирають dirty read за замовчуванням.

Lost update

Lost update означає втрачені оновлення, і переклад досить точно відображає суть проблеми:

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Фактично, результат транзакції Т2 було скасовано. Виправляється така ситуація явними чи неявними блокуваннями запису. Тобто ми або просто здійснюємо оновлення запису, і тоді виникає неявне блокування, або ми виконуємо виберіть для оновлення, викликаючи блокування на читання і на записи. Зверніть увагу, що така операція досить небезпечна: своїм «невинним» читанням, ми блокуємо інші читання. Деякі бази пропонують безпечніший select for share, що дозволяє читати дані, але не дозволяє їх змінювати.

Cursor lost update

Для більш тонкого контролю бази можуть пропонувати інші інструменти, наприклад курсор. Курсор- це структура, що містить набір рядків і що дозволяє за ними ітеруватися. declare cursor_name for select_statement. Вміст курсору описується select'ом.

Навіщо потрібний курсор? Справа в тому, що деякі бази даних пропонують блокування на всі записи, вибрані select'ом (read stability), або тільки на той запис, на якому знаходиться зараз курсор (cursor stability). При cursor stability здійснюється short lock, що дозволяє знизити кількість блокувань у разі, якщо ми итерируемся з великої вибірці даних. Тому аномалію lost update виділяють для курсору окремо.

Non-repeatable read

Non-repeatable read полягає в тому, що під час виконання нашої транзакції 2 послідовні читання одного і того ж запису призведе до отримання різних результатів, тому що інша транзакція втрутилася між цими двома читаннями, змінила наші дані і була закоммічена.

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Чому це взагалі проблема? Уявіть собі, що мета транзакції Т2 на зображенні вибрати всі товари, ціна яких менша, ніж 150 у.о. Хтось інший оновив ціну до 200 у.о. Таким чином, встановлений фільтр не спрацював.

Дані аномалії перестають виникати при додаванні двофазних блокувань або використання механізму MVCC, про що хотілося б поговорити окремо.

Phantom read

Фантомним називається читання даних, доданих іншою транзакцією.

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Як приклад можна спостерігати неправильну вибірку найдешевшого товару у разі даної аномалії.

Позбутися фантомних читань вже досить складно. Звичайного блокування недостатньо, адже не можемо заблокувати те, чого ще немає. 2PL-системи використовують предикативне блокування, тоді як MVCC-системи планувальник транзакцій скасовує транзакції, які можуть бути порушені вставкою. Як перший, так і другий механізми досить важковагові.

Read skew

Read skew виникає коли ми працюємо з кількома таблицями, зміст яких має змінюватись узгоджено.

Припустимо, є таблиці, що представляють посади та їх метаінформацію:

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Одна транзакція читає з таблиць, інша їх змінює:

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

В результаті виконання транзакції Т1 у посту title = Good, а updated_by = T2, що є деякою невідповідністю.

Фактично, це non-repeatable read, але у складі кількох таблиць.

Для виправлення, Т1 може вішати блокування на всі рядки, які вона читатиме, що не дасть транзакції Т2 змінювати інформацію. У випадку MVCC транзакція Т2 буде скасована. Захист від цієї аномалії може стати важливим, якщо ми використовуємо курсори.

Write skew

Цю аномалію теж простіше пояснити на прикладі: припустимо, що в нашій системі хоча б один лікар повинен бути на чергуванні, але обидва лікарі вирішили своє чергування скасувати:

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

Аномалія призвела до того, що жоден із лікарів не вийде на чергування. Чому так вийшло? Тому що транзакція перевіряла умову, яка може бути порушена іншою транзакцією, а через ізоляцію ми не побачили цієї зміни.

Це той же non-repeatable read. Як варіант, select'и можуть вішати блокування на ці записи.

Write skew та read skew є комбінаціями попередніх аномалій. Можна розглянути write skew, що є по суті phantom read'ом. Розглянемо таблицю, в якій є імена співробітників, їх зарплата та проект, на якому вони працюють:

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

До чого може призвести ослаблення рівня ізоляції транзакцій у базах даних

У підсумку ми отримуємо наступну картину: кожен менеджер думав, що його зміна не призведе до виходу за бюджет, тому вони внесли кадрові зміни, які в сумі призвели до перевитрати.

Причина виникнення проблеми така сама, як і у фантомному читанні.

Висновки

Ослаблення рівня ізоляції транзакцій у базі даних є компромісом між безпекою та продуктивністю, до вибору цього рівня слід підходити виходячи з потенційних ризиків для бізнесу у разі виникнення тих чи інших аномалій.

Дізнатись детальніше про курс.

Джерело: habr.com

Додати коментар або відгук