Атомный "вставить, если не существует" с NHibernate
Рассмотрим приложение, которое делает что-то вроде:
var user = session.QueryOver<User>().Where(x => x.Name==name).SingleOrDefault();
if (user == null)
{
user = new User(name);
session.Save(user);
}
Бизнес-правила гласят, что имя пользователя должно быть уникальным, и это подтверждается UNIQUE INDEX
в базе данных. Приведенный выше код работает просто отлично, пока два пользователя не попытаются зарегистрироваться одновременно с одним и тем же именем, оба получат user == null
и попробуйте создать новый User
, Первый из них будет выполнен успешно, второй завершится ошибкой с исключением из базы данных.
Один из способов избежать таких состязаний состоит в том, чтобы обернуть критический код в lock { }
блокировать, но это не поможет, если несколько экземпляров приложений работают с одной и той же базой данных. Теперь, если бы я только мог использовать механизмы блокировки RDBS (в данном случае MS SQL)…
Вот где я застрял. Из того, что я могу прочитать в документах NHibernate, я мог бы решить эту проблему, добавив некоторую подсказку к своему QueryOver()
Цепочка, запрашивающая явную блокировку. Это должна быть блокировка таблицы, поскольку у меня еще нет реальных строк. Это возможно? Как
В качестве альтернативы, можно ли повысить уровень изоляции транзакции и автоматически получить необходимые блокировки? Учитывая, что другие (не проблемные) запросы выполняются в пределах одной и той же единицы работы, я подозреваю, что такого рода изменения могут привести к большому количеству блокировок / ожиданий. Это так?
2 ответа
Увеличьте уровень изоляции до Serializable. Это будет блокировать чтение и запись по всему запросу, включая ваше предложение 'where'.
Убедитесь, что вы выполняете запросы в том же сеансе NHibernate, что и при сохранении. Это позволит вам использовать ту же транзакцию, автоматически используя механизмы блокировки базы данных.
Тем не менее, вы всегда должны быть готовы к провалу транзакции, и ваш код должен корректно обрабатывать этот случай. Придумывание серийных блоков и блокировок усложняет ваш код, но не может исключить вероятность сбоя на уровне базы данных, поэтому вы добавляете эту сложность к небольшому выигрышу.