12.4. 應用層的資料完整性檢查

因為不管哪種隔離級別,對 PostgreSQL 的讀動作不會鎖定資料, 一個交易讀取的資料可能被另一個交易覆蓋。換句話說,如果一條 SELECT 返回了一行, 這並不意味著在返回該行時該行還存在(也就是說在語句完成或交易開始後的某時) 該行可能已經被一個在此交易開始之後提交的交易更新或者刪除。 即使該行"現在"仍然有效,那它也可能在目前交易提交或者回滾之前被改變或者刪除。

另外一個認識它的方法是每個交易都看到一個資料庫內容的快照, 而並行執行的事物很可能看到不同的快照。 因此不管怎樣,整個"現在"的概念都是定義不清的。 不過如果客戶端應用相互隔離,那麼這就不是個大問題, 但是如果客戶端之間在資料庫外部相互之間透過通道通訊,那就可能有嚴重的歧義。

要保證一行的實際存在和避免其被並行更新,我們必須使用 SELECT FOR UPDATE 或者或者合適的 LOCK TABLE 語句。 (SELECT FOR UPDATE 只是對其它的並行更新鎖住返回的行,而 LOCK TABLE 保護整個資料表。) 當從其他環境向 PostgreSQL 裡用可串行化模式移植應用時一定要把這些問題考慮進去。 (在版本 6.5 前,PostgreSQL 使用讀動作鎖,因而當從以前的 PostgreSQL 版本向6.5(或更高版本)升級時也要考慮這些問題。)

MVCC 環境下,全局有效性檢查需要一些額外的考慮。 比如,一個銀行應用可能會希望檢查一個資料表重的所有扣款總和等於另外一個資料表中的加款總和, 同時兩個資料表還會被活躍地更新。在讀已提交模式下比較兩個連續的 SELECT sum(...) 命令的結果是不可靠的, 因為第二個查詢很可能會包含第一個沒計算的交易提交的結果。 在一個可串行化的交易裡進行兩個求和則給出在可串行化交易開始之前提交的所有交易產生的精確的結果 — 但我們還是會合理地置疑在結果提交的時候,它們是否還相關。 如果可串行化交易本身在試圖做一致性檢查之前進行了某些變更, 那麼檢查的有用性就更加值得討論了,因為現在它包含了一些,但不是全部,交易開始後的變化。 在這種情況下,一個仔細的人會希望鎖住所有需要檢查的資料表, 這樣才能獲得一個無可置疑的目前現狀的圖像。 一個 SHARE 模式(或者更高級)的鎖保證在被鎖定資料表中除了目前交易之外,沒有未提交的更新。

還要注意如果我們依賴明確鎖定來避免並發更新,那麼我們應該使用讀已提交模式, 或者是在可串行化模式裡在執行命令之前小心地獲取鎖。 在可串行化交易裡的獲取的鎖保證了不會有其它正在執行的修改該資料表的交易存在, 但是如果交易看到的快照提前獲取了鎖,那麼它可能提前把一些現在已經提交的改變放到資料表中。 一個可串行化交易的快照實際上是在它的第一個查詢或者資料修改命令開始的時候凍結的(SELECTINSERTUPDATE,或 DELETE), 因此我們可能在快照凍結之前明確獲取鎖。