Что произойдет, если 2 или более человек обновят запись одновременно? - PullRequest
5 голосов
/ 09 ноября 2011

Я использую NHibernate со свойством version, которое автоматически увеличивается каждый раз, когда обновляется мой сводный корень.Что произойдет, если 2 или более человек обновят одну и ту же запись в одно и то же время?

Кроме того, как бы я это протестировал?

Обратите внимание, что я не в такой ситуации, просто интересно.

Ответы [ 5 ]

5 голосов
/ 09 ноября 2011

Что атомарно, а что нет

Как уже говорили другие, обновления в SQL Server являются атомарными операциями.Однако при обновлении данных с помощью NHibernate (или любого O / RM) вы обычно сначала select данных, вносите изменения в объект, а затем update базу данных с вашими изменениями.Эта последовательность событий не атомная.Даже если выбор и обновление были выполнены в течение миллисекунд друг от друга, существует вероятность того, что другое обновление проскользнет посередине.Если два клиента извлекли одну и ту же версию одних и тех же данных, они могли бы невольно перезаписать изменения друг друга, если бы предположили, что они единственные, кто редактировал эти данные в то время.

Иллюстрация проблемы

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

public class BodyOfWater
{
    public virtual int Id { get; set; }
    public virtual StateOfMatter State { get; set; }

    public virtual void Freeze()
    {
        if (State != StateOfMatter.Liquid)
            throw new InvalidOperationException("You cannot freeze a " + State + "!");
        State = StateOfMatter.Solid;
    }

    public virtual void Boil()
    {
        if (State != StateOfMatter.Liquid)
            throw new InvalidOperationException("You cannot boil a " + State + "!");
        State = StateOfMatter.Gas;
    }
}

Допустим, в базе данных записан следующий водоем:

new BodyOfWater
{
    Id = 1,
    State = StateOfMatter.Liquid
};

Два пользователя выбирают эту запись изПримерно в то же время измените базу данных и сохраните изменения в базе данных.Пользователь А замерзает вода:

using (var transaction = sessionA.BeginTransaction())
{
    var water = sessionA.Get<BodyOfWater>(1);
    water.Freeze();
    sessionA.Update(water);

    // Same point in time as the line indicated below...

    transaction.Commit();
}

Пользователь Б пытается кипятить воду (теперь лед!) ...

using (var transaction = sessionB.BeginTransaction())
{
    var water = sessionB.Get<BodyOfWater>(1);

    // ... Same point in time as the line indicated above.

    water.Boil();
    sessionB.Update(water);
    transaction.Commit();
}

... и успешно !!!Какие?Пользователь А заморозил воду.Разве не должно быть выброшено исключение, говорящее «Вы не можете варить твердое тело!»?Пользователь B извлек данные до того, как Пользователь A сохранил свои изменения, поэтому для обоих пользователей вода изначально представляла собой жидкость, поэтому обоим пользователям было разрешено сохранять свои конфликтующие изменения состояния.

Solution

Чтобы предотвратить этот сценарий, мы можем добавить свойство Version к классу и отобразить его в NHibernate с отображением <version />:

public virtual int Version { get; set; }

Это просто числочто NHibernate будет увеличивать каждый раз, когда обновляет запись, и он проверит, чтобы никто не увеличил версию, пока мы не наблюдали.Вместо наивного параллельного обновления sql, например ...

update BodyOfWater set State = 'Gas' where Id = 1;

... NHibernate теперь будет использовать более умный запрос, подобный следующему:

update BodyOfWater set State = 'Gas', Version = 2 where Id = 1 and Version = 1;

Если на число строк влияетзапрос равен 0, тогда NHibernate знает, что что-то пошло не так - либо кто-то еще обновил строку, чтобы номер версии теперь был неправильным, либо кто-то удалил строку, чтобы этот Id больше не существовал.Затем NHibernate выдаст StaleObjectStateException.

Специальное примечание о веб-приложениях

Чем больше промежуток времени между начальным select данных и последующим update, тем большешанс для этого типа проблемы параллелизма.Рассмотрим типичную форму редактирования в веб-приложении.Существующие данные для объекта выбираются из базы данных, помещаются в форму HTML и отправляются в браузер.Пользователь может потратить несколько минут на изменение значений в форме, прежде чем отправить их обратно на сервер.Может быть вполне реальный шанс, что кто-то еще редактировал ту же информацию одновременно, и они сохранили свои изменения раньше, чем мы.

Убедившись, что версия не меняется в течение тех нескольких миллисекунд, которые мы на самом делеСохранение изменений может быть недостаточно в таком сценарии.Чтобы решить эту проблему, вы можете отправить номер версии в браузер как скрытое поле вместе с остальными полями формы, а затем проверить, чтобы убедиться, что версия не изменилась, когда вы извлекаете сущность из базы данных перед сохранением.,Кроме того, вы можете ограничить промежуток времени между начальным select и окончательным update, предоставляя отдельные представления «view» и «edit» вместо того, чтобы просто использовать представление «edit» для всего.Чем меньше времени пользователь тратит на представление «редактирование», тем меньше вероятность того, что ему будет выдано досадное сообщение об ошибке, в котором говорится, что его изменения не могут быть сохранены.

4 голосов
/ 09 ноября 2011

Прежде чем вы сможете обновить строку, вы должны владеть блокировкой для этой строки. SQL Server блокирует строки атомарным способом. То есть блокировка может получить только один из конкурирующих процессов. Все остальные потенциальные заявители должны дождаться снятия блокировки.

4 голосов
/ 09 ноября 2011

Зависит от того, как были установлены уровни изоляции, когда транзакции (если используются) используются с SQL Server. (Хотя это технически невозможно для редактирования записи «точно в одно и то же время»)

Некоторая базовая информация об этом доступна на Серия параллелизма: основы уровней изоляции транзакций

4 голосов
/ 09 ноября 2011

Проще говоря: они не могут. Обновления обрабатываются в последовательности. Каждое обновление - или, по крайней мере, должно быть - атомарным. Таким образом, свойство увеличивается в два раза.

1 голос
/ 09 ноября 2011

Как сказал Майк Адлер, обновления обрабатываются последовательно.но один не удастся, я думаю, что это будет сделано, выдав исключение устаревшего объекта, потому что версия, если часть фильтра для обновления строки.

MyTable  
Id | RowVersion | Description  
1  | 1          | this description

SQL:
1-е обновление
Обновить описание набора MyTable = 'test', rowversion = 2, где id = 1 и rowversion = 1

Результат :

MyTable  
Id | RowVersion | Description  
1  | 2          | test

2-е обновление
Обновить описание набора MyTable = 'второе обновление', версия строки = 2, где id = 1 и версия строки = 1

ничего не обновлено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...