|
本文就如何組織 SQL 存儲過(guò)程中的邏輯以增強性能提供了一些具體建議。 有關(guān)如何調優(yōu)數據庫系統和應用程序的可用建議來(lái)源有很多。諸如 OLTP 應用程序的 DB2 調優(yōu)技巧(以前在 IBM? DB2? 開(kāi)發(fā)者園地上發(fā)表)之類(lèi)的文章通過(guò)使用事務(wù)和數據并行性以及分析查詢(xún)方案,給出了從表空間和索引設計到緩沖池的內存分配等方面的建議。這些方面的內容是性能調優(yōu)的基礎知識。 但是,有關(guān)如何組織存儲過(guò)程自身中的邏輯并著(zhù)眼于其性能的專(zhuān)門(mén)建議卻并不多見(jiàn)。本文就提供了這樣一種建議。盡管本文著(zhù)重于介紹 SQL 過(guò)程,但是這里所提供的大多數信息同樣適用于用其它語(yǔ)言編寫(xiě)的在應用程序中或存儲過(guò)程中嵌入的 SQL 邏輯。
在 深入研究詳細問(wèn)題之前,讓我們先回顧 DB2 中有關(guān)過(guò)程化 SQL 的一些基本術(shù)語(yǔ)和概念。過(guò)程化 SQL 構造(例如標量變量、IF 語(yǔ)句和 WHILE 循環(huán))是在 DB2 Universal Database? (UDB) V7 發(fā)行版中引入 DB2 的。以前的 DB2 發(fā)行版支持 C 和 Java? 作為存儲過(guò)程的語(yǔ)言。V7 引入了 SQL 存儲過(guò)程,以及其它許多可以促進(jìn) OLTP 應用程序開(kāi)發(fā)的特性(例如臨時(shí)表、應用程序保存點(diǎn)和標識列)。 當創(chuàng )建 SQL 過(guò)程時(shí),DB2 將過(guò)程主體中的 SQL 查詢(xún)與過(guò)程邏輯區分開(kāi)來(lái)。為了使性能最優(yōu),SQL 查詢(xún)被靜態(tài)地編譯成包中的節。(對于靜態(tài)編譯的查詢(xún)而言,節主要是由 DB2 優(yōu)化器為該查詢(xún)選擇的存取方案構成的。包是節的集合。有關(guān)包和節的更多信息,請參閱 DB2 SQL 參考大全,第 1 卷。)另一方面,過(guò)程邏輯被編譯成 DLL(動(dòng)態(tài)鏈接庫)。 在 過(guò)程的執行期間,每當控制從過(guò)程邏輯流向 SQL 語(yǔ)句時(shí),在 DLL 和 DB2 引擎之間就存在“上下文切換”。(在 DB2 V8 中,SQL 過(guò)程是在“不受保護的方式”下運行的,即與 DB2 引擎在相同的尋址空間中。因此我們這里談及的上下文切換并不是操作系統級別上的完全的上下文切換,而是指 DB2 中層的更換。)減少頻繁調用的過(guò)程(例如 OLTP 應用程序中的過(guò)程)或者處理大量行的過(guò)程(例如執行數據清理的過(guò)程)中的上下文切換次數,對它們的性能有顯著(zhù)的影響。本文中的幾個(gè)技巧恰好旨在減少這些上 下文切換。 剛開(kāi)始的時(shí)候(DB2 通用數據庫 V7 GA),只允許在 SQL 過(guò)程中使用 SQL 過(guò)程語(yǔ)言(通常稱(chēng)為 SQL PL)。后來(lái)(在 DB2 UDB V7.2 中),在 SQL 函數和觸發(fā)器主體中開(kāi)始支持該語(yǔ)言的子集。SQL PL 的這個(gè)子集即所謂的 內聯(lián)(inline)SQL PL?!皟?聯(lián)”一詞突出顯示了它與完整語(yǔ)言的重要區別。SQL PL 過(guò)程是通過(guò)將其單獨的 SQL 查詢(xún)靜態(tài)地編譯成包中的節實(shí)現的,而內聯(lián) SQL PL 函數就象其名稱(chēng)所展示的,是通過(guò)將函數主體內聯(lián)到使用它的查詢(xún)中實(shí)現的。稍后我們將再看一下內聯(lián) SQL PL 及其用法的一些示例。 現在,讓我們研究在使用 SQL 過(guò)程語(yǔ)言時(shí)可用來(lái)提高性能的一些具體工作。
在只使用一條語(yǔ)句即可做到時(shí)避免使用多條語(yǔ)句 讓我們從一個(gè)簡(jiǎn)單的編碼技巧開(kāi)始。如下所示的單個(gè) INSERT 行序列:
可以改寫(xiě)成:
執行這個(gè)多行 INSERT 語(yǔ)句所需時(shí)間大約是執行原來(lái)三條語(yǔ)句的三分之一。孤立地看,這一改進(jìn)看起來(lái)似乎是微乎其微的,但是,如果這一代碼段是重復執行的(例如該代碼段位于循環(huán)體或觸發(fā)器體中),那么改進(jìn)是非常顯著(zhù)的。 類(lèi)似地,如下所示的 SET 語(yǔ)句序列:
可以寫(xiě)成一條 VALUES 語(yǔ)句:
如果任何兩條語(yǔ)句之間都沒(méi)有相關(guān)性,那么這一轉換保留了原始序列的語(yǔ)義。為了說(shuō)明這一點(diǎn),請考慮:
將上面兩條語(yǔ)句轉換成:
不會(huì )保留原始的語(yǔ)義,因為是以“并行”方式對 INTO 關(guān)鍵字之前的表達式進(jìn)行求值的。這意味著(zhù)賦給 B 的值并不以賦給 A 的值為基礎,這是原始語(yǔ)句預期的語(yǔ)義。
從多個(gè) SQL 語(yǔ)句到一個(gè) SQL 表達式 跟 其它編程語(yǔ)言一樣,SQL 語(yǔ)言提供了兩類(lèi)條件構造:過(guò)程型(IF 和 CASE 語(yǔ)句)和函數型(CASE 表達式)。在大多數環(huán)境中,可使用任何一種構造來(lái)表達計算,到底使用哪一種只是喜好問(wèn)題。但是,使用 CASE 表達式編寫(xiě)的邏輯不但比使用 CASE 或 IF 語(yǔ)句編寫(xiě)的邏輯更緊湊,而且更有效。 請考慮下面的 SQL PL 代碼片段:
IF 子句中的條件僅用于決定將什么值插入 tab_comp.Val 列中。為了避免過(guò)程層和數據流層之間的上下文切換,可利用 CASE 表達式將相同的邏輯表示成一個(gè) INSERT 語(yǔ)句:
值得注意的是,CASE 表達式可在任何希望有標量值的上下文中使用。特別地,可在賦值符號的右邊使用它們。例如:
可以改寫(xiě)成:
實(shí)際上,這個(gè)特殊的示例有一個(gè)更好的解決方案:
諸 如循環(huán)、賦值和游標之類(lèi)的過(guò)程化構造允許我們表達那些只使用 SQL DML 語(yǔ)句是不可能表達的計算。但是,當我們擁有一些可以隨意使用的過(guò)程語(yǔ)句時(shí),即使我們手頭的計算實(shí)際上僅使用 SQL DML 語(yǔ)句就可表達,但轉換成過(guò)程語(yǔ)句還是有風(fēng)險的。正如我們以前提到的,過(guò)程計算的性能與使用 DML 語(yǔ)句表達的同一個(gè)計算的性能相比會(huì )慢幾個(gè)數量級。請考慮下面的代碼片段:
首先,通過(guò)應用上一節討論的轉換可以改進(jìn)循環(huán)體:
但是通過(guò)進(jìn)一步觀(guān)察,我們發(fā)現整個(gè)代碼塊可以寫(xiě)成一個(gè)帶有 SELECT 子句的 INSERT 語(yǔ)句:
在原始的表述中,SELECT 語(yǔ)句中每行的過(guò)程層和數據流層之間都有一個(gè)上下文切換。在最后一個(gè)表述中,根本沒(méi)有上下文切換,并且優(yōu)化器有機會(huì )對整個(gè)計算進(jìn)行全局優(yōu)化。另一方面,如果 每個(gè) INSERT 語(yǔ)句針對的都是不同的表,那么這種引人注目的簡(jiǎn)化是不可能的,如下所示。
但是,這里也可以利用 SQL 的一次處理一個(gè)集合(set-at-a-time)特性:
在研究改進(jìn)現有過(guò)程邏輯的性能時(shí),為消除游標循環(huán)而花費的任何時(shí)間都可能是值得的。
如果存儲過(guò)程中的邏輯確實(shí)需要游標,那么要使性能最優(yōu),請牢記下面這些內容。 首 先,請確保不使用高于您所需的隔離級別。隔離級別決定了 DB2 對過(guò)程讀取或更新的行應用的鎖定的數量。隔離級別越高,DB2 將執行的鎖定越多,因此為同一資源而競爭的應用程序之間的并發(fā)就越少。例如,使用可重復讀(Repeatable Read,RR)隔離級別的過(guò)程將形成對其讀取的任何行的共享鎖,而使用游標穩定性(Cursor Stability,CS)的過(guò)程只會(huì )鎖定任何可更新游標的當前行??梢允褂?DB2_SQLROUTINE_PREPOPTS 注冊表變量來(lái)指定 SQL 過(guò)程的隔離級別。例如,要將 SQL 過(guò)程的隔離級別設置為未提交的讀(Uncommitted Read)(最低的級別,用于訪(fǎng)問(wèn)只讀數據的過(guò)程),請使用下面這條命令:
注:要使該設置生效,必須重新啟動(dòng) db2 實(shí)例。 DB2 中缺省的隔離級別是游標穩定性。但是,當然了,為了保持應用程序的正確性,有時(shí)需要使用可重復讀。還需記住一件重要的事情,一旦創(chuàng )建了需要可重復讀的過(guò)程,必須將 DB2_SQLROUTINE_PREPOPTS 重新設置回較低的隔離級別。 有關(guān)隔離級別還值得一提的是,DB2 允許我們在單獨的查詢(xún)中覆蓋缺省的隔離級別,如下所示:
上面的查詢(xún)將以隔離級別 UR 進(jìn)行執行,而不管 DB2_SQLROUTINE_PREPOPTS 中指定的隔離級別。 在嘗試改進(jìn)游標性能時(shí)需要牢記的一個(gè)相關(guān)問(wèn)題是游標的可更新能力。如果游標涉及的行是可以使用 INSERT 或 DELETE 語(yǔ)句中的 WHERE CURRENT OF 子句進(jìn)行更新或刪除,那么它就是 可刪除的。當游標可刪除時(shí),DB2 必須獲取行上的 互斥鎖(與 共享鎖相對),并且不能執行行分塊。行上的互斥鎖甚至可以防止其它應用程序讀取該行(在互斥鎖被釋放之前,這些應用程序必須等待,除非它們的隔離級別是 UR),而行分塊通過(guò)在一個(gè)操作中檢索行塊,從而減少了用于游標的數據庫管理器開(kāi)銷(xiāo)。 只 有不可刪除的游標才可以進(jìn)行行分塊。這就是為什么讓 DB2 了解將如何使用游標是很重要的原因。通過(guò)在 SELECT 語(yǔ)句中指定 FOR READ ONLY 子句,可以將游標顯式地聲明為不可刪除,或者通過(guò)在 SELECT 語(yǔ)句中使用 FOR UPDATE 子句將其聲明為可刪除。根據該信息(并且還根據下面描述的 BLOCKING 選項),DB2 將確定是否將行分塊用于給定的游標。 缺 省情況下,對于那些使用 FOR READ ONLY 子句定義的游標,DB2 將始終使用行分塊,除非指定了 BLOCKING NO 綁定選項。另一方面,如果使用了 BLOCKING ALL 綁定選項,那么對于含混游標(既不是定義成 FOR READ ONLY 也不是定義成 FOR UPDATE 的游標),DB2 將使用行分塊。 簡(jiǎn)而言之:如果可能,則在游標定義中使用 FOR READ ONLY 子句;如果您的過(guò)程包含含混游標,那么請使用 BLOCKING ALL 綁定選項。要設置 BLOCKING 綁定選項的值,我們還可以使用 DB2_SQLROUTINE_PREPOPTS 注冊表變量。例如,要將 SQL 過(guò)程的隔離級別設置為未提交的讀,并將行分塊設置為 BLOCKING ALL,請使用下面這條命令:
對于返回大型結果集的過(guò)程而言,分塊特別重要。 通過(guò)使用 DB2_SQLROUTINE_PREPOPTS 注冊表,還可以為存儲過(guò)程指定其它綁定選項。請參閱 Application Development Guide: Building and Running Applications中有關(guān)“Setting Up the SQL Procedures Environment”方面的內容以獲得更多信息。此外,有關(guān)隔離級別、鎖定和分塊的完整說(shuō)明已超出了本文范圍。請參閱 DB2 Administration Guide: Performance和 SQL Reference中有關(guān) DECLARE CURSOR 的條目,以獲取完整的說(shuō)明。
正如我們在簡(jiǎn)介中提及的,SQL 過(guò)程和 SQL 函數是使用不同技術(shù)實(shí)現的。SQL 過(guò)程中的查詢(xún)是單獨編譯的,每個(gè)查詢(xún)都成為包中的一個(gè)節。編譯是在過(guò)程創(chuàng )建時(shí)進(jìn)行的,直到重新創(chuàng )建過(guò)程或者直到重新綁定其相關(guān)的包時(shí)才重新編譯這些查詢(xún)。 另一方面,SQL 函數中的查詢(xún)是一起編譯的,就好象函數體是一個(gè)查詢(xún)一樣。每當編譯一條使用 SQL 函數的語(yǔ)句時(shí),也會(huì )對 SQL 函數進(jìn)行編譯。 與 SQL 過(guò)程中所發(fā)生的情況不同,SQL 函數中的過(guò)程語(yǔ)句與數據流語(yǔ)句是在同一個(gè)層中執行的。因此,每當控制從過(guò)程語(yǔ)句流向數據流語(yǔ)句或相反時(shí),并不發(fā)生上下文切換。 因 為存在這些區別,所以當給定的過(guò)程代碼段作為函數實(shí)現時(shí)的執行速度通常比作為過(guò)程實(shí)現時(shí)要快。但是,當然了,有一個(gè)小問(wèn)題。函數只能包含那些不會(huì )改變數據 庫狀態(tài)的語(yǔ)句(例如 INSERT、UPDATE 或 DELETE 語(yǔ)句是不允許的)。并且只允許完整 SQL PL 語(yǔ)言的子集出現在 SQL 函數中(不能是 CALL 語(yǔ)句、游標和條件處理)。 盡管有這些限制,但大多數 SQL 過(guò)程都可以在無(wú)副作用的情況下轉換成 SQL 函數。例如,下面的過(guò)程:
等同于下面的函數:
請注意,盡管使用了 CALL 語(yǔ)句來(lái)調用過(guò)程,但還需要使用 VALUES 語(yǔ)句從命令行調用函數:
另一方面,與過(guò)程不同的是,您可以在允許表達式的任何上下文中調用函數:
因此,正如本節標題所展示的,當您只是從數據庫抽取數據而不執行任何更改時(shí),請考慮使用 SQL 函數而不是使用 SQL 過(guò)程。
在 V7 中,DB2 引入了臨時(shí)表。對臨時(shí)表的操作通常比對常規表的操作快。讓我們看一些原因:
在對 SQL 過(guò)程中的臨時(shí)表進(jìn)行任何應用之前,表定義在編譯環(huán)境中必須是可用的。例如,在下面的 CLP 腳本(該腳本使用“%”作為語(yǔ)句的終結符)中,表定義的唯一目的就是能夠創(chuàng )建 SQL 過(guò)程:
在執行了 CONNECT RESET 命令后,臨時(shí)表將不復存在。在運行時(shí),應用程序必須確保在執行使用臨時(shí)表的首個(gè)查詢(xún)之前該表是存在的。最后的這個(gè)觀(guān)察引出了一個(gè)我們從未提及的要點(diǎn):引用 臨時(shí)表的任何查詢(xún)都將被動(dòng)態(tài)地編譯,即使該查詢(xún)被寫(xiě)成靜態(tài)的 SQL。跟其它任何動(dòng)態(tài)查詢(xún)一樣,在編譯該查詢(xún)之后,它將以已編譯的形式保留在包高速緩存中。在下一次執行相同的查詢(xún)時(shí),僅當無(wú)法在高速緩存發(fā)現它時(shí), DB2 才重新編譯它。 如果您打算創(chuàng )建相對較大的臨時(shí)表,并對這些表運行幾個(gè)查詢(xún),請考慮定義索引并對它們運行 runstats(顯然后者是填充了表后進(jìn)行的)。 下一節將介紹更多這方面的內容。 有關(guān)在 SQL 過(guò)程中使用臨時(shí)表的最后一個(gè)說(shuō)明是:如果需要根據在同一個(gè)過(guò)程中創(chuàng )建的臨時(shí)表返回結果集,那么必須在嵌套的復合語(yǔ)句中定義結果集,如下面的示例所示:
必須在嵌套的復合語(yǔ)句中定義結果集的理由是, DECLARE GLOBAL TEMPORARY TABLE 是一個(gè)可執行語(yǔ)句,而可執行語(yǔ)句只能在聲明語(yǔ)句(例如 DECLARE CURSOR)之后編寫(xiě)。如果我們在游標定義之后在外部作用域中聲明表,那么當編譯 DECLARE CURSOR 語(yǔ)句時(shí),該表在編譯環(huán)境中將不可用,因此編譯會(huì )失敗。
當 創(chuàng )建了一個(gè)過(guò)程時(shí),其單獨的 SQL 查詢(xún)被編譯成包中的節。其中,DB2 優(yōu)化器根據表的統計信息(例如,表大小或某列中數據值出現的相對頻率)以及編譯查詢(xún)時(shí)可用的索引來(lái)選擇查詢(xún)的執行方案。當表經(jīng)過(guò)了重大更改時(shí),讓 DB2 再次收集有關(guān)這些表的統計信息可能是個(gè)好主意。當更新了統計信息時(shí),或者當創(chuàng )建了新的索引時(shí),重新綁定那些與使用表的 SQL 過(guò)程相關(guān)聯(lián)的包,以使 DB2 創(chuàng )建使用最新統計信息和索引的方案,這可能也是一個(gè)好主意。 可以使用 RUNSTATS 命令更新表的統計信息。要重新綁定與 SQL 過(guò)程關(guān)聯(lián)的包,可以使用 REBIND_ROUTINE_PACKAGE 內置過(guò)程(在 DB2 V8 中可用)。例如,可以使用下面這條命令來(lái)重新綁定過(guò)程 MYSCHEMA.MYPROC 的包:
其中 ‘P‘ 表明該包對應于一個(gè)過(guò)程,而 ‘ANY‘ 表明 SQL 路徑中的任何函數和類(lèi)型都被當作函數和類(lèi)型解析。(請參閱 REBIND 命令的 Command Reference 條目,以獲取更多詳細信息。)
在本文中,我提供了一類(lèi)可能有助于改進(jìn) SQL 過(guò)程的性能的提示和技巧(請查閱 Yip等 編寫(xiě)的書(shū)籍,以獲取 SQL PL 的良好簡(jiǎn)介)。作為一般規則,首先考慮系統性能(硬件和 OS)和數據庫管理器(緩沖池、容器和表空間等等)這些基本的問(wèn)題。DB2 配置顧問(wèn)程序對于后者非常有幫助。然后請確保應用程序中一些關(guān)鍵查詢(xún)的方案是合適的。最后,利用本文提供的建議來(lái)研究改進(jìn)您的存儲過(guò)程和應用程序。祝您在 調優(yōu)的工作中好運!
感謝 Lee Johnson、Paul Yip、Drew Bradstock 和 Clara Liu 對本文草稿提供的寶貴意見(jiàn)。
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
聯(lián)系客服