Здравствуйте, Poul_Ko, Вы писали:
P_K>Здравствуйте, Qulac, Вы писали:
P_K>>>Например, вы создаёте заказ. Заказ для клиента, у клиента есть какой-то свой процент скидки, и соответственно в заказе вы должны на все позиции проставить эту скидку. P_K>>>Сам по себе клиент не является частью агрегата заказа. Но на время операции создания заказа мы должны обеспечить неизменность процента скидки клиента. Иначе есть риск создать заказ со скидкой, которая уже не действительна (была изменена параллельно с созданием заказа).
Q>>Проблемы конкурентного доступа это естественная проблема доступа к данным. Пользователь (заказчик) должен озвучить, как ваша программа в таких случаях должна работать. Изолированность бизнес-транзакций являются частью т.з. на вашу программу. А так размышлять об абстрактных вещах — пустое дело.
P_K>Вашу мысль не понял. P_K>Вернёмся к примеру. При создании заказа должна быть использована скидка клиента. Это и есть постановка задачи, и странно было если бы она звучала как-то так: "При создании заказа должна быть использована скидка клиента, но допустимо если система глюкнет и будет использована другая скидка, которая была до момента создания заказа."
Не один раз читал в т.з., что должна использоваться оптимистическая блокировка. В некоторых случаях приемлема только пессимистическая блокировка. Например: клиент выбрал номер в гостинице и пошел заполнять анкету, номер в это время заблокирован, поселение в него осуществить нельзя. Все эти вещи согласовываются с заказчиком.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Речь не об оплате, а о создании некого заказа. Цена в нём должна строго соответствовать цене в справочнике, до определённого момента в жизни заказа. P_K>Если цена поменялась на секунду раньше — она подтянется из справочника и заказ создастся уже с новой ценой. P_K>Если цена поменялась на секунду позже — обработчик изменения цены найдёт заказ и проапдейтит его. P_K>Если цена поменялась в то же самое время — вот где проблема! Можем получить заказ со старой ценой, вот что нужно побороть.
Ага. Осталось уточнить что такое "время" в многопоточной системе.
Даже в нашем физическом времени благодаря Эйнштейну понятие "в то же самое время" — относительно.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
P_K>>На примере, мы показываем пользователю что заказ будет с ценой такой-то, он жмёт кнопку "Создать заказ", данные уходят на сервер, там проверятся соответствует ли цена в справочнике той, которая была на экране — это и есть оптимистичная блокировка. Цена не соответствует — показываем ошибку "Цена поменялась". Цена соответствует — создаём заказ. И в это время цена в справочнике меняется. Заказ создаётся со старой ценой в своей транзакции, цена в справочнике меняется в своей. В итоге бизнес-правило нарушено. ·>Это же транзакция — должно быть понятие точки коммита, которая даёт OK или FAIL. Мы не "создаём" заказ (что это вообще конкретно значит?), а _атомарно_ коммитим его — с подтвержёнными на момент коммита ценами. Если коммит провалился (цены поменялись до), то транзакция фейлится и, при желании, идёт на второй круг, до тех пор, пока не даст OK.
Не представляю как это должно выглядеть.
Алгоритм создания заказа:
1. начать транзакцию
2. найти клиента
3. найти товар
4. создать и сохранить сущность заказа
5. commit
Между 4 и 5 опять бежим в справочник и проверяем что цена не изменилась? Это ничего не изменит! Всё равно после этой проверки и до commit она может поменяться.
·> Любые последующие изменения цены "официально" считаются, что произошли после, а значит не должны влиять на заказ.
Категорически нет. Смотрите выше — изменение цены после приводит к пересчёту неподтверждённых заказов!
Здравствуйте, ·, Вы писали:
·>Ага. Осталось уточнить что такое "время" в многопоточной системе.
Вот как раз таки за счёт блокировок и создаётся линейное время. Блокировки не дают исполняться одновременно тем операциям, которые друг другу мешают. Как в данном случае — имея какую-то блокировку мы либо сначала создадим заказ, либо обновим справочник (смотря что захватит блокировку раньше), но одновременно и то и другое никогда не произойдёт.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Здравствуйте, ·, Вы писали:
P_K>>>На примере, мы показываем пользователю что заказ будет с ценой такой-то, он жмёт кнопку "Создать заказ", данные уходят на сервер, там проверятся соответствует ли цена в справочнике той, которая была на экране — это и есть оптимистичная блокировка. Цена не соответствует — показываем ошибку "Цена поменялась". Цена соответствует — создаём заказ. И в это время цена в справочнике меняется. Заказ создаётся со старой ценой в своей транзакции, цена в справочнике меняется в своей. В итоге бизнес-правило нарушено. P_K>·>Это же транзакция — должно быть понятие точки коммита, которая даёт OK или FAIL. Мы не "создаём" заказ (что это вообще конкретно значит?), а _атомарно_ коммитим его — с подтвержёнными на момент коммита ценами. Если коммит провалился (цены поменялись до), то транзакция фейлится и, при желании, идёт на второй круг, до тех пор, пока не даст OK. P_K>Не представляю как это должно выглядеть. P_K>Алгоритм создания заказа: P_K>1. начать транзакцию P_K>2. найти клиента P_K>3. найти товар P_K>4. создать и сохранить сущность заказа P_K>5. commit P_K>Между 4 и 5 опять бежим в справочник и проверяем что цена не изменилась? Это ничего не изменит! Всё равно после этой проверки и до commit она может поменяться.
Или я тебя совсем не понимаю, или ты не ту цель пытаешься достигнуть. Цель не добиться "одновременности" (что это вообще значит?!), а создать согласованное состояние, установить причинность событий.
P_K>·> Любые последующие изменения цены "официально" считаются, что произошли после, а значит не должны влиять на заказ. P_K>Категорически нет. Смотрите выше — изменение цены после приводит к пересчёту неподтверждённых заказов!
1. Создать неподтверждённый заказ товара по ценам из справочника.
2. Показать заказ клиенту, клиент шлёт команду "подтвердить".
3. Сверяем цены в заказе с ценами в справочнике
4.1 Цены равны — ставим статус "подтверждён"
4.2 Цены не равны — возвратить клиенту ошибку, пересчитать заказ с учётом новых цен и goto 2.
Т.е. по сути у тебя тут распределённая система из двух компонент — клиент и сервер — они и должны согласовываться.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Poul_Ko, Вы писали:
P_K>·>Ага. Осталось уточнить что такое "время" в многопоточной системе. P_K>Вот как раз таки за счёт блокировок и создаётся линейное время. Блокировки не дают исполняться одновременно тем операциям, которые друг другу мешают. Как в данном случае — имея какую-то блокировку мы либо сначала создадим заказ, либо обновим справочник (смотря что захватит блокировку раньше), но одновременно и то и другое никогда не произойдёт.
Нет, линейное время может существовать только если у тебя ровно одна глобальная блокировка на всё, т.е. по сути однопоточное последовательное (линейное) исполнение. В многопоточной среде другие модели, например векторные часы — т.е. не линия, а частично-упорядоченное множество.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Poul_Ko, Вы писали:
P_K>На примере, мы показываем пользователю что заказ будет с ценой такой-то, он жмёт кнопку "Создать заказ", данные уходят на сервер, там проверятся соответствует ли цена в справочнике той, которая была на экране — это и есть оптимистичная блокировка. Цена не соответствует — показываем ошибку "Цена поменялась". Цена соответствует — создаём заказ. И в это время цена в справочнике меняется. Заказ создаётся со старой ценой в своей транзакции, цена в справочнике меняется в своей. В итоге бизнес-правило нарушено.
Проблему можно решить на уровне БД. Например в транзакции с уровнем Repeatable Read прочитать цены из справочника (прочитанные строки из справочника заблокируются и цены не смогут поменяться другими транзакциями), сравнить, и если все ок — подтвердить заказ и закоммитить транзакцию.
Если хочется избежать блокировки (которая в общем-то скорее всего будет на долю секунды), то можно вообще одним запросом, типа (пишу на глаз без проверки):
UPDATE
[Order]
SET
Status = Confirmed
WHERE
OrderID = X AND
(SELECT COUNT(*) FROM OrderItem i INNER JOIN Price p ON i.ItemID = p.ItemID AND i.Price = p.Price WHERE OrderID = X) = ItemsInOrderCount
Здравствуйте, Poul_Ko, Вы писали:
S>>Покупки немного не так работают... P_K>Блин, пример искусственный, давайте думать по факту проблемы.
Не вопрос, только решение будет тоже искусственным.
P_K>Речь не об оплате, а о создании некого заказа. Цена в нём должна строго соответствовать цене в справочнике, до определённого момента в жизни заказа.
Ну так хранить в "заказе" ссылку на справочники и считать цену динамически. Если значение в справочнике регулярно меняется — хранить ссылку на запись истории справочника.
Единственное но: из опыта примерно через 3-4 итерации в том же духе проект или наворачивается, или переходит к команде, которая предпочитает чинить реальные проблемы (в том числе смягчением требований) вместо гонки за перфекционизмом.
P_K>Если цена поменялась на секунду позже — обработчик изменения цены найдёт заказ и проапдейтит его.
Даже скромная база на пару миллионов заказов и процесс обновления цены становится очень интересным занятием. Особенно если клиенту уже отправлено письмо с предыдущей ценой.
P_K>Если цена поменялась в то же самое время — вот где проблема! Можем получить заказ со старой ценой, вот что нужно побороть.
С чего бы это? Ты ж сам говоришь, что обработчик изменения цены найдёт и обновит заказ? Или обработчик синхронным сделан, чтобы веселее было?
Здравствуйте, Sinix, Вы писали: S>Здравствуйте, Poul_Ko, Вы писали: S>>>Покупки немного не так работают... P_K>>Блин, пример искусственный, давайте думать по факту проблемы. S>Не вопрос, только решение будет тоже искусственным.
Точнее будет сказать что "имена действующих лиц изменены", но сюжет сохранён. P_K>>Если цена поменялась на секунду позже — обработчик изменения цены найдёт заказ и проапдейтит его. S>Даже скромная база на пару миллионов заказов и процесс обновления цены становится очень интересным занятием. Особенно если клиенту уже отправлено письмо с предыдущей ценой.
Никаких писем нет, не придумывайте
Обновляются не все заказы, а только те которые находятся в определённом состоянии (неподтверждённые), их относительно немного. Но к рассматриваемой проблеме это отношения не имеет. P_K>>Речь не об оплате, а о создании некого заказа. Цена в нём должна строго соответствовать цене в справочнике, до определённого момента в жизни заказа. S>Ну так хранить в "заказе" ссылку на справочники и считать цену динамически. Если значение в справочнике регулярно меняется — хранить ссылку на запись истории справочника. S>Единственное но: из опыта примерно через 3-4 итерации в том же духе проект или наворачивается, или переходит к команде, которая предпочитает чинить реальные проблемы (в том числе смягчением требований) вместо гонки за перфекционизмом. P_K>>Если цена поменялась в то же самое время — вот где проблема! Можем получить заказ со старой ценой, вот что нужно побороть. S>С чего бы это? Ты ж сам говоришь, что обработчик изменения цены найдёт и обновит заказ? Или обработчик синхронным сделан, чтобы веселее было?
А давайте на примере, чего это мы всё словами...
Запросы
Что мы видим на картине? Две транзакции, идущие параллельно. Параллельно — это значит что одна начинается когда другая ещё не завершилась.
Слева — создание заказа, вначале читается цена (в локальную переменную).
Справа после прочтения цены запускается транзакция на обновление цены. Она обновляет неподтверждённые заказы, но нового заказа, создаваемого в левой части она ещё не видит.
В левой части мы создаём и сохраняем заказ. Цену взяли из локальной переменной, старую.
Транзакции завершаются, обе успешно.
Проверка говорит что в заказе цена старая (100500), а в прайсе — новая (100600). Что вы ответите юзерам, когда они прибегут к вам с вопросом "как так?"? Скажете "так транзакции сложились"? "Это неважно"?
Теперь поменяем уровень изоляции на SERIALIZABLE. Всё заработает как надо — необходимые блокировки будет делать база. Чем это плохо — см. первое сообщение.
Конечно в правой части на самом деле будет не такой апдейт — там будет сначала SELECT (поиск всех неподтверждённых заказов), а потом серия апдейтов — по одному на каждый заказ. Но принципиально это ничего не меняет, даже наоборот, повышает вероятность такой логической неконсистентности.
Здравствуйте, ·, Вы писали:
·>Или я тебя совсем не понимаю, или ты не ту цель пытаешься достигнуть. Цель не добиться "одновременности" (что это вообще значит?!), а создать согласованное состояние, установить причинность событий.
Не пытаюсь добиться одновременности, а наоборот, пытаюсь от неё избавиться, так как она порождает проблемы.
Способов избавления пока вижу два — либо всегда всё жёстко делать последовательно, либо на каких-то блокировках не давать делать одновременно некоторые операции.
P_K>>·> Любые последующие изменения цены "официально" считаются, что произошли после, а значит не должны влиять на заказ. P_K>>Категорически нет. Смотрите выше — изменение цены после приводит к пересчёту неподтверждённых заказов! ·>1. Создать неподтверждённый заказ товара по ценам из справочника. ·>2. Показать заказ клиенту, клиент шлёт команду "подтвердить". ·>3. Сверяем цены в заказе с ценами в справочнике ·>4.1 Цены равны — ставим статус "подтверждён" ·>4.2 Цены не равны — возвратить клиенту ошибку, пересчитать заказ с учётом новых цен и goto 2. ·>Т.е. по сути у тебя тут распределённая система из двух компонент — клиент и сервер — они и должны согласовываться.
Нет, это не соответствует действительности.
У клиента одно действие — создать новый неподтверждённый заказ. Этот заказ создаётся и болтается в системе пока его не подтвердят или не отменят. И пока он болтается неподтверждённый цена в нём всегда должна строго соответствовать цене из справочника.
Вполне допустимо, что перед созданием нового неподтверждённого заказа на экране юзер видел одну цену, а после создания она стала другой. Это нормальный кейс, здесь никакой оптимистической блокировки не нужно. Да даже если бы она была, это бы не изменило ситуацию в корне.
Подтверждение заказа — это другое действие клиента, там тоже могут быть похожие проблемы, но об этом сейчас не будем.
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Poul_Ko, Вы писали:
P_K>>·>Ага. Осталось уточнить что такое "время" в многопоточной системе. P_K>>Вот как раз таки за счёт блокировок и создаётся линейное время. Блокировки не дают исполняться одновременно тем операциям, которые друг другу мешают. Как в данном случае — имея какую-то блокировку мы либо сначала создадим заказ, либо обновим справочник (смотря что захватит блокировку раньше), но одновременно и то и другое никогда не произойдёт. ·>Нет, линейное время может существовать только если у тебя ровно одна глобальная блокировка на всё, т.е. по сути однопоточное последовательное (линейное) исполнение. В многопоточной среде другие модели, например векторные часы — т.е. не линия, а частично-упорядоченное множество.
С терминологией спорить не буду, хоть горшком это назовите
Ещё раз: для некоторых операций одновременности не должно быть, спор о там как это называется в викапедиях решению проблемы не поможет.
Здравствуйте, MozgC, Вы писали:
MC>Проблему можно решить на уровне БД. Например в транзакции с уровнем Repeatable Read...
Кажется вы поняли проблему!
О решении на транзакциях было в самом первом сообщении. Да, оно работает, но не совсем И имеет свои минусы.
Здравствуйте, Poul_Ko, Вы писали:
P_K>У клиента одно действие — создать новый неподтверждённый заказ. Этот заказ создаётся и болтается в системе пока его не подтвердят или не отменят. И пока он болтается неподтверждённый цена в нём всегда должна строго соответствовать цене из справочника. P_K>Вполне допустимо, что перед созданием нового неподтверждённого заказа на экране юзер видел одну цену, а после создания она стала другой.
Я бы, на месте клиента, такой юмор не оценил. Это уже вопрос к аналитикам, которые не понимают то ли чего им надо, то ли как функционирует ПО. Если цена в неподтвержденном заказе должна всегда браться из справочника — так и берите ее всегда из справочника, а не храните в заказе. Проблема решена)
Одно из самых простых решений проблемы рассогласованности в системе — не дублировать данные. И кешировать при необходимости, но кешировать явно, с управлением времени жизни и возможностью инвалидации при необходимости.
Что касается исходного вопроса, помимо укрупнения локов, что уже посоветовали, можно лочить более избирательно. В примере с кинотеатром, serializable не так уж страшен, если ограничить его строками для конкретного зала. Или вообще завести отдельную таблицу специально под локи и захватывать их руками в хранимке.
Здравствуйте, scf, Вы писали:
scf>Здравствуйте, Poul_Ko, Вы писали:
P_K>>У клиента одно действие — создать новый неподтверждённый заказ. Этот заказ создаётся и болтается в системе пока его не подтвердят или не отменят. И пока он болтается неподтверждённый цена в нём всегда должна строго соответствовать цене из справочника. P_K>>Вполне допустимо, что перед созданием нового неподтверждённого заказа на экране юзер видел одну цену, а после создания она стала другой.
scf>Я бы, на месте клиента, такой юмор не оценил. Это уже вопрос к аналитикам, которые не понимают то ли чего им надо, то ли как функционирует ПО.
Требования — это то что имеем. Хотя честно говоря, в описанном примере я не вижу ничего сильно противоречащего здравому смыслу. В реальной системе конечно крутятся не заказы, а что-то другое, и там такое требование к поведению выглядит вполне логично.
scf> Если цена в неподтвержденном заказе должна всегда браться из справочника — так и берите ее всегда из справочника, а не храните в заказе. Проблема решена)
Этот вариант решения уже пройден...
Ещё раз повторю — в реальности всё озвученное нужно умножать на десять и смотреть на вопрос шире. Представьте, что сама возможность создания заказа зависит от состояния других сущностей, которые могут параллельно измениться.
Пример совсем из пальца — новый заказ не может быть создан, если у клиента уже есть два неподтверждённых заказа. Но ведь второй заказ может создать параллельно несколько пользователей, в итоге заказов станет больше двух, что является нарушением требований. И тут уже решение "не дублировать данные" никак не лезет.
Здравствуйте, Poul_Ko, Вы писали:
S>>Даже скромная база на пару миллионов заказов и процесс обновления цены становится очень интересным занятием. Особенно если клиенту уже отправлено письмо с предыдущей ценой. P_K>Никаких писем нет, не придумывайте
Ну а если появятся? Скажем, напоминания клиенту, что он забыл оплатить набранную корзину, если корзина набрана, но не оплачена в течении суток.
Ещё раз, вы гонитесь за чисто сферическими требованиями, которые в реальной жизни не нужны. И при этом принимаете решения, которые блокируют или усложняют реализацию реальных требований.
P_K>Проверка говорит что в заказе цена старая (100500), а в прайсе — новая (100600). Что вы ответите юзерам, когда они прибегут к вам с вопросом "как так?"? Скажете "так транзакции сложились"? "Это неважно"?
Отвечу, что это цена на момент покупки и что она обновится при следующем отображении заказа пользователю.
Если хочется всегда иметь актуальную цену — надо хранить в заказе не Cost, а OrderPriceId + считать стоимость заказа динамически. Или хранить hash oт rowversion всех зависимых полей и пересчитывать стоимость заказа при его несовпадении. В любом случае стоимость неоплаченного заказа — штука несколько эфемерная (по определению) и пытаться натянуть на неё любые ограничения кроме eventual consistency — дело очень неблагодарное.
P_K>Теперь поменяем уровень изоляции на SERIALIZABLE. Всё заработает как надо — необходимые блокировки будет делать база. Чем это плохо — см. первое сообщение.
Даже с serializable не факт без дополнительных телодвижений. Самый простой пример — у каждой валюты своя таблица, цена считается в нескольких валютах, пока считаем цену в одной валюте, для второй обновилась запись истории.
Ну, т.е. возвращаемся к тому, с чего начали — при изменении большинства справочников придётся неоднократно пересчитать кучу заказов. Не самое лучшее решение.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Пример совсем из пальца — новый заказ не может быть создан, если у клиента уже есть два неподтверждённых заказа. Но ведь второй заказ может создать параллельно несколько пользователей, в итоге заказов станет больше двух, что является нарушением требований. И тут уже решение "не дублировать данные" никак не лезет.
В данном конкретном примере, если 99.5% юзеров не смогут создать третий заказ, а у 0.5% третий заказ появится, то все это переживут. Реальный мир по определению многозадачный и асинхронный и современные системы не зря предпочитают eventual consistency.
Еще пример из той же оперы — от нас часто требуют 100% сохранность введенных данных. А когда в такую систему уже инвестировано много сил и средств, оказывается, что в случае необходимости данные за последние сутки девочки могут вбить из первичных документов.
Стоимость заказа — нарисовать в гуй мелкими буквами "предварительная стоимость, окончательная будет определена в момент подтверждения заказа". Сделать неподтвежденные заказы протухающими в течение, скажем, суток.
Возможно, все ваши проблемы просто из-за недостаточной квалификации или бесхребетности архитектора.
Здравствуйте, Sinix, Вы писали:
P_K>>Проверка говорит что в заказе цена старая (100500), а в прайсе — новая (100600). Что вы ответите юзерам, когда они прибегут к вам с вопросом "как так?"? Скажете "так транзакции сложились"? "Это неважно"? S>Отвечу, что это цена на момент покупки и что она обновится при следующем отображении заказа пользователю.
То есть ваш вариант решения — пускай хранится неправильно, будем пересчитывать на каждый чих. S>Если хочется всегда иметь актуальную цену — надо хранить в заказе не Cost, а OrderPriceId + считать стоимость заказа динамически.
Ответил на такое же предложение в соседней ветке товарщу scf. Смотрите на вопрос шире. Само создание заказа может зависеть от изменяющихся данных, тут пересчётом не решится. S>Или хранить hash oт rowversion всех зависимых полей и пересчитывать стоимость заказа при его несовпадении. В любом случае стоимость неоплаченного заказа — штука несколько эфемерная (по определению) и пытаться натянуть на неё любые ограничения кроме eventual consistency — дело очень неблагодарное.
Выглядит страшно и сложно
P_K>>Теперь поменяем уровень изоляции на SERIALIZABLE. Всё заработает как надо — необходимые блокировки будет делать база. Чем это плохо — см. первое сообщение. S>Даже с serializable не факт без дополнительных телодвижений. Самый простой пример — у каждой валюты своя таблица, цена считается в нескольких валютах, пока считаем цену в одной валюте, для второй обновилась запись истории.
Через блокировки я надеюсь что можно решить всё
Перед созданием заказа блокируем нафиг все изменения по ценам и валютам, по окончании — блокировку отпускаем. Если кто-то хочет поменять курс валюты — он сделает это строго после создания заказа, и все заказы пересчитаются, новые же заказы не могут быть созданы в это время — благодаря той же самой блокировке. Вроде всё красиво. S>Ну, т.е. возвращаемся к тому, с чего начали — при изменении большинства справочников придётся неоднократно пересчитать кучу заказов. Не самое лучшее решение.
Это недостаток данного примера. Считайте что куча небольшая и пересчёт не является чем-то тяжёлым.
Здравствуйте, Poul_Ko, Вы писали: P_K>Никаких писем нет, не придумывайте P_K>Обновляются не все заказы, а только те которые находятся в определённом состоянии (неподтверждённые), их относительно немного. Но к рассматриваемой проблеме это отношения не имеет.
Дизайн хромает... S>>С чего бы это? Ты ж сам говоришь, что обработчик изменения цены найдёт и обновит заказ? Или обработчик синхронным сделан, чтобы веселее было? P_K>А давайте на примере, чего это мы всё словами... P_K>
P_K>Что мы видим на картине? Две транзакции, идущие параллельно. Параллельно — это значит что одна начинается когда другая ещё не завершилась. P_K>Слева — создание заказа, вначале читается цена (в локальную переменную). P_K>Справа после прочтения цены запускается транзакция на обновление цены. Она обновляет неподтверждённые заказы, но нового заказа, создаваемого в левой части она ещё не видит. P_K>В левой части мы создаём и сохраняем заказ. Цену взяли из локальной переменной, старую. P_K>Транзакции завершаются, обе успешно. P_K>Проверка говорит что в заказе цена старая (100500), а в прайсе — новая (100600). Что вы ответите юзерам, когда они прибегут к вам с вопросом "как так?"? Скажете "так транзакции сложились"? "Это неважно"?
У тебя проблема, что одни и те же данные копируются неконтролируемо в разные места. Ну так не делай так. Я вижу два варианта:
Не копировать цену вообще, пусть "незавершенный ордер" вообще не содержит в себе копию цены — она будет просто браться каждый раз из справочника. Только в момент фиксации ордера — цену копируем.
Либо надо версировать цену или продукт (упрощёная вариация векторных часов):
SELECT Price, PriceVer FROM Products...;
INSERT INTO Orders(...) VALUES( ..., Cost, PriceVer...);
Update Products SET Price=100600, PriceVer=PriceVer+1 WHERE Id = 1;
тогда ты сможешь сразу видеть что у ордера устаревшая цена, т.е. тот у которого версия цены не совпадает с последней:
UPDATE Orders SET Cost=p.Price * Qty, PriceVer=p.PriceVer ... JOIN Products p ON p.id=o.productId WHERE (Orders.PriceVer <> p.PriceVer)
Т.е. в этом случае избавиться от неконсистентного состояния невозможно, но зато это состояние обнаруживается и фиксится. P_K>Теперь поменяем уровень изоляции на SERIALIZABLE. Всё заработает как надо — необходимые блокировки будет делать база. Чем это плохо — см. первое сообщение.
Это понятно, но это неинтересно — система становится однопоточной, и следовательно — с линейным временем.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Poul_Ko, Вы писали:
P_K>О решении на транзакциях было в самом первом сообщении. Да, оно работает, но не совсем И имеет свои минусы.
Для этой проблемы другого решения нет.
просто с транзакциями надо работать аккуратнее. Во-первых, Serializable не обязательно, до него обычно не доходит. А во-вторых, объем изменений в транзакции надо минимизировать, и все будет предсказуемо, без всяких дедлоков.
P_K>Теперь поменяем уровень изоляции на SERIALIZABLE. Всё заработает как надо — необходимые блокировки будет делать база. Чем это плохо — см. первое сообщение.
Не надо Serializable, достаточно добавить хинт UPDLOCK в первом SELECT-е. То есть решение, в том виде в котором вы хотите, заключается в более аккуратной работе с блокировками и запросами на уровне сиквела.