Вы находитесь на странице: 1из 31

Лекция 20.10.2020 г.

Транзакции. Уровни изоляции.


Блокировки.

1. Понятие транзакции.
2. Операторы транзакций.
3. Уровни изоляции.
4. Блокировки.
Понятие транзакции

Транзакция — это операция, состоящая из


одного или нескольких запросов к базе данных.

Суть транзакций — обеспечить корректное


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

Любая транзакция либо выполняется


полностью, либо не выполняется вообще.
В транзакционной модели есть два
фундаментальных понятия: COMMIT и
ROLLBACK.

COMMIT означает фиксацию всех изменений в транзакции.

ROLLBACK означает отмену (откат) изменений, произошедших в транзакции.

При старте транзакции все последующие изменения сохраняются во


временном хранилище.

В случае выполнения COMMIT, все изменения, выполненные в рамках одной


транзакции, сохранятся в физическую БД.

В случае выполнения ROLLBACK произойдет откат и все изменения,


выполненные в рамках этой транзакции, не сохранятся.
Транзакция начинается со специального
запроса «START TRANSACTION»,
либо «BEGIN».

Чтобы закончить транзакцию, нужно либо


зафиксировать изменения (запрос COMMIT),
либо откатить их (запрос ROLLBACK).
Пример с COMMIT:

set autocommit=0; //отключаем autocommit


Start transaction; (также, можно написать
BEGIN; )
…какие-то действий с БД (insert,
update,delete…)
commit; //Фиксация действий, запись их в
физическую БД
Пример с ROLLBACK:

set autocommit=0; //отключаем autocommit


Start transaction;

…какие-то действия с БД (insert,


update,delete…)

rollback; // отменяем серию действий, не


производим запись в физическую БД
В MySQL не существует механизма
вложенных транзакций. Одно соединение
с БД — одна транзакция.

Новая транзакция в пределах одного


соединения может начаться только после
завершения предыдущей.

Для некоторых операторов нельзя


выполнить откат с помощью ROLLBACK.
Сюда входят запросы CREATE, ALTER,
DROP, TRUNCATE, COMMENT,
RENAME.
Следующие операторы неявно завершают транзакцию (как если
бы перед их выпол­нением был выдан COMMIT):

• ALTER TABLE
• DROP DATABASE
• LOAD MASTER DATA
• SET AUTOCOMMIT = 1
• BEGIN
• DROP INDEX
• LOCK TABLES
• START TRANSACTION
• CREATE INDEX
• DROP TABLE
• RENAME TABLE
• TRUNCATE TABLE
Рассмотрим практический пример:

Есть 2 таблицы, пользователи — users и


информация о пользователях — user_info.

Представим, что нам нужно либо выполнить


3 запроса к базе данных, либо не выполнять
их вообще, так как иначе это приведет к
сбоям в работе приложения.
Start transaction;

INSERT INTO user (id, nik) VALUES (1, 'nikola');

INSERT INTO user_info (id, id_user, item_name,


item_value) VALUES (1, 1, 'Имя', 'Николай');

INSERT INTO user_info (id, id_user, item_name,


item_value) VALUES (2, 1, 'Возраст', '24');

commit;
Представим,
что во время выполнения этой транзакции, другой
пользователь создал вторую параллельную
транзакцию и сделал запрос

SELECT * FROM user

после того, как в нашей транзакции был выполнен


первый запрос

«INSERT INTO user (id, nik) VALUES (1, ‘nikola’)».


Что увидит пользователь второй
транзакции?

Сможет ли он увидеть
вставленную запись даже тогда,
когда результаты первой
транзакции еще не
зафиксировались (не произошел
COMMIT)?

Или он сможет увидеть изменения


только после того, как результаты
первой транзакции будут
зафиксированы?
Оказывается имеют место
быть оба варианта.

Все зависит от уровня


изоляции транзакции.
Уровни изоляций
0 — Чтение неподтверждённых данных (грязное чтение) (Read Uncommitted, Dirty
Read) — самый низкий уровень изоляции.
При этом уровне возможно чтение незафиксированных изменений параллельных транзакций.
Как раз в этом случае второй пользователь увидит вставленную запись из первой
незафиксированной транзакции.
Нет гарантии, что незафиксированная транзакция будет в любой момент откачена, поэтому
такое чтение является потенциальным источником ошибок.

1 — Чтение подтверждённых данных (Read Committed) 


