单个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) 

错误的价值; 因为可以在MAXUPDATE之前插入另一个发票。 或者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); 

没有专有的SalesYTDSalesYTD是错误的。

这些年来我怎么能做任何事情。

我一直在假设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