Optimistic Lock

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Алена
Сообщения: 26
Зарегистрирован: 2019.01.01, 18:02

Optimistic Lock

Сообщение Алена »

Привет всем! Нужно реализовать следующее - два юзера одновременно вносят правки в одну и ту же таблицу в БД. В таблице несколько полей. Если редактируемые поля пересекаются, тогда срабатывает что-то типа optimistic lock, если не пересекаются, то изменения просто мержатся, т.е. и первый юзер, и второй (в теории - и третий, и четвертый и фиг знает какой) спокойно сохраняют изменения.
Просто делать через optimistic lock не вариант, потому что он сравнивает одно поле. Мне же нужно сравнить, какие стобцы изменяются, совпадают ли они у разных юзеров...
Пробовала вариант получить значения $model->getOldAttributes(); сразу после открытия формы, потом получить то же самое перед сохранением формы и сравнить оба массива через array_diff(). Ничего не дало. Кто-нибудь знает, куда копать?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Optimistic Lock

Сообщение ElisDN »

Просто так в классическом варианте с сервером Apache или PHP-FPM сравнить запросы не получится, так как при отправке формы у каждого пользователя запускается свой экземпляр PHP-кода, который ничего не знает о соседних запросах.

И oldAttributes не поможет, так как во время между Model::findOne и model->save уже мог прилететь соседний запрос и в базе уже другие данные.

Поэтому чтобы реально определить изменения нужно и свои прошлые значения дублировать в форме в виде hidden-полей. И в момент отправки формы строить diff уже по ним.

А далее вместо прямого параллельного сохранения в БД можно сделать отложенное последовательное. А именно команды на редактирование только с изменёнными данными можно складывать в очередь. А потом разбирать её и сохранять последовательно, уже сравнивая текущие изменения с предыдущими.

Либо если данных много, то вместо всего этого можно большую таблицу с кучей полей разбить на несколько мелких таблиц и редактировать каждую своей отдельной формой с использованием Optimistic Lock. Тогда вероятность одновременной правки одной таблицы будет меньше.
Алена
Сообщения: 26
Зарегистрирован: 2019.01.01, 18:02

Re: Optimistic Lock

Сообщение Алена »

Т.е в момент отправки формы нужно сравнить значения скрытых полей с чем?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Optimistic Lock

Сообщение ElisDN »

Алена писал(а): 2022.01.20, 17:16 Т.е в момент отправки формы нужно сравнить значения скрытых полей с чем?
С переданными открытыми полями.
Аватара пользователя
maleks
Сообщения: 1985
Зарегистрирован: 2012.12.26, 12:56

Re: Optimistic Lock

Сообщение maleks »

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

- При отображении модели в форме можно $model->attributes, сохранить где то в базе по ключу
- в форму передать этот ключ
- при обработке сабмита, мы можем:
--- по этому ключу достать нашу стартовую модель, в нее загрузить запрос, и сравнив в нами засабмитиным, узнаем что мы изменили
--- измененные поля сравниваем между стартовой моделью и той что реально сейчас новенькая в БД, получим инфу что кто то уже изменил до нас.

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

Я встречал успешные реализации другого подхода - при заходе на редактирование некоей сущности, она блокируется на редактирование другими на какое то время, но тут тоже тонкостей хватило.
Yii2 universal module sceleton - for basic and advanced templates
Алена
Сообщения: 26
Зарегистрирован: 2019.01.01, 18:02

Re: Optimistic Lock

Сообщение Алена »

maleks писал(а): 2022.01.21, 09:15 Мне видится, что в скрытых формах хранить всю массу старых параметров будет очень хлопотно.
Тут же еще будет разница в типах, что из формы пришло и что в модели.
Да и формы бывают большими.

- При отображении модели в форме можно $model->attributes, сохранить где то в базе по ключу
- в форму передать этот ключ
- при обработке сабмита, мы можем:
--- по этому ключу достать нашу стартовую модель, в нее загрузить запрос, и сравнив в нами засабмитиным, узнаем что мы изменили
--- измененные поля сравниваем между стартовой моделью и той что реально сейчас новенькая в БД, получим инфу что кто то уже изменил до нас.

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

