2013-05-23 21 views
0

У меня есть таблица, которая выглядит, как этотПопытка заблокировать базу данных, но не работает

Create Table Items(
    [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY, 
    [UniqueCol] [nvarchar](20) NOT NULL, 
    [Col] int NOT NULL 
) 

ALTER TABLE Items ADD CONSTRAINT UN_UniqueCol UNIQUE(UniqueCol) 

У меня есть хранимая процедура в SQL Server 2008, который выглядит следующим образом.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 
BEGIN TRANSACTION 

if not exists(select * from Items where UniqueCol=uniqueval) 
begin 
    insert into Items (UniqueCol, Col) values (uniqueval, val) 
end 
select * from Items where UniqueCol = uniqueval 
COMMIT TRANSACTION 

EDIT: Это Merge работает правильно

MERGE INTO Items as Target USING (VALUES(uniqueval)) as source (UniqueCol) 
on (source.UniqueCol = Target.UniqueCol) 
WHEN MATCHED THEN 
    update set col = val 
WHEN NOT MATCHED BY Target THEN 
    insert (UniqueCol, col) VALUES (uniqueval, val); 

Я бегу веб-сайт в ASP.NET MVC, снимающей эту процедуру очень часто. Причина, по которой я делаю это в хранимой процедуре, - это то, что я считал, что это самое простое место для обработки проблем параллелизма, которые возникали, когда две вещи пытались вставить одну и ту же вещь одновременно.

Выбор в конце как-то возвращает значение моей модели в asp.net mvc. Я не уверен, что это правильный способ сделать это, но он работает, когда я пытаюсь вызвать хранимую процедуру с веб-сервера.

Эта хранимая процедура вызывается очень часто и потенциально имеет 2 одинаковой вещи в в то же время. Уникальный ключ предотвращает хранение плохих данных, но я бы не захотел зафиксировать и исключить уникальное ключевое исключение для обработки параллельных вставок, когда они происходят. Могу ли я заблокировать таблицу, не убивая производительность здесь?

Я предполагал, что это заблокирует Элементы, чтобы 2 запроса не могли попытаться вставить в него одновременно (и вызвать дублирование идентификатора), и они не могли попытаться вставить одно и то же значение в таблицу дважды (2 ряда с UniqueCol = uniqueval)

Я делаю это неразумно? Я просто пытался несколько раз вызвать этот хранимый процесс параллельно, и один из них дал мне сообщение о том, что произошло дублирование ПК.

Похоже, что уровень изоляции транзакций не предназначен для блокировки таблиц таким образом. Может ли кто-то объяснить, для чего он предназначен, а также правильный способ блокировки таблицы для этого типа транзакций?

+0

Я бы ожидал, что «Id» будет объявлен первичным ключом. –

+0

о, извините, но. Я забыл поместить это. – bdwain

+0

Команда 'INSERT' ** только ** блокирует строки (строки), которые она свежая вставка - ничего больше. Он не блокирует всю таблицу - и это хорошая вещь *! В противном случае производительность пострадает ужасно .... –

ответ

1

Лучшее решение - заставить MERGE работать.

Но вот исправить вашу проблему, используя ваше оригинальное решение:

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

Итак, пользователь1 с T1 запускается, и он видит, что значение не создано, поэтому он начинает создавать новую строку. User2 с T2 начинается, пока T1 еще не зафиксирован (т. Е. Посередине), он начнет считывать значение. Поскольку T1 еще не зафиксирован, значение изолировано и не будет видно T2/user2, и поэтому попытается также вставить новую строку.

Обе транзакции попытаются создать две строки со значением.

Чтобы ваша логическая работа работала так, как вы ее предполагали, вам необходимо установить уровень изоляции транзакции: READ UNCOMMITTED.
Что бы это могло сделать, позволит T2 видеть изменения, сделанные T1, даже до того, как T1 будет зафиксирован.
Добавить еще после чтения. Если это то же значение, что и текущее, тогда идите вперед и совершите, в противном случае откат. Это эффективно «Оптимистичный параллелизм», но реализованный вами на уровне SQL. Вы могли бы это сделать и на уровне приложений.

USE mytstdb; 
-- This also applies the spirit of the double-checked locking pattern/concept 
-- It also applies optimistic concurrency 
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 
BEGIN TRANSACTION 

DECLARE @uniqueval NVARCHAR(20) = 'unique value'; 
DECLARE @val INT = 10; 

DECLARE @valCount INT; 

-- This is a dirty read, but for what you need it is OK 
SET @valCount = (select count(*) from Items where [email protected]); 
if(@valCount = 0) 
begin 
    insert into Items (UniqueCol, Col) values (@uniqueval, @val) 
    SET @valCount = (select count(*) from Items where [email protected]); 
    -- Optimistic concurrency 
    if(@valCount > 1) 
    begin 
     ROLLBACK TRANSACTION; 
     return; 
    end 
end 

COMMIT TRANSACTION 

Надеюсь, это поможет.

+0

Я попытался использовать слияние, но вижу одно и то же дублирующее нарушение PK. Любая идея, почему это может случиться? Я поставлю команду слияния в вопросе – bdwain

+0

У меня получилось слияние. Мой источник не имел никакого смысла. Я отредактировал вопрос, чтобы показать, какой источник должен был быть – bdwain

1

В SQL Server serializable изоляция документирована как имеющая следующее поведение.

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

  • Никакие другие транзакции не могут изменять данные, которые были прочитаны текущей транзакцией до завершения текущей транзакции.

  • Другие транзакции не могут вставлять новые строки с ключевыми значениями, которые бы попадают в диапазон ключей, считываемых какие-либо заявления в текущей транзакции, пока текущая транзакция не будет завершена.

Сериализуемая изоляция не блокирует таблицу.

Я считаю, что, когда ваш пункт if not exists не работает, транзакция не считывает данные. (Это терпит неудачу, потому что идентификатор, который вы, по-видимому, ищете, не существует.) Таким образом, этот SELECT не блокирует.

Вы можете заблокировать стол с помощью TABLOCK table hint, но я не думаю, что когда-либо видел, чтобы кто-то делал это в реальной жизни. Это плохо для одновременного доступа.

Возможно, вы можете получить лучший ответ, отредактировав свой вопрос и включив информацию обо всех ключах-кандидатах (а не только в первичный ключ) и о том, почему вы считаете, что вам нужно заблокировать таблицу. Лучше всего, как всегда, с вопросами SQL, включить SQL DDL для ваших таблиц. DDL - наиболее точное описание; это стоит больше тысячи слов.

+0

да, я прочитал это, и это звучало как то, что я хотел. Наверное, это не так. Можете ли вы предложить, как правильно его заблокировать? – bdwain

+0

Я изменил бла и все, что было более наглядным. – bdwain

+0

также говорит: «Другие транзакции не могут вставлять новые строки ...» Разве это не блокировка? – bdwain

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