Optimistic Locking

80 views
Skip to first unread message

wholegroup

unread,
Jul 23, 2010, 4:47:31 AM7/23/10
to ruGrails
Помогите разобраться с оптимистичными блокировками.

Простая модель для тестирования:
class Registration
{
Date dateRequest;
}

в таблице есть одна запись с id = 1


1) Следующий код отрабатывает без проблем:

def testOne = {
def one = Registration.get(1);
def two = Registration.get(1);

one.dateRequest = new Date();
one.save(flush: true);

sleep(1000);

two.dateRequest = new Date();
two.save(flush: true);
}

По моей логике на строке two.save должен сработать exception, так как
при one.save поле `version` в таблице увеличивается на единицу и
данные считаются уже измененными.

2) Кучу не понятных exception, хотя должен быть быть только один (судя
по документации http://www.grails.org/doc/latest/guide/single.html#5.3.5
Pessimistic and Optimistic Locking)

def testTwo
{
def existReg = Registration.get(1);

if (params.test)
{
sleep(5000);
}

try
{
existReg.dateRequest = new Date();
existReg.save(flush: true);
}
catch(org.springframework.dao.OptimisticLockingFailureException e)
{
}

}

запускаю в следующей последовательности:
* /site/testTwo?test=1 (открывает запись и засыпает на 5 секунд)
* /site/testTwo (открывает запись, изменяет, сохраняет успешно)

Далее первый запрос через 5 секунд при сохранении отваливается с
ошибками:

Error 500: Object of class [Registration] with identifier [1]:
optimistic locking failed; nested exception is
org.hibernate.StaleObjectStateException: Row was updated or deleted by
another transaction (or unsaved-value mapping was incorrect):
[Registration#1]
Servlet: grails
URI: /app/grails/site/testTwo.dispatch
Exception Message: Row was updated or deleted by another transaction
(or unsaved-value mapping was incorrect): [Registration#1]
Caused by: Object of class [Registration] with identifier [1]:
optimistic locking failed; nested exception is
org.hibernate.StaleObjectStateException: Row was updated or deleted by
another transaction (or unsaved-value mapping was incorrect):
[Registration#1]
Class: Unknown

Кто как работает с оптимистичными блокировками ? И что делать?

Sergey Dolgopolov

unread,
Jul 23, 2010, 5:00:48 AM7/23/10
to ruGrails
По поводу 1)
Всё правильно, т.к. после выполнения кода

def one = Registration.get(1);
def two = Registration.get(1);
переменный one и two ссылаются на один и тот же объект. К тому же код
выполняется последовательно внутри одной Hibernate-сессии
Здесь никаких блокировок нет и в помине.

Sergey Dolgopolov

unread,
Jul 23, 2010, 5:07:58 AM7/23/10
to ruGrails

ig78

unread,
Jul 23, 2010, 7:57:49 AM7/23/10
to ruGrails
В вашем случае работают ДВА механизма блокировок:
1. блокировка со стороны jvm
2. блокировка со стороны hibernate.

когда вы "засыпаете", то jvm, точнее ее менеджер защиты блокирует все
созданные в текущем контексте исполнения объекты, чтобы кто-нибудь
случайно что не повредил. в то же время hibernate - это отдельный
механизм, отдельная "кухня", которая работает по своим правилам,
управляя менеджируемыми в ЕЕ текущем контексте объектами
(синхронизация данных с базой...), т.е. управляя текущей сессией. Вот
у вас и получается что hibernate-кухня ломится к заблокированным на
уровне jvm объектам (как работает hibernate -
http://java-money.blogspot.com/2009/10/hibernate.html).

варианты решения:
1. считывать объект после sleep (сделать это вам вообще ничто не
мешает)
2. пробовать блокировать ваш exitReg средствами hibernate (точно не
помню, но что-то вроде exitReg.lock())

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


On Jul 23, 11:47 am, wholegroup <wholegr...@gmail.com> wrote:
> Помогите разобраться с оптимистичными блокировками.
>
> Простая модель для тестирования:
> class Registration
> {
>         Date dateRequest;
>
> }
>
> в таблице есть одна запись с id = 1
>
> 1) Следующий код отрабатывает без проблем:
>
> def testOne = {
>         def one = Registration.get(1);
>         def two = Registration.get(1);
>
>         one.dateRequest = new Date();
>         one.save(flush: true);
>
>         sleep(1000);
>
>         two.dateRequest = new Date();
>         two.save(flush: true);
>
> }
>
> По моей логике на строке two.save должен сработать exception, так как
> при one.save поле `version` в таблице увеличивается на единицу и
> данные считаются уже измененными.
>
> 2) Кучу не понятных exception, хотя должен быть быть только один (судя

> по документацииhttp://www.grails.org/doc/latest/guide/single.html#5.3.5

wholegroup

unread,
Jul 27, 2010, 2:36:43 PM7/27/10
to ruGrails

Этот код исключительно для тестирования блокировок в Grails.
Попробывал заменить sleep долгим пустым циклом, но не помогло. Все
равно исключение не отлавливается. Т.е. исключение
org.springframework.dao.OptimisticLockingFailureException по
документации отлавливается, но grails все равно выкидывает
StaleObjectStateException, которое уже не отлавливается (т.е.
catch(org.hibernate.StaleObjectStateException e){} не отрабатывает) и
в итоге grails выводит 500 ошибку.

Пессимистические блокировки .lock() работают. Но т.к. в MySQL
блокировка записи SELECT FOR UPDATE не работает при включенном
autocommit'е (а он по-умолчанию включен), то код надо заключать в
транзакцию Model.withTransaction{}.

Сейчас все таки хочется разобраться с оптимистическими блокировками.
Сергей, если у вас есть время, вы можете протестировать мой код на
обработку исключения? Т.к. логически все правильно, но не работает.
Возможно в старых версиях работало и сейчас что-то сломалось.

wholegroup

unread,
Jul 27, 2010, 2:39:26 PM7/27/10
to ruGrails
Я сейчас подумал, возможно нужно выполнить какой-то код в
catch(org.springframework.dao.OptimisticLockingFailureException e){}.
Вопрос только какой?!

wholegroup

unread,
Jul 28, 2010, 11:51:13 AM7/28/10
to ruGrails
Разобрался с блокировками.
После попадания в
org.springframework.dao.OptimisticLockingFailureException нужно
очищать hibernate сессию, иначе производится повторная попытка
обновления элемента.
Код для обработки exception оптимистических блокировок должен
выглядеть как:

registration.withSession { session ->


try
{
existReg.dateRequest = new Date();
existReg.save(flush: true);
}
catch(org.springframework.dao.OptimisticLockingFailureException e)
{

session.clear();
}
}

Это в принципе логично и соответствует документации, но было не совсем
очевидно (или "смотрю в книгу, вижу фигу" ;).

Reply all
Reply to author
Forward
0 new messages