Я встречал успешные реализации другого подхода - при заходе на редактирование некоей сущности, она блокируется на редактирование другими на какое то время, но тут тоже тонкостей хватило.
Не подскажете, где посмотреть? тоже думала об этом, как отследить, если форму для редактирования уже кто-то открыл, то на какое-то время ее заблокировать для редактирования...
но как это реализовать, что-то не придумывается
Аватара пользователя
maleks
Сообщения: 1985
Зарегистрирован: 2012.12.26, 12:56

Re: Optimistic Lock

Сообщение maleks »

Я видел в коммерческих проектах.
Не свободные расширения.

Но суть такая, что в табличку lock, такого плана
id modelclass model_id user_who_blocked_id time_create time_expire

записывается кто заблокировал и какую модель.

При этом по крону такие записи удаляются, если time_expire стало больше time()

Фишка, теперь при работе с такой формой:
- когда форма сабмитится успешно, то блокировка убирается
- когда форма сабмитится с ошибкой валидации, то блокировка увеличивается
- если просто открыли форму и ДОЛГО в ней открытой сидят, то
--- смотрим что если есть действия пользователя, мышкой там или фокус на элементах, то надо аяксом блокировку продливать
--- если нет никаких действий, и долгенько, то можно всплывающее окно вывести - "Долго, нет действий, закрыть?" с таймером, и уйти с этой страницы, убирая блокировку.
Yii2 universal module sceleton - for basic and advanced templates
Алена
Сообщения: 26
Зарегистрирован: 2019.01.01, 18:02

Re: Optimistic Lock

Сообщение Алена »

а можно ли как-то поставить флаг на открытие формы? т.е. если форма открыта кем-то, то просто получаем сообщение, что в данный момент форма кем-то редактируется...
Аватара пользователя
maleks
Сообщения: 1985
Зарегистрирован: 2012.12.26, 12:56

Re: Optimistic Lock

Сообщение maleks »

Ну вот же я выше говорил:
записывается кто заблокировал и какую модель.
при открытии формы добавляется эта запись.
Yii2 universal module sceleton - for basic and advanced templates
Алена
Сообщения: 26
Зарегистрирован: 2019.01.01, 18:02

Re: Optimistic Lock

Сообщение Алена »

ага... обдумаю... я тут думала вот над такой логикой
- получить attribute всех полей в момент открытия формы (array1)
- получить поля, которые я редактирую (array2)
- перед отправкой формы снова получить аттрибуты всех полей(array3)
- сравнить array3 и array1 - получить array4 всех полей, которые изменились с момента, когда я только открыла форму
- сравнить array4 с array2 - если пересечения есть, то сохранение блокируем, если нет, то сохраняем
вроде как должно сработать... не слишком ли это будет много всяких сравнений?
Аватара пользователя
maleks
Сообщения: 1985
Зарегистрирован: 2012.12.26, 12:56

Re: Optimistic Lock

Сообщение maleks »

если аккуратно сделать, то должно работать норм
Yii2 universal module sceleton - for basic and advanced templates
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Optimistic Lock

Сообщение SiZE »

Алена писал(а): 2022.01.21, 11:18 ага... обдумаю... я тут думала вот над такой логикой
- получить attribute всех полей в момент открытия формы (array1)
- получить поля, которые я редактирую (array2)
- перед отправкой формы снова получить аттрибуты всех полей(array3)
- сравнить array3 и array1 - получить array4 всех полей, которые изменились с момента, когда я только открыла форму
- сравнить array4 с array2 - если пересечения есть, то сохранение блокируем, если нет, то сохраняем
вроде как должно сработать... не слишком ли это будет много всяких сравнений?
Я продублирую из чата. На всякий уточню, получаем мы данные только ОДИН раз.
1. Читаешь один раз перед редактированием оригинальные значения

Код: Выделить всё

SELECT id, col1, col2, col3, colN FROM table WHERE id=:id;
2. Прокидываешь оригинальные значения в форму

3. Отправляешь вместе с измененными значениями на сервер

4. Проверяешь какие поля изменились, записываешь исходные значения и новые

Код: Выделить всё

$changed = [];
if ($form->originalCol1 != $form->userCol1) {
   $changed['col1'] = [
      'orignial' => $form->originalCol1,
      'user' => $form->userCol1,
   ];
}
if ($form->originalCol2 != $form->userCol2) {
   $changed['col2'] = [
      'orignial' => $form->originalCol2,
      'user' => $form->userCol2,
   ];
}
...

