Почему CTE открыт для потерянных обновлений?

8

Я не понимаю, что имел в виду Крейг Рингер, когда он комментировал:

Это решение может потерять обновления, если транзакция вставки откатывается; нет никакой проверки, чтобы убедиться, что ОБНОВЛЕНИЕ затронуло любые строки.

на https://stackoverflow.com/a/8702291/14731 . Пожалуйста, предоставьте пример последовательности событий (например, поток 1 выполняет X, поток 2 выполняет Y), который демонстрирует, как могут происходить потерянные обновления.

Гили
источник
1
Спросите меня о комментарии, который я оставил более года назад на сложную тему ... весело! Теперь я должен вспомнить, в чем именно заключалась проблема. Пересматривая это сейчас.
Крейг Рингер

Ответы:

14

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

Запрос на основе wCTE на самом деле не решает проблему, которую он должен, но после повторного рассмотрения через год я не вижу возможности потерять обновления в версии wCTE.

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

Версия с двумя утверждениями может быть утеряна.

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

Представьте, что произойдет, если у вас есть две транзакции в READ COMMITTEDизоляции.

  • TX1 запускает UPDATE(без эффекта)
  • TX1 запускает INSERT(вставляет строку)
  • TX2 запускает UPDATE(никакого эффекта, строка, вставленная TX1, еще не видна)
  • TX1 COMMITс.
  • TX2 запускает INSERT, *, который получает новый снимок, который может видеть строку, зафиксированную TX1. Предложение EXISTSвозвращает true, потому что TX2 теперь может видеть строку, вставленную TX1.

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

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

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

Версия wCTE защищает от проблемы потерянных обновлений, поскольку INSERTзависит от того, UPDATEвлияет ли она на какие-либо строки, а не на отдельный запрос.

WCTE не устраняет уникальные нарушения

Записываемая версия CTE все еще не является надежной версией.

Рассмотрим две транзакции, которые запускают это одновременно.

  • Оба выполняют предложение VALUES.

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

  • Теперь оба запустите INSERTчасть. Так как UPDATEвозвращено ноль строк для обоих запросов, оба стремятся к INSERTстроке.

Один преуспевает. Один бросает уникальное нарушение и прерывает.

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

Преимущество, которое wCTE предлагает по сравнению с существующей версией с двумя утверждениями, состоит в том, что она использует выходные данные UPDATEдля принятия решения INSERTвместо использования отдельного запроса к таблице. Это частично оптимизация, но частично защищает от проблемы с версией с двумя утверждениями, которая вызывает потерю обновлений; см. ниже.

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

WCTE не выглядит уязвимым для потерянных обновлений

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

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

Или, возможно, я упустил тот факт, что INSERTв wCTE зависит от того, UPDATEзатронуты ли какие-либо строки, а не от того, существует ли строка-кандидат в таблице.

Конфликтующие INSERTs по уникальному индексу ждут фиксации / отката

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

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

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

INSERTБудучи зависимым от UPDATEROWCOUNT защищает от потери обновлений

В отличие от случая с двумя утверждениями, я не думаю, что wCTE уязвим для потерянных обновлений.

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

Крейг Рингер
источник