Здесь возможно чтение данных только зафиксированных транзакций. Но на этом уровне
существуют две проблемы. В этом режиме строки, которые участвуют в выборке в рамках
транзакции, для других параллельных транзакций не блокируются, из этого вытекает
проблема № 1: «Неповторяемое чтение» (non-repeatable read) — это ситуация, когда в рамках
транзакции происходит несколько выборок (SELECT) по одним и тем же критериям, и между
этими выборками совершается параллельная транзакция, которая изменяет данные,
участвующие в этих выборках. Так как параллельная транзакция изменила данные, результат
при следующей выборке по тем же критериям в первой транзакции будет другой.  Проблема
№ 2 — «Фантомное чтение» — этот случай рассмотрен ниже.
Уровни изоляций
2 — Повторяемое чтение (Repeatable Read, Snapshot) 

На этом уровне изоляции так же возможно чтение данных только зафиксированных транзакций.
Также на этом уровне отсутствует проблема «Неповторяемого чтения», то есть строки, которые
участвуют в выборке в рамках транзакции, блокируются и не могут быть изменены другими
параллельными транзакциями.
Но таблицы целиком не блокируются.

Из-за этого остается проблема «фантомного чтения».

«Фантомное чтение» — это когда за время выполнения одной транзакции результат одних и тех же
выборок может меняться по причине того, что блокируется не вся таблица, а только те строки,
которые участвуют в выборке. Это означает, что параллельные транзакции могут вставлять строки
в таблицу, в которой совершается выборка, поэтому два запроса SELECT * FROM table могут дать
разный результат в разное время при вставке данных параллельными транзакциями.

3 — Сериализуемый (Serializable) — сериализуемые транзакции.


Самый надежный уровень изоляции транзакций, но и при этом самый медленный. На этом уровне
вообще отсутствуют какие либо проблемы параллельных транзакций, но за это придется платить
быстродействием системы, а быстродействие в большинстве случаев крайне важно.
По умолчанию в MySQL установлен уровень
изоляции № 2 (Repeatable Read)
Он наиболее удачный для большинства случаев.

С первого раза может показаться, что самый лучший


вариант № 3 — он самый надежный, но на практике вы
можете испытать большие неудобства из-за очень
медленной работы вашего приложения.

Помните, что многое зависит не от того, насколько


хорош уровень изоляции транзакций в БД, а от того, как
спроектировано ваше приложение.

При грамотном программировании, можно даже


использовать самый низкий уровень изоляции
транзакций — все зависит от особенностей структуры
и грамотности разработки вашего приложения.

Но не нужно стремиться к самому низкому уровню


изоляции — нет, просто если вы используете не самый
защищенный режим, следует помнить о проблемах
параллельных транзакций, в этом случае вы не
растеряетесь и все сделайте правильно.
Сводная таблица возможностей уровней
изоляции
SET TRANSACTION
Этот оператор устанавливает уровень изоляции следующей
транзакции, глобально либо только для текущего сеанса.

SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL


{ READ UNCOMMITTED | READ COMMITTED |
REPEATABLE READ | SERIALIZABLE }

Существующие соединения не затрагиваются. Для выполнения


этого оператора нужно иметь привилегию SUPER. Применение
ключевого слова SESSION уста­навливает уровень изоляции по
умолчанию всех будущих транзакций только для теку­щего
сеанса.
Банковское приложение является
классическим примером, демонстрирующим
необходимость транзакций.

Представьте банковскую базу данных с двумя табли­


цами:

checking (текущие счета) и savings (сберегательные


счета).

Чтобы перевести 200 долларов с текущего счета Джейн


на ее сберегательный счет, вам нужно сделать по
меньшей мере три шага.
Убедиться, что остаток на ее
текущем счете больше 200
долларов.

Вычесть 200 долларов из остатка


текущего счета.

Добавить 200 долларов к остатку


сберегательного счета.

Вся операция должна быть


организована как транзакция, чтобы
в случае неудачи на любом из трех
этапов все выполненные ранее шаги
были отменены.
Вы начинаете транзакцию
командой START
TRANSACTION,
а затем либо сохраняете
изменения командой
COMMIT,

либо отменяете их командой


ROLLBACK.

Код SQL для транзакции может выглядеть


следующим образом:
START TRANSACTION;

SELECT balance FROM checking WHERE customer_id =


10233276;

UPDATE checking SET balance = balance - 200.00


WHERE customer_id = 10233276;

UPDATE savings SET balance = balance + 200.00


WHERE customer_id = 10233276;

COMMIT;
Но сами по себе транзакции — это еще
не все.

