【问题标题】:SQL Server: preventing dirty reads in a stored procedureSQL Server:防止存储过程中的脏读
【发布时间】:2010-04-26 23:25:21
【问题描述】:

考虑一个 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 Profiler 显示它们使用的是相同的 SPID 和 ClientProcessID

以下是 SQL Profiler 显示的内容:

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

【问题讨论】:

    标签: sql-server sql-server-2005 tsql


    【解决方案1】:

    您无法防止脏读。写入器采用排他锁来防止诚实的、已提交的读取、读取。但是您没有可以防止脏读。脏读器必须停止脏读,期间。

    假设轮询 Customer 表的代码在您的控制之下,解决方案是从查询中删除脏读提示。这可能会导致争用,因为轮询现在将阻止写入。最好的解决方案是启用row versioning:

    ALTER DATABASE [<DBNAME>] SET ALLOW_SNAPSHOT_ISOLATION ON; 
    ALTER DATABASE [<DBNAME>] SET READ_COMMITTED_SNAPSHOT ON;
    

    然后只需像普通查询一样从客户那里进行轮询,无需任何提示。您的投票不会阻止写入,因为行版本控制将启动并将查询扫描重定向到该行的更新前、非锁定版本。

    另一个注意事项:每 500 毫秒轮询一次?也许您应该使用查询通知机制来使缓存无效,请参阅The Mysterious Notification

    【讨论】:

    • 谢谢莱姆斯。民意调查的一个有趣的情况。 Java 应用程序通过 JDBC 连接调用这些 proc,因此,正如您的文章所述,这可能会使该解决方案无效。谢谢您的建议。可能更好的方案是每 10 秒执行一次批处理或基于集合的解决方案轮询。
    • +1 字里行间很好读,我没有从轮询查询使用nolock 运行的问题中得到答案
    • @Andomar:我不确定我是否正确阅读。可能是 OP 过程的“缺失”部分进行了脏读。但作为一个通用答案,防止脏读是不可能的(缺少 SCH_M_LCK-ing 表,但那是 DDL)所以我给出了一个通用答案:)
    【解决方案2】:

    默认的isolation levelread committed。在该隔离级别下不会发生脏读。

    您可能忽略了另一个原因。

    【讨论】:

    • 第二个过程不是也必须在事务中运行吗?
    • @Michael:谢谢你的想法。那次读取没有交易。我想知道在事务中添加读取是否会产生任何影响。
    【解决方案3】:

    将以下内容放在程序的顶部(或就在 BEGIN TRAN 之前)。

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED 
    

    您也可以选择REPEATABLE READSERIALIZABLE。也就是说,我同意 Andomar 的观点,因为默认级别是 READ COMMITTED,所以看起来隔离级别的变化可能还有另一个原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多