很久以前,某同事 (Prem Mehra) 向我介紹了無用 SQL 的概念,以及他再三引用的真理:“沒有比不執行的 SQL 性能更好的 SQL 了。”在本系列文章中,我們將討論完全沒有用的、應該刪除的 SQL、過度執行的 SQL,以及應該被替換的 SQL。目標是什麼呢?如果可能,減少到 DB2 的連接,並完全刪除一些連接。還有什麼目標呢?了解 IBM DB2 8 和 DB2 9 中針對這些老問題的最新解決方案。因此,了解了我們了目標之後,歡迎閱讀本系列文章的第 1 部分:如何刪除或減少到 DB2 的連接。
COUNT(*):問題 1
在編寫應該時應該嚴格遵守一些基本的性能規則。其中之一是刪除程序中的所有沒有用的 SELECT COUNT。另一個是如果無法安全避免則應該減少執行 COUNT 的數量。下面我們來看一些例子:
我在看到這樣的 SQL 時,不免會產生疑問。為何會有一個 COUNT 在 UPDATE 前面?我進行了深入研究。是否檢查了主機變量?如果它是 0,那麼它的操作是什麼呢?如果 COUNT 大於 0 會怎麼樣呢?下一個操作會依賴於 :hvcount 的內容嗎?
COUNT 經常都是不必要的,並且可以完全清除。在本例中,程序員可能希望知道在執行 UPDATE 語句時會有多少個臨時 (T) 員工將被更新為永久 (P) 狀態。
這種方法存在兩處錯誤。首先,除非您使用 ISOLATION RR,或者擁有對表的獨占性維護訪問,否則 COUNT 數量的合格的行讀取與實際更新的行的數量會有不同。可以在 COUNT 和實際 UPDATE 之間執行刪除、插入或更新操作。
但是,另一個更加重要的問題在於 COUNT 是沒有必要的,因為 DB2 會在行更新時計算並在 SQL Communications Area (SQLCA) 的 SQLERRD(3) 中返回計算。您在測試程序時,可以檢查這個 SQLCA 字段是否包含所需的 COUNT 信息。
據我所知,在執行維護時,DB2 會盡可能計入您的行。我知道至少有兩個例外:
在分段表空間中刪除(DELETE)某個表中的所有行時(也就是沒有 WHERE 子句)
在通用表空間中刪除(DELETE)某個表中的所有行時(DB2 9 中新增)
對位於這兩類表空間中的表執行批量刪除(DELETE)時,DB2 不需要單獨尋址行,因此不會對它們計數 —— 但有三個例外:
啟用 DATA CAPTURE 時
調用 VALIDPROC 時
使用行級安全性時
但是,批量 DELETE 最常用於工作表或臨時表,它們極有可能體符合上述三種例外之一。
DB2 9 的一項新特性可允許您截斷表,從而對它執行 “重新初始化”(通過重新設置 High-Used Relative Byte Address [HURBA] 或者通過刪除並重新定義底層 VSAM 數據集 —— 非常類似於帶空 INPUT 數據集的 LOAD)。這與批量 DELETE 相類似,也不會將它們計入行數。
COUNT(*):問題 2
我們來看另一個實際生活中的不正確使用的例子:
我再次保證:我並不會在實際產生中使用這些例子。下面是程序員非常敬仰的信條:如果只有一個符合條件的行,則使用低速 CPU、路徑較短的 Searched SELECT,如果多於一個符合條件的行,則使用路徑較長的 CURSOR。
但是,讀取並計入行,然後再讀取行並處理它是不能解決問題的。不建議執行兩次讀取,即使分頁已經在緩沖池中。我不知道是多少次聽到這種方法了?只是因為,分頁可以在緩沖池中並不意外著可以隨意執行第二次讀取。由於 DB2 是一台重要、動態的維護機器,因此消除連接是一種可顯著降低 CPU 和 GET PAGE 開銷的方法。
那麼,針對這個問題有什麼更好的方法呢?程序員都在避免較高開銷的指針。因此,可以考慮一下這個問題:經常會遇到只有一個行的情況嗎?你說有百分之九十八的機率?那就先執行單體 SELECT。然後,如果接收到一條 -811 ("multiple rows returned to a Singleton Select") 錯誤消息,則打開指針。如果大多數時間都可以找到多個行,則立即打開指針,而不用擔心來自單體 SELECT 的反饋。換句話說,首先采用常普遍的方式,如果不行再執行 COUNT。
COUNT(*):問題 3
我們再看另一個實際生活中的例子:
此 COUNT 采用的邏輯如下:將 :hvcount 除以 10(屏幕上可顯示的行數),計算數據需要占用多少個屏幕。為什麼呢?這樣,每個屏幕(包括初始屏幕在內)都可以為用戶顯示 "Page 1 of n"。此處,我們可能會遇到 “不能刪除” 的情況。但是,如果用戶點擊 PF8 直到沒有行 / 屏幕時,是否可以采用更好的方式來確定有多少個 10 行的屏幕呢?
是的,首先應該問:“通常有多少個屏幕?”如果回答是一個屏幕或多個屏幕,則在閱讀之前不應計入這些行數。相反,打開指針,獲取行並在閱讀時計入這些行。如果在點擊第 11 行時到達 End of File,那麼應該可以知道答案,而不用執行 COUNT。並且,您已經刪除了 DB2 在執行 COUNT 時引入的所有 GET PAGE(或許是 READ I/Os)。
如果行數通常介於 11 和 30 呢?也應該讀取這些行,並在執行 FETCH 時計入它們。如果在讀取到第 31 行時計數已經達到 +100,則將這些行保存在屏幕顯示中間的某個位置(比如說 CICS 的 COMMAREA)。在第一頁上添加 "Page 1 of 3",然後刪除 PF8 顯示 "Page 2 of 3" 和 "Page 3 of 3" 所需的指令。
如果有 3 個以上頁面,則將用戶實際看到的內容保存在 COMMAREA 中(或任意位置)。然後,執行 COUNT。您已經將 COUNT 延緩到了最遲執行。您已經避免了大多數情況下的兩次讀取操作。最為重要的是,您的程序已經具備了良好的邏輯,其他人采用相同的方式編寫自己的程序。
結束語
對於日常需要使用 DB2 的用戶,以及對我的示例非常了解的用戶,我感覺感謝你們的支持和幫助,感謝你們分享了自己的思想和智慧。對於其他用戶,下一期將提供更多無用 SQL 的例子,並提供一些更加優秀的替換代碼的解決方案。敬請期待第 2 部分。