Что произойдет в случае сбоя сервера


базы данных во время выполнения
четвертой строки?

Кто знает... Клиент, вероят­но, потеряет


200 долларов.

А если другой процесс вклинится


между выполнением строк 3 и 4 и
снимет весь остаток с текущего счета?

Банк предоставит клиенту кредит 200


долларов, даже не зная об этом.
Транзакций недостаточно, пока
система не прошла тест ACID.

Аббревиатура ACID
расшифровывается как atomicity,
consistency, isolation и durability
(атомарность, со­гласованность,
изолированность и долговечность).

Это тесно связанные критерии,


которым должна соответствовать
правильно функционирующая
система обработки транзакций.
Свойства транзакций
Атомарность
Транзакция должна функционировать как единая неделимая ра­бочая единица таким образом, чтобы вся она
была либо выполнена, либо отменена. Для атомарных транзакций не существует такого понятия, как
частичное выполнение: все или ничего.

Согласованность
База данных всегда должна переходить из одного согласован­ного состояния в другое. В нашем примере
согласованность гарантирует, что сбой между строками 3 и 4 не приведет к исчезновению с текущего счета
200 долларов. Поскольку транзакция не будет подтверждена, ни одно из изменений не отразится в базе
данных.

Изолированность
Результаты транзакции обычно невидимы другим транзак­циям, пока она не подтверждена. В нашем примере
это гарантирует, что, если программа суммирования остатков на банковских счетах будет запущена после
третьей строки перед четвертой, она по-прежнему увидит 200 долларов на те­кущем счете. Когда будем
рассматривать уровни изолированности, вы поймете, почему здесь сказано «обычно невидимы».

Долговечность (Устойчивость)
После подтверждения внесенные в ходе транзакции изменения становятся постоянными. Это значит, что они
должны быть записаны так, чтобы данные не потерялись при сбое системы. Долговечность, однако, является
несколько расплывчатой концепцией, поскольку у нее довольно много уровней.
Взаимоблокировки
Взаимоблокировка возникает тогда, когда две и более
транзакции взаимно удержи­вают и запрашивают блокировку
одних и тех же ресурсов, создавая циклическую за­висимость.

Такие состояния наблюдаются и в том случае, если транзакции


пытаются заблокировать ресурсы в разном порядке.

Они могут возникнуть, когда несколько транзакций блокируют


одни и те же ресурсы.

Для примера рассмотрим две транзакции, обращающиеся к


таблице StockPrice:
Транзакция № 1

START TRANSACTION;

UPDATE StockPrice SET close = 45.50 WHERE


stock_id = 4 and date = '2002-05-01';

UPDATE StockPrice SET close = 19.80 WHERE


stock_id = 3 and date = '2002-05-02';

COMMIT;
Транзакция № 2

START TRANSACTION;

UPDATE StockPrice SET high = 20.12 WHERE


stock_id = 3 and date = '2002-05-02';

UPDATE StockPrice SET high = 47.20 WHERE


stock_id = 4 and date = '2002-05-01';

COMMIT;
Ведение журнала транзакций

Помогает сделать транзакции более эффективными.

Вместо обнов­ления таблиц на диске после каждого изменения подсистема


хранения данных может изменить находящуюся в памяти копию данных.

Это происходит очень быстро.

Затем подсистема хранения запишет сведения об изменениях в журнал


транзакции, который хранится на диске и поэтому долговечен.

Это тоже доволь­но быстрая операция, поскольку добавление событий в


журнал сводится к опе­рации последовательного ввода/вывода в пределах
ограниченной области диска вместо случайного ввода/вывода в разных
местах.
Ведение журнала транзакций
Позже процесс обновит табли­цу на диске.

Таким образом, большинство подсистем хранения данных, которые


используют этот метод (упреждающую запись в журнал), дважды
сохраняют из­менения на диске.

Если сбой произойдет после внесения записи в журнал


транзакции, но до обнов­ления самих данных, подсистема хранения
может восстановить изменения после перезагрузки сервера.

Методы восстановления у каждой подсистемы хранения данных


различны.
Явные и неявные блокировки
В подсистеме хранения InnoDB применяется
двухфазный протокол блокировки.
Она может устанавливать блокировки в любой
момент транзакции, но не снимает их до
выполнения команд COMMIT или ROLLBACK.
Все блокировки снимаются одновременно. Ранее
описанные механизмы блокировки являются
неявными.
InnoDB обрабатывает блокировки автоматически
в соответствии с вашим уровнем изоляции.