2015-11-20 5 views
1

У меня многопоточная среда, и каждый поток хочет выбрать строку (или вставить ее, если она не существует) в таблицу и привить что-то в ней.Параллельные вставки/обновления в таблице SQL Server

В основном, каждый поток делает что-то вроде этого:

using (var context = new Entity.DBContext()) { 
    if(!context.MyTable.Any(...)) { 
     var obj = new MyTable() { 
      SomeValue = 0 
     }; 
     context.MyTable.Add(obj) 
    } 
    var row = context.MyTable.SingleOrDefault(...); 
    row.SomeValue += 1; 
    context.SaveChanges(); 
} 

Проблемы в примере: конкретная строка имеет SomeValue = 0. Две нити выбрать этот конкретный ряд в то же время, они оба видят 0. -> они оба увеличивают его один раз, а конечный результат в SomeValue будет равен 1, но мы хотим, чтобы оно было 2.

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

Спасибо.

+1

Чтобы подтвердить - какую систему баз данных вы используете? Вероятно, это должно быть разрешено на уровне SQL. –

+1

Я согласен с Damien_The_Unbeliever. Вы должны использовать [RowVersion] (https://msdn.microsoft.com/en-us/library/ms182776 (v = sql.120) .aspx) для обеспечения атомарного доступа. – JPVenson

+0

Я использую EntityFramework и SQL-Server. –

ответ

1

Предполагая, что SQL Server, вы можете сделать что-то вроде этого:

create table T1 (
    Key1 int not null, 
    Key2 int not null, 
    Cnt int not null 
) 
go 
create procedure P1 
    @Key1 int, 
    @Key2 int 
as 
    merge into T1 WITH (HOLDLOCK) t 
    using (select @Key1 k1,@Key2 k2) s 
    on 
     t.Key1 = s.k1 and 
     t.Key2 = s.k2 
    when matched then update set Cnt = Cnt + 1 
    when not matched then insert (Key1,Key2,Cnt) values (s.k1,s.k2,0) 
    output inserted.Key1,inserted.Key2,inserted.Cnt; 
go 
exec P1 1,5 
go 
exec P1 1,5 
go 
exec P1 1,3 
go 
exec P1 1,5 
go 

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

Результаты:

Key1  Key2  Cnt 
----------- ----------- ----------- 
1   5   0 

Key1  Key2  Cnt 
----------- ----------- ----------- 
1   5   1 

Key1  Key2  Cnt 
----------- ----------- ----------- 
1   3   0 

Key1  Key2  Cnt 
----------- ----------- ----------- 
1   5   2 

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

+0

Использование 'merge' в многопоточной среде - очень плохая идея, см. [Aaron Bertrand at mssqltips] (https://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement /) Мы были укушены несколькими из этих проблем на работе. – Andomar

+0

Это действительно работает очень хорошо, кажется, что делать вставку/обновление в хранимой процедуре, чтобы избежать вставки/обновления, которые будут выполняться в одно и то же время, каждое обновление будет рассмотрено.Спасибо Damien_The_Unbeliever;) –

+0

Заключительное примечание: блокировка должна была использоваться в запросах SQL (с (UPDLOCK, HOLDLOCK) –

0

Если только один процесс записывает в базу данных одновременно, вы можете обернуть свой код в инструкции C# lock(obj) {}. Это ограничивает вас одним активным запросом, который не будет оптимально использовать базу данных, но если это нормально, это простое решение.

Другой вариант - создать уникальный индекс в столбцах, которые определяют, существует ли строка уже. Если вы закончите insert, вы получите исключение duplicate key. Вы можете catch, что в C# и вместо этого введите update.

Если вы можете написать исходный SQL, вы можете использовать блокирующие подсказки, например with (updlock, holdlock), или set isolation level serializable. Это, вероятно, дает вам лучшую производительность за счет сложности.

Смежные вопросы