DB2 9 引入了 pureXML 支持,這意味著 XML 數據將以其固有的層次格式被存儲和查詢。為了查詢 XML 數據,DB2 提供了兩種語言,SQL/XML 和 XQuery。此外,DB2 9 還具有成熟的 XML 索引功能和對 XML 模式驗證的支持。雖然大多數已有的關於 DB2 性能的指南同樣適用於 XML 數據,但是本文還將提供其他一些特定於 XML 的性能方面的提示。
簡介
DB2 9 中的 pureXML 支持為管理 XML 數據提供了有效的、多方面的功能。對於很多 XML 應用程序而言,性能是高度優先考慮的一個方面。DBA 和應用程序設計人員都可以通過他們份內的工作來確保良好的性能。首先,我們有關於 DB2 各個方面的所有傳統的性能指南,包括 CPU/內存/磁盤配置的平衡、表空間和緩沖池的調優、鎖、日志記錄、查詢執行計劃等。所有這些話題在之前的 DB2 文章裡都曾論述過(見 參考資料),並在管理 DB2 中的 XML 數據時仍然適用。
幸運的是,這些問題當中有很多問題是由 DB2 的自治存儲和自調優內存管理等自治功能來處理的。它們為很多應用程序提供了高水平的性能,要求的手動干預很少。但是,具有更高性能需求的 XML 應用程序還可以從其他性能方面的考慮當中受益。本文集中討論這方面的情形,同時給出為 DB2 9 中與 XML 相關的應用程序取得最佳性能的提示和指南。
本文將討論和闡述 15 個 XML 性能提示(排序不分先後)。這 15 個提示涵蓋了很多領域,但是經驗表明,存在性能問題的應用程序通常只需要應用其中一兩個提示就能達到所需的性能。
提示 1: 理智選擇 XML 文檔的粒度。
提示 2: 為了取得更好的 XML 性能,使用 DMS 和更大的頁。
提示 3: 必要時,如何將 XML 數據放入一個單獨的表空間中。
提示 4: 如何配置 DB2,以便快速地成塊插入 XML 數據。
提示 5: 使用新的快照監視器元素檢查 XML 性能。
提示 6: 了解 XML 模式驗證的開銷。
提示 7: 在 XPath 表達式中,盡可能使用全限定路徑。
提示 8: 定義傾斜的 XML 索引,並避免為任何東西都建索引。
提示 9: 將文檔過濾謂詞放入 XMLEXISTS 中,而不是放入 XMLQUERY 中。
提示 10: 使用方括號 [ ] 來避免 XMLEXISTS 中的 Boolean 謂詞。
提示 11: 使用 RUNSTATS 收集 XML 數據和索引的統計信息。
提示 12: 如何使用 SQL/XML 發布視圖將關系數據暴露為 XML。
提示 13: 如何使用 XMLTABLE 視圖以關系格式暴露 XML 數據。
提示 14: 對於短小的查詢或 OLTP 應用程序,使用帶參數占位符的 SQL/XML 語句。
提示 15: 避免 XML 插入和檢索期間出現代碼頁轉換。
在對這些性能提示的討論中,我們假設您熟悉基本的 DB2 管理和性能實踐,並熟悉基本的 DB2s pureXML 支持。例如,您應該知道 XML 列、XML 索引,以及如何用 SQL/XML 和 XQuery 查詢 XML 數據。
DB2 XML 性能提示
提示 1: 理智選擇 XML 文檔的粒度
當設計 XML 應用程序和 XML 文檔結構時,您可能面臨將哪些業務數據放在一個單獨的 XML 文檔中的選擇。例如,在下面的部門表中,我們為每個部門使用一個 XML 文檔(中等粒度)。如果部門是應用程序訪問和處理數據所依賴的主要粒度,那麼這是合理的選擇。或者,我們也可以決定將多個部門組合到一個 XML 文檔中,例如,將屬於同一個單位的部門放入一個 XML 文檔中(粗粒度)。然而,如果通常一次只處理一個部門,那麼這種粒度帶來的性能就是次優的。
表 1. 創建表 dept( unitID char(8), deptdoc XML)
unitID
deptdoc
WWPR
<dept deptID='PR27'>
WWPR
<employee id='901'>
<name>Jim Qu</name>
<phone>408 555 1212</phone>
</employee>
<employee id='902'>
<name>Peter Pan</name>
<office>216</Office>
</employee>
</dept><dept deptID='V15'>
S-USE
...
...
...
<employee id='673'>
<name>Matt Foreman</name>
<phone>416 891 7301</phone>
<office>216</Office>
</employee>
<description>This dept supports sales world wide</description>
</dept>
我們還可以決定為每個雇員使用一個 XML 文檔(細粒度),並使用一個附加的 “dept” 屬性,表明他或她屬於哪個部門。如果雇員本身使用經常獨立於同部門其他雇員被訪問和處理的業務對象,那麼這會是一個很好的選擇。但是,如果應用程序常常一起處理一個部門中的所有雇員,那麼每個部門一個文檔會更好一些。
尤其是,我們不推薦將很多獨立的業務對象成批地放入一個文檔中。DB2 使用 XML 數據上的索引對文檔進行過濾。因此,XML 文檔的粒度越細,從基於索引的訪問中得到的好處就越大。而且,如果應用程序使用 DOM 解析器來攝取從 DB2 檢索的 XML,那麼小型文檔可以帶來更好的性能。
與 XML 文檔設計相關的一個常見的問題是,何時使用屬性,何時使用元素,以及這裡做出的選擇對性能有怎樣的影響。與其說這是一個性能問題,不如說這是一個數據建模問題。這個問題像 XML 的先驅 SGML 一樣古老,而且曾經歷過熱烈的辯論,只是最後也沒有廣泛地達成一致。然而,與此有關的一個重要事實是,XML 元素比屬性更靈活,因為 XML 元素可以重復和嵌套。例如,在我們的部門文檔中,我們使用一個 “phone” 元素,如果一個雇員有多個電話號碼,那麼可以讓這個元素出現多次。而且,當以後需要將電話號碼拆成數段時,這個元素還是可擴展的。即 “phone” 元素可以有表示國家代碼、區代碼、分機號等等的子元素。如果 “phone” 是雇員元素的一個屬性,那麼對於每個雇員,它只能出現一次,我們也不能為之添加子元素,這將妨礙模式隨時間而變化。雖然不用屬性也可以對所有數據進行建模,但是對於預先知道不會重復(對於每個元素而言),也沒有任何子字段的數據項,使用屬性仍是最直觀的選擇。屬性有助於使 XML 變得更短,因為屬性只需一個標記,而元素則需要一對開始標記和結束標記。在 DB2 中,查詢、謂詞和索引定義中都可以使用屬性,就像使用元素一樣容易。由於屬性的可擴展性不如元素,DB2 可以應用某些存儲和訪問優化。這應該看作是額外的性能收益,而不是將屬性轉換成元素的決定因素,尤其是當數據建模確實傾向於使用元素的時候更是如此。
總而言之,應該根據預期的訪問的主要粒度來選擇 XML 文檔的粒度。如果仍然心存猶豫,那麼傾向於較細的粒度和較小的 XML 文檔要穩妥些。
提示 2:為了取得更好的 XML 性能,使用 DMS 和更大的頁
數據庫管理的表空間(DMS)可以比系統管理的表空間(SMS)提供更高的性能。對於關系數據是如此,對於 XML 的讀和寫訪問更是如此。在 DB2 9 中,缺省情況下,新創建的表空間是 DMS。還建議將 DMS 表空間與自治存儲一起使用,使 DMS 容器可以隨需增長,而不需要人為干預。如果 XML 文檔太大,一個表空間中的一個頁面容納不下,那麼 DB2 就將該文檔劃分成多個區域,然後將它們分開存儲到多個頁面上。這一點對於應用程序而言是透明的,這也使得 DB2 可以處理 2 GB 的 XML 文檔。
通常,每個文檔劃分成的區域越少,性能就越好,對於插入和全文搜索更是如此。如果一個文檔大小超出了一個頁面,每個文檔分成的子塊的數量取決於頁寬(4KB、8KB、16KB 或 32KB)。表空間的頁寬越大,每個文檔劃分成的子塊數量就越小。例如,假設一個給定的文檔要劃分到 40 個 4KB 的頁面上。那麼,同樣是這個文檔,可以存儲在 20 個 8KB 的頁面上,或者存儲在 10 個 16KB 的頁面或 5 個 32KB 的頁面上。如果 XML 文檔遠小於所選擇的頁寬,那麼就不會浪費空間,因為多個小的文檔可以存儲在一個頁面上。
經驗法則是,為 XML 數據選擇一個不小於平均預期文檔大小的兩倍而且不超過 32 KB 最大值的頁寬。如果為關系數據和 XML 數據,或者為數據和索引使用單一的頁寬,那麼 32KB 的頁寬對於 XML 數據而言可能有利,但是不利於關系數據和索引的訪問。在這種情況下,16KB 或 8KB 的頁寬對於兩者而言都是較好的選擇。
提示 3:必要時,如何將 XML 數據放入一個單獨的表空間中
如果您做了性能分析,發現對於 XML 數據需要大的頁寬,而對於關系數據或索引則需要小的頁寬,那麼可以使用不同的表空間來實現這一點。當定義一個表時,可以將 “long” 數據定向到一個具有不同頁寬的單獨的表空間中。long 數據包括 LOB 和 XML 數據。
下面的例子定義了兩個緩沖池和兩個表空間,一個表空間的頁寬為 4KB,另一個表空間的頁寬為 32KB。(注意,一個表空間總是要有一個緩沖池,且緩沖池要有一個匹配的頁寬。)表 product 被分配到具有 4KB 頁面的表空間 "relData"。它的所有列被存儲在那個表空間中,只有 XML 列 "description" 存儲在表空間 "XMLdata" 中的 32KB 的頁面上。
清單 1. 兩個緩沖池和兩個表空間的定義
create bufferpool bp4k pagesize 4k;
create bufferpool bp32k pagesize 32k;
create tablespace relData
pagesize 4K
managed by automatic storage
bufferpool bp4k;
create tablespace XMLData
pagesize 32K
managed by automatic storage
bufferpool bp32k;
create table product(pid bigint, name varchar(20), brand varchar(35),
category integer, price decimal, description XML)
in relData
long in XMLData;
DB2 9 中的缺省表空間有所變化。除非顯式指定,否則新創建的表空間都是具有較大行 ID 的 DMS。這意味著,一個具有 4KB 頁面的表空間可以增長到 2TB,而不是 DB2 8 中的 64GB,具有 32KB 頁面的表空間可以增長到 16TB,而不是 512GB。而且,每個頁面 255 行的限制也去掉了,32KB 的頁面上允許至多 2335 行。因此,每頁的行數限制本身不再是為關系數據使用小型頁面的理由。
總而言之,在為 XML 數據選擇不同的表空間時,仍應按常理來考慮。更少的緩沖池和表空間,以及更少的頁寬種類,可以導致更簡單的物理數據庫設計,從而更容易管理、維護和調優。因此,要避免引入多種頁寬,除非您知道這樣做確實可以帶來性能好處。
提示 4:如何配置 DB2,以便快速地成塊插入 XML 數據
DB2 9 支持兩種將 XML 數據從文件系統轉移到 DB2 表中的選項:插入和導入。從性能和調優的角度來看,這兩種選項具有類似的特征,因為導入實用程序實際上是執行一系列的插入。不管是應用程序執行批量插入(可能通過並發插入線程),還是使用導入,下面的性能指南都適用:
務必使用具有較大頁寬的 DMS 表空間(見 提示 2),這是一個關鍵的先決條件。
即使沒有在要成批插入數據的表上定義任何索引,DB2 pureXML 存儲機制仍然會透明地維護所謂的區域和路徑索引,以便於進行高效的 XML 存儲訪問。因此,應提供足夠的緩沖池空間來支持索引讀操作。
如果表上需要多個用戶定義的 XML 索引,那麼通常更好的做法是在批量插入之前定義它們,而不是事後才創建它們。在插入期間,每個 XML 文檔只被處理一次,便可以為所有 XML索引生成索引條目。然而,如果發出多條 “create index” 語句,那麼 XML 列中的所有文檔將被訪問多次。
“ALTER TABLE <tablename> APPEND ON” 為表啟用追加(append)模式。新的數據被追加到表的末尾,而不是在已有的頁面上搜索空閒空間。這可以提高批量插入的運行時性能。
增加日志緩沖池大小(LOGBUFSZ)和日志文件大小(LOGFILSIZ)有助於提高插入性能。這一點對於 XML 插入尤其重要,因為每一行的數據量都比關系數據大得多。建議為日志使用快速的 I/O 設備。
如果使用 “ALTER TABLE <tablename> ACTIVATE NOT LOGGED INITIALLY” (NLI),可以避免日志記錄。然而要注意,如果有語句遭到失敗,那麼表將被標記為不可訪問,而且必須被刪除。這一點將妨礙生產系統中進行 NLI 的增量批量插入,但是對於空表的初始填充比較有用。
在使用導入的情況下,如果為 COMMITCOUNT 參數采用一個較小的值,那麼會不利於性能。每次提交 100 行或更多行比每次提交一行在性能上更好。也可以忽略 COMMITCOUNT 參數,讓 DB2 按適當的頻率進行提交。
為了更好地利用多個 CPU 和磁盤,可以並發運行多個導入命令。但是應確保每個導入命令都是在它自己的數據庫連接上運行的,並使用 “ALLOW WRITE Access” 子句以避免表被鎖定。為運行並發導入,不需要分割輸入文件(DEL 文件)。每個導入命令可以讀取一個輸入文件的不同片段,因為導入命令允許指定 “SKIPCOUNT m ROWCOUNT n” 來讀取輸入文件中的 m+1 到 m+n 行。
如果需要將非常多的小型 XML 文件從文件系統轉移到一個 DB2 表中,那麼將它們放在一個專用的、文件系統緩存被禁用的文件系統中有助於提高性能。由於每個文件只被讀取和插入一次,因此緩存完全沒有必要。在 AIX 上,用 -o cio 選項掛載這種文件系統被證明是有益的。
要獲得更多關於插入性能的指南,請參閱文章 “Tips for improving INSERT performance in DB2 Universal Database”(見 參考資料)。
總而言之,傳統的插入和日志記錄性能調優對於 XML 插入和導入仍然適用。如果將 ALLOW WRITE Access 子句添加到每個導入命令中,則可以運行並行導入會話。
提示 5:使用新的快照監視器元素檢查 XML 性能
無論您是正在調查不同頁寬的優點還是 XML 性能的其他方面,很可能都要像對於關系數據一樣使用 DB2 快照監視器。您將發現,與用於數據和索引的已有的計數器相對應,DB2 9 為 XML 數據提供了新的緩沖池快照監視器元素。由於關系數據和索引存儲在表空間內不同的存儲對象中,因此它們具有不同的讀和寫計數器。DDB2 9 中的 pureXML 存儲為 XML 數據引入了一種新的存儲對象,即 XDA,它也有自己的緩沖池計數器。
下面的例子是快照監視器輸出的一個片段。可以看到用於三種不同存儲對象(即數據、索引和 XDA)各自的快照監視器元素。這樣便於單獨針對 XML 監視和分析緩沖和 I/O 活動,而與關系數據互不影響。與 XML 索引有關的任何活動都被包括在已有的索引計數器中。新的 XDA 計數器的解釋與對應的關系計數器的解釋相同。例如,如果 XDA 物理讀與 XDA 邏輯讀的比率較低,則表明 XML 數據的緩沖池命中率較高,這正符合期望。要了解關於緩沖池快照監視器元素的更多詳細信息,請參閱 DB2 文檔。
清單 2. 數據、索引和 XDA 存儲對象的監視器輸出
Buffer pool data logical reads = 221759
Buffer pool data physical reads = 48580
Buffer pool temporary data logical reads = 10730
Buffer pool temporary data physical reads = 0
Buffer pool data writes = 6
Asynchronous pool data page reads = 0
Asynchronous pool data page writes = 6
Buffer pool index logical reads = 8340915
Buffer pool index physical reads = 54517
Buffer pool temporary index logical reads = 0
Buffer pool temporary index physical reads = 0
Buffer pool index writes = 0
Asynchronous pool index page reads = 0
Asynchronous pool index page writes = 0
Buffer pool xda logical reads = 2533633
Buffer pool xda physical reads = 189056
Buffer pool temporary xda logical reads = 374243
Buffer pool temporary xda physical reads = 0
Buffer pool xda writes
= 0
Asynchronous pool xda page reads = 97728
Asynchronous pool xda page writes = 0
Asynchronous data read requests = 0
Asynchronous index read requests = 0
Asynchronous xda read requests = 83528
總而言之, 快照監視器中新的 XDA 計數器反映了 XML 活動。它們對於了解 XML 數據的緩沖池、I/O 和臨時空間的使用情況很有用。
提示 6:了解 XML 模式驗證的開銷
XML 模式可以定義一組 XML 文檔所允許的結構、元素和屬性、它們的數據類型、取值范圍等。DB2 允許(可選地)根據 XML 模式驗證 XML 文檔。如果選擇驗證文檔,那麼通常是在插入時進行驗證。這是為了達到兩個目的。首先,通過驗證可以確保插入到數據庫中的數據符合模式定義,也就是說可以防止將 “垃圾數據(junk data)” 輸入到表中。其次,模式驗證將來自模式的類型注釋添加到每個 XML 元素和屬性,這些類型在 DB2 XML 存儲中得到持久化。例如,如果一個 XML 模式定義 dept 表中的雇員 ID(如 提示 1 所示)為整數,並根據該模式對文檔進行驗證,那麼 DB2 會記得,在每個文檔中雇員 ID 具有類型 xs:integer。在查詢運行時,嘗試在雇員 ID 上執行字符串比較將遭到失敗,並產生一個類型錯誤。
在 XML 解析期間,XML 模式驗證是一個可選的活動。性能研究表明,如果啟用模式驗證,那麼 XML 解析通常要更密集地使用 CPU。根據 XML 文檔的結構和大小,尤其是根據使用的 XML 模式的大小和復雜性的不同,這一開銷相差很大。例如,您可以發現,由於采用中等復雜程度的模式進行模式驗證,對 CPU 的消耗增加了 50%。除非 XML 插入中 I/O 占極大一部分,否則增加的 CPU 消耗通常會導致插入的吞吐量下降。
判斷應用程序是否需要更嚴格地對 XML 查詢進行類型檢查,以及檢查 XML 模式的遵從性。例如,如果在將 XML 文檔存儲到數據庫之前,使用一個應用服務器來接收、驗證和處理 XML 文檔,那麼可能不需要在 DB2 中對文檔進行驗證。此時,您已經知道它們是有效的。在這種情況下,應避免進行模式驗證,以提高插入性能。但是,如果 DB2 數據庫從不信任的地方接收 XML 數據,而又需要在 DB2 上確保模式遵從性,那麼就需要在這方面花費一些額外的 CPU 周期。
總而言之,為提高插入性能,如果沒有必要,應避免在 DB2 中執行模式驗證。
提示 7:在 XPath 表達式中,盡可能使用全限定路徑
假設有一個包含 XML 列的表
create table customer(info XML);
要管理具有以下結構的 “customerinfo” 文檔:
清單 3. 示例 XML 文檔
<customerinfo Cid="1004">
<name>Matt Foreman</name>
<addr country="Canada">
<street>1596 Baseline</street>
<city>Toronto</city>
<state>Ontario</state>
<pcode>M3Z-5H9</pcode>
</addr>
<phone type="work">905-555-4789</phone>
<phone type="home">416-555-3376</phone>
</customerinfo>
如果要檢索客戶的電話號碼或他們所居住的城市,無論使用 XQuery 還是 SQL/XML,都有多種可能的路徑表達式可獲得該數據。通過 /customerinfo/phone 和 //phone 都可以獲得電話號碼。同樣,/customerinfo/addr/city 和 /customerinfo/*/city 都返回城市。為了得到最佳的性能,使用全限定路徑比使用 * 或 // 更可取,因為使用全限定路徑可以使 DB2 直接導航到所需的元素,而忽略文檔中不相關的部分。
換句話說,如果您知道所需的元素位於文檔中的什麼位置,那麼以全限定路徑的形式提供位置信息會比較有幫助。如果請求 //phone 而不是 /customerinfo/phone,那麼就是在請求文檔中任何地方的 phone 元素。這需要 DB2 向下導航到文檔的 "addr" 子樹中,在文檔的任何級別上查找 phone 元素,而這本是可以避免的開銷。
注意,* 和 // 還可能導致不需要的或期望之外的查詢結果。例如,如果有些 “customerinfo” 文檔還包含 “assistant” 信息,就像下面的文檔一樣。那麼路徑 //phone 將返回客戶的電話號碼和助手的電話號碼,而沒有將它們區分開。從查詢結果中無法知道是客戶的電話號碼還是助手的電話號碼,甚至會把助手的電話號碼當作客戶的電話號碼來處理。
清單 4. 文檔中多個層次中的 phone 和 name 元素
<customerinfo Cid="1004">
<name>Matt Foreman</name>
<addr country="Canada">
<street>1596 Baseline</street>
<city>Toronto</city>
<state>Ontario</state>
<pcode>M3Z-5H9</pcode>
</addr>
<phone type="work">905-555-4789</phone>
<phone type="home">416-555-3376</phone>
<assistant>
<name>Peter Smith</name>
<phone type="home">416-555-3426</phone>
</assistant>
</customerinfo>
總而言之,在路徑表達式中避免使用 * 和 //,盡量使用全限定路徑。
提示 8:定義傾斜的 XML 索引,並避免為任何東西都建索引
假設我們的查詢常常根據客戶姓名搜索 “customerinfo” 文檔。客戶姓名元素上的索引可以大大提高那些查詢的性能。讓我們來看看下面的例子:
清單 5. 利用索引為根據客戶姓名搜索文檔提供支持
create table customer(info XML);
create index custname1 on customer(info)
generate key using XMLpattern '/customerinfo/name' as sql varchar(20);
create index custname2 on customer(info)
generate key using XMLpattern '//name' as sql varchar(20);
select * from customer
where XMLexists('$i/customerinfo[name = "Matt Foreman"]' passing info as $i);
上面定義的兩個索引都適合用於客戶姓名上的 XMLEXISTS 謂詞的計算。但是實際上,索引 custname2 比索引 custname1 更大一些,因為它不僅包含客戶姓名的索引條目,而且包括助手姓名的索引條目。只是因為 XML 模式 //name 與文檔中任何地方的 name 元素相匹配。但是,如果我們永遠不需要根據助手姓名來進行搜索,那麼就不需要為它們編索引。
對於讀操作,索引 custname1 更小一些,因此可能帶來更好的性能。對於插入、更新和刪除操作,索引 custname1 只會引起用於客戶姓名的維護開銷,而索引 custname2 則需要用於客戶和助手姓名的索引維護。如果想得到最佳的插入/更新/刪除性能,並且不需要根據助手姓名進行基於索引的訪問,那麼當然不想花費額外的代價。
另外,請考慮下面的 heavyIndex 索引,它 “為任何東西編索引”。它包含每個文本節點(即 XML 列中的每個 XML 文檔中的每個葉子元素值)的索引條目。在插入/更新/刪除操作期間,那樣的索引維護起來非常消耗成本,因而通常不推薦使用這樣的索引。惟一的例外是,具有較少寫活動和不可預測的查詢工作負載的應用程序,這種應用程序難於定義更明確的索引。
create index heavyIndex on customer(info)
generate key using XMLpattern '//text()' as sql varchar(20);
總而言之,在定義 XML 索引時,應該盡可能精確一點,盡量避免使用 * 和 //。
提示 9:將文檔過濾謂詞放入 XMLEXISTS 中,而不是放入 XMLQUERY 中
讓我們來考慮下面的表和數據:
create table customer(info XML);
表 2. customer 表中的三行數據
<customerinfo>
<name>Matt Foreman</name>
<phone>905-555-4789</phone>
</customerinfo><customerinfo>
<name>Peter Jones</name>
<phone>905-123-9065</phone>
</customerinfo><customerinfo>
<name>Mary Poppins</name>
<phone>905-890-0763</phone>
</customerinfo>
對於這個表,假設您想返回電話號碼為 “905-555-4789” 的客戶的姓名。 您可能禁不住想編寫下面這樣的查詢:
select XMLquery('$i/customerinfo[phone = "905-555-4789"]/name' passing info as "i")
from customer;
但是,這個查詢並不是您想要的,原因有好幾個:
它返回下面這樣的結果集,其中的行數與表中的行數一樣多。這是因為 SQL 語句沒有 where 子句,因此不能排除任何行。
<name>Matt Foreman</name>
3 record(s) selected
對於表中與謂詞不匹配的每一行,返回一個包含空的 XML 序列的行。這是因為 XMLQUERY 函數中的 XQuery 表達式每次應用於一行(文檔),並不會從結果集中去掉一行,只是修改它的值。那個 XQuery 產生的值,當謂詞為 true 時為客戶的 name 元素,否則為空的序列。這些空行在語義上是正確的(根據 SQL/XML 標准),如果按這種方式編寫查詢,則必須返回它們。
該查詢的性能並不好。首先,不能使用 /customerinfo/phone 上的索引,因為查詢不允許排除行。其次,返回很多空行使查詢速度不必要地慢了下來。
為了解決性能問題並 得到所需的輸出,應該在 select 子句中使用 XMLQUERY 函數,只提取客戶姓名,將應該排除行的搜索條件轉移到 where 子句的 XMLEXISTS 謂詞中。這將允許使用索引和對行進行過濾,還可以避免空結果行帶來的開銷。像下面這樣編寫查詢:
select XMLquery('$i/customerinfo/name' passing info as "i")
from customer
where XMLexists('$i/customerinfo[phone = "905-555-4789"]' passing info as "i")
<name>Matt Foreman</name>
1 record(s) selected
總而言之,XMLQUERY 函數中的謂詞只應用於每個 XML 值當中,所以它們不會排除任何行。文檔過濾和行過濾謂詞應該放入到 XMLEXISTS 函數中。
提示 10:使用方括號 [ ] 來避免 XMLEXISTS 中的 Boolean 謂詞
一種常見的錯誤是在 XMLEXISTS 函數中不使用方括號來編寫前面的查詢:
select XMLquery('$i/customerinfo/name' passing info as "i")
from customer
where XMLexists('$i/customerinfo/phone = "905-555-4789"' passing info as "i")
這將產生以下結果:
<name>Matt Foreman</name> <name>Peter Jones</name> <name>Mary Poppins</name>
3 record(s) selected
XMLEXISTS 謂詞中的表達式使 XMLEXISTS 總是為 true。因此,沒有行被排除。這是因為,對於一個給定的行,只有當裡面的 XQuery 表達式返回空序列時,XMLEXISTS 謂詞才為 false。然而,如果不使用方括號,XQuery 表達式就是一個總是返回 Boolean 值的 Boolean 表達式,而不會返回空序列。注意,XMLEXISTS 只是檢查一個值的存在,如果存在一個值,即使這個值碰巧為 Boolean 值 “false”,XMLEXISTS 也將返回 true。雖然這並不是您想實現的效果,但是根據 SQL/XML 標准,這的確是正確的行為。
同樣,其影響是不能使用 phone 上的索引,因為沒有行被排除,所以會收到大量不需要的行。而且,在使用兩個或更多謂詞時,不要犯同樣的錯誤,例如在下面這個查詢中:
清單 6. XMLEXISTS 中兩個謂詞的不恰當的使用
select XMLquery('$i/customerinfo/name' passing info as "i")
from customer
where XMLexists('$i/customerinfo[phone = "905-555-4789"] and
$i/customerinfo[name = "Matt Foreman"]'
passing info as "i")
這個查詢使用了方括號,那麼它錯在哪裡呢?XQuery 表達式仍然是一個 Boolean 表達式,因為它的形式是 “exp1 and exp2”。下面是編寫這個查詢的正確方式,這樣編寫查詢可以過濾行,並允許使用索引:
清單 7. 可以過濾行並允許使用索引的正確查詢
select XMLquery('$i/customerinfo/name' passing info as "i")
from customer
where XMLexists('$i/customerinfo[phone = "905-555-4789" and name = "Matt Foreman"]'
passing info as "i")
總而言之,在 XMLEXISTS 中不要使用 Boolean 謂詞。將謂詞放在方括號中,包括任何 “and” 和 “or”。
提示 11:使用 RUNSTATS 收集 XML 數據和索引的統計信息
RUNSTATS 實用程序已經被擴展,現在可以收集關於 XML 數據和 XML 索引的統計信息。DB2 基於成本的優化器使用這些統計信息為 XQuery 和 SQL/XML 查詢生成有效的執行計劃。因此,像對待關系數據那樣,繼續使用 RUNSTATS。如果表包含關系數據和 XML 數據,而您又只想刷新關系數據的統計信息,那麼可以帶新的子句 “EXCLUDING XML COLUMNS” 來執行 RUNSTATS。如果沒有這個子句,缺省的也是可取的行為是總是收集關系數據和 XML 數據的統計信息。
對於關系數據和 XML 數據,可以啟用抽樣(sampling)來減少執行 runstats 的時間。在一個大型的數據集上,10% 的數據(或者更少)的統計信息通常仍然非常具有代表性。無論選擇多大的抽樣百分比,runstats 允許對行(Bernoulli sampling)或頁面(system sampling)進行抽樣。行級的抽樣讀取所有數據頁,但是只考慮每頁上一定百分比的行。而頁級的抽樣則可以顯著減少 I/O,因為它只讀取一定百分比的數據頁。因此,如果表中不僅包含 XML 數據,而且還包含相當數量的關系數據,頁抽樣可以顯著提高性能。但是,如果關系數據值是高度聚集的,那麼行級抽樣可以產生更精確的統計信息。
下面是一些例子。第一個 runstats 命令為表 customer 和它的所有索引收集最全面、最詳細的統計信息,而沒有采用抽樣。如果執行時間允許的話,這樣做是理想的。第二個命令收集同樣的統計信息,但是只收集 10% 的頁面的統計信息。在很多情況下,這樣做可以為優化器提供接近於第一個命令的精確性的統計信息,但是可以更快地返回結果。第三個命令抽取 15% 的行,但是不收集分布統計信息,並對索引也應用了抽樣,這不同於第一個命令和第二個命令。
清單 8. 使用 RUNSTATS 收集統計信息
runstats on table myschema.customer
with distribution on all columns and detailed indexes all;
runstats on table myschema.customer
with distribution on all columns and detailed indexes all tablesample system (10);
runstats on table myschema.customer
on all columns and sample detailed indexes all tablesample bernoulli (15);
總而言之,如果有可用的 XML 統計信息,DB2 可以生成更好的執行計劃。像通常那樣使用 runstats,或者利用抽樣使用 runstats,以減少它的執行時間。
提示 12:如何使用 SQL/XML 發布視圖將關系數據暴露為 XML
SQL/XML 發布函數允許將關系數據轉換成 XML 格式。較好的做法是將 SQL/XML 發布函數藏在一個視圖定義中,使應用程序和其他查詢可以從視圖中選擇構造好的 XML 文檔,而不必與發布函數本身打交道。
清單 9. 隱藏在視圖中的 SQL/XML 發布函數
create table unit( unitID char(8), name char(20), manager varchar(20));
create view UnitVIEw(unitID, name, unitdoc) as
select unitID, name,
XMLELEMENT(NAME "Unit",
XMLELEMENT(NAME "ID", u,unitID),
XMLELEMENT(NAME "UnitName", u.name),
XMLELEMENT(NAME "Mgr", u.manager)
)
from unit u;
注意,我們在視圖定義中包括了一些關系列。這並沒有產生任何物理上的冗余,因為它只是一個視圖,而不是物化的視圖。暴露關系列有助於有效地查詢這個視圖。假設我們需要取一個對應於特定單位的 XML 文檔。下面三個查詢都可以實現這一點,但是第三個查詢要好於前兩個查詢。
在前兩個查詢中,過濾謂詞是在構造的 XML 上表達的。但是,XML 謂詞不能應用於底層關系列或它的索引。因此,這些查詢要求視圖為所有單位構造 XML,然後選出對應於單位 “WWPR” 的 XML。這並非最佳方法。
以下查詢可能取得次優性能:
清單 10. 次優的查詢
select unitdoc
from UnitVIEw
where XMLexists('$i/Unit[ID = "WWPR"]' passing unitdoc as "i");
for $u in db2-fn:XMLcolumn('UNITVIEW.UNITDOC')/UNIT
where $u/ID = "WWPR"
return $u;
第三種方法使用一個關系謂詞來確保只為 “WWPR” 構造 XML 文檔,從而減少了運行時間,對於大型的數據集,這種方法帶來的性能好處尤其顯著。這個查詢可以取得很好的性能:
清單 11. 性能較好的查詢
select unitdoc
from UnitVIEw
where UnitID = "WWPR";
總而言之,將關系列包括在 SQL/XML 發布視圖中,在查詢視圖時,在那些關系列上表達謂詞,而不是在構造的 XML 上表達謂詞。
提示 13:如何使用 XMLTABLE 視圖以關系格式暴露 XML 數據
創建一個視圖以 XML 格式暴露關系數據,這樣做很有用。同樣,您可能也想使用一個視圖將 XML 數據暴露為關系格式。提示 12 中的提醒也適用於這裡的情況,只不過順序要倒過來。讓我們來看看下面的例子,其中使用了 SQL/XML 函數 XMLTABLE 以標量格式返回 XML 文檔中的值:
清單 12. 以標量格式返回的 XML 文檔中的值
create table customer(info XML);
create view myvIEw(CustomerID, Name, Zip, Info) as
SELECT T.*, info
FROM customer, XMLTABLE ('$c/customerinfo' passing info as "c"
COLUMNS
"CID" INTEGER PATH './@Cid',
"Name" VARCHAR(30) PATH './name',
"Zip" CHAR(12) PATH './addr/pcode' ) as T;
注意,我們將 XML 列 info 包括在視圖定義中,以便幫助有效地查詢這個視圖。假設您想基於給定的 ZIP 號碼檢索客戶 ID 和姓名的一個標量列表。下面兩個查詢都可以實現這一點,但是第二個查詢比第一個查詢的性能要好。在第一個查詢中,過濾謂詞是在由 XMLTABLE 函數生成的 CHAR 列 “Zip” 上表達的。但是,關系謂詞不能應用於底層的 XML 列或它的索引。因此,這個查詢要求視圖為所有 客戶生成行,然後根據郵政編碼 “95141” 選擇符合條件的行。這不是最佳方法。第二個查詢使用一個 XML 謂詞來確保只生成對應於 “95141” 的行,從而減少了運行時間,對於大型的數據集,這種方法帶來的性能好處尤其顯著。
清單 13. 包含 XML 謂詞的查詢
-- may perform suboptimal:
select CustomerID, Name
from myvIEw
where Zip = "95141";
-- will perform well:
select CustomerID, Name
from myVIEw
where XMLexists('$i/customerinfo[addr/pcode = "95141"]' passing info as "i");
如果定義視圖所用的基表不僅包含一個 XML 列,還包含有索引的關系列,那麼應該將那些關系列包含在視圖定義中。如果對視圖的查詢包含關系列上的具有高度限制性的謂詞,那麼 DB2 使用關系索引來過濾符合條件的行,得到較小數量的行,然後在返回最終結果集之前,對這個中間結果應用 XMLTABLE 和其他謂詞。
總而言之,在使用 XMLTABLE 視圖將 XML 數據暴露為關系格式時要加以小心。應該盡可能在視圖定義中包含其他的列,以便在那些列上表達過濾謂詞,而不是在 XMLTABLE 列上表達過濾謂詞。
提示 14:對於短小的查詢或 OLTP 應用程序,使用帶參數占位符的 SQL/XML 語句
非常短的數據庫查詢常常執行得很快,以至於編譯和優化它們的時間占總體響應時間的很大比例。因此,只將它們編譯(“准備”)一次,然後在每次執行時只傳遞謂詞的字面值,這樣做很有用。雖然 DB2 9 XQuery 不支持外部參數,但是 SQL/XML 函數 XMLQUERY、XMLTABLE 和 XMLEXISTS 卻支持外部參數。這些函數允許傳遞 SQL 參數占位符,作為嵌入式 XQuery 表達式的一個變量。對於具有短小、重復的查詢的應用程序,建議這樣做。
清單 14. 硬編碼的字面值
for $c in db2-fn:XMLcolumn('CUSTOMER.INFO')/customer
where $c/phone = "905-555-4789"
return $c;
select info
from customer
where XMLexists('$i/customerinfo[phone = "905-555-4789"]'
passing info as "i")
清單 15. 使用參數占位符
select info
from customer
where XMLexists('$i/customerinfo[phone = $p]'
passing info as "i", cast(? as varchar(12)) as "p")
總而言之,短小的查詢和 OLTP 事務就像含參數占位符的預置語句一樣,具有更快的速度。對於 XML,這要求 SQL/XML 將 SQL 型參數傳遞給 XQuery 表達式。
提示 15:避免 XML 插入和檢索期間出現代碼頁轉換
XML 不同於 DB2 中其他類型的數據,因為它可以在內部編碼,也可以在外部編碼。內部編碼意味著 XML 數據的編碼可以源於數據本身。外部編碼意味著編碼源於外部信息。用於與 DB2 交換 XML 數據的應用程序變量的數據類型決定了如何得到編碼。如果應用程序為 XML 使用字符類型的變量,那麼它就要在外部編碼,即在應用程序代碼頁中編碼。如果使用二進制應用程序數據類型,那麼 XML 數據考慮采用內部編碼。內部編碼意味著編碼是由 XML 文檔本身包含的 Unicode Byte-Order mark(BOM)或編碼聲明決定的,例如
<?XML version="1.0" encoding="UTF-8" ?>
從性能的角度來看,我們的目標是盡可能避免代碼頁轉換,因為它們要消耗額外的 CPU 周期。內部編碼的 XML 數據比外部編碼的數據更為可取,因為它可以防止不必要的代碼頁轉換。這意味著,在應用程序中,應該優先選擇二進制數據類型,而不是字符類型。例如,在 CLI 中,當使用 SQLBindParameter() 將參數占位符綁定到輸入數據緩沖區時,應該使用 SQL_C_BINARY 數據緩沖區,而不是 SQL_C_CHAR、SQL_C_DBCHAR 或 SQL_C_WCHAR 數據緩沖區。當從 Java 應用程序中插入 XML 數據時,將 XML 數據讀入為二進制流(setBinaryStream)比字符串(setString)更好。類似地,當 Java 應用程序從 DB2 接收 XML,並將它寫到一個文件的時候,如果 XML 被寫成非二進制數據,就會發生代碼頁轉換。
當從 DB2 中將 XML 數據檢索到應用程序中時,該數據被串行化。串行化是 XML 解析的逆向操作。這個過程將 DB2 的內部 XML 格式(解析後的樹型表示)轉換成應用程序能理解的原始的 XML 格式。在大多數情況下,最好是讓 DB2 執行隱式的串行化。這意味著 SQL/XML 語句只需選擇 XML 類型的值,而 DB2 盡可能高效地將其串行化為應用程序變量,如下面的例子所示:
清單 16. 含隱式串行化的查詢
create table customer(info XML);
select info from customer where...;
select XMLquery('$i/customerinfo/name' passing info as "i")
from customer
where...;
如果應用程序要處理非常大的 XML 文檔,那麼可以將 LOB 定位符用於數據檢索。這就要求顯式地串行化為 LOB 類型,更可取的是 BLOB 類型,因為串行化為字符類型(例如 CLOB)會帶來編碼問題和不必要的代碼頁轉換。顯式串行化使用 XMLSERIALIZE 函數:
select XMLSERIALIZE(info as BLOB(1M)) from customer where...;
總而言之,在應用程序中應該使用二進制數據類型來與 DB2 交換 XML,因為這樣做可以避免不必要的代碼頁轉換。要了解編碼問題,如果有疑問,可以參考 DB2 9 文檔中的詳細指南。
結束語
為了在 DB2 中達到最佳 XML 性能,首先要從使用 DB2 的自治特性(例如自治存儲和自調優內存管理)開始。這樣可以為很多應用程序提供很好的、開箱即用的性能。這樣做還為 DBA 節省了寶貴的時間,讓他們可以將精力放在更有針對性的性能調優上。所有傳統的 DB2 性能方面的知識仍然適用於 XML,下面列出的各種 developerWorks 文章都對它們做了論述。
在此基礎上,本文中的 15 個提示可以在一些常見的與 XML 有關的性能方面為您提供幫助。如果需要改善 XML 應用程序的性能,不必應用所有這 15 個提示,其中往往只有一兩個提示是與您的情況相關的。例如,如果由於表空間配置有問題,系統在 I/O 方面存在瓶頸,那麼減少不必要的代碼頁轉換仍然無濟於事。類似地,如果實際上是需要執行 runstats 來支持更好的查詢執行計劃,那麼使用 SQL/XML 參數占位符可能沒有幫助。簡而言之,本文中的提示可以幫助您避免 性能問題,但是要修復觀察到的 性能問題,首先需要找出問題的根源和瓶頸。和對待關系數據一樣,可以使用 DB2 中的標准診斷工具(例如 visual explain、db2exfmt 和快照監視器)來調查 XML 性能。