Является ли отдельный оператор SQL Server атомарным и последовательным?

Это заявление в SQL Server?ACID

Что я имею в виду под этим

Учитывая один оператор T-SQL, не заключенный в /BEGIN TRANSACTIONCOMMIT TRANSACTIONдействия этого утверждения:

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

У меня есть одно утверждение в действующей системе, которое, кажется, нарушает правила запроса.

По сути, мое утверждение T-SQL:

--If there are any slots available, 
--then find the earliest unbooked transaction and mark it booked
UPDATE Transactions
SET Booked = 1
WHERE TransactionID = (
   SELECT TOP 1 TransactionID
   FROM Slots
      INNER JOIN Transactions t2
      ON Slots.SlotDate = t2.TransactionDate
   WHERE t2.Booked = 0 --only book it if it's currently unbooked
   AND Slots.Available > 0 --only book it if there's empty slots
   ORDER BY t2.CreatedDate)

Заметка: Но более простой концептуальный вариант может быть:

--Give away one gift, as long as we haven't given away five
UPDATE Gifts
SET GivenAway = 1
WHERE GiftID = (
   SELECT TOP 1 GiftID
   FROM Gifts
   WHERE g2.GivenAway = 0
   AND (SELECT COUNT(*) FROM Gifts g2 WHERE g2.GivenAway = 1) < 5
   ORDER BY g2.GiftValue DESC
)

В обоих этих заявлениях обратите внимание, что они являются отдельными утверждениями (UPDATE...SET...WHERE).

Есть случаи, когда происходит неправильная транзакциязабронирована»; Это'на самом деле выбираяпотом сделка. Посмотрев на это в течение 16 часов, яЯ в тупике. Это'Как будто SQL Server просто нарушает правила.

Я задавался вопросом, что если результатыSlots вид меняется до обновления? Что делать, если SQL Server не держитSHARED замки наоперации на чтоДата? Возможно ли, что одно утверждение может быть противоречивым?

Поэтому я решил проверить это

Я решил проверить, не противоречат ли результаты подзапросов или внутренних операций. Я создал простую таблицу с однимint колонка:

CREATE TABLE CountingNumbers (
   Value int PRIMARY KEY NOT NULL
)

Из нескольких соединений, в тесной петле, я называюодин оператор T-SQL:

INSERT INTO CountingNumbers (Value)
SELECT ISNULL(MAX(Value), 0)+1 FROM CountingNumbers

Другими словами, псевдокод:

while (true)
{
    ADOConnection.Execute(sql);
}

И через несколько секунд я получаю:

Violation of PRIMARY KEY constraint 'PK__Counting__07D9BBC343D61337'. 
Cannot insert duplicate key in object 'dbo.CountingNumbers'. 
The duplicate value is (1332)
Являются ли заявления атомарными?

Тот факт, что ни одно заявление не былоt atomic заставляет меня задуматься, являются ли отдельные утверждения атомарными?

Или есть ещетонкий значениезаявлениеЭто отличается от (например) того, что SQL Server считает оператором:

Значит ли это принципиально, что в пределах одного оператора T-SQL операторы SQL Server не являются атомарными?

И если одно утверждение является атомарным, что объясняет нарушение ключа?

Из хранимой процедуры

Вместо того, чтобы удаленный клиент открывал соединения, я попробовал это с помощью хранимой процедуры:

CREATE procedure [dbo].[DoCountNumbers] AS

SET NOCOUNT ON;

DECLARE @bumpedCount int
SET @bumpedCount = 0

WHILE (@bumpedCount < 500) --safety valve
BEGIN
SET @bumpedCount = @bumpedCount+1;

PRINT 'Running bump '+CAST(@bumpedCount AS varchar(50))

INSERT INTO CountingNumbers (Value)
SELECT ISNULL(MAX(Value), 0)+1 FROM CountingNumbers

IF (@bumpedCount >= 500)
BEGIN
    PRINT 'WARNING: Bumping safety limit of 500 bumps reached'
END
END

PRINT 'Done bumping process'

и открыл 5 вкладок в SSMS, нажал F5 в каждой и наблюдал, как они тоже нарушали ACID:

Running bump 414
Msg 2627, Level 14, State 1, Procedure DoCountNumbers, Line 14
Violation of PRIMARY KEY constraint 'PK_CountingNumbers'. 
Cannot insert duplicate key in object 'dbo.CountingNumbers'. 
The duplicate key value is (4414).
The statement has been terminated.

Таким образом, сбой не зависит от ADO, ADO.net или ничего из вышеперечисленного.

15 лет ямы работали в предположении, что один оператор в SQL Server согласован; и единственный

А как насчет УРОВНЯ СДЕЛКИ СДЕЛКИ ххх?

Для разных вариантов выполнения пакета SQL:

по умолчанию (чтение зафиксировано): нарушение ключа

INSERT INTO CountingNumbers (Value)
SELECT ISNULL(MAX(Value), 0)+1 FROM CountingNumbers

по умолчанию (чтение зафиксировано), явная транзакция:Нет ошибок нарушение ключа

BEGIN TRANSACTION
INSERT INTO CountingNumbers (Value)
SELECT ISNULL(MAX(Value), 0)+1 FROM CountingNumbers
COMMIT TRANSACTION

сериализуемым: тупик

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
INSERT INTO CountingNumbers (Value)
SELECT ISNULL(MAX(Value), 0)+1 FROM CountingNumbers
COMMIT TRANSACTION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

снимок (после изменения базы данных для включения изоляции моментального снимка): нарушение ключа

SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRANSACTION
INSERT INTO CountingNumbers (Value)
SELECT ISNULL(MAX(Value), 0)+1 FROM CountingNumbers
COMMIT TRANSACTION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
бонусMicrosoft SQL Server 2008 R2 (SP2) - 10.50.4000.0 (X64)Уровень изоляции транзакции по умолчанию ()READ COMMITTEDОказывается каждый запрос якогда-либо написано сломан

Это, безусловно, меняет вещи. Каждое обновление якогда-либо написанное в корне сломано. Например.:

--Update the user with their last invoice date
UPDATE Users 
SET LastInvoiceDate = (SELECT MAX(InvoiceDate) FROM Invoices WHERE Invoices.uid = Users.uid)

Неверное значение; потому что другой счет может быть вставлен послеMAX и передUPDATE, Или пример из BOL:

UPDATE Sales.SalesPerson
SET SalesYTD = SalesYTD + 
    (SELECT SUM(so.SubTotal) 
     FROM Sales.SalesOrderHeader AS so
     WHERE so.OrderDate = (SELECT MAX(OrderDate)
                           FROM Sales.SalesOrderHeader AS so2
                           WHERE so2.SalesPersonID = so.SalesPersonID)
     AND Sales.SalesPerson.BusinessEntityID = so.SalesPersonID
     GROUP BY so.SalesPersonID);

без эксклюзивных замков,SalesYTD неправильно.

Как я смог сделать что-нибудь все эти годы.

Ответы на вопрос(2)

Ваш ответ на вопрос