Чи є оператором SQL Server ACID
?
Що я маю на увазі під цим
Враховуючи єдиний оператор T-SQL, не загорнутий у BEGIN TRANSACTION
/ COMMIT 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)
Чи є атомарними твердження?
Той факт, що одне твердження не було атомним, змушує мене задуматися, чи є окремі твердження атомними?
Або існує більш тонка дефініція висловлювання , яка відрізняється від (наприклад) того, що SQL Server вважає заявою:
Чи це принципово означає, що в межах одного оператора T-SQL оператори SQL Server не є атомними?
І якщо одне твердження є атомним, що пояснює ключове порушення?
Зсередини збереженої процедури
Замість того, щоб віддалений клієнт відкривав n підключень, я спробував це за допомогою збереженої процедури:
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 у кожній і спостерігав, як вони теж порушують кислоту:
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 є послідовним; і єдиний
А як щодо РІВНЯ ІЗОЛЯЦІЇ ОПЕРАЦІЇ xxx?
Для різних варіантів пакета 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
це неправильно.
Як я міг щось робити за всі ці роки.