Страница 1 из 1

Optimistic Lock

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

Re: Optimistic Lock

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

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

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

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

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

Re: Optimistic Lock

Добавлено: 2022.01.20, 17:16
Алена
Т.е в момент отправки формы нужно сравнить значения скрытых полей с чем?

Re: Optimistic Lock

Добавлено: 2022.01.20, 20:31
ElisDN
Алена писал(а): 2022.01.20, 17:16 Т.е в момент отправки формы нужно сравнить значения скрытых полей с чем?
С переданными открытыми полями.

Re: Optimistic Lock

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

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

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

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

Re: Optimistic Lock

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

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

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

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

Re: Optimistic Lock

Добавлено: 2022.01.21, 10:43
maleks
Я видел в коммерческих проектах.
Не свободные расширения.

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

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

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

Фишка, теперь при работе с такой формой:
- когда форма сабмитится успешно, то блокировка убирается
- когда форма сабмитится с ошибкой валидации, то блокировка увеличивается
- если просто открыли форму и ДОЛГО в ней открытой сидят, то
--- смотрим что если есть действия пользователя, мышкой там или фокус на элементах, то надо аяксом блокировку продливать
--- если нет никаких действий, и долгенько, то можно всплывающее окно вывести - "Долго, нет действий, закрыть?" с таймером, и уйти с этой страницы, убирая блокировку.

Re: Optimistic Lock

Добавлено: 2022.01.21, 10:53
Алена
а можно ли как-то поставить флаг на открытие формы? т.е. если форма открыта кем-то, то просто получаем сообщение, что в данный момент форма кем-то редактируется...

Re: Optimistic Lock

Добавлено: 2022.01.21, 11:07
maleks
Ну вот же я выше говорил:
записывается кто заблокировал и какую модель.
при открытии формы добавляется эта запись.

Re: Optimistic Lock

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

Re: Optimistic Lock

Добавлено: 2022.01.21, 14:43
maleks
если аккуратно сделать, то должно работать норм

Re: Optimistic Lock

Добавлено: 2022.01.21, 20:10
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. На больших объемах это будет тормозить очень

Re: Optimistic Lock

Добавлено: 2022.01.24, 13:24
Алена
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.

Re: Optimistic Lock

Добавлено: 2022.01.26, 18:44
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 выполняется, второй пользователь пишет в БД и ваше решение не будет работать.

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