Re[14]: Блокировки в бизнес-слое
От: Poul_Ko Казахстан  
Дата: 29.09.17 13:59
Оценка:
Здравствуйте, itslave, Вы писали:
P_K>>Либо перенести блокировки с базы на уровень выше — в бизнес-слой. Вот с целью ресёча такого решения я и затеял данную тему.
I>Вот это тот самый случай, когда лечение хуже болезни. Нормально реализовать транзакционность в бизнес слое — весьма нетривиальная задачка, смело умножайте прикидочные эстимейты на 10 и прибавьте 100% вероятность иметь невоспроизводимые баги месяцами.

Да тут у нас всё просто — любой не-read метод app-слоя всегда оборачивается в транзакцию, и либо целиком выполняется, либо нет. Внутри каких-то частичных откатов или коммитов не допускаем.
Другими словами, бизенс-слой вообще не знает о транзакциях и не рулит ими, они снаружи. По соглашению любое исключение приводит к откату транзакции.
Brainbench transcript #6370594
Re[15]: Блокировки в бизнес-слое
От: itslave СССР  
Дата: 29.09.17 14:04
Оценка:
Здравствуйте, Poul_Ko, Вы писали:

P_K>Да тут у нас всё просто — любой не-read метод app-слоя всегда оборачивается в транзакцию, и либо целиком выполняется, либо нет. Внутри каких-то частичных откатов или коммитов не допускаем.

P_K>Другими словами, бизенс-слой вообще не знает о транзакциях и не рулит ими, они снаружи. По соглашению любое исключение приводит к откату транзакции.

На каждый чих блокировать абсолютно все? Упретесь в перфоманс моментально.
Re[16]: Блокировки в бизнес-слое
От: Poul_Ko Казахстан  
Дата: 29.09.17 14:53
Оценка:
Здравствуйте, itslave, Вы писали:
P_K>>Да тут у нас всё просто — любой не-read метод app-слоя всегда оборачивается в транзакцию, и либо целиком выполняется, либо нет. Внутри каких-то частичных откатов или коммитов не допускаем.
P_K>>Другими словами, бизенс-слой вообще не знает о транзакциях и не рулит ими, они снаружи. По соглашению любое исключение приводит к откату транзакции.
I>На каждый чих блокировать абсолютно все? Упретесь в перфоманс моментально.
Это делается для обеспечения атомарности. В идеале видим использование транзакций только для атомарности и изоляции (READ COMMITTED), а блокировки через что-то другое, например, свой механизм в бизнес-слое.
Brainbench transcript #6370594
Re[5]: Блокировки в бизнес-слое
От: wildwind Россия  
Дата: 01.10.17 05:59
Оценка:
Здравствуйте, IB, Вы писали:

W>>Да, но в вышеописанном сценарии это не помогает.

IB>Должен помогать, поскольку используется все тот же менеджер блокировок, то он как раз знает, какие строки относятся к заблокированным объектам. Иными словами, ваше требование про изменение бизнес-объектов через менеджер блокировок выполняется автоматически.

Можно подробнее? Насколько мне известно, блокировки, полученные sp_getapplock, никак не связаны с какими-то объектами в БД. Из семантика полностью определяется кодом приложения. Но может, я чего-то не знаю...
Re[6]: Блокировки в бизнес-слое
От: Poul_Ko Казахстан  
Дата: 01.10.17 13:09
Оценка:
Здравствуйте, wildwind, Вы писали:
W>Можно подробнее? Насколько мне известно, блокировки, полученные sp_getapplock, никак не связаны с какими-то объектами в БД. Из семантика полностью определяется кодом приложения. Но может, я чего-то не знаю...
Сам не пользовался, но из документации понял что блокировка всегда к чему-то привязывается: либо к транзакции, либо к сессии.
Brainbench transcript #6370594
Re[7]: Блокировки в бизнес-слое
От: wildwind Россия  
Дата: 01.10.17 16:11
Оценка:
Здравствуйте, Poul_Ko, Вы писали:

P_K>Сам не пользовался, но из документации понял что блокировка всегда к чему-то привязывается: либо к транзакции, либо к сессии.


