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

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

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

Данный:

  • комнаты [id]
  • теги [id, name]
  • room_tags[room_id, tag_id]
    • room_id and tag_id are foreign keys

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

  • Initially:
    • rooms contains [id = 1]
    • теги содержат [id = 1, name = 'cats']
    • room_tags содержит [room_id = 1, tag_id = 1]
  • Thread 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: [совершает транзакцию]
  • Thread 1: SELECT tags.name FROM room_tags, tags WHERE room_tags.room_id = 1 AND tags.id = room_tags.tag_id;
    • returns an empty list

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

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

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


person Gili    schedule 07.06.2012    source источник
comment
Какую СУБД вы используете?   -  person Quassnoi    schedule 07.05.2013
comment
@Quassnoi, как упоминалось в конце вопроса, я ищу портативное (не зависящее от базы данных) решение.   -  person Gili    schedule 07.05.2013
comment
Варианты REPEATABLE_READ и READ_COMMITTED даже переносные? Единственные результаты, которые я получаю для них, относятся к серверу MSSQL.   -  person Billy ONeal    schedule 07.05.2013
comment
@BillyONeal, эти уровни изоляции определены стандартом SQL, так что да, они портативный.   -  person Gili    schedule 07.05.2013
comment
@BillyONeal: обратите внимание, что режимы изоляции гарантируют, что вы не увидите причуд, которые они не допускают, но ничего не говорите о допустимых причудах. Это означает, что установка, скажем, READ COMMITTED режима не определяет, действительно ли вы будете видеть записи, переданные другой транзакцией: это только гарантирует, что вы никогда не увидите незафиксированные записи.   -  person Quassnoi    schedule 07.05.2013
comment
select ... for update на rooms по-прежнему разрешает удаление room_tags, потому что это отдельные таблицы. Вы хотели спросить, предотвратит ли предложение for update удаление из rooms?   -  person Chris Saxon    schedule 12.05.2013
comment
@ChrisSaxon, да. Спасибо за исправление!   -  person Gili    schedule 14.05.2013
comment
ВЫБЕРИТЕ tags.name ИЗ room_tags, tags ГДЕ room_tags.tag_id = 1 И tags.id = room_tags.tag_id; , вы имеете в виду ГДЕ room_tags.room_id = 1 вместо tag_id = 1?   -  person luochen1990    schedule 01.06.2021
comment
@ luochen1990 хороший улов. Фиксированный.   -  person Gili    schedule 02.06.2021


Ответы (2)


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

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


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

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

  • MyISAM в 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», мне действительно следовало добавить «из-за побочных эффектов определенной реализации механизма базы данных».

person Quassnoi    schedule 07.05.2013
comment
Я думаю, что последний момент - это суть дела: вам не нужен SELECT FOR UPDATE в сериализуемом режиме, но они нужны в REPEATABLE READ или READ COMMITED. - person Colin 't Hart; 07.05.2013
comment
Ты прав. Второй вопрос должен был быть задан, когда SERIALIZABLE следует использовать по сравнению с READ_COMMITTED с SELECT ... FOR UPDATE. Не могли бы вы обновить свой ответ, чтобы отразить этот обновленный вопрос? - person Gili; 07.05.2013
comment
@Gili: вам не нужен SELECT FOR UPDATE в сериализуемом режиме с InnoDB. В других MVCC системах эти две системы являются синонимами, и вам действительно нужно SELECT FOR UPDATE. - person Quassnoi; 07.05.2013
comment
Я считаю, что сообщение Колина отвечает на мои конкретные вопросы лучше, чем ваш ответ, но я ценю все предоставленные вами ссылки. Я приму ответ, который лучше всего сочетает в себе эти два (конкретные ответы вверху, вспомогательные ссылки ниже). - person Gili; 07.05.2013
comment
This depends on the concurrency control your database system is using: Я думаю, что ты секешься. Во всех случаях, которые вы перечисляете ниже, говорится, что комната не удаляется с SELECT до конца транзакции. Итак, разве ответ не должен быть просто Yes со вспомогательными ссылками ниже? - person Gili; 07.05.2013
comment
@Gili: в Oracle, PostgreSQL и MySQL с InnoDB в режиме READ COMMITTED комната может быть удалена в другой транзакции после SELECT без FOR UPDATE. Фактически может даже в SQL Server, если DELETE запрос начинается после завершения первого SELECT. - person Quassnoi; 07.05.2013
comment
Да, но мы не говорим о SELECT без FOR UPDATE. Вопрос конкретно касается SELECT с FOR UPDATE. Я думаю, что вы тоже ответите на второй вопрос: If you don't want to show the rooms after they have been deleted, you should lock the rows with SELECT FOR UPDATE. При выборе между SERIALIZABLE или READ_COMMITTED с SELECT ... FOR UPDATE любой подход исправит приложение, но база данных может отличаться от того, как это будет реализовано под капотом. Поправьте меня если я ошибаюсь. - person Gili; 07.05.2013
comment
@Gili: в первом вопросе вы спрашиваете, следует ли использовать FOR UPDATE. Согласно RFC-определению «следует», вы спрашиваете о веских причинах в определенных обстоятельствах игнорировать конкретный пункт, с полным пониманием и тщательным взвешиванием, прежде чем выбрать другой курс. Я пытаюсь объяснить такие последствия, чтобы вы могли их понять и тщательно взвесить. В некоторых системах можно обойтись без указания FOR UPDATE. Скажем, в SQL Server нельзя просто использовать SELECT FOR UPDATE, не без объявления курсора. - person Quassnoi; 07.05.2013
comment
Итак, в части 1 вы говорите, что системы MVCC нуждаются в SELECT ... FOR UPDATE, в то время как некоторые другие системы являются атомарными без этого. И во второй части вы говорите, что SELECT ... FOR UPDATE необходимо для READ_COMMITTED или REPEATABLE_READ, но не SERIALIZABLE. Это верно? Если да, попробуйте перефразировать ответ более лаконично. У меня проблемы с просмотром леса за деревьями, если вы понимаете, о чем я. - person Gili; 08.05.2013

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

Q1: Да.

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

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

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

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

select * from my_table where my_condition;

update my_table set my_column = my_column where my_condition;

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

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

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

person Colin 't Hart    schedule 07.05.2013
comment
Я думаю, What transaction isolation levels do guarantee [...] is the consistency of data once transactions are completed. неправильно подразумевает, что уровни изоляции не влияют на то, что происходит во время транзакции. Я рекомендую пересмотреть этот раздел и предоставить более подробную информацию о том, как они влияют на то, что вы видите (или не видите) во время транзакции. - person Gili; 07.05.2013
comment
Я считаю, что ваше сообщение отвечает на мои конкретные вопросы лучше, чем Quassnoi, но я ценю все предоставленные им ссылки. Я приму ответ, который лучше всего сочетает в себе эти два (конкретные ответы вверху, вспомогательные ссылки ниже). - person Gili; 07.05.2013
comment
Запирание и изоляция взаимозаменяемы. Так есть ли какие-нибудь книги, чтобы узнать об этом? - person Chao; 27.01.2015