Строка была обновлена или удалена другой транзакцией (или неправильное сопоставление несохраненных значений)
У меня есть проект java, который выполняется на веб-сервере. Я всегда попадал в это исключение.
Я прочитал некоторую документацию и обнаружил, что пессимистическая блокировка (или оптимистичная, но я читал, что пессимистично лучше) — лучший способ предотвратить это исключение.
Но я не нашел ни одного ясного примера, объясняющего, как его использовать.
- Email — это класс спящего режима (это будет таблица в базе данных)
- getEmailById(String id) — это функция, которая возвращает Email (этот метод не аннотируется с помощью @Transctional )
- updateEmail(email) : это метод, который обновляет электронную почту.
Примечание. Я использую спящий режим для сохранения, обновления и т.д. (пример: session.getcurrentSession.save(email) )
Пессимистическая блокировка обычно не рекомендуется, и это очень дорого с точки зрения производительности на стороне базы данных. Проблема, о которой вы упомянули (часть кода), несколько неясна, например:
- Если ваш код обращается к нескольким потокам в одно и то же время.
- Как вы создаете объект session (не уверены, используете ли вы Spring)?
Hibernate Объекты сеанса НЕ являются потокобезопасными. Поэтому, если есть несколько потоков, обращающихся к одному сеансу и пытающихся обновить один и тот же объект базы данных, ваш код может потенциально оказаться в ситуации с ошибкой, подобной этому.
Итак, что происходит здесь, так это то, что более одного потока пытается обновить один и тот же объект, один поток успешно завершается, и когда следующий поток отправляется для фиксации данных, он видит, что он уже был изменен и заканчивается бросанием StaleObjectStateException .
ИЗМЕНИТЬ
Существует способ использования пессимистического блокирования в спящем режиме. Проверьте эту ссылку. Но, похоже, проблема с этим механизмом. Однако я столкнулся с сообщением об ошибке в спящем режиме (HHH-5275). Сценарий, упомянутый в ошибке, выглядит следующим образом:
Два потока читают одну и ту же запись базы данных; один из этих потоков следует использовать пессимистическую блокировку, тем самым блокируя другую нить. Но оба потока могут считывать запись базы данных, приводя к сбою теста.
Это очень близко к тому, с чем вы столкнулись. Попробуйте это, если это не сработает, единственный способ, которым я могу думать, — использовать Собственные SQL-запросы, где вы можете достичь пессимистическая блокировка в postgres с запросом SELECT FOR UPDATE .
Не похоже, что вы фактически используете электронную почту, которую вы извлекаете из базы данных, но более старую копию, которую вы получаете как параметр. Все, что используется для управления версиями в строке, изменилось между тем, когда была восстановлена предыдущая версия, и когда вы делаете обновление.
Вероятно, вам понравится ваш код:
Я знаю, что это старый вопрос, но некоторые из нас все еще бьют по нему и смотрят на небо, блуждая как. Вот одна проблема, с которой я столкнулся,
У нас есть менеджер очередей, который опрашивает данные и передает их обработчикам для обработки. Чтобы избежать повторного получения тех же событий, менеджер очередей блокирует запись в базе данных с состоянием LOCKED.
этот метод не был транзакционным, но dao.getLockedEntity() был транзакционным с ‘REQUIRED’.
Все хорошо и в дороге, после нескольких месяцев в производстве, он потерпел неудачу с оптимистичным исключением блокировки,
После многих отладок и проверки деталей мы могли бы узнать, что кто-то изменил код следующим образом:
Таким образом, запись была поставлена в очередь еще до того, как транзакция в dao.getLockedEntity() была зафиксирована (она использует ту же транзакцию метода poll), и объект был изменен обработчиками (различными потоками) к тому времени, когда транзакция метода poll() получила comitted.
Мы исправили проблему, и теперь она выглядит хорошо.
Я подумал о том, чтобы поделиться им, потому что исключение из-за оптимистической блокировки может сбить с толку и его трудно отладить. Кто-то может получить пользу от моего опыта.
Это исключение, вероятно, вызвано оптимистичной блокировкой (или ошибкой в коде). Вы, вероятно, используете его, не зная. И ваш псевдокод (который должен быть заменен реальным кодом, чтобы иметь возможность диагностировать проблему) ошибочен. Hibernate автоматически сохраняет все изменения, сделанные для прикрепленных объектов. Вы никогда не должны ссылаться на update, merge или saveOrUpdate на прикрепленный объект. Просто сделайте
Не нужно вызывать обновление. Спящий режим автоматически очистит изменения до совершения транзакции.
У меня была эта проблема в моем проекте.
После внедрения оптимистической блокировки я получил то же исключение. Моя ошибка заключалась в том, что я не удалял установщика поля, который стал @Version . Когда сеттер вызывался в пространстве java, значение поля больше не соответствовало значению, сгенерированному БД. Поэтому в основном поля версии больше не совпадали. В этот момент любая модификация объекта привела к:
org.hibernate.StaleObjectStateException: строка была обновлена или удалена другая транзакция (или неправильное сопоставление несоответствующих значений)
Я использую H2 в памяти DB и Hibernate.
проверить, существует или нет объект в БД, если он существует, получить объект и обновить его:
если это не удается, если условие. найти объект с идентификатором в БД, выполнить операцию, в которой вы нуждаетесь, в этом случае будут отражаться именно изменения.
У меня была одна и та же проблема в другом контексте моего проекта, и есть разные сценарии, такие как
Когда я выдаю сервер cal, перед тем как сохранить этот объект, его один вызов из js и попытка сохранить и другое место, мне понравилось: js call идет два, три раза (я вещь, которая вызывает привязку, вызывает проблему )
Эта ошибка возникла для меня, когда я пытался обновить ту же строку из двух разных сеансов. Я обновил поле в одном браузере, а второй был открыт и уже сохранил исходный объект в своем сеансе. Когда я попытался обновить этот второй «устаревший» сеанс, я получаю ошибку устаревшего объекта. Чтобы исправить это, я обновляю свой объект для обновления из базы данных, прежде чем устанавливать значение, которое необходимо обновить, а затем сохраните его как обычно.
На всякий случай кто-то проверил эту тему и имел ту же проблему, что и моя.
Строка была обновлена или удалена другой транзакцией (или неверное отображение неверных значений)
Я использую NHibernate, я получаю ту же ошибку при создании объекта.
Я передавал ключ вручную, а также указывал генератор GUID при сопоставлении, поэтому спящий режим создает для меня точно такую же ошибку, поэтому, как только я удалил GUID и оставил поле пустым, все прошло отлично.
этот ответ может не помочь вам, но поможет кому-то вроде меня, кто только ваша нить, из-за той же ошибки
У меня была та же проблема, и в моем случае проблема отсутствовала и/или некорректная эквивалентная реализация в некоторых типах полей объекта сущности. Во время фиксации Hibernate проверяет все объекты, загруженные в сеанс, чтобы проверить, загрязнены ли они. Если какое-либо из объектов грязно, спящий режим пытается их устранить — независимо от того, что фактический объект, который запрашивает операцию сохранения, не связан с другими объектами.
Сознательность сущности выполняется путем сравнения каждого свойства данного объекта (с их методами равенства) или UserType.equals , если свойство имеет ассоциированный org.Hibernate.UserType .
Еще одна вещь, которая меня удивила, заключалась в том, что в моей транзакции (используя Spring аннотация @Transactional ) я имел дело с одним объектом. Hibernate жаловался на какую-то случайную сущность, которая не связана с тем, что этот объект сохраняется. Я понял, что на уровне контроллера REST существует самая внешняя транзакция, поэтому объем сеанса слишком велик, и поэтому все объекты, которые когда-либо загружались как часть обработки запроса, проверялись на отсутствие грязи.
Надеюсь, кому-то это поможет, когда-нибудь.
Я также столкнулся с этой ошибкой, пытаясь обновить существующую строку после создания новой, и целую вечность чесал голову, копался в логике транзакций и версий, пока не понял, что использовал неправильный тип для одного из моих столбцов первичного ключа. ,
Я использовал LocalDate когда должен был использовать LocalDateTime — я думаю, что это LocalDateTime тому, что hibernate не мог различать объекты, что приводило к этой ошибке.
После изменения ключа на LocalDateTime ошибка исчезла. Кроме того, начало работать также обновление отдельных строк — раньше не было возможности найти строку для обновления, и тестирование этой отдельной проблемы фактически привело меня к моим выводам относительно сопоставления первичного ключа.
org.hibernate.StaleObjectStateException Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
This error has been plaguing me for 6 months, and dont know how to solve it.
The first problem is the stack trace doesnt give any hints as to what line, query or controller is causing the issue. The error happens in PageFragmentCachingFilter.java
I do a couple of concurrent things, I update a counter in the DB each time a specific function is called. If two people execute this at exactly the same time, there could be locking issue.
My controller does 3 things:
- reads a row from the grails DB of a domain object called Query (using Query.get(id))
- calls a service to pull a bunch of data from an external db. This takes several minutes
- increments a value in the row which was read from the db.
- saves the row with (failOnError: false)
The failOnError = false was an attempt to make the problem go away, by ignoring an update error, but it didnt work — still exception occurs.
- I dont know if this is the problem. or how to find where the problem is actually occurring.
- I dont know why setting failOnError: false did not fix it.
- i dont know how to turn the pessimistic lock into a real lock which makes you wait (see below attempts at this)
- I dont know at what point the locking is occurring, so it only locks for the minimum amount of time.
I can reproduce the issue by calling the controller from two different browser windows at the same time.
=== update 1 === I tried putting steps 3 and 4 in a service, on the understanding that services start and end transactions automatically. This did not solve the problem, but does throw the same exeption on the line which calls this new service to update a a counter, so this at least points to the issue.
Lets say there is a domain object report which looks like this:
- Read the required Query instance from the grails DB, e.g. Query.get(1)
- Call a service which takes a long time, passing in the sql value.
- call a service, passing the query instance object. This service increments counter, then calls save. (see below code)
Two different people do the above steps at the same time on the same query object.
The above, even though its transactional, fails with the same error.
I assume that i need to do something like re-read the instance object again.
tried changing the service to do this:
get same error when two people try to do this a the same time. Doesnt work.
looked for a way to do «select for update», so that it would lock the row, update it, release. This MUST be multi-user safe.
Guess what? Same error, but this time throws it on the lock line. How can doing a fresh select for update cause a staleObjectStateException?
Guess what? Same error. Seems there is no way to update a counter used by 2 or more people. Also, I can reproduce 100% of the time, which is very odd as this error should only occur very infrequent due to the speed of this simple update. Something is very wrong.
Getting more desperate. Tried the following:
Same error on the lock row. It seems its just not possible in grails to do the equivalent of
Interestingly, the read which happens in step 1 uses get. I tried changing this to read(id), in the hopes that this might help. Interestingly, if I read the instance using read, the app fails on this line every time, even with 1 user:
WTH? firstly, I have not deleted any instances, secondly, I have attempted to lock a nice NEW read, thirdly, I didnt edit or change the original read object.
How to fix & avoid: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
I am doing performance test on my grails application using jMeter. In a scenario when concurrent users hit the site, sometime I see the following error in the logs:
Excepton: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) [com.mypack.app.MyClass#1113]
How can I fix and avoid errors like these in the future?
Below is the full stack:
1 Answer 1
Grails defaults to optimistic locking, where it’s assumed that there will be few or no concurrent updates, or that when they occur you have a strategy in place to resolve issues. This is similar to many source control systems, where two users can edit one file but if the edits overlap there is a conflict that must be manually resolved.
In general it’s impractical to resolve concurrent edit issues. If you change one property in a MyClass instance and I change a different one, we could theoretically merge those two changes into one new record, but what if your change would have affected the change that I made if I had seen it first?
To keep things simple, if you allow concurrent edits and don’t have a ‘merge’ strategy, you should use explicit locking. This does affect performance, but if you only lock for specific domain classes or for specific workflows, the impact should be small, especially if the time that a record is kept locked is short.
But locking in web apps is tricky. You need to lock when you start the ‘edit’ workflow and load the domain class instance to populate that GSP, and keep the record locked while the user makes changes in the browser, and release the lock when the ‘update’ action finishes (either successfully or not). This isn’t supported in Grails because each request gets its own Hibernate Session and it’s closed (along with its Connection , which would release your lock) at the end of each request. And if you were to implement this you’d have to make sure that if the update action isn’t called (or is called after a max allowable time) that the lock is freed to allow others to edit.
So you’re back to a manual merge strategy (or Webflow or something similar). You can remove automatic version checking in the domain class by adding version false in the mapping block. Then in your update action, convert the block that checks for stale versions to custom code that looks at versions but also at changed fields, and uses whatever logic makes sense for your domain class(es) to resolve/merge concurrent edits.