Речь не об этом. Почитай всю нашу с IB ветку дискуссии.
Re[3]: Блокировки в бизнес-слое
От: Sinclair Россия https://github.com/evilguest/
Дата: 02.10.17 03:15
Оценка: 3 (1)
Здравствуйте, ·, Вы писали:
S>>К примеру, "дедлок", которого вы так боитесь — это, фактически, обнаружение конфликта изменений.
·>Ты что-то путаешь. Обычно deadlock это взаимоблокировка в результате ошибки имплементации. Скажем, классический пример — перевод денег с аккаунта A1 на аккаунт A2 при наивной имплементации возможен deadlock если аккаунты будут будут блокироваться в произвольном порядке — один пытается заблокировать A1, затем A2, а второй пытается A2, затем A1 — то они зависнут. Если же ввести отношение порядка и блокировать всегда A1,A2 — то дедлоков никаких не будет.
Это я просто очень коротко пишу. Разверну подробнее. Давайте рассмотрим ваш изолированный пример. Реальные проблемы обычно на порядок сложнее.
Итак, откуда вообще взялась проблема? Оттуда, что у нас транзакция T2 пытается захватить ресурс A1, который уже захвачен эксклюзивно транзакцией T1. То есть обе транзакции хотят внести изменения в A1 — это и есть конфликт изменений.
Что произойдёт в пессимистике? Одна из транзакций будет отстрелена с ошибкой, вторая — успешно завершится.
Что произойдёт в optimistic? Одна из транзакций успешно завершится, вторая упадёт с timestamp doesn't match или что там выкидывается в конкретном движке.
В обоих случаях придётся делать повторную попытку проводки упавшей транзакции.
Ок, предположим, мы упорядочили в пессимистике захват блокировок. Это решило проблему? Не факт. Это, возможно, решило проблему дедлоков. Но конкуренция продолжается — то есть транзакция T2 будет просто стоять в ожидании окончания T1. В итоге может оказаться так, что общий throughput не отличается — т.е. сумма "выполнение+простой+выполнение" примерно такая же, как "выполнение+откат+повторное выполнение". Всё зависит от частоты реальных дедлоков.

S>>Какой бы способ вы ни выбрали, у вас всё равно возможна ситуация "ой, между тем, как мы показали вам цену, и вашим нажатием на кнопку Save, произошли изменения. Будете повторять?".

·>Так это OL. А в PL будет просто "Не могу открыть Соглашение, т.к. кто-то сейчас проверяет Заказ связанный с этим Соглашением. Попробуйте позже." или просто песочные часики.
Вот тут есть нюанс. При лобовой реализации окажется, что исправить прайслист в принципе невозможно — потому, что не угадаешь момент, когда нет открытых заказов, ссылающихся на эту позицию.
В решении с "дедлоками" шансы завершиться успешно выше у транзакции изменения прайс-листа, т.к.она дороже, чем транзакция заказа.
·>С OL может возникнуть livelock и starvation... что тоже неприятно.
Ну вот да.
И опять же — для одной и той же стратегии блокировки могут быть разные реализации. И разные условия работы. В частности, одно дело — неинтерактивная процедура (неважно, хранимая или нет), пробегающая за секунды, а другое дело — интерактивная операция редактирования заказа, где длительность транзакции — минуты.

Преимущество низкоуровневых средств типа блокировок или версионности — в том, что их не надо изобретать. Прикладные блокировки хороши до тех пор, пока понятны границы ресурсов. То есть залочить заказ вместе с его позициями — ок, это понятно. А вот как лочить прайслист? Весь? По категориям? По позициям? По колонкам? По пересечению колонка/категория?

А то ведь можно легко реализовать систему, в которой один телефонный звонок замдиректору по продажам в неудачный момент повесит всю работу отдела продаж.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[9]: Блокировки в бизнес-слое
От: Sinclair Россия https://github.com/evilguest/
Дата: 02.10.17 04:05
Оценка:
Здравствуйте, Poul_Ko, Вы писали:
P_K>Теперь поменяем уровень изоляции на SERIALIZABLE. Всё заработает как надо — необходимые блокировки будет делать база. Чем это плохо — см. первое сообщение.
Достаточно repeatable read.

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

Не надо так делать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Блокировки в бизнес-слое
От: wildwind Россия  
Дата: 02.10.17 07:03
Оценка: +1 :)
Здравствуйте, Sinclair, Вы писали:

S>Что произойдёт в пессимистике? Одна из транзакций будет отстрелена с ошибкой, вторая — успешно завершится.

S>Что произойдёт в optimistic? Одна из транзакций успешно завершится, вторая упадёт с timestamp doesn't match или что там выкидывается в

