Я думаю, что, вероятно, хотел добавить этот комментарий к предыдущему ответу, о двух отдельных утверждениях. Это было больше года назад, поэтому я не совсем уверен.
Запрос на основе 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
часть. Поскольку нет строк, соответствующих предложению UPDATE
s, где, оба возвращают пустой набор результатов из обновления и не вносят изменений.
Теперь оба запустите INSERT
часть. Так как UPDATE
возвращено ноль строк для обоих запросов, оба стремятся к INSERT
строке.
Один преуспевает. Один бросает уникальное нарушение и прерывает.
Это не повод для беспокойства по поводу потери данных, если приложение проверяет результаты ошибок в своих запросах (т. Е. В любом прилично написанном приложении) и повторяет попытки, но делает решение не лучше существующих версий с двумя утверждениями. Это не устраняет необходимость повторного цикла.
Преимущество, которое wCTE предлагает по сравнению с существующей версией с двумя утверждениями, состоит в том, что она использует выходные данные UPDATE
для принятия решения INSERT
вместо использования отдельного запроса к таблице. Это частично оптимизация, но частично защищает от проблемы с версией с двумя утверждениями, которая вызывает потерю обновлений; см. ниже.
Вы можете запустить wCTE SERIALIZABLE
изолированно, но тогда вы получите только ошибки сериализации вместо уникальных нарушений. Это не изменит необходимость повторного цикла.
WCTE не выглядит уязвимым для потерянных обновлений
Мой комментарий предполагал, что это решение может привести к потере обновлений, но после проверки я думаю, что, возможно, я ошибся.
Это более года назад, и я не могу вспомнить точные обстоятельства, но я думаю, что, возможно, я упустил тот факт, что уникальные индексы имеют частичное исключение из правил видимости транзакции, чтобы позволить одной вставляемой транзакции ждать, пока другая вставит или откатит назад, прежде чем продолжить.
Или, возможно, я упустил тот факт, что INSERT
в wCTE зависит от того, UPDATE
затронуты ли какие-либо строки, а не от того, существует ли строка-кандидат в таблице.
Конфликтующие INSERT
s по уникальному индексу ждут фиксации / отката
Скажем, что одна копия запроса выполняется, вставляя строку. Изменение еще не совершено. Новый кортеж существует в куче и уникальном индексе, но пока не виден другим транзакциям, независимо от уровней изоляции.
Теперь выполняется еще одна копия запроса. Вставленная строка еще не видна, так как первая копия не зафиксирована, поэтому обновление ничего не соответствует. Запрос будет продолжен, чтобы попытаться вставить, который увидит, что другая текущая транзакция вставляет тот же ключ, и заблокирует ожидание этой транзакции для ее фиксации или отката .
Если первая транзакция фиксируется, вторая завершится неудачей с уникальным нарушением, как указано выше. Если первая транзакция откатывается, вторая продолжит вставку.
INSERT
Будучи зависимым от UPDATE
ROWCOUNT защищает от потери обновлений
В отличие от случая с двумя утверждениями, я не думаю, что wCTE уязвим для потерянных обновлений.
Если UPDATE
не имеет никакого эффекта, INSERT
всегда будет выполняться, потому что это строго зависит от того, UPDATE
сделал ли что- нибудь, а не от состояния внешней таблицы. Таким образом, он все еще может потерпеть неудачу с уникальным нарушением, но он не может молча потерять какой-либо эффект и полностью потерять обновление.