5. Делаешь UPDATE изменений, собираешь SQL или через AR без разницы

Код: Выделить всё

UPDATE table SET col1=:userCol1Val, col2=:userCol2Val WHERE col1=:originalCol1Val AND col2=:originalCol2Val
6. Если affected_rows = 1 сообщаешь об успехе, иначе возвращаешь на редактирование обратно пользователю

P.S. На больших объемах это будет тормозить очень
Алена
Сообщения: 26
Зарегистрирован: 2019.01.01, 18:02

Re: Optimistic Lock

Сообщение Алена »

SiZE писал(а): 2022.01.21, 20:10
Алена писал(а): 2022.01.21, 11:18 ага... обдумаю... я тут думала вот над такой логикой
- получить attribute всех полей в момент открытия формы (array1)
- получить поля, которые я редактирую (array2)
- перед отправкой формы снова получить аттрибуты всех полей(array3)
- сравнить array3 и array1 - получить array4 всех полей, которые изменились с момента, когда я только открыла форму
- сравнить array4 с array2 - если пересечения есть, то сохранение блокируем, если нет, то сохраняем
вроде как должно сработать... не слишком ли это будет много всяких сравнений?
Я продублирую из чата. На всякий уточню, получаем мы данные только ОДИН раз.
1. Читаешь один раз перед редактированием оригинальные значения

Код: Выделить всё

SELECT id, col1, col2, col3, colN FROM table WHERE id=:id;
2. Прокидываешь оригинальные значения в форму

3. Отправляешь вместе с измененными значениями на сервер

4. Проверяешь какие поля изменились, записываешь исходные значения и новые

Код: Выделить всё

$changed = [];
if ($form->originalCol1 != $form->userCol1) {
   $changed['col1'] = [
      'orignial' => $form->originalCol1,
      'user' => $form->userCol1,
   ];
}
if ($form->originalCol2 != $form->userCol2) {
   $changed['col2'] = [
      'orignial' => $form->originalCol2,
      'user' => $form->userCol2,
   ];
}
...

5. Делаешь UPDATE изменений, собираешь SQL или через AR без разницы

Код: Выделить всё

UPDATE table SET col1=:userCol1Val, col2=:userCol2Val WHERE col1=:originalCol1Val AND col2=:originalCol2Val
6. Если affected_rows = 1 сообщаешь об успехе, иначе возвращаешь на редактирование обратно пользователю

P.S. На больших объемах это будет тормозить очень

Данных много. Да, тормозит. Уже голова сломалась тут думать. По мне, так самое простое и верное решение - не давать редактировать форму одновременно нескольким юзерам. И проблем не будет. Все-таки пытаюсь сейчас со сравнением полей, вот так как-то
Старые данные - a1 =Yii::$app->session->get('directory/country')['modalData']
Новые данные - a2 = $model->getAttributes()
Данные из БД - a3 = $this->findModel($id)->getAttributes()
потом
$arrayValuesFirstUser = array_diff_assoc($oldData, $dbData); // $array4 - поля, измененные другим юзером
$arrayValuesNextUser = array_diff_assoc($newData, $oldData);// $array5 - поля, измененные данным юзером

Если a5 и a4 по ключам пересекаются, то до свидания, если нет, то применить a4 в a2.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Optimistic Lock

Сообщение SiZE »

Алена писал(а): 2022.01.24, 13:24 Данных много. Да, тормозит. Уже голова сломалась тут думать. По мне, так самое простое и верное решение - не давать редактировать форму одновременно нескольким юзерам. И проблем не будет. Все-таки пытаюсь сейчас со сравнением полей, вот так как-то
Старые данные - a1 =Yii::$app->session->get('directory/country')['modalData']
Новые данные - a2 = $model->getAttributes()
Данные из БД - a3 = $this->findModel($id)->getAttributes()
потом
$arrayValuesFirstUser = array_diff_assoc($oldData, $dbData); // $array4 - поля, измененные другим юзером
$arrayValuesNextUser = array_diff_assoc($newData, $oldData);// $array5 - поля, измененные данным юзером

Если a5 и a4 по ключам пересекаются, то до свидания, если нет, то применить a4 в a2.
Так суть вся в том, что пока у вас array_diff_assoc выполняется, второй пользователь пишет в БД и ваше решение не будет работать.

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