Может показаться, что выбор между pessimistic и optimistic locking это дихотомия, но это не так. Есть и третий путь — eventual consistency (в отдельных местах). И во многих случаях это приемлемо для бизнеса.

К слову, при бумажном учете он был основным и ничего, бизнес работал.
Re[10]: Блокировки в бизнес-слое
От: Poul_Ko Казахстан  
Дата: 02.10.17 07:33
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Poul_Ko, Вы писали:

P_K>>Теперь поменяем уровень изоляции на SERIALIZABLE. Всё заработает как надо — необходимые блокировки будет делать база. Чем это плохо — см. первое сообщение.
S>Достаточно repeatable read.
В этом конкретном случае может быть да. Но в целом когда есть проверка типа "чего-то нет" (EXISTS, COUNT) и нужно обеспечить чтобы в ходе транзакции она оставалась неизменной, то приходится использовать serializable.
Да, мы выяснили, что наверняка такие проверки можно свести к проверкам значений свойств и использовать repeatable read, но это требует дополнительных телодвижений, вплоть до введения новых принципиально бесполезных для бизнеса сущностей.

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

S>Не надо так делать.
А другого пути я не вижу. Это в примере расчёт простой — умножить одно на другое, по факту же там играет бизнес-логика. А она в коде и оперирует загруженными сущностями. То есть, пересчёт быдет выглядеть как-то так:
// Найти заказы для пересчёта
foreach(var order = FindOrdersToRecalc(contractId))
{
    // Пересчитать каждый заказ
    _orderingService.RecalculateOrder(order);
}
Brainbench transcript #6370594
Re[11]: Блокировки в бизнес-слое
От: Sinclair Россия https://github.com/evilguest/
Дата: 02.10.17 09:09
Оценка: 3 (1) +1
Здравствуйте, Poul_Ko, Вы писали:
P_K>В этом конкретном случае может быть да. Но в целом когда есть проверка типа "чего-то нет" (EXISTS, COUNT) и нужно обеспечить чтобы в ходе транзакции она оставалась неизменной, то приходится использовать serializable.
Охх. Лень искать исчерпывающий пост. Кратко, тезисно:
1. Уровней изоляции не бывает. Изоляция — она либо есть, либо её нет. Для бизнеса "изоляции нет" — это смерть, т.к. это означает, что данные повреждены.
2. То, что в РСУБД называют "уровнями изоляции" — это компромисс; способ добиться сериализуемости транзакций дешевле, чем "поставив всё колом". При этом программист даёт СУБД определённые обещания.
Ну типа запрашивая repeatable read программист как бы говорит "я обещаю, что не буду перечитывать данные дважды (или вообще пользоваться данными, прочитанными "вначале", ближе к "концу" транзакции). В ответ на что СУБД говорит себе "ок, тогда я не буду удерживать shared lock до конца транзакции". Когда программист запрашивает "repeatable read", это означает, что "я не буду использовать тот факт, что некие данные отсутствовали "вначале" транзакции, ближе к "концу" транзакции (в частности, не буду полагаться на значение select count() from ... where <condition>)". На что СУБД говорит себе "а, ну, норм, можно обойтись без key range локов на запрошенные диапазоны".
3. Нарушение этих обещаний приводит к нарушению изоляции. Увы.
P_K>Да, мы выяснили, что наверняка такие проверки можно свести к проверкам значений свойств и использовать repeatable read, но это требует дополнительных телодвижений, вплоть до введения новых принципиально бесполезных для бизнеса сущностей.
Вообще все сущности для бизнеса принципиально бесполезны. Вопрос в том, как решить задачу техническими средствами, а не как сделать так, чтобы структура таблиц была похожа на графы бланка типового договора.

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

S>>Не надо так делать.
P_K>А другого пути я не вижу. Это в примере расчёт простой — умножить одно на другое, по факту же там играет бизнес-логика. А она в коде и оперирует загруженными сущностями. То есть, пересчёт быдет выглядеть как-то так:
P_K>
P_K>// Найти заказы для пересчёта
P_K>foreach(var order = FindOrdersToRecalc(contractId))
P_K>{
P_K>    // Пересчитать каждый заказ
P_K>    _orderingService.RecalculateOrder(order);
P_K>}
P_K>

