Когда использовать SELECT… FOR UPDATE?

119

Пожалуйста, помогите мне разобраться в сценарии использования SELECT ... FOR UPDATE.

Вопрос 1. Является ли следующий хороший пример того, когда SELECT ... FOR UPDATEследует использовать?

Дано:

  • комнаты [ID]
  • теги [id, name]
  • room_tags [room_id, tag_id]
    • room_id и tag_id - внешние ключи

Приложение хочет вывести список всех комнат и их тегов, но ему необходимо различать комнаты без тегов и комнаты, которые были удалены. Если SELECT ... FOR UPDATE не используется, может произойти следующее:

  • Первоначально:
    • комнаты содержат [id = 1]
    • теги содержат [id = 1, name = 'cats']
    • room_tags содержит [room_id = 1, tag_id = 1]
  • Поток 1: SELECT id FROM rooms;
    • returns [id = 1]
  • Поток 2: DELETE FROM room_tags WHERE room_id = 1;
  • Поток 2: DELETE FROM rooms WHERE id = 1;
  • Поток 2: [совершает транзакцию]
  • Поток 1: SELECT tags.name FROM room_tags, tags WHERE room_tags.tag_id = 1 AND tags.id = room_tags.tag_id;
    • возвращает пустой список

Теперь поток 1 думает, что у комнаты 1 нет тегов, но на самом деле комната была удалена. Чтобы решить эту проблему, поток 1 должен SELECT id FROM rooms FOR UPDATEпредотвратить удаление потока 2 roomsдо тех пор, пока поток 1 не будет завершен. Это правильно?

Вопрос 2 : Когда следует использовать SERIALIZABLEизоляцию транзакций, а не READ_COMMITTEDс SELECT ... FOR UPDATE?

Ожидается, что ответы будут переносимы (не привязаны к базе данных). Если это невозможно, объясните, почему.

Гили
источник
2
Какую СУБД вы используете?
Quassnoi
2
@Quassnoi, как упоминалось в конце вопроса, я ищу портативное (не зависящее от базы данных) решение.
Гили
2
Есть варианты REPEATABLE_READи READ_COMMITTEDдаже переносные варианты? Единственные результаты, которые я получаю для них, относятся к серверу MSSQL
Билли Онил,
3
@BillyONeal: обратите внимание, что режимы изоляции гарантируют, что вы не увидите причуд, которые они не разрешают, но ничего не говорите о допустимых причудах. Это означает, что установка, скажем, READ COMMITTEDрежима не определяет, действительно ли вы будете видеть записи, зафиксированные другой транзакцией: это только гарантирует, что вы никогда не увидите незафиксированные записи.
Quassnoi
3
А select ... for updateпо- roomsпрежнему можно room_tagsбудет удалить, потому что это отдельные таблицы. Вы хотели спросить, for updateпредотвратит ли этот пункт удаление из rooms?
Крис Саксон,

Ответы:

84

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

Однако в некоторых системах блокировка является побочным эффектом управления параллелизмом, и вы можете добиться тех же результатов без FOR UPDATEявного указания .


Чтобы решить эту проблему, поток 1 должен SELECT id FROM rooms FOR UPDATEпредотвратить удаление потока 2 roomsдо тех пор, пока поток 1 не будет завершен. Это правильно?

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

  • MyISAMin MySQL(и несколько других старых систем) блокирует всю таблицу на время выполнения запроса.

  • В SQL Server, SELECTзапросы в месте , разделяемые блокировки на записи / страницах / таблицах они были допрошены, в то время как DMLзапросы место блокировки обновления (которые позже получить звание исключающих или понижены до разделяемых блокировок). Эксклюзивные блокировки несовместимы с разделяемыми блокировками, поэтому любой запрос SELECTили DELETEбудет блокироваться до тех пор, пока не будет зафиксирован другой сеанс.

  • В базах данных, использование которых MVCC(например Oracle, PostgreSQL, MySQLс InnoDB), DMLзапрос создает копию записи (в той или иной способ) и , как правило читатели не блокируют писателей и наоборот. Для этих баз данных, SELECT FOR UPDATEбы пригодиться: он будет блокировать либо SELECTили DELETEзапрос до другого сеанса фиксаций, так же , как SQL Serverделает.

