单个SQL Server语句是primefaces性的还是一致的?
在SQL Server ACID
是一个语句吗?
我的意思是
给定一个单独的T-SQL语句,不包含在BEGIN TRANSACTION
/ COMMIT TRANSACTION
,是该语句的动作:
- primefaces :要么执行所有的数据修改,要么不执行它们。
- 一致性 :完成后,交易必须保持所有数据处于一致状态。
- 隔离 :并发事务所做的修改必须与其他并发事务所做的修改隔离。
- 耐久性 :交易完成后,其影响永久存在于系统中。
我问的原因
在现场系统中,我有一个单一的声明,似乎违反了查询的规则。
实际上,我的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
)。
有些情况下,错误的交易被“预订” ; 它实际上是select一个以后的交易。 盯着这个16个小时后,我很难过。 就好像SQL Server只是违反规则一样。
我想知道如果在更新发生之前Slots
视图的结果发生了变化? 如果SQL Server没有在该date的事务上持有SHARED
锁,该怎么办? 单个语句可能不一致吗?
所以我决定testing一下
我决定检查子查询或内部操作的结果是否不一致。 我用一个int
列创build了一个简单的表格:
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)
语句是primefaces吗?
单一的陈述不是primefaces的事实让我怀疑单个陈述是否是primefaces的?
或者是有一个更微妙的语句的定义,它不同于(例如)SQL Server认为是一个声明:
这从根本上意味着在单个T-SQL语句的范围内,SQL Server语句是不是primefaces的?
如果一个单一的陈述是primefaces,是什么说明了违规?
从存储过程中
而不是一个远程客户端打开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
, SalesYTD
是错误的。
这些年来我怎么能做任何事情。
我一直在假设SQL Server中的单个语句是一致的
这个假设是错误的。 以下两个事务具有相同的locking语义:
STATEMENT BEGIN TRAN; STATEMENT; COMMIT
没有任何区别。 单个语句和自动提交不会改变任何东西。
所以把所有的逻辑合并成一个陈述是没有帮助的(如果是这样的话,那是因为计划改变了)。
我们来解决这个问题。 SERIALIZABLE
将解决你所看到的不一致性,因为它保证你的事务像单线程执行一样。 等同地,它们的行为就好像它们立即执行一样。
你将会陷入僵局。 如果你可以使用重试循环,那么你就完成了。
如果您想投入更多时间,请使用locking提示强制对相关数据进行独占访问:
UPDATE Gifts -- U-locked anyway SET GivenAway = 1 WHERE GiftID = ( SELECT TOP 1 GiftID FROM Gifts WITH (UPDLOCK, HOLDLOCK) --this normally just S-locks. WHERE g2.GivenAway = 0 AND (SELECT COUNT(*) FROM Gifts g2 WITH (UPDLOCK, HOLDLOCK) WHERE g2.GivenAway = 1) < 5 ORDER BY g2.GiftValue DESC )
现在您将看到降低的并发性。 这可能是完全正确的,取决于你的负载。
您的问题的本质使得难以实现并发。 如果您需要解决scheme,我们需要应用更多的侵入性技术。
你可以简化一下UPDATE:
WITH g AS ( SELECT TOP 1 Gifts.* FROM Gifts WHERE g2.GivenAway = 0 AND (SELECT COUNT(*) FROM Gifts g2 WITH (UPDLOCK, HOLDLOCK) WHERE g2.GivenAway = 1) < 5 ORDER BY g2.GiftValue DESC ) UPDATE g -- U-locked anyway SET GivenAway = 1
这摆脱了一个不必要的连接。
下面是一个UPDATE语句的例子,它自动增加一个计数器的值
-- Do this once for test setup CREATE TABLE CountingNumbers (Value int PRIMARY KEY NOT NULL) INSERT INTO CountingNumbers VALUES(1) -- Run this in parallel: start it in two tabs on SQL Server Management Studio -- You will see each connection generating new numbers without duplicates and without timeouts while (1=1) BEGIN declare @nextNumber int -- Taking the Update lock is only relevant in case this statement is part of a larger transaction -- to prevent deadlock -- When executing without a transaction, the statement will itself be atomic UPDATE CountingNumbers WITH (UPDLOCK, ROWLOCK) SET @nextNumber=Value=Value+1 print @nextNumber END