考虑一个SQL Server数据库及其两个存储过程:
* 1。在事务中执行3个重要事项的proc:创建客户,调用sproc执行另一个插入,并有条件地插入带有新标识的第三个记录。
BEGIN TRAN
INSERT INTO Customer(CustName) (@CustomerName)
SELECT @NewID = SCOPE_IDENTITY()
EXEC CreateNewCustomerAccount @NewID, @CustomerPhoneNumber
IF @InvoiceTotal > 100000
INSERT INTO PreferredCust(InvoiceTotal, CustID) VALUES (@InvoiceTotal, @NewID)
COMMIT TRAN
* 2。存储过程,用于轮询Customer
表中不的新条目具有相关的PreferredCust
条目。客户端应用程序通过每500毫秒调用此存储过程执行轮询。 Customer
上的SELECT不涉及交易。
--not in the Preferred list
SELECT C.ID
FROM Customer AS C
LEFT JOIN PreferredCust AS PRE ON PRE.CustID = C.ID
WHERE PRE.CustID IS NULL
出现了一个问题,即轮询存储过程在Customer
表中找到了一个条目,并将其作为结果的一部分返回。问题是它已经把这个记录拿起来,我假设,作为脏读的一部分。该记录最后在PreferredCust
中有一个条目,最终在下游产生了问题。
问题
环境是SQL Server 2005,其默认配置是开箱即用的。在这两个存储过程中都没有给出其他锁定命中。
这两个存储过程是通过JDBC连接从Java客户端调用的。不知道他们是否使用相同的连接,但SQL事件探查器显示他们正在使用相同的SPID和ClientProcessID 。
以下是SQL事件探查器显示的内容:
SELECT @@MAX_PRECISION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SET IMPLICIT_TRANSACTIONS OFF
SET QUOTED_IDENTIFIER ON
SET TEXTSIZE 2147483647
go
EXEC WriteNewCustomer 'CustomerX', 199000
go
--get any customers in the priority
SELECT @@MAX_PRECISION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SET IMPLICIT_TRANSACTIONS OFF
SET QUOTED_IDENTIFIER ON
SET TEXTSIZE 2147483647
go
EXEC GetCustomersWithLowInvoice
go
答案 0 :(得分:3)
您无法阻止脏读。编写者采用独占锁来防止诚实,读取已提交,读取。但是你可以做 nothing 以防止脏读。脏读者必须停止做脏读,期间。
假设轮询Customer表的代码在您的控制之下,解决方案是从查询中删除脏读提示。这可能会引起争用,因为轮询现在会阻止写入。最好的解决方案是启用row versioning:
ALTER DATABASE [<DBNAME>] SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE [<DBNAME>] SET READ_COMMITTED_SNAPSHOT ON;
然后只需从客户那里作为普通查询进行投票,没有任何提示。您的民意调查不会阻止写入,因为行版本将启动并将查询扫描重定向到行的更新前的非锁定版本。
还有一点需要注意:每500毫秒轮询一次?也许你应该使用查询通知机制来使你的缓存失效,请参阅The Mysterious Notification。
答案 1 :(得分:1)
默认isolation level为read committed
。在该隔离级别下不会发生脏读。
你可能忽略了另一个原因。
答案 2 :(得分:0)
将以下内容放在程序的顶部(或在BEGIN TRAN之前)。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
您也可以选择REPEATABLE READ
或SERIALIZABLE
。也就是说,我同意Andomar的观点,鉴于默认级别为READ COMMITTED
,可能会出现另一个导致隔离级别发生变化的原因。