Когда следует использовать REPEATABLE_READизоляцию транзакций по сравнению READ_COMMITTEDс SELECT ... FOR UPDATE?

Как правило, REPEATABLE READне запрещает фантомные строки (строки, которые появились или исчезли в другой транзакции, а не были изменены)

  • В Oracleи более ранних PostgreSQLверсиях REPEATABLE READэто фактически синоним SERIALIZABLE. По сути, это означает, что транзакция не видит изменений, внесенных после ее запуска. Таким образом, в этой настройке последний Thread 1запрос вернет комнату, как если бы она никогда не удалялась (что может быть, а может и не быть тем, что вы хотели). Если вы не хотите показывать комнаты после того, как они были удалены, вы должны заблокировать строки с помощьюSELECT FOR UPDATE

  • В InnoDB, REPEATABLE READи SERIALIZABLEэто разные вещи: читатели в SERIALIZABLEнаборе режима следующего ключа на записях , которые они оценивают, эффективно предотвращающие попутной DMLна них. Таким образом, вам не нужны SELECT FOR UPDATEв сериализуемом режиме, но они нужны в REPEATABLE READили READ COMMITED.

Обратите внимание, что стандарт по режимам изоляции предписывает, что вы не видите определенных причуд в своих запросах, но не определяет, каким образом (с блокировкой или с MVCCили иначе).

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

Quassnoi
источник
1
Последний пункт - это суть дела, я думаю: «вам не нужен SELECT FOR UPDATE в сериализуемом режиме, но они нужны в REPEATABLE READ или READ COMMITED».
Колин т Харт
Ты прав. Второй вопрос следовало бы спросить , когда SERIALIZABLEследует использовать в сравнении READ_COMMITTEDс SELECT ... FOR UPDATE. Не могли бы вы обновить свой ответ, чтобы отразить этот обновленный вопрос?
Gili
1
@Gili: «вам не нужен SELECT FOR UPDATEв сериализуемом режиме», с InnoDB. В других MVCCсистемах эти два слова являются синонимами, и они вам действительно нужны SELECT FOR UPDATE.
Quassnoi
1
Я считаю, что сообщение Колина отвечает на мои конкретные вопросы лучше, чем ваш ответ, но я ценю все ссылки, которые вы предоставили. Я приму ответ, который лучше всего сочетает в себе два (конкретные ответы вверху, вспомогательные ссылки ниже).
Гили
This depends on the concurrency control your database system is using: Я думаю, что вы секете волосы. Все случаи, которые вы перечисляете ниже, говорят о том, что комната не удаляется SELECTдо конца транзакции. Итак, не следует ли отвечать просто Yesс помощью вспомогательных ссылок ниже?
Gili
33

Короткие ответы:

Q1: Да.

Q2: Неважно, что вы используете.

Длинный ответ:

A select ... for updateбудет (как это подразумевается) выбирать определенные строки, но также блокировать их, как если бы они уже были обновлены текущей транзакцией (или как если бы обновление идентификатора было выполнено). Это позволяет вам снова обновить их в текущей транзакции и затем зафиксировать, при этом другая транзакция не сможет каким-либо образом изменить эти строки.

С другой стороны, это как если бы следующие два оператора выполнялись атомарно:

select * from my_table where my_condition;

update my_table set my_column = my_column where my_condition;

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

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

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

Колин т Харт
источник
1
Я думаю, What transaction isolation levels do guarantee [...] is the consistency of data once transactions are completed.неправильно подразумевает, что уровни изоляции не влияют на то, что происходит во время транзакции. Я рекомендую пересмотреть этот раздел и предоставить более подробную информацию о том, как они влияют на то, что вы видите (или не видите) во время транзакции.
Gili
1
Я считаю, что ваш пост отвечает на мои конкретные вопросы лучше, чем Квассной, но я ценю все предоставленные им ссылки. Я приму ответ, который лучше всего сочетает в себе два (конкретные ответы вверху, вспомогательные ссылки ниже).
Gili
Запирание и изоляция взаимозаменяемы. Есть ли какие-нибудь книги, чтобы узнать об этом?
Чао