【问题标题】:SQL Server Query Returning Multiple RowsSQL Server 查询返回多行
【发布时间】:2011-01-17 01:24:35
【问题描述】:

我目前正在开发一个 SSIS 包,该包将表从一个数据库提取到另一个数据库。两个数据库中的表都使用同一列作为主键。我提取数据的选择语句是一个简单的选择语句。当我运行这个包时,我收到了一个错误,那里有重复的主键值。

我查看了我的 select 语句并确认我的 select 语句没有返回重复的行。因此,为了测试这一点,我从要插入数据的表中删除了主键,并重新运行了 SSIS 包。运行后,我查看了表格以查看重复的行。我发现的是,在提取运行时正在编辑的行被复制,在编辑之前有它的记录,在编辑之后有它的记录。我可以很容易地看出这一点,因为该表有一个上次修改字段,每次更新记录时都会更新。

我在我的选择语句中添加了一个 NOLOCK 提示,它停止返回重复的行。

所以我的问题是为什么?我本来希望带有 NOLOCK 表提示的 select 语句返回重复行的机会更高,因为它没有使用锁定,并且没有 NOLOCK 提示的 select 语句应该使用锁定来确保它不会返回重复行行。

这是我用来选择数据的选择语句。我确实验证了连接不会导致它重复行:

SELECT pe.enc_id,
       pe.enc_nbr,
       pe.billable_ind,
       pe.clinical_ind AS clinical_ind,
       pe.budget_ind,
       pe.print_stmt_ind,
       pe.send_coll_letter_ind,
       pe.outsource_exempt_ind,
       cb.First_name + ' ' + cb.last_name AS CreatedBy,
       pe.create_timestamp AS create_timestamp,
       mb.first_name + ' ' + mb.last_name AS ModifiedBy,
       pe.modify_timestamp AS modify_timestamp
FROM   patient_encounter pe WITH(NOLOCK) 
       LEFT OUTER JOIN user_mstr cb WITH(NOLOCK) ON
           pe.created_by = cb.user_id
       LEFT OUTER JOIN user_mstr mb WITH(NOLOCK) ON
           pe.modified_by = mb.user_id

【问题讨论】:

    标签: sql-server ssis


    【解决方案1】:

    NOLOCK 提示会导致脏读异常,其中一种异常是重复读取。如果更新更改了查询扫描的索引中行的位置,则此类读取会很频繁:

    • 假设表中有 2 行,带有 ID 键,行带有键值 1 和 2
    • 一个请求 (T1) 运行 UPDATE table SET key=3 WHERE key=1;
    • 第二个请求 (T2) 运行 SELECT ... FROM table WITH(NOLOCK);
    • T1 锁定键值为 1 的行
    • T2 忽略 T1 拥有的锁并读取键值为 1 的行
    • T2 继续读取键值为 2 的行
    • T1 更新行,行在索引中移动到键值 3 的新位置
    • T2 继续扫描并读取键值为 3 的行

    所以 SELECT 读取了一行两次,一次是键值为 1,另一次是键值为 3。这只是可能发生的事情的一个简单示例。实际上,更复杂的查询可以运行复杂的计划并使用其他索引,所有这些都会受到此类异常的影响。

    简而言之:NOLOCK 提示是邪恶的。如果您想避免争用,请使用snapshot isolation。

    【讨论】:

    • 感谢您的回复,以及对NOLOCK的精彩解释。让我困惑的不是当我使用 NOLOCK 时会发生这种情况,而是当我不使用 NOLOCK 时会发生这种情况。主键列未更改,记录更新了非 id 字段,当我不使用 NOLOCK 时,它被读取了两次。
    • 很抱歉以对角线阅读您的问题,实际上完全没有抓住重点。
    • 您能否展示一个在提取过程中发生的编辑示例?
    • 我正在尝试在测试系统中复制场景。我想针对测试数据运行我的数据提取,并在数据提取运行时使用应用程序更新测试数据库中的一条记录。我想对所有这些进行跟踪,以便查看记录是如何更新的。设置可能需要一点时间,但希望我能在一天结束之前完成。
    • 只有在主键发生更新时才会出现重复?
    【解决方案2】:

    WITH NOLOCK 提示只是告诉数据库服务器忽略锁,并从数据库中选择当前值 - 因此它只是在命中该行时选择所有当前行值。

    请注意,您不会在新表中获得正在更新的行的更新。

    没有看到你的 SQL,我猜它的构造方式是抓取当前行,等待锁清除,然后也选择新行。

    锁定整个表格可以防止更改/重复,但您可能会在选择时将所有人锁定在表格之外。

    编辑: 仅供参考:使用 READPAST——被其他进程锁定的行被跳过并 TABLOCK——在表级别锁定,这当然会阻塞其他进程并且可能不需要。

    注意:在事务写入期间,UPDLOCK 被转换为 XLOCK。

    提示有两个类别:粒度和隔离级别。粒度包括 PAGLOCK、NOLOCK、ROWLOCK 和 TABLOCK。隔离级提示包括 HOLDLOCK、NOLOCK、READCOMMITTED、REPEATABLEREAD 和 SERIALIZABLE。每组最多可以使用一个。

    EDIT2:仅出于完整性考虑:READCOMMITTED - 只读来自已提交事务的数据。这是默认行为。

    EDIT3:更多信息:NOLOCK 将读取行,但如果事务发生 ROLLBACK 可能会影响所选集合的准确性,您可能会读取将更改的“脏”数据或不存在的数据。

    另一个重要的信息是发现事务正在使用什么样的锁,这样您就可以做出相应的计划。

    【讨论】:

    • 感谢您的信息。我在帖子中添加了我正在使用的 select 语句。为什么它会抢行两次?在它第一次读取它之后,为什么对记录的更改会导致它再次读取它?
    • 请记住,“提示”可以被服务器覆盖,即使您不使用 NOLOCK,它也可以在内部使用它,从而重复行。
    • 好点,谢谢。有没有办法告诉内部使用的是什么?
    • 我认为您将不得不进行设置以了解在此活动期间使用了哪些类型的锁。它可能会有所涉及,有一些例子(书籍,谷歌等)。您还可以查看正在更新的其他代码,并查看它正在使用什么以及服务配置/设置 - 然后出现问题:它是否使用默认值,在锁定期间更新范围是否良好(小)等。这经常看到@ 大约 50-100 个用户标准设置(见上文),它们试图扩展并且有点艺术:)。如果这是“滚动”更新/数据移动,您可以考虑 READPAST
    猜你喜欢
    • 2014-01-15
    • 1970-01-01
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-09
    • 1970-01-01
    相关资源
    最近更新 更多