SQL Server: предотвращение грязного чтения в хранимой процедуре

Рассмотрим базу данных SQL Server и два ее хранимых процесса:

* 1. Proc, который выполняет 3 важные вещи в транзакции: создать клиента, вызвать sproc для выполнения другой вставки и условно вставить третью запись с новым идентификатором.

BEGIN  TRAN
    INSERT INTO Customer(CustName) (@CustomerName)
    SELECT @NewID = SCOPE_IDENTITY()

    EXEC  CreateNewCustomerAccount @NewID, @CustomerPhoneNumber

    IF @InvoiceTotal > 100000
         INSERT INTO  PreferredCust(InvoiceTotal, CustID) VALUES (@InvoiceTotal, @NewID)

COMMIT TRAN

* 2. Сохраненный процесс, который опрашивает Customer таблица для новых записей, которые не имеют связанных PreferredCust запись. Клиентское приложение выполняет опрос, вызывая этот сохраненный процесс каждые 500 мс. ВЫБРАТЬ на Customer НЕ включает транзакцию.

  --not in the Preferred list
   SELECT C.ID
   FROM Customer    AS C
   LEFT JOIN PreferredCust AS PRE ON PRE.CustID = C.ID
   WHERE PRE.CustID IS NULL  

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

Вопрос

  • Как вы можете явно предотвратить грязное чтение этим вторым хранимым процессом?
  • Насколько вероятно мое предположение о сценарии грязного чтения?

Среда - SQL Server 2005 со стандартной конфигурацией по умолчанию. Никаких других попаданий блокировки не дается ни в одной из этих хранимых процедур.

Эти два хранимых процесса вызываются из клиента Java через соединение JDBC. Неизвестно, используют ли они одно и то же соединение, но SQL Profiler показывает, что они используют один и тот же SPID и ClientProcessID.

Вот что показывает SQL Profiler:

SELECT @@MAX_PRECISION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SET IMPLICIT_TRANSACTIONS OFF
SET QUOTED_IDENTIFIER ON
SET TEXTSIZE 2147483647
go
EXEC WriteNewCustomer  'CustomerX', 199000
go

--get any customers in the priority 
SELECT @@MAX_PRECISION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SET IMPLICIT_TRANSACTIONS OFF
SET QUOTED_IDENTIFIER ON
SET TEXTSIZE 2147483647
go
EXEC GetCustomersWithLowInvoice
go

3 ответа

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

Предполагая, что код, который опрашивает таблицу Customer, находится под вашим контролем, решение состоит в том, чтобы удалить из запроса грязную подсказку о прочтении. Это, вероятно, вызовет конфликт, так как опрос теперь будет блокировать записи. Лучшее решение для этого - включить управление версиями строк:

ALTER DATABASE [<DBNAME>] SET ALLOW_SNAPSHOT_ISOLATION ON; 
ALTER DATABASE [<DBNAME>] SET READ_COMMITTED_SNAPSHOT ON;

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

Еще одно примечание: опрос каждые 500 мс? Возможно, вам следует использовать механизм уведомлений о запросах, чтобы сделать ваши кэши недействительными, см. "Таинственное уведомление".

Уровень изоляции по умолчанию read committed, Грязное чтение не может происходить при этом уровне изоляции.

Вероятно, есть еще одна причина, которую вы упустили из виду.

Поместите следующее в начало вашей процедуры (или непосредственно перед BEGIN TRAN).

SET TRANSACTION ISOLATION LEVEL READ COMMITTED 

Вы также можете выбрать REPEATABLE READ или же SERIALIZABLE, Тем не менее, я бы согласился с Andomar в том, что, вероятно, есть еще одна причина для изменения уровня изоляции, учитывая, что уровень по умолчанию READ COMMITTED,

Другие вопросы по тегам