為什麼查詢違反外鍵的記錄會返回不正確的結果?
在添加新外鍵之前對錶進行一些預檢查時,我們正在查詢有多少目前行會違反新鍵。這是一個相當活躍的數據庫,在相關表上幾乎不斷地插入。
FK 將從 MessagePatientIdentifier.MessageID 到 Message.MessageID。
我們使用的查詢盡可能簡單:
select * from MessagePatientIdentifier as mpi where MessageID not in (select MessageID from Message)
我們看到的問題是從 MessagePatientIdentifier 返回的行
MessagePatientID | MessageID 553bde76-47d4-4ec3-96d1-b5d2e98931e1 | 7d45464d-8cc4-4a2e-8828-020722165b39
在這種情況下,當你 then 時
select * from Message where MessageID = 7d45464d-8cc4-4a2e-8828-020722165b39
,該記錄確實存在。然後我們繼續嘗試相同的查詢,但在一個確實有 FK 的表上,以相同的方式引用 Message 表。相同的結果…查詢報告子表記錄存在而沒有相應的父表(消息)記錄。
MessageRecipID | MessageID 26d6d632-87b3-407e-aeb0-04552981e5f8 | 750f0fb4-3e6c-485d-996e-f061f8caa360
然後,再次如果你
select * from Message where MessageID = 750f0fb4-3e6c-485d-996e-f061f8caa360
,這將返回記錄。此數據通過來自 Mirth Server 的儲存過程以及通過 BizTalk WCF-SQL 發送埠傳入。proc 插入消息記錄,獲取 new
uniqueidentifier
作為輸出變數,然後使用它來呼叫輔助儲存 procs 以插入到MessagePatient.MessageID
andMessageRecip.MessageID
中。這是預期的行為,我只是沒有受過 SQL 的內部工作原理的教育嗎?從技術上講,我相信 BizTalk 在事務中執行所有內容,因此它不應該不同步,即使它做了,如果不是從消息插入,它會在哪裡獲得 MessageID 值?
我在這裡想念什麼?
這是一個相當活躍的數據庫
如果您可以在某處恢復靜態備份,並對該不變的副本進行分析,我想您會發現您所看到的奇怪行為消失了。
讀已送出
SQL Server 中的預設隔離級別是READ COMMITTED。在此隔離級別下,您讀取目前送出到數據庫的數據。它提供的唯一保證是,當 SQL Server 讀取給定頁面時,它永遠不會返回未送出的結果(稱為臟讀)。
在您的情況下,您正在掃描兩個表並比較它們。在 SQL Server 進行掃描時,會發生數據移動。
- 您讀取了前幾頁數據並獲得了第一位數據。
- 您尚未閱讀的某些行已更新。也許 the
MessageSubject
或其他一些列被更新為更長的值,這會導致頁面拆分。或者,鍵列可能已更新,並且該行物理上將索引“向上”移動到您已經閱讀的部分。- 在這兩種情況下,一行(或多行)都可能從您尚未閱讀的地方移動到您已經閱讀的地方。您將永遠看不到該行,因為它四處移動。這會導致該行從您的掃描中“失去”。
- 一行也有可能向另一個方向移動:從一個你已經閱讀過的地方到一個你還沒有到達過的地方。在這種情況下,您將看到該行兩次,並且您的結果將有一個神秘的雙重結果。
您的範例使用整數,但在您的問題中您提到
uniqueidentifier
. 由於uniqueidentifier
s 是隨機的,隨機性意味著行不斷地插入到表上的隨機位置。這增加了頁面填滿並且必須拆分以容納新頁面的機會,並增加了您看到這些現象的機會。閱讀上述連結文章中標題為“鎖定已送出行為”的部分以獲得詳盡的解釋。
修復?
如果要避免這些關閉現象,請不要使用 SQL Server 中的預設隔離級別。我最喜歡的預設隔離級別是READ COMMITTED SNAPSHOT,它可以避免這些問題。(閱讀此處的所有隔離級別,以決定哪一個適合您。)
或者,如果只是為了一次性分析,您可以使用數據庫快照創建靜態圖像進行查詢,或者您可以停止寫入數據庫,或在其他地方恢復副本。停止寫入將停止數據移動,您不會遇到問題。