"Оперирование загруженными сущностями" свидетельствует о том, что вы рассматриваете фундаментальный вопрос про изоляцию в рамках какой-то конкретной архитектуры.
Во-первых надо понять, как вообще это всё выглядит с точки зрения СУБД. Потому что именно она разруливает то, что в итоге "сохранится для потомков". И если программист Вася перевод денег со счёта А на счёт Б сделал "в транзакции", но при этом он полагается на значения остатков на счетах, прочитанных вне этой транзакции, то виновата не СУБД и не архитектура, а лично Вася.
Васе нужно понять, что СУБД должна увидеть код вида
begin transaction
update accounts set amount=amount-@transfer_amount where ID = @source_acc_id
update accounts set amount=amount+@transfer_amount where ID = @target_acc_id
commit transaction

А за код вида
select @source_acc_amount = amount from accounts where ID = @source_acc_id
select @target_acc_amount = amount from accounts where ID = @target_acc_id
-- .... 30 секунд активности в миддл-тир ....
begin transaction
update accounts set amount=@new_source_amount where ID = @source_acc_id
update accounts set amount=@new_target_amount where ID = @target_acc_id
commit transaction

его поставят в пятую нормальную форму и будут иметь всю ночь.
Ну, то есть у Васи есть и запасные варианты — типа попытаться сделать свои механизмы изоляции снаружи СУБД. Но это — путь самурая. В основном — потому, что, как правило, он приходит в голову тем, кто не освоил использование готовой изоляцией в промышленной СУБД. Шансы, что реализация, сварганенная таким человеком, будет корректной — пренебрежимо малы.

Поэтому Васе надо сосредоточиться на том, чтобы SQL выхлоп от той прослойки, которая используется в его мидл-тире, соответствовал чертежу.
Способов у этого несколько:
1. Воспользоваться хранимыми процедурами. За идеи типа "оперировать загруженными сущностями" стоять на горохе по 40 минут за идею.
2. Оставить логику в миддл-тир, но оборудовать все чтения данных, которые будут использованы в рамках бизнес-транзакции, рукопашными блокировками (например, через sp_getapplock в случае MS SQL). Или через жосткий патчинг любимого ORM, чтобы тот сам трекал чтения
3. Оставить логику в миддл-тир, но переписать её на продвинутые средства вроде linq[2db]. Т.е. получить функциональный аналог п.1, но с нормальной декомпозицией и современным языком, а не процедурным языком образца 1993 года.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: Блокировки в бизнес-слое
От: Poul_Ko Казахстан  
Дата: 02.10.17 10:03
Оценка:
Здравствуйте, Sinclair, Вы писали:
S>Охх. Лень искать исчерпывающий пост. Кратко, тезисно:
Не хочу обидеть, но это всё давно мне известно. Согласен, во многом здесь вопрос аккуратности и понимания.

P_K>>Да, мы выяснили, что наверняка такие проверки можно свести к проверкам значений свойств и использовать repeatable read, но это требует дополнительных телодвижений, вплоть до введения новых принципиально бесполезных для бизнеса сущностей.

S>Вообще все сущности для бизнеса принципиально бесполезны. Вопрос в том, как решить задачу техническими средствами, а не как сделать так, чтобы структура таблиц была похожа на графы бланка типового договора.
Под бизнесом я имел в виду domain model. Это та модель бизнеса, которая реализована в ПО (думаю определение вам итак известно).
Получается что для уменьшения используемого уровня изоляции в определённых условиях необходимо вводить новые атрибуты и/или сущности, нужно написать код в бизнес-слое, который бы оперировал ими. Это получаются своеобразные "индексы", необходимость которых получается обусловленной чисто техническими нуждами. Вот я о чём.

S>Во-первых надо понять, как вообще это всё выглядит с точки зрения СУБД.

В описанном мной коде и используемом подходе на СУБД это ляжет как-то так:
begin serializable transaction
update products set price = 10600 where id = 101;    // обновить прайс

select orders where state = 'N' and productId = 101;    // найти заказы

// среднее звено посчитало и сохраняет найденные ранее заказы
update order set cost = 23232 where id = 1111;
update order set cost = 65465 where id = 2222;
...

commit transaction


S>Способов у этого несколько:

S>1. Воспользоваться хранимыми процедурами.
Внутри хранимки будет исполняться по сути тот же SQL, что и в случае когда логика отдельно. Только это будет работать быстрее, в рамках одного соединения и т.д. Но проблемы конкурренции по-моему остаются ровно те же самые. Точно также, если внутри хранимки два раза прочитать одну и ту же строку при уровне изоляции READ COMMITTED, то можем поиметь разные значения и нарушение всей логики.

