Связаны ли транзакции базы данных с условиями гонки?

Мне не совсем понятно, что делают транзакции в системах баз данных. Я знаю, что они могут быть использованы для полного отката списка обновлений (например, вычитайте деньги на одной учетной записи и добавьте их в другую), но это все, что они делают? В частности, можно ли их использовать для предотвращения условий гонки? Например:

// Java/JPA example
em.getTransaction().begin();
User u = em.find(User.class, 123);
u.credits += 10;
em.persist(u); // Note added in 2016: this line is actually not needed
em.getTransaction().commit();

(Я знаю, что это, вероятно, может быть написано как один запрос на обновление, но это не всегда так)

Защищен ли этот код от условий гонки?

Меня больше всего интересует MySQL5 + InnoDB, но общие ответы также приветствуются.

Ответы

Ответ 1

TL/DR: транзакции по своей сути не предотвращают всех условий гонки. Вам по-прежнему требуется блокировка, обработка прерываний и повторных попыток или другие защитные меры во всех реализациях реальной базы данных. Транзакции - это не секретный соус, который вы можете добавить к своим запросам, чтобы сделать их безопасными из всех concurrency эффектов.

Выделение

Что вы получаете от своего вопроса: I в ACID - isolation. Академически чистая идея заключается в том, что транзакции должны обеспечивать идеальную изоляцию, чтобы результат был таким же, как если бы каждая транзакция выполнялась последовательно. На самом деле это редко случается в реальных реализациях РСУБД; возможности варьируются в зависимости от реализации, и правила могут быть ослаблены с помощью более слабого уровня изоляции, такого как READ COMMITTED. На практике вы не можете предположить, что транзакции предотвращают все условия гонки, даже при SERIALIZABLE изоляции.

Некоторые РСУБД имеют более сильные способности, чем другие. Например, PostgreSQL 9.2 и new имеют неплохую изоляцию SERIALIZABLE, которая обнаруживает большинство (но не все) возможных взаимодействий между транзакциями и прерывает все, кроме одной из конфликтующих транзакций. Таким образом, он может безопасно выполнять транзакции параллельно.

Немногие, если есть 3 системы имеют поистине совершенную изоляцию SERIALIZABLE, которая предотвращает все возможные расы и аномалии, включая такие проблемы, как блокировка эскалации и блокировка порядка блокировки.

Даже при сильной изоляции некоторые системы (например, PostgreSQL) прерывают конфликтующие транзакции, а не заставляют их ждать и запускать их последовательно. Ваше приложение должно помнить, что он делает, и повторить попытку транзакции. Поэтому, в то время как транзакция помешала concurrency -связанным аномалиям быть сохраненной в БД, она сделала это способом, который не является прозрачным для приложения.

Атомарность

Возможно, основной целью транзакции базы данных является то, что она обеспечивает атомарную фиксацию. Изменения не вступают в силу до момента совершения транзакции. Когда вы совершаете фиксацию, все изменения вступают в силу одновременно с другими транзакциями. Никакая транзакция никогда не увидит только некоторые изменения, сделанные транзакцией 1,2. Аналогично, если вы ROLLBACK, то ни одна из транзакционных изменений никогда не увидит никакую другую транзакцию; как будто ваша транзакция никогда не существовала.

Что A в ACID.

Прочность

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

Консистенция:

См. wikipedia

Оптимистичное управление concurrency

Вместо использования уровней блокировки и/или высокой изоляции для ORM, таких как Hibernate, EclipseLink и т.д., используется оптимистичный concurrency контроль (часто называемая "оптимистичная блокировка" ) для преодоления ограничений более слабых уровней изоляции при сохранении производительности.

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

Ссылки

В дополнение к текстовым ссылкам см. раздел документации по PostgreSQL по блокировке, изоляции и concurrency. Даже если вы используете другую РСУБД, вы узнаете многое из понятий, которые она объясняет.


1 Я игнорирую редко реализованный уровень изоляции READ UNCOMMITTED здесь для простоты; он допускает грязные чтения.

2 Как отмечает @meriton, следствие не обязательно верно. Phantom reads происходит в чем-либо ниже SERIALIZABLE. Одна часть транзакции, выполняемой в процессе выполнения, не видит некоторых изменений (транзакцией, еще не совершенной), то следующая часть транзакции, выполняемой в процессе выполнения, видит изменения, когда совершается другая транзакция.

3 Ну, IIRC SQLite2 делает это благодаря блокировке всей базы данных при попытке записи, но это не то, что я бы назвал идеальным решением для проблем concurrency.

Ответ 2

Уровень базы данных поддерживает атомарность транзакций в разной степени, называемые уровнями изоляции. Проверьте документацию вашей системы управления базами данных на поддерживаемые уровни изоляции и их компромиссы. Самый сильный уровень изоляции, Serializable, требует выполнения транзакций, как если бы они выполнялись один за другим. Обычно это реализуется с использованием эксклюзивных блокировок в базе данных. Это может быть причиной взаимоблокировок, которые система управления базами данных обнаруживает и исправляет, откатываясь от некоторых задействованных транзакций. Этот подход часто упоминается как пессимистическая блокировка.

Многие объектно-реляционные сопоставители (включая провайдеры JPA) также поддерживают оптимистичную блокировку, когда конфликты обновления не предотвращаются в базе данных, а обнаруживаются в уровне приложений, что затем откатывает транзакцию. Если вы включили оптимистичную блокировку, типичное выполнение кода вашего примера будет выдавать следующие sql-запросы:

select id, version, credits from user where id = 123;  

Скажем, это возвращает (123, 13, 100).

update user set version = 14, credit = 110 where id = 123 and version = 13;

В базе данных указывается, сколько строк обновляется. Если это было одно, не было никакого противоречивого обновления. Если это было ноль, произошло конфликтное обновление, и поставщик JPA выполнит

rollback;

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

Резюме. При любом подходе ваше утверждение может быть безопасным из условий гонки.

Ответ 3

Это зависит от уровня изоляции (в сериализованном состоянии это предотвратит состояние гонки, так как обычно в сериализованном изолированном уровне транзакции обрабатываются последовательно, а не в паралеллере (или используется, по крайней мере, эксклюзивная блокировка, поэтому транзакции, которые изменяют одни и те же строки, выполняются последовательно). Чтобы предотвратить состояние гонки, лучше вручную заблокировать запись (mysql, например, поддерживает "select... for update", который использует блокировку записи для выбранных записей)

Ответ 4

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

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

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