2009-11-05 9 views
7

(Примечание: это для MS SQL Server)Условие состояния ранга SQL Server Вопрос

Скажите, что у вас есть таблица ABC с столбцом идентификации первичного ключа и столбец CODE. Мы хотим, чтобы каждая строка имела уникальный, последовательно сгенерированный код (на основе некоторой типичной формулы контрольной цифры).

Скажите, что у вас есть еще одна таблица DEF с одной строкой, в которой хранится следующий доступный код (представьте простой номер автонабора).

Я знаю, что логика, как показано ниже будет представлять состояние гонки, в которой два пользователи могут в конечном итоге с тем же кодом:

1) Run a select query to grab next available code from DEF 
2) Insert said code into table ABC 
3) Increment the value in DEF so it's not re-used. 

Я знаю, что два пользователи могут застрять на стадии 1), и может в конечном итоге с тем же CODE в таблице ABC.

Каков наилучший способ справиться с этой ситуацией? Я думал, что могу просто включить «начать транс»/«совершить переход» вокруг этой логики, но я не думаю, что это сработало. Я имел хранимую процедуру, как это проверить, но я не избежать состояния гонки, когда я выбежала из двух различных окон в MS:

begin tran 

declare @x int 

select @x= nextcode FROM def 

waitfor delay '00:00:15' 

update def set nextcode = nextcode + 1 

select @x 

commit tran 

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

EDIT: Я попытался переместить ожидание после утверждения «update», и у меня есть два разных кода ... но я подозревал это. У меня есть команда waitfor, чтобы имитировать задержку, поэтому состояние гонки можно легко увидеть. Я думаю, что ключевой проблемой является неправильное понимание того, как работают транзакции.

+0

Вы должны пересмотреть свой поздний ответ: принят один не правильно ... – gbn

ответ

6

Установите уровень изоляции транзакций на Serializable.
При более низких уровнях изоляции другие транзакции могут считывать данные в строке, которая считывается (но еще не изменена) в этой транзакции. Таким образом, две транзакции действительно могут читать одно и то же значение. При очень низкой изоляции (чтение незафиксированного) другие операции могут даже считывать данные после того, как он был изменен (но до привержен) ...

детали Обзора об уровнях изоляции SQL Server here

Так Суть заключается в том, что уровень изоляции является crtitical частью здесь, чтобы контролировать, какой уровень доступа к другим транзакциям входит в этот.

ПРИМЕЧАНИЕ. От link, около Serializable
Заявления не могут прочитать данные, которые были изменены, но еще не совершены другими транзакциями.
Это связано с тем, что блокировки размещаются при изменении строки, а не при возникновении Begin Trans. Таким образом, все, что вы сделали, может позволить другой транзакции прочитать старое значение до момента, когда вы его изменяете.Поэтому я бы изменил логику, чтобы изменить ее в том же самом утверждении, что и вы ее прочитали, тем самым одновременно заблокировав ее.

begin tran 
declare @x int 
update def set @x= nextcode, nextcode += 1 
waitfor delay '00:00:15' 
select @x 
commit tran 
+0

Я думаю, вы имели в виду * обновление def set @ x = nextcode, nextcode + = 1 * –

+0

Да, спасибо за это! –

0

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

Persisted Computed Columns

ПРИМЕЧАНИЕ

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

Реализация

Дайте колонке следующие свойства под вычисленной спецификации столбца.

формула = dbo.GetNextCode()

ли сохранялось = Да

Create Function dbo.GetNextCode() 
Returns VarChar(10) 
As 
Begin 

    Declare @Return VarChar(10); 
    Declare @MaxId Int 

    Select @MaxId = Max(Id) 
    From Table 

    Select @Return = Code 
    From Table 
    Where Id = @MaxId; 

    /* Generate New Code ... */ 

    Return @Return; 

End 
+0

В теле функции как Вы получаете доступ к последним (т.е. сохраняется) значение? –

+0

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

+0

Извините, я должен был задать два вопроса в первом комментарии! Как будет сохраняться следующий код или, где возможно? –

3

Резюме:

  • Вы начали транзакцию. Это фактически не «делает» ничего само по себе, оно изменяет последующее поведение.
  • Вы читаете данные из таблицы. Уровень изоляции по умолчанию - Read Committed, поэтому этот оператор выбора не сделал часть транзакции.
  • Вы затем ждете 15 секунд
  • Затем вы публикуете обновление. С объявленной транзакцией это приведет к блокировке до тех пор, пока транзакция не будет выполнена.
  • Затем вы совершаете транзакцию, освобождая блокировку.

Так, угадывание запускали это одновременно в двух окнах (А и В):

  • для чтения «следующий» значение из таблицы четкости, а затем вошел в режим ожидания
  • B читать то же «следующее» значение из таблицы, а затем перешло в режим ожидания. (Так как A только прочитал, транзакция ничего не заблокировала.)
  • Затем обновил таблицу и, возможно, совершил изменение до того, как B вышло из состояния ожидания.
  • B затем обновил таблицу после того, как была записана запись A.

Попробуйте поставить инструкцию wait после обновления перед фиксацией и посмотреть, что произойдет.

0

Это на самом деле обычная проблема в SQL-базах данных, поэтому большинство (всех?) Из них имеют встроенные функции, чтобы позаботиться об этой проблеме получения уникального идентификатора. Вот некоторые вещи, на которые нужно обратить внимание, если вы используете Mysql или Postgres. Если вы используете другую базу данных, я уверен, что вы получите нечто очень похожее.

Хорошим примером этого является Postgres последовательности, которые вы можете проверить здесь:

Postgres Sequences

Mysql использует то, что называется шагом авто.

Mysql auto increment

1

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

5

Как и другие ответчики упоминали, вы можете установить уровень изоляции транзакций, чтобы гарантировать, что все, что вы «читать» с помощью ЗЕЬЕСТА не может изменить в рамках транзакции.

В качестве альтернативы, вы можете вынуть замок специально на столе DEF, добавив синтаксис WITH HOLDLOCK после имени таблицы, например,

SELECT nextcode FROM DEF WITH HOLDLOCK 

Это не имеет большого значения, здесь, как и ваша сделка мала , но может быть полезно вынуть блокировки для некоторых SELECT, а не других в транзакции. Речь идет о «повторяемости по сравнению с параллелизмом».

Несколько релевантных документов MS-SQL.

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