S>2. Оставить логику в миддл-тир, но оборудовать все чтения данных, которые будут использованы в рамках бизнес-транзакции, рукопашными блокировками (например, через sp_getapplock в случае MS SQL).

Во что это выльется на СУБД? Если не ошибаюсь, то во что-то вроде такого:
begin read committed transaction

sp_getapplock('Products', 'Exclusive');
sp_getapplock('Orders', 'Exclusive');

update products set price = 10600 where id = 101;    // обновить прайс

select orders where state = 'N' and productId = 101;    // найти заказы

// среднее звено посчитало и сохраняет найденные ранее заказы
update order set cost = 23232 where id = 1111;
update order set cost = 65465 where id = 2222;
...

sp_releaseapplock('Orders');
sp_releaseapplock('Products');

commit transaction

S>3. Оставить логику в миддл-тир, но переписать её на продвинутые средства вроде linq[2db]. Т.е. получить функциональный аналог п.1, но с нормальной декомпозицией и современным языком, а не процедурным языком образца 1993 года.
Подскажите пожалуйста как это решение будет выглядеть с точки зрения СУБД. Или ссылку на похожий пример дайте.
Если там внутри будет что-то вроде update order set cost = price * quantity (т.е. логика расчёта), то боюсь что такое возможно далеко не всегда. Не любую логику можно выразить через SQL-запрос.
Brainbench transcript #6370594
Re[4]: Блокировки в бизнес-слое
От: Sharov Россия  
Дата: 02.10.17 12:33
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Что произойдёт в пессимистике? Одна из транзакций будет отстрелена с ошибкой, вторая — успешно завершится.


Разве отстрелена? Или она(транзакция) просто встанет в очередь? Или же поведение и в опт. и пес. блокировке зависит от модели изоляции.
Потому как пес. модель мне казалось эквивалентна сериализации... У меня небольшая путаница с моделью блокировок и уровнем изоляции -- зачем надо
париться над уровнем изоляции в случае пессимистичной блокировки?
Кодом людям нужно помогать!
Re[13]: Блокировки в бизнес-слое
От: Sinclair Россия https://github.com/evilguest/
Дата: 03.10.17 08:08
Оценка:
Здравствуйте, Poul_Ko, Вы писали:


S>>Во-первых надо понять, как вообще это всё выглядит с точки зрения СУБД.

P_K>В описанном мной коде и используемом подходе на СУБД это ляжет как-то так:
P_K>
P_K>begin serializable transaction
P_K>update products set price = 10600 where id = 101;    -- обновить прайс

P_K>select orders where state = 'N' and productId = 101;    -- найти заказы

P_K>// среднее звено посчитало и сохраняет найденные ранее заказы
Вот этот момент - опасен. Надо профилировать среднее звено, чтобы "посчитать заказы" не привело к затягиванию транзакции. Ещё опаснее то, что среднее звено может подтянуть ещё какие-то данные, и не факт, что в той же транзакции. 
P_K>update order set cost = 23232 where id = 1111;
P_K>update order set cost = 65465 where id = 2222;
P_K>...

P_K>commit transaction
P_K>


S>>Способов у этого несколько:

S>>1. Воспользоваться хранимыми процедурами.
P_K>Внутри хранимки будет исполняться по сути тот же SQL, что и в случае когда логика отдельно. Только это будет работать быстрее, в рамках одного соединения и т.д. Но проблемы конкурренции по-моему остаются ровно те же самые. Точно также, если внутри хранимки два раза прочитать одну и ту же строку при уровне изоляции READ COMMITTED, то можем поиметь разные значения и нарушение всей логики.
Конечно. Придётся делать repeatable read.
S>>2. Оставить логику в миддл-тир, но оборудовать все чтения данных, которые будут использованы в рамках бизнес-транзакции, рукопашными блокировками (например, через sp_getapplock в случае MS SQL).
P_K>Во что это выльется на СУБД? Если не ошибаюсь, то во что-то вроде такого:
P_K>
P_K>begin read committed transaction

P_K>sp_getapplock('Products', 'Exclusive');
P_K>sp_getapplock('Orders', 'Exclusive');

P_K>update products set price = 10600 where id = 101;    // обновить прайс

