是SQL Server中的语句ACID
吗?
我的意思是
给定一个未包装在BEGIN TRANSACTION
/COMMIT TRANSACTION
中的T-SQL语句,该语句的操作如下:
- 原子的:要么执行所有数据修改,要么不执行任何数据修改。
- 一致:完成后,事务必须使所有数据保持一致状态。
- 隔离:必须将并发事务所做的修改与任何其他并发事务所做的修改隔离。
- 持久:事务完成后,其影响将在系统中永久存在。
我问的原因
我在实时系统中只有一条语句似乎违反了查询规则。
实际上,我的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)
陈述是原子的吗?
单个语句不是原子的事实使我想知道单个语句是否是原子的?
还是存在一个更微妙的statement定义,该定义不同于(例如)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'
并在SSMS中打开5个标签,在每个标签中按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中的一条语句是一致的;而唯一的
交易隔离级别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
是错误的。
这些年来,我怎么能做任何事情。