Здравейте на всички. Аз съм Владислав Родин. В момента аз ръководя курса "Архитект на високи натоварвания" в OTUS и също така преподавам на курсове, посветени на архитектурата на софтуера.
Освен преподаването, както сте забелязали, аз пиша авторски материали за блога на OTUS в Хабре и днешната статия искам да посветя на старта на курса , за който в момента е открито записване.

Въведение
В С вас говорихме за това, че транзакциите в базите данни служат за решаване на две задачи: осигуряване на устойчивост на грешки и достъп до данни в конкурентна среда. За да изпълнява тези задачи, транзакцията трябва да притежава свойствата ACID. Днес ще говорим подробно за буквата I (изолация) в тази абревиатура.
Изолация
Изолацията решава задачата за достъп до данни в конкурентна среда, предоставяйки фактически защита от 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.
Загубено обновление
Загубеното обновление означава загубени актуализации, и преводът точно отразява същността на проблема:

Всъщност резултатът от транзакция T2 е отменен. Тази ситуация се поправя с явни или неявни блокировки на записа. Тоест, или просто извършваме обновление на записа, при което възниква неявна блокировка, или изпълняваме select for update, предизвиквайки възникване на блокировка на четене и на запис. Обърнете внимание, че такава операция е доста опасна: с нашето "невинно" четене блокираме други четения. Някои бази предлагат по-безопасен select for share, който позволява четене на данни, но не разрешава тяхното променяне.
Загубено обновление на курсора
За по-фин контрол, базите данни могат да предлагат и други инструменти, например курсор. Курсорът е структура, съдържаща набор от редове, и позволява итериране през тях. declare cursor_name for select_statement. Съдържанието на курсора се описва с select.
Каква е нуждата от курсор? Факт е, че някои бази данни предлагат блокировка на всички записи, избрани от select (read stability), или само на записа, на който в момента се намира курсорът (cursor stability). При cursor stability се извършва кратка блокировка, което позволява да се намали броят на блокировките, когато итерираме през голям набор от данни. Затова аномалията загубено обновление се отличава за курсора отделно.
Неповторяемо четене
Неповторяемото четене означава, че по време на изпълнението на нашата транзакция, 2 последователни четения на една и съща запис ще доведат до получаване на различни резултати, защото друга транзакция е намесила между тези две четения, променила е нашите данни и е била комитната.

Защо е изобщо проблем? Представете си, че целта на транзакцията T2 на изображението е да избере всички стоки с цена под 150 у.е. Някой друг е актуализирал цената на 200 у.е. По този начин зададеният филтър не сработва.
Тези аномалии спират да се появяват при добавяне на двуфазни заключвания или при използване на механизма MVCC, за което бих искал да поговоря подробно.
Фантомно четене
Фантомно четене е, когато данните са добавени от друга транзакция.

Като пример можем да наблюдаваме неправилна селекция на най-евтината стока при възникването на тази аномалия.
Премахването на фантомни четения е доста сложно. Обикновеното заключване не е достатъчно, тъй като не можем да блокираме неща, които все още не съществуват. 2PL-системите използват предикативно заключване, докато MVCC-системите отменят транзакции, които могат да бъдат нарушени от вложени операции. И двата механизма са доста тежки.
Проблем с четене
Проблем с четене възниква, когато работим с няколко таблици, чийто съдържание трябва да се променя синхронизирано.
Да предположим, че имаме таблици, представляващи публикации и тяхната метаинформация:

Една транзакция чете от таблиците, докато друга ги променя:

В резултат на изпълнението на транзакция T1, заглавието на публикацията е Good, а updated_by = T2, което представлява известно несъответствие.
Всъщност, това е non-repeatable read, но в рамките на няколко таблици.
За да се поправи, T1 може да блокира всички редове, които ще чете, което ще забрани на транзакция T2 да променя информацията. В случай на MVCC транзакция T2 ще бъде отменена. Защитата от тази аномалия може да стане важна, ако използваме курсори.
Проблем с писане
Тази аномалия също е по-лесна за обяснение с пример: да предположим, че в нашата система поне един доктор трябва да бъде на дежурство, но и двамата доктори решават да отменят своето дежурство:


Аномалията води до това, че нито един от докторите не ще излезе на дежурство. Защо се получи така? Защото транзакцията проверяваше условие, което може да бъде нарушено от друга транзакция, а заради изолацията не видяхме това изменение.
Това е същото non-repeatable read. Като опция, select командите могат да блокират тези записи.
Записаното изкривяване и прочетеното изкривяване са комбинации от предишните аномалии. Можем да разгледаме записаното изкривяване, което по същество е фантомно четене. Нека да разгледаме таблица, в която има имена на служители, заплатите им и проектите, по които работят:


В крайна сметка получаваме следната картинa: всеки мениджър смяташе, че промяната му няма да доведе до излизане извън бюджета, затова направиха кадрови промени, които в сумата доведоха до надхвърляне на разходите.
Причината за възникването на проблема е точно същата като при фантомното четене.
Изводи
Облекчаването на нивото на изолация на транзакциите в базата данни е компромис между безопасност и производителност, и изборът на това ниво трябва да бъде направен, вземайки предвид потенциалните рискове за бизнеса при възникване на подобни аномалии.
Източник: habr.com