P_K>select orders where state = 'N' and productId = 101;    // найти заказы

P_K>// среднее звено посчитало и сохраняет найденные ранее заказы
P_K>update order set cost = 23232 where id = 1111;
P_K>update order set cost = 65465 where id = 2222;
P_K>...
P_K>sp_releaseapplock('Orders');
P_K>sp_releaseapplock('Products');

P_K>commit transaction
P_K>


Нет.
-- процедура смены цены
begin transaction -- read_committed

  sp_getapplock('Product_101', 'Exclusive'); -- захватили лок _нужного нам_ продукта. 
  -- Если в это время висит незакрытая транзакция по добавлению заказа, то мы встанем в ожидании.

  update products set price = 10600 where id = 101;    // обновить прайс

  select orders where state = 'N' and productId = 101;    // найти заказы

  -- среднее звено посчитало и сохраняет найденные ранее заказы
  update order set cost = 23232 where id = 1111;
  update order set cost = 65465 where id = 2222;
  sp_releaseapplock('Product_101'); -- не обязательно - коммит/роллбэк транзакции сам отпустит лок
commit transaction

-- процедура добавления заказа
begin transaction -- read_committed
  sp_getapplock('Product_101', 'Exclusive'); -- захватили лок _нужного нам_ продукта. 
  -- Если в это время висит незакрытая транзакция по изменению цены на продукт, то мы встанем в ожидании.
  insert into orders(cost, productId, quantity, state) 
              values(23232, 101, 50, 'N')
  sp_releaseapplock('Product_101'); -- не обязательно - коммит/роллбэк транзакции сам отпустит лок
commit transaction

S>>3. Оставить логику в миддл-тир, но переписать её на продвинутые средства вроде linq[2db]. Т.е. получить функциональный аналог п.1, но с нормальной декомпозицией и современным языком, а не процедурным языком образца 1993 года.
P_K>Подскажите пожалуйста как это решение будет выглядеть с точки зрения СУБД. Или ссылку на похожий пример дайте.
P_K>Если там внутри будет что-то вроде update order set cost = price * quantity (т.е. логика расчёта), то боюсь что такое возможно далеко не всегда. Не любую логику можно выразить через SQL-запрос.
Да, именно что-то в этом роде и будет. Я вас уверяю, в бизнес-логике ничего сложнее ветвлений и арифметики не встречается. Никаких тебе логарифмов и функций Бесселя там нет.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: Блокировки в бизнес-слое
От: Sinclair Россия https://github.com/evilguest/
Дата: 03.10.17 08:23
Оценка: 6 (1)
Здравствуйте, Sharov, Вы писали:
S>Разве отстрелена? Или она(транзакция) просто встанет в очередь?
Дедлок означает, что в очереди оказался цикл. Чтобы его разорвать, надо выбрать жертву. Стандартная стратегия — отстреливать транзакцию с минимальной накопленной ценой. Это обеспечивает хоть какие-то шансы на успешное завершение: если так не делать, то может оказаться, что в воронку постоянно попадают новые транзакции, которые по очереди становятся жертвами, и коммитов не происходит вообще.
S>Или же поведение и в опт. и пес. блокировке зависит от модели изоляции.
Нет. От модели изоляции зависит то, какие локи накладываются в процессе исполнения транзакций.
S>Потому как пес. модель мне казалось эквивалентна сериализации... У меня небольшая путаница с моделью блокировок и уровнем изоляции -- зачем надо
S>париться над уровнем изоляции в случае пессимистичной блокировки?
Затем, что честный serializable требует накладывания довольно-таки параноидальных блокировок. В частности, если какая-то транзакция сделала select с неким предикатом P, то вплоть до конца транзакции нам надо "заморозить" не только изменение/удаление попавших под предикат записей, но и вставку таких записей. А это довольно сложно сделать точно, поэтому обычно СУБД идёт на компромисс.
Скажем, мы сделали
select * from people where lastname = 'Иванов' and age > 30
.
В параллельной транзакции вставляется запись ('Иванов', 29). Казалось бы, можно продолжать — она не попадает под предикат, так что результат первой транзакции не будет зависеть от того, закоммитили мы вторую или нет.
На практике СУБД не умеют делать "блокировку на предикат". Это было бы слишком дорого. Поэтому обходятся компромиссами — если есть индекс по фамилии, то заблокированы будут все операции с фамилией Иванов, независимо от возраста.
Если нету — то вообще при чтении придётся накладывать shared lock на всю таблицу. И только если нам повезло и есть индекс по (lastname, age), то мы сможем настолько аккуратно выбрать гранулярность, что вставить 29-летнего Иванова будет можно, а 40-летний будет ждать окончания первой транзакции.
Таким образом, мы мало того что теряем время на захват локов, мы ещё и уменьшаем на ровном месте степень параллелизма; даже независимые транзакции вынуждены ждать друг друга.
А вот если мы не используем прочитанные данные "во зло", то можно и не удерживать блокировки так долго — подержали range lock в процессе чтения, и тут же отпустили. Выборка ивановых, полученная таким образом, гарантированно непротиворечива — к примеру, если наша операция вставки всегда вставляет однофамильцев попарно, то подобное чтение всегда будет возвращать чётное количество записей.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: Блокировки в бизнес-слое
От: Poul_Ko Казахстан  
Дата: 03.10.17 08:47
Оценка:
Здравствуйте, Sinclair, Вы писали:
S>
S>-- процедура смены цены
S>begin transaction -- read_committed

