NHibernate: эксклюзивная блокировка

В NHibernate я хочу получить экземпляр и поместить исключительную блокировку в запись, которая представляет извлеченный объект в базе данных.

Сейчас у меня есть этот код:

With.Transaction (session, IsolationLevel.Serializable, delegate
{
    ICriteria crit = session.CreateCriteria (typeof (TarificationProfile));

    crit.SetLockMode (LockMode.Upgrade);

    crit.Add (Expression.Eq ("Id", tarificationProfileId));

    TarificationProfile profile = crit.UniqueResult<TarificationProfile> ();

    nextNumber = profile.AttestCounter;

    profile.AttestCounter++;

    session.SaveOrUpdate (profile);
});

Как вы можете видеть, я установил LockMode для этого критерия на "Upgrade". Это вызывает инструкцию SQL для SQL Server, которая использует подсказки для блокировки updlock и rowlock:

SELECT ... FROM MyTable with (updlock, rowlock)

Однако я хочу использовать реальный эксклюзивный замок. То есть, не позволяйте другим читать эту самую запись, пока я не отпущу замок. Другими словами, я хочу использовать подсказку xlock для блокировки вместо updlock.

Я не знаю, как (или даже если) я могу добиться этого... Может быть, кто-то может дать мне несколько намеков об этом:)

Если это действительно необходимо, я могу использовать функциональность SQLQuery для NHibernate и написать свой собственный SQL-запрос, но я бы хотел как можно больше избежать этого.

Ответы

Ответ 1

Запрос HQL DML выполнит ваше обновление без необходимости блокировки.

Это доступно в NHibernate 2.1, но еще не включено в справочную документацию. Java hibernate documentation очень близок к реализации NHibernate.

Предполагая, что вы используете ReadCommitted Isolation, вы можете спокойно прочитать свое значение внутри транзакции.

With.Transaction (session, IsolationLevel.Serializable, delegate
{
    session.CreateQuery( "update TarificationProfile t set t.AttestCounter = 1 + t.AttestCounter where t.id=:id" )
        .SetInt32("id", tarificationProfileId)
        .ExecuteUpdate();

    nextNumber = session.CreateQuery( "select AttestCounter from TarificationProfile where Id=:id" )
        .SetInt32("id", id )
        .UniqueResult<int>();
}

В зависимости от ваших имен таблиц и столбцов сгенерированный SQL будет:

update TarificationProfile
set    AttestCounter = 1 + AttestCounter
where  Id = 1 /* @p0 */

select tarificati0_.AttestCounter as col_0_0_
from   TarificationProfile tarificati0_
where  tarificati0_.Id = 1 /* @p0 */

Ответ 2

Я сомневаюсь, что это можно сделать из NHibernate. Лично я бы использовал хранимую процедуру, чтобы выполнить то, что вы пытаетесь выполнить.

Обновление: Учитывая продолжение downvotes, я расскажу об этом. Фредерик спрашивает, как использовать подсказки для блокировки, которые являются деталями синтаксиса и конкретной реализации его базового механизма базы данных, с его уровня ORM. Это неправильный уровень, чтобы попытаться выполнить такую ​​операцию - даже если это было возможно (это не так), вероятность того, что она когда-либо будет работать последовательно во всех поддерживаемых NHibernate базах данных, является исчезающе низкой.

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

Ответ 3

Если все, что вы читаете, выполняется с помощью IsolationLevel of Serializable, и все записи также выполняются с помощью IsolationLevel of Serializable. Я не понимаю, почему вам нужно делать блокировку строк базы данных самостоятельно.

Таким образом, сериализация сохраняет данные в безопасности, теперь у нас все еще есть проблема возможных мертвых замков....

Если взаимоблокировки не являются общими, то просто положить [запуск транзакции, чтение, обновление, сохранение] в цикле повтора, когда вы получите тупик, может быть достаточно хорошим.

В противном случае простой оператор "выбрать для обновления", сгенерированный напрямую (например, не с nhibernate), может быть использован для остановки другой транзакции, считывающей строку до ее изменения.

Однако я продолжаю думать, что если скорость обновления достаточно быстра, чтобы получить множество блокировок, ORM может оказаться не лучшим инструментом для обновления, или схема базы данных может потребоваться перепроектировать, чтобы избежать значения, которое должно быть прочитано/(например, вычислять его при чтении данных)

Ответ 4

Можно использовать уровень изоляции "повторяемое чтение", если вы хотите убедиться, что значения, которые вы читаете из базы данных, не изменяются во время транзакции. Но вы должны делать это во всех критических транзакциях. Или вы заблокируете его в транзакции критического чтения с блокировкой обновления.