为什么查询违反外键的记录会返回错误的结果?

Ben*_*ius 4 foreign-key stored-procedures uniqueidentifier sql-server-2012

在添加新外键之前对表进行一些预检查时,我们正在查询当前有多少行会违反新键。这是一个相当活跃的数据库,在有问题的表上有近乎恒定的插入。

FK 将从 MessagePatientIdentifier.MessageID 到 Message.MessageID。

我们使用的查询非常简单:

select * from MessagePatientIdentifier as mpi
where
    MessageID not in (select MessageID from Message)
Run Code Online (Sandbox Code Playgroud)

我们看到的问题是从 MessagePatientIdentifier 返回行

MessagePatientID                     | MessageID 
553bde76-47d4-4ec3-96d1-b5d2e98931e1 | 7d45464d-8cc4-4a2e-8828-020722165b39

Run Code Online (Sandbox Code Playgroud)

在这种情况下,当您然后时select * from Message where MessageID = 7d45464d-8cc4-4a2e-8828-020722165b39,该记录确实存在。

然后,我们继续前行,并试图相同的查询,但在一个表DOES有FK的地方,以同样的方式引用信息表。相同的结果...查询报告存在子表记录,而没有父表对应的父表(消息)记录。

MessageRecipID                       | MessageID 
26d6d632-87b3-407e-aeb0-04552981e5f8 | 750f0fb4-3e6c-485d-996e-f061f8caa360

Run Code Online (Sandbox Code Playgroud)

然后,如果你再次select * from Message where MessageID = 750f0fb4-3e6c-485d-996e-f061f8caa360,这将返回记录。

这些数据来自 Mirth Server 的存储过程以及 BizTalk WCF-SQL 发送端口。proc 插入消息记录,获取 newuniqueidentifier作为输出变量,然后使用它来调用辅助存储过程以插入MessagePatient.MessageIDMessageRecip.MessageID

这是预期的行为吗,我只是没有了解 SQL 的内部工作原理?从技术上讲,我相信 BizTalk 会在事务中运行所有内容,因此它不应该不同步,即使它做了,如果不是从消息插入中获取 MessageID 值,它会从哪里获得?

我在这里缺少什么?

AMt*_*two 5

这是一个相当活跃的数据库 

如果您可以在某处恢复静态备份,并对那个不变的副本进行分析,我想您会发现您所看到的奇怪行为消失了。

读已提交

SQL Server 中的默认隔离级别是READ COMMITTED。在此隔离级别下,您读取当前提交到数据库的数据。它提供的唯一保证是当 SQL Server 读取给定页面时,它永远不会返回未提交的结果(称为脏读)。

在您的情况下,您正在扫描两个表并进行比较。在 SQL Server 进行扫描时,会发生数据移动。

  • 您阅读了前几页数据并获得了第一位数据。
  • 尚未阅读的某些行更新。也许该MessageSubject列或某个其他列被更新为更长的值,这会导致页面拆分。或者,键列可能已更新,并且该行在物理上沿着索引“向上”移动到您已阅读的部分中。
  • 在这两种情况下,一行(或多行)都可能从您尚未阅读的地方移动到您已阅读的地方。你永远不会看到这一行,因为它四处移动。这会导致该行从您的扫描中“丢失”。
  • 也有可能一行向另一个方向移动:从你已经读过的地方到你还没有去过的地方。在这种情况下,您将看到该行两次,并且您的结果将有一个神秘的双重结果。

您的示例使用整数,但在您的问题中您提到了uniqueidentifier. 由于uniqueidentifiers 是随机的,随机性意味着行不断地被插入到表上的随机位置。这增加了页面填满并必须拆分以容纳新页面的机会,并增加了您看到这些现象的机会。

阅读上面链接文章中标题为“锁定读取已提交行为”的部分以获得详尽的解释。

修复?

如果要避免这些关闭现象,请不要使用 SQL Server 中的默认隔离级别。最喜欢和默认的隔离级别是READ COMMITTED SNAPSHOT,它避免了这些问题。(在此处阅读有关所有隔离级别的信息,以确定哪一个适合您。)

或者,如果只是为了一次性分析,您可以使用数据库快照创建静态图像进行查询,或者您可以停止写入数据库,或在其他地方恢复副本。停止写入将停止数据移动,您不会遇到问题。

  • 它可能不是围绕索引的数据移动。执行计划可能会使用 Message 中的 MessageID 构建一个哈希表,然后扫描 MessagePatientIdentifier 中的所有行并查看它们是否存在于哈希表中。在构建哈希表后,两个表都可能发生插入,从而导致所描述的症状。 (5认同)