S>  sp_getapplock('Product_101', 'Exclusive'); -- захватили лок _нужного нам_ продукта. 
S>  -- Если в это время висит незакрытая транзакция по добавлению заказа, то мы встанем в ожидании.

S>  update products set price = 10600 where id = 101;    // обновить прайс

S>  select orders where state = 'N' and productId = 101;    // найти заказы

S>  -- среднее звено посчитало и сохраняет найденные ранее заказы
S>  update order set cost = 23232 where id = 1111;
S>  update order set cost = 65465 where id = 2222;
S>  sp_releaseapplock('Product_101'); -- не обязательно - коммит/роллбэк транзакции сам отпустит лок
S>commit transaction

S>-- процедура добавления заказа
S>begin transaction -- read_committed
S>  sp_getapplock('Product_101', 'Exclusive'); -- захватили лок _нужного нам_ продукта. 
S>  -- Если в это время висит незакрытая транзакция по изменению цены на продукт, то мы встанем в ожидании.
S>  insert into orders(cost, productId, quantity, state) 
S>              values(23232, 101, 50, 'N')
S>  sp_releaseapplock('Product_101'); -- не обязательно - коммит/роллбэк транзакции сам отпустит лок
S>commit transaction
S>

Раз мы прочитали заказы и собираемся их обновлять, то по-хорошему их также нужно блокировать. Чтобы никто другой не мог параллельно изменить / удалить заказ.
Хотя в этом случае может помочь как раз таки оптимистическая блокировка. Тогда апдейты должны в коде выше должны снабжаться чем-то вроде 'and version = 234'.

S>Я вас уверяю, в бизнес-логике ничего сложнее ветвлений и арифметики не встречается. Никаких тебе логарифмов и функций Бесселя там нет.

Речь не об этом. При определённой сложности (в плане количества ветвлений) простая реализация логики в виде одного метода становится невозможной. Получаем ад из ветвлений и всяких флагов... В итоге всё это рефакторится в отдельные классы (например, что-то вроде стратегий), приобретает понятную форму и тестируемость. На хранимых процедурах такой финт невозможен, думаю согласитесь. Свести всё к запросам? Интересная идея, хорошо бы увидеть реальные примеры такого дизайна чтобы понять границы применимости.
Brainbench transcript #6370594
Re[15]: Блокировки в бизнес-слое
От: Sinclair Россия https://github.com/evilguest/
Дата: 03.10.17 10:41
Оценка:
Здравствуйте, Poul_Ko, Вы писали:

P_K>Раз мы прочитали заказы и собираемся их обновлять, то по-хорошему их также нужно блокировать. Чтобы никто другой не мог параллельно изменить / удалить заказ.

В приведённой архитектуре при любом изменении заказа нужно получать блокировки на продукт — ровно по той же причине.
-- внесение изменения в заказ
begin tran -- read_committed
select @old_productId = productId from orders with updlock where id = @order_id -- заранее захватываем U-блокировку, чтобы избежать дедлоков 
set @product1 = min(@old_productId, @new_productId) -- предотвращаем дедлоки, вводя строгую упорядоченность в захвате локов на продукт
set @product2 = max(@old_productId, @new_productId)
sp_getapplock('product_'+@product1)
sp_getapplock('product_'+@product2)

