Optimistic Lock
Optimistic Lock
Привет всем! Нужно реализовать следующее - два юзера одновременно вносят правки в одну и ту же таблицу в БД. В таблице несколько полей. Если редактируемые поля пересекаются, тогда срабатывает что-то типа optimistic lock, если не пересекаются, то изменения просто мержатся, т.е. и первый юзер, и второй (в теории - и третий, и четвертый и фиг знает какой) спокойно сохраняют изменения.
Просто делать через optimistic lock не вариант, потому что он сравнивает одно поле. Мне же нужно сравнить, какие стобцы изменяются, совпадают ли они у разных юзеров...
Пробовала вариант получить значения $model->getOldAttributes(); сразу после открытия формы, потом получить то же самое перед сохранением формы и сравнить оба массива через array_diff(). Ничего не дало. Кто-нибудь знает, куда копать?
Просто делать через optimistic lock не вариант, потому что он сравнивает одно поле. Мне же нужно сравнить, какие стобцы изменяются, совпадают ли они у разных юзеров...
Пробовала вариант получить значения $model->getOldAttributes(); сразу после открытия формы, потом получить то же самое перед сохранением формы и сравнить оба массива через array_diff(). Ничего не дало. Кто-нибудь знает, куда копать?
Re: Optimistic Lock
Просто так в классическом варианте с сервером Apache или PHP-FPM сравнить запросы не получится, так как при отправке формы у каждого пользователя запускается свой экземпляр PHP-кода, который ничего не знает о соседних запросах.
И oldAttributes не поможет, так как во время между Model::findOne и model->save уже мог прилететь соседний запрос и в базе уже другие данные.
Поэтому чтобы реально определить изменения нужно и свои прошлые значения дублировать в форме в виде hidden-полей. И в момент отправки формы строить diff уже по ним.
А далее вместо прямого параллельного сохранения в БД можно сделать отложенное последовательное. А именно команды на редактирование только с изменёнными данными можно складывать в очередь. А потом разбирать её и сохранять последовательно, уже сравнивая текущие изменения с предыдущими.
Либо если данных много, то вместо всего этого можно большую таблицу с кучей полей разбить на несколько мелких таблиц и редактировать каждую своей отдельной формой с использованием Optimistic Lock. Тогда вероятность одновременной правки одной таблицы будет меньше.
И oldAttributes не поможет, так как во время между Model::findOne и model->save уже мог прилететь соседний запрос и в базе уже другие данные.
Поэтому чтобы реально определить изменения нужно и свои прошлые значения дублировать в форме в виде hidden-полей. И в момент отправки формы строить diff уже по ним.
А далее вместо прямого параллельного сохранения в БД можно сделать отложенное последовательное. А именно команды на редактирование только с изменёнными данными можно складывать в очередь. А потом разбирать её и сохранять последовательно, уже сравнивая текущие изменения с предыдущими.
Либо если данных много, то вместо всего этого можно большую таблицу с кучей полей разбить на несколько мелких таблиц и редактировать каждую своей отдельной формой с использованием Optimistic Lock. Тогда вероятность одновременной правки одной таблицы будет меньше.
Re: Optimistic Lock
Т.е в момент отправки формы нужно сравнить значения скрытых полей с чем?
Re: Optimistic Lock
Мне видится, что в скрытых формах хранить всю массу старых параметров будет очень хлопотно.
Тут же еще будет разница в типах, что из формы пришло и что в модели.
Да и формы бывают большими.
- При отображении модели в форме можно $model->attributes, сохранить где то в базе по ключу
- в форму передать этот ключ
- при обработке сабмита, мы можем:
--- по этому ключу достать нашу стартовую модель, в нее загрузить запрос, и сравнив в нами засабмитиным, узнаем что мы изменили
--- измененные поля сравниваем между стартовой моделью и той что реально сейчас новенькая в БД, получим инфу что кто то уже изменил до нас.
Но вообще, довольно недружелюбный функционал получается, люди мучаются, заполняют форму, чтобы получить отворот-поворот.
Далеко не везде такой функционал примут.
Я встречал успешные реализации другого подхода - при заходе на редактирование некоей сущности, она блокируется на редактирование другими на какое то время, но тут тоже тонкостей хватило.
Тут же еще будет разница в типах, что из формы пришло и что в модели.
Да и формы бывают большими.
- При отображении модели в форме можно $model->attributes, сохранить где то в базе по ключу
- в форму передать этот ключ
- при обработке сабмита, мы можем:
--- по этому ключу достать нашу стартовую модель, в нее загрузить запрос, и сравнив в нами засабмитиным, узнаем что мы изменили
--- измененные поля сравниваем между стартовой моделью и той что реально сейчас новенькая в БД, получим инфу что кто то уже изменил до нас.
Но вообще, довольно недружелюбный функционал получается, люди мучаются, заполняют форму, чтобы получить отворот-поворот.
Далеко не везде такой функционал примут.
Я встречал успешные реализации другого подхода - при заходе на редактирование некоей сущности, она блокируется на редактирование другими на какое то время, но тут тоже тонкостей хватило.
Re: Optimistic Lock
Не подскажете, где посмотреть? тоже думала об этом, как отследить, если форму для редактирования уже кто-то открыл, то на какое-то время ее заблокировать для редактирования...maleks писал(а): ↑2022.01.21, 09:15 Мне видится, что в скрытых формах хранить всю массу старых параметров будет очень хлопотно.
Тут же еще будет разница в типах, что из формы пришло и что в модели.
Да и формы бывают большими.
- При отображении модели в форме можно $model->attributes, сохранить где то в базе по ключу
- в форму передать этот ключ
- при обработке сабмита, мы можем:
--- по этому ключу достать нашу стартовую модель, в нее загрузить запрос, и сравнив в нами засабмитиным, узнаем что мы изменили
--- измененные поля сравниваем между стартовой моделью и той что реально сейчас новенькая в БД, получим инфу что кто то уже изменил до нас.
Но вообще, довольно недружелюбный функционал получается, люди мучаются, заполняют форму, чтобы получить отворот-поворот.
Далеко не везде такой функционал примут.
Я встречал успешные реализации другого подхода - при заходе на редактирование некоей сущности, она блокируется на редактирование другими на какое то время, но тут тоже тонкостей хватило.
но как это реализовать, что-то не придумывается
Re: Optimistic Lock
Я видел в коммерческих проектах.
Не свободные расширения.
Но суть такая, что в табличку lock, такого плана
id modelclass model_id user_who_blocked_id time_create time_expire
записывается кто заблокировал и какую модель.
При этом по крону такие записи удаляются, если time_expire стало больше time()
Фишка, теперь при работе с такой формой:
- когда форма сабмитится успешно, то блокировка убирается
- когда форма сабмитится с ошибкой валидации, то блокировка увеличивается
- если просто открыли форму и ДОЛГО в ней открытой сидят, то
--- смотрим что если есть действия пользователя, мышкой там или фокус на элементах, то надо аяксом блокировку продливать
--- если нет никаких действий, и долгенько, то можно всплывающее окно вывести - "Долго, нет действий, закрыть?" с таймером, и уйти с этой страницы, убирая блокировку.
Не свободные расширения.
Но суть такая, что в табличку lock, такого плана
id modelclass model_id user_who_blocked_id time_create time_expire
записывается кто заблокировал и какую модель.
При этом по крону такие записи удаляются, если time_expire стало больше time()
Фишка, теперь при работе с такой формой:
- когда форма сабмитится успешно, то блокировка убирается
- когда форма сабмитится с ошибкой валидации, то блокировка увеличивается
- если просто открыли форму и ДОЛГО в ней открытой сидят, то
--- смотрим что если есть действия пользователя, мышкой там или фокус на элементах, то надо аяксом блокировку продливать
--- если нет никаких действий, и долгенько, то можно всплывающее окно вывести - "Долго, нет действий, закрыть?" с таймером, и уйти с этой страницы, убирая блокировку.
Re: Optimistic Lock
а можно ли как-то поставить флаг на открытие формы? т.е. если форма открыта кем-то, то просто получаем сообщение, что в данный момент форма кем-то редактируется...
Re: Optimistic Lock
Ну вот же я выше говорил:
при открытии формы добавляется эта запись.записывается кто заблокировал и какую модель.
Re: Optimistic Lock
ага... обдумаю... я тут думала вот над такой логикой
- получить attribute всех полей в момент открытия формы (array1)
- получить поля, которые я редактирую (array2)
- перед отправкой формы снова получить аттрибуты всех полей(array3)
- сравнить array3 и array1 - получить array4 всех полей, которые изменились с момента, когда я только открыла форму
- сравнить array4 с array2 - если пересечения есть, то сохранение блокируем, если нет, то сохраняем
вроде как должно сработать... не слишком ли это будет много всяких сравнений?
- получить attribute всех полей в момент открытия формы (array1)
- получить поля, которые я редактирую (array2)
- перед отправкой формы снова получить аттрибуты всех полей(array3)
- сравнить array3 и array1 - получить array4 всех полей, которые изменились с момента, когда я только открыла форму
- сравнить array4 с array2 - если пересечения есть, то сохранение блокируем, если нет, то сохраняем
вроде как должно сработать... не слишком ли это будет много всяких сравнений?
Re: Optimistic Lock
если аккуратно сделать, то должно работать норм
Re: Optimistic Lock
Я продублирую из чата. На всякий уточню, получаем мы данные только ОДИН раз.Алена писал(а): ↑2022.01.21, 11:18 ага... обдумаю... я тут думала вот над такой логикой
- получить attribute всех полей в момент открытия формы (array1)
- получить поля, которые я редактирую (array2)
- перед отправкой формы снова получить аттрибуты всех полей(array3)
- сравнить array3 и array1 - получить array4 всех полей, которые изменились с момента, когда я только открыла форму
- сравнить array4 с array2 - если пересечения есть, то сохранение блокируем, если нет, то сохраняем
вроде как должно сработать... не слишком ли это будет много всяких сравнений?
1. Читаешь один раз перед редактированием оригинальные значения2. Прокидываешь оригинальные значения в формуКод: Выделить всё
SELECT id, col1, col2, col3, colN FROM table WHERE id=:id;
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 без разницы
6. Если affected_rows = 1 сообщаешь об успехе, иначе возвращаешь на редактирование обратно пользователюКод: Выделить всё
UPDATE table SET col1=:userCol1Val, col2=:userCol2Val WHERE col1=:originalCol1Val AND col2=:originalCol2Val
P.S. На больших объемах это будет тормозить очень
Re: Optimistic Lock
SiZE писал(а): ↑2022.01.21, 20:10Я продублирую из чата. На всякий уточню, получаем мы данные только ОДИН раз.Алена писал(а): ↑2022.01.21, 11:18 ага... обдумаю... я тут думала вот над такой логикой
- получить attribute всех полей в момент открытия формы (array1)
- получить поля, которые я редактирую (array2)
- перед отправкой формы снова получить аттрибуты всех полей(array3)
- сравнить array3 и array1 - получить array4 всех полей, которые изменились с момента, когда я только открыла форму
- сравнить array4 с array2 - если пересечения есть, то сохранение блокируем, если нет, то сохраняем
вроде как должно сработать... не слишком ли это будет много всяких сравнений?
1. Читаешь один раз перед редактированием оригинальные значения2. Прокидываешь оригинальные значения в формуКод: Выделить всё
SELECT id, col1, col2, col3, colN FROM table WHERE id=:id;
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 без разницы
6. Если affected_rows = 1 сообщаешь об успехе, иначе возвращаешь на редактирование обратно пользователюКод: Выделить всё
UPDATE table SET col1=:userCol1Val, col2=:userCol2Val WHERE col1=:originalCol1Val AND col2=:originalCol2Val
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
Так суть вся в том, что пока у вас array_diff_assoc выполняется, второй пользователь пишет в БД и ваше решение не будет работать.Алена писал(а): ↑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.
Если оставить такой код, то придется изолировать транзакцию от чтения, те блокировать строку на изменение, читать, сравнивать, писать, снимать блокировку. В это время параллельная транзакция будет ждать, когда снимется блокировка. Если пару человек работают, то норм.