Использование столбца SQL dB в качестве блокировки для параллельных операций в Entity Framework

У нас есть длительная пользовательская операция, которая обрабатывается пулом рабочих процессов. Ввод и вывод данных осуществляется из Azure SQL.

Столбцы структуры основной таблицы Azure SQL приближены к

[UserId, col1, col2, ... , col N, beingProcessed, lastTimeProcessed ] 

beingProcessed — логическое значение, а lastTimeProcessed — DateTime. Логика в каждой рабочей роли показана ниже, и с обработкой нескольких рабочих (каждый со своим собственным уровнем Entity Framework), по сути, beingProcessed используется блокировка для целей MutEx.

Вопрос: как я могу решить проблемы параллелизма в самой "блокировке" beingProcessed на основе указанной выше нагрузки? Я думаю, что операция read-modify-write над beingProcessed должна быть атомарной, но я открыт для других стратегий. Также открыты для других усовершенствований кода.

[Обновление]: интересно, нужно ли здесь TransactionScope... http://msdn.microsoft.com/en-US/library/system.transactions.transactionscope(v=vs.110).aspx

Код:

public void WorkerRoleMain()
{
    while(true)
    {
        try
        {
            dbContext db = new dbContext();

            // Read
            foreach (UserProfile user in db.UserProfile
                    .Where(u => DateTime.UtcNow.Subtract(u.lastTimeProcessed) 
                            > TimeSpan.FromHours(24) & 
                            u.beingProcessed == false))
            {
                user.beingProcessed = true; // Modify
                db.SaveChanges();           // Write
                // Do some long drawn processing here
                ...
                ...
                ...
                user.lastTimeProcessed = DateTime.UtcNow;
                user.beingProcessed = false;
                db.SaveChanges();
            }
        }
        catch(Exception ex)
        {
            LogException(ex);
            Sleep(TimeSpan.FromMinutes(5));
        }
    } // while ()
}

person DeepSpace101    schedule 12.12.2012    source источник


Ответы (1)


Что мы обычно делаем, так это:

В начале длинной операции запускаем транзакцию:

BEGIN TRANSACTION

Затем мы выбираем строку из таблицы, которую мы хотели бы обновить/удалить, используя эти подсказки:

SELECT * FROM Table WITH (ROWLOCK, NOWAIT) Where ID = 123;

Затем мы проверяем, что у нас есть строка. Если строка заблокирована другим процессом, будет ошибка SQL. В этом случае мы откатываем транзакцию и советуем пользователю. Если запись заблокирована, мы обрабатываем запись и делаем необходимые обновления, используя тот же объект транзакции, который мы использовали для блокировки записи:

UPDATE Table SET Col1='value' WHERE ID = 123;

Затем мы COMMIT транзакцию.

COMMIT;

Это всего лишь псевдокод процесса. Вы должны будете реализовать это в своей программе.

Одно небольшое замечание относительно вышеописанного процесса. Когда вы блокируете запись в SQL Server (или Azure), используйте первичный ключ в предложении WHERE, иначе SQL Server решит использовать блокировку страницы или блокировку таблицы.

person cha    schedule 13.12.2012
comment
Спасибо, но это потребовало бы, чтобы мы имели вышеуказанное как хранимую процедуру, а затем вызывали хранимую процедуру из EF (сначала во время базы данных она также запрашивает чтение сохраненных процедур). Можно ли оставаться в Entity Framework для атомарности чтения-изменения-записи, не прибегая к хранимой процедуре? - person DeepSpace101; 13.12.2012
comment
простой выбор не является проблемой (.where(...)) - суть вопроса заключается в атомарности. - person DeepSpace101; 13.12.2012
comment
этот процесс в значительной степени атомарен, если вы сохраняете обновления в BEGIN... COMMIT TRANSACTION - person cha; 13.12.2012
comment
Вы упускаете вопрос, я пытаюсь это сделать в EF, а не в сценарии SQL и не в хранимой процедуре SQL. Хотя в конечном итоге EF может использовать те же механизмы, мне нужно создать их внутри самого EF. - person DeepSpace101; 13.12.2012
comment
Я не пропускаю вопрос. Описанный выше процесс — это всего лишь псевдокод, который мы используем в наших различных системах (PowerBuilder, MFC, WinApp). Мы создали классы-оболочки для поддержки этого процесса, который в конечном итоге будет выполнять эти шаги. Важно выбрать из таблицы WITH (ROWLOCK, NOWAIT), чтобы проверить, заблокирована ли запись, и при необходимости заблокировать ее. Это эквивалент FOR UPDATE NOWAIT в Oracle, если вы с ним знакомы. - person cha; 14.12.2012