update orders set productId = @new_productId, cost = @new_Cost, ..., where id = @order_id

commit tran

-- удаление заказа
begin tran -- read_committed
select @productId = productId from orders with updlock where id = @order_id -- заранее захватываем U-блокировку, чтобы избежать дедлоков 
sp_getapplock('product_'+@productIв)
delete from orders set productId = @new_productId, cost = @new_Cost, ... where id = @order_id

commit tran


P_K>Речь не об этом. При определённой сложности (в плане количества ветвлений) простая реализация логики в виде одного метода становится невозможной. Получаем ад из ветвлений и всяких флагов... В итоге всё это рефакторится в отдельные классы (например, что-то вроде стратегий), приобретает понятную форму и тестируемость. На хранимых процедурах такой финт невозможен, думаю согласитесь.

Поэтому я вам и говорю про Linq. Он позволяет писать аккуратный и понятный код, который будет транслироваться в корректный SQL без лишних усилий с вашей стороны.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 03.10.2017 10:42 Sinclair . Предыдущая версия .
Re[16]: Блокировки в бизнес-слое
От: Poul_Ko Казахстан  
Дата: 03.10.17 11:18
Оценка:
Здравствуйте, Sinclair, Вы писали:
S>Поэтому я вам и говорю про Linq. Он позволяет писать аккуратный и понятный код, который будет транслироваться в корректный SQL без лишних усилий с вашей стороны.
Эту мысль я понял.
Смотрите, ведь linq будет оперировать сущностями слоя доступа к данным, а не сущностями бизнес-уровня. Получаем переезд части логики в DAL.
Конечно, если бизнес-сущности и таблицы соотносятся очень просто, то можно написать linq на бизнес-сущностях и это всегда транслируется в SQL. Но к сожалению так бывает не всегда.
Поэтому пока складывается мнение что подход интересный, но работает в относительно простых случаях.
Brainbench transcript #6370594
Re[17]: Блокировки в бизнес-слое
От: Sinclair Россия https://github.com/evilguest/
Дата: 03.10.17 16:45
Оценка:
Здравствуйте, Poul_Ko, Вы писали:
P_K>Смотрите, ведь linq будет оперировать сущностями слоя доступа к данным, а не сущностями бизнес-уровня. Получаем переезд части логики в DAL.
P_K>Конечно, если бизнес-сущности и таблицы соотносятся очень просто, то можно написать linq на бизнес-сущностях и это всегда транслируется в SQL. Но к сожалению так бывает не всегда.
P_K>Поэтому пока складывается мнение что подход интересный, но работает в относительно простых случаях.
Хотелось бы убедительный пример.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[18]: Блокировки в бизнес-слое
От: Stalker. Австралия  
Дата: 03.10.17 23:11
Оценка: 66 (1)
Здравствуйте, Sinclair, Вы писали:

S>Хотелось бы убедительный пример.


Я никаких преимуществ linq так и не нашел, зато недостатков вагон и маленькая тележка. Да, в простых случаях, если простенькую бизнес-логику впихнуть в классы контекста, то можно быстро сваять приложение даже не зная что такое SQL Server. Оно само там будет внутри как-то работать благодаря EF, подходит студентам и разработчикам не знающим SQL.
А как только сложность приложения перерастает определенный уровень, то мгновенно вылазят проблемы, Во-первых, логику в контекстные файлы конечно-же не сложишь, нужен будет маппинг между ними. Научится писать запросы на linq — это по-сути выучить SQL по-новой, т.к. запросы там пишутся совершенно по-другому синтаксически. В простых случаях "для студентов" это прокатывает т.к. они вместо написания нормального запроса лепят просто циклы на циклах, а во что это превращается на стороне сервера и понятия не имеют. В сложных запросах с джойнами, группировками и прочим начинают вылезать ограничения linq, скажем там нет оператора IN, приходится часто искать обходные пути, компилятор отказывается воспринимать определеннные конструкции, скажем при несовпадении типа данных при джойне таблиц комилятор заставит сделать приведение к нужному типу, после чего радостно превратит это в CAST в where запроса убив мне индекс. Написать запрос конечно можно будет, только хранимка с нормальным SQL займет куда меньше времени и часто будет куда эффективней
Единственный недостаток хранимок что они начинают плодится под разные запросы, при изменении структуры базы их все надо будет обновлять, что менее удобно т.к. в linq соответствующие ошибки будут подсвечены компилятором
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.