免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
在 DB2 Universal Database 中提高 INSERT 性能的技巧
本文精妙地解釋了執(zhí)行 insert 操作時(shí)所發(fā)生的事情,考察了 insert 的一些替代方案,并研究了影響 insert 性能的一些問題,例如鎖、索引維護(hù)以及約束管理。

簡(jiǎn)介

在使用 DB2® Universal Database (UDB) 的時(shí)候,行的插入是我們要執(zhí)行的最常見、也是最重要的任務(wù)之一。本文是關(guān)于優(yōu)化 insert 、尤其是插入量比較大的 insert 的一個(gè)技巧匯編。談到性能,往往都存在著某些權(quán)衡,這里也不例外。我們將討論在優(yōu)化 insert 的過程中可能帶來的權(quán)衡問題。例如,您對(duì) insert 采用了某種優(yōu)化技巧,但是這種技巧可能要求在 insert 之后還要進(jìn)行附加的處理,或者可能影響查詢的性能。我會(huì)提供一些性能測(cè)試的結(jié)果,以便讓您了解很多優(yōu)化技巧的作用。 附錄 A包含了對(duì)這些結(jié)果的一個(gè)小結(jié),并且編了號(hào)。我將在全文各處以測(cè)試編號(hào)來引用結(jié)果。在后面的 結(jié)束語一節(jié)中,總結(jié)了大部分有益的技巧,而 附錄 B則列出了本文給出的所有建議。在本文中,我們無意研究關(guān)于如何實(shí)現(xiàn)這些技巧的細(xì)節(jié),但是這方面的信息可以在 DB2 手冊(cè)中找到。請(qǐng)閱讀 參考資料一節(jié),以了解更多細(xì)節(jié)。





回頁首


INSERT 處理過程概述

首先讓我們快速地看看插入一行時(shí)的處理步驟。這些步驟中的每一步都有優(yōu)化的潛力,對(duì)此我們?cè)诤竺鏁?huì)一一討論。

  1. 在客戶機(jī)準(zhǔn)備 語句。對(duì)于動(dòng)態(tài) SQL,在語句執(zhí)行前就要做這一步,此處的性能是很重要的;對(duì)于靜態(tài) SQL,這一步的性能實(shí)際上關(guān)系不大,因?yàn)檎Z句的準(zhǔn)備是事先完成的。
  2. 在客戶機(jī),將要插入的行的各個(gè) 列值組裝起來,發(fā)送到 DB2 服務(wù)器。
  3. DB2 服務(wù)器確定將這一行插入到哪一頁中。
  4. DB2 在 用于該頁的緩沖池中預(yù)留一個(gè)位置。如果 DB2 選定的是一個(gè)已有的頁,那么就需要讀磁盤;如果使用一個(gè)新頁,則要在表空間(如果是 SMS,也就是系統(tǒng)管理存儲(chǔ)的表空間)中為該頁物理地分配空間。插入了新行的每一頁最后都要從緩沖池寫入到磁盤。
  5. 在目標(biāo)頁中對(duì)該行進(jìn)行格式化,并獲得該行上的一個(gè) X(exclusive,獨(dú)占的) 行鎖
  6. 將反映該 insert 的一條 記錄寫入到日志緩沖區(qū)中。
  7. 最后 提交包含該 insert 的事務(wù),如果這時(shí)日志緩沖區(qū)中的記錄還沒有被寫入日志文件的話,則將這些記錄寫到日志文件中。
此外,還可能發(fā)生很多類型的附加處理,這取決于數(shù)據(jù)庫配置,例如,索引或觸發(fā)器的存在。這種額外的處理對(duì)于性能來說也是意義重大的,我們?cè)诤竺鏁?huì)討論到。

 





回頁首


insert 的替代方案

在詳細(xì)討論 insert 的優(yōu)化之前,讓我們先考慮一下 insert 的兩種替代方案:load 和 import。import 實(shí)用程序?qū)嶋H上是 SQL INSERT 的一個(gè)前端,但它的某些功能對(duì)于您來說也是有用的。load 也有一些有用的額外功能,但是我們使用 load 而不使用 insert 的主要原因是可以提高性能。這里我們不會(huì)進(jìn)一步討論 import,不過讀者可以參閱后面的 參考資料一節(jié),在那里可以找到指向 Data Movement Utilities Guide 的鏈接,該指南討論了 import 與 load 之間的不同之處。

load 直接格式化數(shù)據(jù)頁,而避免了由于插入導(dǎo)致的對(duì)每一行進(jìn)行處理的大部分開銷(例如,日志記錄在這里實(shí)際上是消除了)。而且,load 可以更好地利用多處理器機(jī)器上的并行性。在 V8 load 中有兩個(gè)新功能,它們對(duì)于 load 成為 insert 的替代方案有著特別的功效,這兩個(gè)功能是:從游標(biāo)裝載和從調(diào)用層接口(CLI)應(yīng)用程序裝載。

從游標(biāo)裝載


這種方法可用于應(yīng)用程序的程序代碼(通過 db2Load API),或用于 DB2 腳本。下面是后一種情況的一個(gè)例子:
declare staffcursor cursor forselect * from staff;
load from staffcursor of cursor insert into myschema.new_staff;

這兩行可以用下面一行替代:
insert into myschema.new_staff select * from staff
附錄 A中的 test 6 和 9 表明,同等效的 INSERT ... SELECT 語句相比,從游標(biāo)裝載幾乎可以提高 20% 的性能。

從 CLI 裝載


這種方法顯然只限于調(diào)用層接口(CLI)應(yīng)用程序,但是它非???。這種技巧非常類似于數(shù)組插入( 后面會(huì)討論),DB2 附帶了這樣的示例,請(qǐng)查看 sqllib/samples/cli/tbload.c。通過查看附錄 A 中的 test 79我們可以看到,使用 load 時(shí)的速度是使用經(jīng)過完全優(yōu)化的數(shù)組插入(test 71)時(shí)的兩倍,幾乎要比未經(jīng)優(yōu)化的數(shù)組插入(比如 test 69)快 10 倍。





回頁首


所有 insert 可以改進(jìn)的地方

讓我們看看插入處理的一些必要步驟,以及我們可以用來優(yōu)化這些步驟的技巧。

1. 語句準(zhǔn)備


作為一條 SQL 語句,INSERT 語句在執(zhí)行之前必須由 DB2 進(jìn)行編譯。這一步驟可以自動(dòng)發(fā)生(例如在 CLP 中,或者在一次 CLI SQLExecDirect 調(diào)用中),也可以顯式地進(jìn)行(例如,通過一條 SQL Prepare、CLI SQLPrepare 或 JDBC prepareStatement 語句)。該編譯過程牽涉到授權(quán)檢查、優(yōu)化,以及將語句轉(zhuǎn)化為可執(zhí)行格式時(shí)所需的其他一些活動(dòng)。在編譯語句時(shí),語句的訪問計(jì)劃被存儲(chǔ)在包緩存中。

如果重復(fù)地執(zhí)行相同的 INSERT 語句,則該語句的訪問計(jì)劃(通常)會(huì)進(jìn)入到包緩存中,這樣就免除了編譯的開銷。然而,如果 insert 語句對(duì)于每一行有不同的值,那么每一條語句都將被看成是惟一的,必須單獨(dú)地進(jìn)行編譯。因此,將像下面這樣的重復(fù)語句:
insert into mytable values (1, 'abc')
insert into mytable values (2, 'def')

等等,
換成帶有參數(shù)標(biāo)記的語句,一次準(zhǔn)備,重復(fù)執(zhí)行,這樣做是十分可取的:
insert into mytable values (?, ?)

test 1test 2相比, 61-6465-68相比,我們可以看到,使用參數(shù)標(biāo)記可以讓一系列的 insert 的運(yùn)行速度提高數(shù)倍。(在靜態(tài) SQL 程序中使用主機(jī)變量也可以獲得類似的好處。)

2. 發(fā)送列值到服務(wù)器


可以歸為這一類的優(yōu)化技巧有好幾種。最重要的一種技巧是在每條 insert 語句中包括多行,這樣就可以避免對(duì)于每一行都進(jìn)行客戶機(jī)-服務(wù)器通信,同時(shí)也減少了 DB2 開銷??捎糜诙嘈胁迦氲募记捎校?ul>

  • 在 VALUES 子句中包含多行的內(nèi)容。例如,下面的語句將插入三行:INSERT INTO mytable VALUES (1, 'abc'), (2, 'def'), (3, 'ghi')
  • 在 CLI 中使用數(shù)組插入(array insert)。這需要準(zhǔn)備一條帶參數(shù)標(biāo)記的 INSERT 語句,定義一個(gè)用于存儲(chǔ)要插入的值的數(shù)組,將該數(shù)組綁定到參數(shù)標(biāo)記,以及對(duì)于每個(gè)數(shù)組中的一組內(nèi)容執(zhí)行一次 insert。請(qǐng)參閱 CLI Guide and Reference以了解詳細(xì)信息。而且,示例程序 sqllib/samples/cli/tbload.c 提供了數(shù)組插入的基本框架(但是執(zhí)行的是 CLI LOAD)。通過比較 test 68(使用了 10x 行)、71、73、74 和 78 各自所耗的時(shí)間,我們可以看到,從不使用數(shù)組改為使用包含 100 行的數(shù)組,可以將時(shí)間縮短大約 2.5 倍。所以應(yīng)該盡可能地使用包含至少 100 行的數(shù)組。
  • 在 JDBC 中使用批處理操作。這跟 CLI 中的數(shù)組插入一樣,基于相同的概念,但是實(shí)現(xiàn)細(xì)節(jié)有所不同。當(dāng)通過 prepareStatement 方法準(zhǔn)備了 insert 語句之后,剩下的步驟是針對(duì)每一列調(diào)用適當(dāng)?shù)?setXXXX 方法(例如,setString 或 setInt),然后是 addBatch。對(duì)于要插入的每一行,都要重復(fù)這些步驟,然后調(diào)用 executeBatch 來執(zhí)行插入。要查看這方面的例子,請(qǐng)參閱“參考資料”一節(jié)中的 JDBC Tutorial。
  • 使用 load 將數(shù)據(jù)快速地裝入到一個(gè) staging 表中,然后使用 INSERT ... SELECT 填充主表。(請(qǐng)參閱后面的“ 通過 staging 表和其他方法增加并行性”小節(jié)。)通過這種方法節(jié)省下來的代價(jià)源于 load 的速度非??欤偌由?INSERT ... SELECT 是在 DB2 內(nèi)(在服務(wù)器上)傳輸數(shù)據(jù)的,從而消除了通信上的代價(jià)。一般情況下我們不會(huì)使用這種方法,除非在 INSERT ... SELECT 中還要另外做 load 無法完成的處理。
  •  

    如果不可能在一條 insert 語句中傳遞多行,那么最好是將多條 insert 語句組成一組,將它們一起從客戶機(jī)傳遞到服務(wù)器。(不過,這意味著每條 insert 都包含不同的值,都需要準(zhǔn)備,因而其性能實(shí)際上要比使用參數(shù)標(biāo)記情況下的性能更差一些,前面“語句準(zhǔn)備”一節(jié)已對(duì)此作了討論。)將多條語句組合成一條語句可以通過 Compound SQL 來實(shí)現(xiàn):

    • 在 SQL 中,復(fù)合語句是通過 BEGIN ATOMIC 或 BEGIN COMPOUND 語句創(chuàng)建的。
    • 在 CLI 中,復(fù)合語句可以通過 SQLExecDirect 和 SQLExecute 調(diào)用來建立。請(qǐng)參閱 CLI Guide and Reference Volume 1 以了解詳細(xì)信息。對(duì)于 DB2 V8 FixPak 4,另一種生成復(fù)合語句的方法是在(對(duì)一條預(yù)處理語句)發(fā)出多個(gè) SQLExecute 調(diào)用之前設(shè)置語句屬性 SQL_ATTR_CHAINING_BEGIN,并在調(diào)用之后設(shè)置語句屬性 SQL_ATTR_CHAINING_END。

     

    下面是關(guān)于該話題的其他一些建議:

    • 如果可能的話,讓客戶機(jī)與要存取的數(shù)據(jù)庫使用相同的代碼頁,以避免在服務(wù)器上的轉(zhuǎn)換代價(jià)。數(shù)據(jù)庫的代碼頁可以通過運(yùn)行“get db cfg for <database>”來確定。
    • 在某些情況下,CLI 會(huì)自動(dòng)執(zhí)行數(shù)據(jù)類型轉(zhuǎn)換,但是這樣同時(shí)也會(huì)帶來看不見的(小小的)性能損耗。因此,盡量使插入值直接處于與相應(yīng)列對(duì)應(yīng)的格式。
    • 將應(yīng)用程序中與插入相關(guān)的設(shè)置開銷最小化。例如,當(dāng)在 CLI 中使用數(shù)組插入時(shí),對(duì)于整個(gè)一組插入,應(yīng)該盡量保證對(duì)于每一列只執(zhí)行一次 SQLBindParameter,而不是對(duì)每一組數(shù)組內(nèi)容都執(zhí)行一次。對(duì)于個(gè)體來說,這些調(diào)用的代價(jià)并不高,但是這些代價(jià)是累積的。

     

    3. 找到存儲(chǔ)行的地方


    DB2 使用三種算法中的一種來確定將行插入到哪里。(如果使用了多維群集(Multi-dimensional Clustering,MDC),則另當(dāng)別論,我們?cè)谶@里不予討論。)要了解關(guān)于插入算法方面的細(xì)節(jié),請(qǐng)參閱 DB2 V8 Administration Guide: Performance中提到的相關(guān)內(nèi)容。

    缺省模式是,DB2 搜索散布在表的各頁上的自由空間控制記錄(Free Space Control Records,F(xiàn)SCR),以找到有足夠自由空間存放新行的頁。顯然,如果每頁上的自由空間都比較少的話,就要浪費(fèi)很多的搜索時(shí)間。為了應(yīng)付這一點(diǎn),DB2 提供了 DB2MAXFSCRSEARCH 注冊(cè)表變量,以便允許將搜索范圍限制為少于缺省的 5 頁。

    當(dāng)表是通過 ALTER TABLE 以 APPEND 模式放置時(shí),就要使用第二種算法。這樣就完全避免了 FSCR 搜索,因?yàn)橹恍韬?jiǎn)單地將行直接放到表的末尾。

    當(dāng)表有群集索引(clustering index)時(shí),就要用到最后一種算法。在這種情況下,DB2 試圖將每一行插入到有相似鍵值的一頁中。如果那一頁沒有空間了,DB2 就會(huì)嘗試附近的頁,如果附近的頁也沒有空間,DB2 就進(jìn)行 FSCR 搜索。

    如果只考慮插入時(shí)間的優(yōu)化,那么使用 APPEND 模式對(duì)于批量插入是最快的一種方法,但是這種方法的效果遠(yuǎn)不如我們這里討論的很多其他方法那么成效顯著。請(qǐng)參考 test 22 和 test 23。第二好的方法應(yīng)該是采用缺省算法,但是,如果在最佳環(huán)境中,更改 DB2MAXFSCRSEARCH 的值影響很小,而在一個(gè) I/O 約束較少的環(huán)境中,這種更改所造成的影響就比較可觀了。

    如果有群集索引,則對(duì) insert 的性能會(huì)有很大的負(fù)面影響(test 32 和 38 表明幾乎有 20% 的開銷),這一點(diǎn)也不驚奇,因?yàn)槭褂萌杭饕哪康木褪峭ㄟ^在插入時(shí)做額外的工作來提高查詢(即 select)性能的。如果的確需要群集索引,那么可以通過確保有足夠的自由空間來使其對(duì)插入的影響降至最小:使用 ALTER TABLE 增加 PCTFREE,然后使用 REORG 預(yù)留自由空間。不過,如果允許太多自由空間的存在,則可能導(dǎo)致查詢時(shí)需要讀取額外的頁,這反而大大違反了使用群集索引的本意。另一種選擇是,在批量插入之前先刪除群集索引,而后再重新創(chuàng)建群集索引,也許這是最優(yōu)的方法(創(chuàng)建群集索引的開銷跟創(chuàng)建常規(guī)索引的開銷差不多,都不是很大,只是在插入時(shí)有額外的開銷)。

    4. 緩沖池、I/O 和頁清除


    每一條 insert 在執(zhí)行時(shí),都是先將新行存儲(chǔ)在一個(gè)頁中,并最終將那個(gè)頁寫到磁盤上。一旦像前面討論的那樣指定了頁,那么在將行添加到該頁之前,該頁必須已經(jīng)在緩沖池中。對(duì)于批量插入,大部分頁都是最新指派給表的,因此讓我們關(guān)注一下對(duì)新頁的處理。

    如果表在系統(tǒng)管理存儲(chǔ)的(System Managed Storage,SMS)表空間中,當(dāng)需要新頁時(shí),缺省情況下是從文件系統(tǒng)中分別為每一頁分配空間。但是,如果對(duì)數(shù)據(jù)庫運(yùn)行了 db2empfa 命令,那么每個(gè) SMS 表空間就會(huì)為新頁一次性分配一個(gè)區(qū)段。test 11 和 test 82 表明,與區(qū)段大小為缺省的 32 頁的情況相比,運(yùn)行 db2empfa 命令可以使對(duì) SMS 表空間的插入快到兩倍,因?yàn)樵?test 82 中對(duì)于一個(gè)頁有 32 次分配,而 test 11 中是 32 個(gè)頁一次分配。test 11 和 test 83-85 表明,如果區(qū)段大小小于 32 頁,則性能會(huì)逐步下降,因?yàn)橐M(jìn)行額外的分配,但是讓區(qū)段大于 32 這樣的建議也未必有幫助。我們建議運(yùn)行 db2empfa 命令,并使用 32 頁的區(qū)段。

    對(duì)于數(shù)據(jù)庫管理的存儲(chǔ)(Database Managed Storage,DMS)表空間,空間是在創(chuàng)建表空間時(shí)就預(yù)先分配的,但是頁的區(qū)段則是在插入處理過程中指派給表的。與 SMS 相比,DMS 對(duì)空間的預(yù)分配可以提高大約 20% 的性能 -- 請(qǐng)參考 test 11 和 test 81。test 81 使用 DMS 文件表空間,而如果使用了 DMS raw 表空間的話,還可以多得到一點(diǎn)好處。使用 DMS 時(shí),更改區(qū)段大小并沒有明顯的效果。

    如果表上有索引,則對(duì)于每個(gè)插入的行,都要添加一個(gè)條目到每條索引。這要求在緩沖池中存在適當(dāng)?shù)乃饕?。晚些時(shí)候我們將討論索引的維護(hù),但是現(xiàn)在只需記住,插入時(shí)對(duì)緩沖池和 I/O 的考慮也類似地適用于索引頁,對(duì)于數(shù)據(jù)頁也是一樣。

    隨著插入的進(jìn)行,越來越多的頁中將填入被插入的行,但是,DB2 不要求在 insert 或 Commit 后將任何新插入的或更新后的數(shù)據(jù)或索引寫入到磁盤。(這是由于 DB2 的 writeahead 日志記錄算法。但是有一個(gè)例外,這將在關(guān)于日志記錄的小節(jié)中論述到。)然而,這些頁需要在某一時(shí)刻寫到磁盤上,這個(gè)時(shí)刻可能會(huì)在數(shù)據(jù)庫關(guān)閉時(shí)才會(huì)輪到。

    一般來說,對(duì)于批量插入,您會(huì)希望積極地進(jìn)行 異步頁清除(asynchronous page cleaning),這樣在緩沖池中就總有可用于新頁的空余位置。頁清除率,或者說總?cè)表撀剩赡軐?dǎo)致計(jì)時(shí)上的很大不同,使得性能比較容易產(chǎn)生誤解。例如,如果使用 100,000 頁的緩沖池,并且不存在頁清除,則批量插入在結(jié)束前不會(huì)有任何新的或更改過的(“臟的”)頁寫到磁盤上,但是隨后的操作(例如選擇,甚至乎關(guān)閉數(shù)據(jù)庫)都將被大大推遲,因?yàn)檫@時(shí)有至多 100,000 個(gè)在插入時(shí)產(chǎn)生的臟頁要寫到磁盤上。另一方面,如果在同一情況下進(jìn)行了積極的頁清除,則批量插入過程可能要花更長(zhǎng)的時(shí)間,但是此后緩沖池中的臟頁要少一些,從而使得隨后的任務(wù)執(zhí)行起來性能更佳。至于那些結(jié)果中到底哪個(gè)要更好些,我們并不是總能分得清,但是通常來說,將所有臟頁都存儲(chǔ)在緩沖池中是不可能的,所以為了取得最佳性能,采取有效的頁清除是有必要的。

    為了盡可能好地進(jìn)行頁清除:

    • 將 CHNGPGS_THRESH 數(shù)據(jù)庫配置參數(shù)的值從缺省的 60 減少到 5 這么低。這個(gè)參數(shù)決定緩沖池中臟頁的閾值百分比,當(dāng)臟頁達(dá)到這個(gè)百分比時(shí),就會(huì)啟動(dòng)頁清除。
    • 嘗試啟用注冊(cè)表變量 DB2_USE_ALTERNATE_PAGE_CLEANING(在 DB2 V8 FixPak 4 中最新提供)。通過將這個(gè)變量設(shè)置成 ON,可以為頁清除提供一種比缺省方法(基于 CHNGPGS_THRESH 和 LSN 間隙觸發(fā)器)更積極的方法。我沒有評(píng)測(cè)過其效果。請(qǐng)參閱 FixPak 4 Release Notes 以了解這方面的信息。
    • 確保 NUM_IOCLEANERS 數(shù)據(jù)庫配置參數(shù)的值至少等于數(shù)據(jù)庫中物理存儲(chǔ)設(shè)備的數(shù)量。

     

    至于 I/O 本身,當(dāng)需要建立索引時(shí),可以通過使用盡可能大的緩沖池來將 I/O 活動(dòng)減至最少。(請(qǐng)參閱后面的“ 索引維護(hù)”一節(jié)。)如果不存在索引,則使用較大的緩沖池幫助不大,而只是推遲了 I/O。也就是說,它允許所有新頁暫時(shí)安放在緩沖池中,但是最終仍需要將這些頁寫到磁盤上。

    當(dāng)發(fā)生將頁寫到磁盤的 I/O 時(shí),通過一些常規(guī)的 I/O 調(diào)優(yōu)步驟可以加快這一過程,例如:

    • 將表空間分布在多個(gè)容器(這些容器映射到不同磁盤)。
    • 盡可能使用最快的硬件和存儲(chǔ)管理配置,這包括磁盤和通道速度、寫緩存以及并行寫等因素。
    • 避免 RAID5(除非是與像 Shark 這樣有效的存儲(chǔ)設(shè)備一起使用)。

     

    5. 鎖


    缺省情況下,每一個(gè)插入的行之上都有一個(gè) X 鎖,這個(gè)鎖是在該行創(chuàng)建時(shí)就開始有的,一直到 insert 被提交。有兩個(gè)跟 insert 和鎖相關(guān)的性能問題:

    • 為獲得和釋放鎖而產(chǎn)生的 CPU 開銷。
    • 可能由于鎖沖突而導(dǎo)致的并發(fā)問題。

     

    對(duì)于經(jīng)過良好優(yōu)化的批量插入,由獲得每一行之上的一個(gè) X 鎖以及后來釋放該鎖引起的 CPU 開銷是比較可觀的。對(duì)于每個(gè)新行之上的鎖,惟一可以替代的是表鎖(DB2 中沒有頁鎖)。test 11 和 test 101 表明,當(dāng)使用表鎖時(shí),耗時(shí)減少了 3%。有 3 種情況可以導(dǎo)致表鎖的使用,在討論表鎖的缺點(diǎn)之前,我們先用一點(diǎn)時(shí)間看看這 3 種情況:

    • 運(yùn)行 ALTER TABLE <name> LOCKSIZE TABLE。這將導(dǎo)致 DB2 為隨后使用該表的所有 SQL 語句使用一個(gè)表鎖,直到 locksize 參數(shù)改回到 ROW。
    • 運(yùn)行 LOCK TABLE <name> IN EXCLUSIVE MODE。這將導(dǎo)致表上立即上了一個(gè) X 鎖。注意,在下一次提交(或回滾)的時(shí)候,這個(gè)表將被釋放,因此,如果您要運(yùn)行一個(gè)測(cè)試,測(cè)試中每 N 行提交一次,那么就需要在每次提交之后重復(fù)執(zhí)行 LOCK TABLE。
    • 使用缺省鎖,但是讓 LOCKLIST 和 MAXLOCKS 數(shù)據(jù)庫配置參數(shù)的值比較小。當(dāng)獲得少量的行鎖時(shí),行鎖就會(huì)自動(dòng)地逐漸升級(jí)為表鎖。

     

    當(dāng)然,所有這些的缺點(diǎn)就在于并發(fā)的影響:如果表上有一個(gè) X 鎖,那么其他應(yīng)用程序除非使用了隔離級(jí)別 UR(未提交的讀),否則都不能訪問該表。如果知道獨(dú)占訪問不會(huì)導(dǎo)致問題,那么就應(yīng)該盡量使用表鎖。但是,即使您堅(jiān)持使用行鎖,也應(yīng)記住,在批量插入期間,表中可能存在數(shù)千個(gè)有 X 鎖的新行,所以就可能與其他使用該表的應(yīng)用程序產(chǎn)生沖突。通過一些方法可以將這些沖突減至最少:

    • 確保鎖的升級(jí)不會(huì)無故發(fā)生。您可能需要加大 LOCKLIST 和/或 MAXLOCKS 的值,以允許插入應(yīng)用程序有足夠的鎖。
    • 對(duì)于其他的應(yīng)用程序,使用隔離級(jí)別 UR。
    • 對(duì)于 V8 FixPak 4,或許也可以通過 DB2_EVALUNCOMMITTED 注冊(cè)表變量來減少鎖沖突:如果將該變量設(shè)置為 YES,那么在很多情況下,只能獲得那些符合某個(gè)謂詞的行上的鎖,而并不是獲得被檢查的所有行上的鎖。
    • 發(fā)出一個(gè) COMMIT 命令以釋放鎖,因此如果更頻繁地提交的話就足以減輕鎖沖突的負(fù)擔(dān)。

     

    注意

    • 在 V7 中,存在涉及 insert 和鍵鎖的并發(fā)問題,但是在 V8 中,由于提供了 type-2 索引,這些問題實(shí)際上已經(jīng)不見了。如果要遷移到 V8 中來,那么應(yīng)該確保使用帶 CONVERT 關(guān)鍵字的 REORG INDEXES 命令,以便將索引從 type-1 轉(zhuǎn)換為 type-2。
    • 在 V7 中,插入過程中可能使用 W 或 NW 鎖,但是在 V8 中只有在使用了 type-1 索引或者隔離級(jí)別為 RR 的情況下才會(huì)出現(xiàn)這兩種鎖。因此,應(yīng)盡可能避免這兩種情況。
    • 一條 insert 所據(jù)有的鎖(通常是一個(gè) X 鎖)通常不會(huì)受隔離級(jí)別的影響。例如,使用隔離級(jí)別 UR 不會(huì)阻止從插入的行上獲得鎖。然而,如果使用了 INSERT ... SELECT,則隔離級(jí)別將影響從 SELECT 獲得的鎖。

     

    6. 日志記錄


    缺省情況下,每條 insert 都會(huì)被記錄下來,以用于恢復(fù)。日志記錄首先被寫到內(nèi)存中的日志緩沖池,然后再寫到日志文件,通常是在日志緩沖池已滿或者發(fā)生了一次提交時(shí)寫到日志文件的。對(duì)批量插入的日志記錄的優(yōu)化實(shí)際上就是最小化日志記錄寫的次數(shù),以及使寫的速度盡可能快。

    這里首先考慮的是日志緩沖池的大小,這由數(shù)據(jù)庫配置參數(shù) LOGBUFSZ 來控制。該參數(shù)缺省值為 8 頁或 32 K,這與大多數(shù)批量插入所需的理想日志緩沖池大小相比要小些。舉個(gè)例子,對(duì)于一個(gè)批量插入,假設(shè)對(duì)于每一行的日志內(nèi)容有 200 字節(jié),則在插入了 160 行之后,日志緩沖池就將被填滿。如果要插入 1000 行,因?yàn)槿罩揪彌_池將被填滿幾次,再加上提交,所以大概有 6 次日志寫。如果將 LOGBUFSZ 的值增加到 64 頁(256K)或者更大,緩沖池就不會(huì)被填滿,這樣的話對(duì)于該批量插入就只有一次日志寫(在提交時(shí))。test 104 和 test 105 表明,通過使用更大的 LOGBUFSZ 可以獲得大約 13% 的性能提升。較大日志緩沖池的不利之處是,緊急事故恢復(fù)所花的時(shí)間可能要稍微長(zhǎng)一點(diǎn)。

    減少日志寫的另一種可能性是對(duì)新行要插入到的那個(gè)表使用“ALTER TABLE <name> ACTIVATE NOT LOGGED INITIALLY”(NLI)。如果這樣做了,那么在該工作單元內(nèi)不會(huì)記錄任何 insert 操作,但是這里存在兩個(gè)與 NLI 有關(guān)的重要問題:

    • 如果有一條語句失敗,那么這個(gè)表將被標(biāo)記為不可訪問的,并且需要被刪除掉。這與其他恢復(fù)問題(請(qǐng)參閱 SQL Reference 關(guān)于 Create Table 的討論)一起使得 NLI 在很多情況下不能成為可行的方法。
    • 在工作單元最后進(jìn)行的提交,必須等到在此工作單元內(nèi)涉及的所有臟頁都被寫到磁盤之后才能完成。這意味著這種提交要占用大量的時(shí)間。實(shí)際上, test 6 和 7已表明,如果沒有積極地進(jìn)行頁清除,那么在使用 NLI 的情況下,Insert 加上提交所耗費(fèi)的總時(shí)間要更長(zhǎng)一些。不過,test 8 表明,將 NLI 與積極的頁清除一起使用的時(shí)候,可以大大減少耗時(shí)。如果使用 NLI,就要瞪大眼睛盯緊提交操作所耗費(fèi)的時(shí)間。

     

    至于提高日志寫的速度,有下面一些可能性:

    • 將日志與新行所要插入到的表分別放在不同的磁盤上。
    • 在操作系統(tǒng)層將日志分放到多個(gè)磁盤。
    • 考慮為日志使用原始設(shè)備(raw device),但是要注意,這樣管理起來要更困難些。
    • 避免使用 RAID 5,因?yàn)樗贿m合于寫密集型(write-intensive)活動(dòng)。

     

    7. 提交


    提交迫使將日志記錄寫到磁盤上,以保證提交的插入肯定會(huì)存在于數(shù)據(jù)庫中,并且釋放新行上的鎖。這些都是有價(jià)值的活動(dòng),但是因?yàn)?Commit 總是要牽涉到同步 I/O(對(duì)于日志),而 insert 則不會(huì),所以 Commit 的開銷很容易高于 insert 的開銷。因此,在進(jìn)行批量插入時(shí),每一行都提交一次的做法對(duì)于性能來說是很糟糕的,所以應(yīng)確保不使用自動(dòng)提交(對(duì)于 CLI 和 CLP 來說缺省情況正是如此)。建議大約每 1000 行提交一次:test 61-78 表明,當(dāng)每 1000 行而不是一兩行提交一次時(shí),性能可以提高大概 10 倍。不過,一次提交多于 1000 行只能節(jié)省少量的時(shí)間,但是一旦出現(xiàn)失敗,恢復(fù)起來所花的時(shí)間要更多。

    對(duì)上述方法的一種修正:如果 MINCOMMIT 數(shù)據(jù)庫配置參數(shù)的值大于 1 (缺省值),則 DB2 就不必對(duì)每次 commit 都進(jìn)行一次同步 I/O,而是等待,并試圖與一組事件一起共享日志 I/O。對(duì)于某些環(huán)境來講,這樣做是有好處,但是對(duì)于批量插入常常沒有作用,甚至有負(fù)作用,因此,如果要執(zhí)行的關(guān)鍵任務(wù)是批量插入,就應(yīng)該讓 MINCOMMIT 的值保持為 1。





    回頁首


    可以選擇性地進(jìn)行改進(jìn)的地方

    對(duì)于一次 insert,有幾種類型的處理將自動(dòng)發(fā)生。如果您的主要目標(biāo)只是減少插入時(shí)間,那么最簡(jiǎn)單的方法是避免所有這些處理的開銷,但是如果從總體上考慮的話,這樣做未必值得。讓我們依次進(jìn)行討論。

    索引維護(hù)


    對(duì)于插入的每一行,必須添加一個(gè)條目到表上的每個(gè)索引中(包括任何主鍵索引)。這一過程主要有兩方面的代價(jià):

    • 遍歷每個(gè)索引樹,在樹的每一層搜索一個(gè)頁,以確定新條目必須存儲(chǔ)在哪里(索引條目總是按鍵順序存儲(chǔ)的),這一過程所引起的 CPU 開銷;
    • 將所有搜索到的頁讀入緩沖池,并最終將每個(gè)更新后的頁寫到磁盤上的 I/O 開銷。

     

    更壞的場(chǎng)景是,在索引維護(hù)期間有大量的隨機(jī) I/O。假設(shè)要插入 10,000 行,在索引的緩沖池中有 5000 頁,并且要插入的各行的鍵值隨機(jī)分布在整個(gè)鍵范圍內(nèi)。那么,有 10,000 個(gè)這么多的葉子頁(可能還有些非葉子頁)需要進(jìn)入緩沖池,以便對(duì)它們進(jìn)行搜索和/或更新,對(duì)于一個(gè)給定的葉子頁,它預(yù)先已經(jīng)在緩沖池中的概率只有 10%。對(duì)于每次的 insert,需要讀磁盤的概率如此之高,使得這種場(chǎng)景往往性能很差。

    對(duì)于逐行插入,將新行添加到已有的索引中比起創(chuàng)建一個(gè)新索引來代價(jià)要高得多。如果是插入到一個(gè)空表,應(yīng)該總是在進(jìn)行了列插入之后創(chuàng)建索引。(注意,如果使用了 load,則應(yīng)該 預(yù)先創(chuàng)建索引。)如果要插入到一個(gè)已經(jīng)填充過的表,那么在列插入之前刪除索引,并在列插入之后重新創(chuàng)建索引,這種方法可能是最快的,但是只有在要插入相當(dāng)多的行 -- 大概大于表的 10-20% 的時(shí)候,才能這么說。如果為索引表空間使用較大的緩沖池,并且盡可能地將不同 insert 排序,以便鍵值是排好序的,而不是隨機(jī)的,就可以幫助加快索引維護(hù)。

    附錄 A 中的 test 31-37給出的結(jié)果表明,insert 的耗時(shí)是如何隨著索引的數(shù)目以及創(chuàng)建索引的時(shí)機(jī)而變化的??偠灾c沒有索引相比,有 4 條索引的情況下 insert 可能要多耗費(fèi)數(shù)倍的時(shí)間,而如果在 insert 之后才創(chuàng)建索引,就可以將總耗時(shí)(insert 耗時(shí)加上創(chuàng)建索引的耗時(shí))縮短 1/4 到幾乎 1/2。

    如果關(guān)鍵目標(biāo)是將 insert 的性能最優(yōu)化,那么增加索引的 PCTFREE 時(shí),就可能減少在隨機(jī)插入索引條目時(shí)出現(xiàn)頁拆分(page split)的次數(shù)。所以這樣做時(shí)要小心,不過,太多的自由空間意味著大量的索引頁,這對(duì)查詢的性能乃至 insert 處理本身都會(huì)產(chǎn)生負(fù)面影響。

    約束驗(yàn)證


    這一類的開銷包括檢查約束驗(yàn)證和外鍵約束(參照完整性(RI))驗(yàn)證。檢查約束開銷很低(請(qǐng)參閱附錄 A 中的 test 11-13),這很大程度上是因?yàn)椴恍枰獙?duì)每一行使用 I/O(因?yàn)橐獧z查的值是在行內(nèi),只是要進(jìn)行一些計(jì)算而已)。

    如果有外鍵,則插入一行就是另外一回事了。對(duì)于每個(gè)外鍵,都必須在父表中進(jìn)行一次查找,以確保有父行存在。雖然這種查找是通過主鍵索引進(jìn)行的,但是這仍然要占用 CPU 循環(huán)來進(jìn)行搜索,而且可能還要占用 I/O 將索引頁讀入緩沖池。test 11、14 和 15 表明,當(dāng)有兩個(gè)外鍵時(shí),insert 的耗時(shí)要翻一番。

    在填充表之后使用 CREATE INDEX 比起通過一條一條的 INSERT 語句建立索引來代價(jià)要小些,同樣,使用 ALTER TABLE 創(chuàng)建外鍵(即進(jìn)行驗(yàn)證)作為批量操作,比起在每次 insert 期間的驗(yàn)證所增加的代價(jià)的總和來,要小一些。比較 test 14 和 test 16、test 15 和 test 17,我們可以看到,在 insert 之后創(chuàng)建外鍵可以將總耗時(shí)減少大約 40%。

    如果可能的話,在大量插入行到一個(gè)表之前,應(yīng)該先使用 ALTER TABLE 刪除表上的所有約束,在插入之后再重新創(chuàng)建這些約束(仍是使用 ALTER TABLE),只有在插入的行不到該表中所有行的 10-20% 時(shí)才可不必這樣做。

    有些應(yīng)用程序本身也會(huì)做一些檢查,以確保表之間的關(guān)系是有效的。也就是說,在插入一個(gè)子行之前,應(yīng)用程序會(huì)讀一個(gè)父行,以確保父行存在。如果這種檢查得以正確執(zhí)行,那么在數(shù)據(jù)庫中定義外鍵約束將增加額外的開銷。但是,至少有三個(gè)原因可以說明為什么在數(shù)據(jù)庫中定義這些約束要更好些:

    1. 沒有檢查的應(yīng)用程序會(huì)更簡(jiǎn)單。
    2. 如果由 DB2 來負(fù)責(zé)檢查,性能要稍微好一些。
    3. 在數(shù)據(jù)庫中定義約束使 DB2 可以知道表之間的關(guān)系,并且在某些情況下允許 DB2 根據(jù)這樣的知識(shí)選擇更好的訪問計(jì)劃。

     

    如果應(yīng)用程序本身的檢查不能少,那么最好的替代方案是在 DB2 中定義外鍵,但是在 CREATE TABLE 或 ALTER TABLE 中帶上 NOT ENFORCED 子句,這樣就避免了檢查開銷,而優(yōu)化器又能使用關(guān)系知識(shí)。

    觸發(fā)器執(zhí)行


    如果在一個(gè)表上定義了一個(gè)或多個(gè)進(jìn)行 Insert 操作的觸發(fā)器,那么每次的 insert 都將引起觸發(fā)器定義中的動(dòng)作的執(zhí)行。由于那些被觸發(fā)的動(dòng)作通常是一條或多條 INSERT、UPDATE 或 DELETE 語句,因此,在數(shù)據(jù)量很大的 insert 中,觸發(fā)器的開銷會(huì)很大。附錄 A 中的 test 18-21表明,增加觸發(fā)器會(huì)導(dǎo)致 insert 的性能減慢數(shù)倍。索引和約束可以臨時(shí)刪除,但是應(yīng)用程序知識(shí)卻必須知道何時(shí)避免觸發(fā)器的執(zhí)行是可接受的。也就是說,知道何時(shí)不會(huì)導(dǎo)致數(shù)據(jù)完整性問題。如果避免觸發(fā)器的執(zhí)行是可接受的,您可以通過 參考資料中列出的文章里所描述的技術(shù)來臨時(shí)禁用觸發(fā)器。

    標(biāo)識(shí)列和序列對(duì)象


    這兩種方法可以讓 DB2 自動(dòng)生成整型列值,這通常是在 insert 期間進(jìn)行的。應(yīng)該清楚的主要性能問題是,由于可恢復(fù)性的原因,生成的值必須做日志記錄。為了減少日志記錄的開銷,可以將這些值預(yù)存(緩存)起來,每當(dāng)緩存用完時(shí),才寫一條日志記錄。缺省情況下是緩存 20 個(gè)值。

    附錄 A 中 test 41-51的結(jié)果表明,如果沒有緩沖的話,耗時(shí)會(huì)非常大(幾乎要比缺省情況慢 9 倍),而如果使用比缺省情況更大的緩存,則可以縮減大半的時(shí)間,并且將使用 Identity(標(biāo)識(shí))或 Sequence(序列) 的時(shí)間減至不到原先的 20%。如果您要在使用 Identity 還是 Sequence 之間作選擇的話,那么我告訴您使用 Identity 要好出幾個(gè)百分點(diǎn)。

    生成的列


    當(dāng)插入行到一個(gè)表,并且該表用“generated as”子句定義了一個(gè)或多個(gè)列,例如: CREATE TABLE t1 (c1 CHAR(5), c2 CHAR(5) generated always as (UPPER(c1))) ,這時(shí),為建立生成的值而進(jìn)行的函數(shù)調(diào)用將導(dǎo)致附加的開銷。然而,這種開銷非常小,不至于影響您使用這種功能的決心。

    “refresh immediate”物化查詢表(MQT)的重新生成


    MQT 可用于通過預(yù)先計(jì)算聚合值來增強(qiáng)查詢性能。例如:
    create table staffsum as
    ( select count(salary) as salcount, sum(salary) as salsum, dept from staff group by dept )
    data initially deferred refresh immediate

    如果 MQT 被定義為“refresh immediate”,則在每次 insert 時(shí)將重新計(jì)算 MQT 中的聚合,因此通常來講,對(duì)帶有 refresh immediate MQT 的表執(zhí)行列插入不大可取。不過,DB2 會(huì)盡其所能優(yōu)化重新計(jì)算,例如掃描總結(jié)表,而不是整個(gè)基本表。我們建議對(duì)于涉及 MQT 的 insert 運(yùn)行 Explain,以便清楚幕后情況。





    回頁首


    其他方面的考慮

    分區(qū)表(在 DPF 中,以前的 V7 EEE)


    當(dāng)插入一行到一個(gè)分區(qū)表(使用 DB2 V8 的 Data Partitioning Feature (DPF))時(shí),首先要散列(hash)該行的分區(qū)鍵值以確定必須將該行插入到哪個(gè)分區(qū),然后 DB2 將該行發(fā)送到那個(gè)分區(qū)。這種一次一行的處理方式比較慢,前面我們已看到,在 CLI 中,這種方式與數(shù)組插入方式比起來的確很慢,而且因?yàn)檫€需要將每一行從協(xié)調(diào)分區(qū)發(fā)送到目標(biāo)分區(qū),使得情況更加糟糕。

    為了把開銷降至最低,可以使用緩沖插入(buffered insert)。如果使用了這種方法,定向于某個(gè)給定分區(qū)的散列行首先會(huì)進(jìn)入一個(gè)緩沖區(qū)中,然后成組地發(fā)送到那個(gè)分區(qū),而不是一次發(fā)送一行。您可以通過 Prep 或 Bind 命令的“INSERT BUF”選項(xiàng)來引起緩沖插入。要了解關(guān)于緩沖插入的細(xì)節(jié),請(qǐng)參閱 Application Development Guide 的參考資料:Programming Client Applications。在開發(fā)者園地也有關(guān)于基于 Java(只包括 SQLJ)的緩沖插入的文章,請(qǐng)參閱后面的“參考資料”。

    在 DPF 環(huán)境中,如果對(duì)于重復(fù)的批量插入要求絕對(duì)最大的性能,那么您可能需要研究?jī)蓚€(gè)相關(guān)的 API。第一個(gè) API 是 sqlugtpi,它讓應(yīng)用程序可以獲得一個(gè)表的分區(qū)信息。之后就可以使用這種信息,再結(jié)合 sqlugrpn API 來找到一行所屬的分區(qū)號(hào)。您可以使用這些 API 將屬于某個(gè)給定分區(qū)的所有數(shù)據(jù)組到一起,然后連接到那個(gè)分區(qū),這樣就不需要在分區(qū)之間傳輸數(shù)據(jù),對(duì)于每個(gè)分區(qū)都重復(fù)這么做。這種方法可以取得非常快的性能,但是要花一定的精力來確保這種方法在有多個(gè)數(shù)據(jù)類型、代碼頁等等的情況下也能十分有效。

    DPF 插入可能引起的另一個(gè)問題是,要插入到的那個(gè)表(子表)上可能有外鍵約束。假設(shè)父表和子表有不同的分區(qū)鍵。那么每個(gè)子行的父親一般會(huì)在一個(gè)不同的分區(qū)上,因此,對(duì)于大多數(shù)插入的行,對(duì)其父親的驗(yàn)證檢查就需要從子分區(qū)跨越到父分區(qū)。對(duì)此的解決辦法是,讓父表和子表的分區(qū)鍵相同(這對(duì)于查詢性能來說不是最好的選擇),或者,如果使用了多個(gè)邏輯分區(qū),就可以將 DB2 注冊(cè)表變量設(shè)置為 YES。

    通過 Staging 表以及其他方法增加并行性


    通過使用 staging 表可以為某些場(chǎng)景下的 insert 提高性能。通常的用法是,不是批量插入行到一個(gè)表中,而是使用 LOAD 命令將行裝載到一個(gè) staging 表中;然后,就可以使用 INSERT ... SELECT 將行插入到主表。不管 LOAD 還是 INSERT ... SELECT,都比常規(guī)的插入要快得多,即使將這兩步加起來也常常要比常規(guī)插入快些。不過,單單就性能而言,在 V8 中還是使用 load 直接將表裝載到主表要快些,因?yàn)樵?V8 中的 load 不像 V7 中那樣有并發(fā)限制。

    除了數(shù)據(jù)的消息傳遞以外,致使您在 V8 中仍想使用 staging 表的主要原因是,這樣可以將批量插入拆散成能夠并行運(yùn)行的更小的塊。在一個(gè)有多個(gè)處理器的系統(tǒng)上,每條插入將在一個(gè) DB2 代理中運(yùn)行,并且不會(huì)占用多于一個(gè)的處理器,即使將 DBMINTRA_PARALLEL 參數(shù)被設(shè)為 ON 也是如此。例如,如果要在一臺(tái) 8-way 的機(jī)器上插入 1M 的行,一般的插入過程通常對(duì) CPU 的利用不會(huì)多于 12% (100 / 8)。(另一方面,load 則會(huì)自動(dòng)使用百分比大得多的 CPU,這也是它比 insert 更可取的另一個(gè)原因。)相反,您可以將 1M 的行裝載到一個(gè) staging 表中,然后運(yùn)行 8 條并發(fā)的 insert ... Select 語句,8 條 Select 語句中都有謂詞,每條語句從 staging 表中檢索大約 1/8 的惟一的行子集。

    最后,您可以通過一個(gè)多線程應(yīng)用程序運(yùn)行并發(fā)的插入,其中每個(gè)線程做它自己的插入。

    對(duì)于一條 INSERT ... SELECT,只要能使選擇更快,就可以減少整條語句的耗時(shí),但是這超出了本文的范圍,不適合進(jìn)行詳細(xì)的討論。下面列出了一些可能性。注意,這些只適用于選擇部分。而同樣的這些因素對(duì)于插入部分一般沒什么幫助。

    • 添加索引(不是在插入表上的索引!)。
    • 使用大的緩沖池。
    • 使用并行(INTRA_PARALLEL=YES 且 DFT_DEGREE > 1)。
    • 使用隔離級(jí)別 UR (例如用一個(gè) WITH UR 子句)。

     

    插入 LOB 和 LONG 列


    這些類型的列是惟一的,它們不會(huì)緩存在緩沖池中。因此,任何包括一個(gè)或多個(gè)這種列的 insert 都會(huì)使得這些列被直接寫到磁盤上,用 DB2 術(shù)語來說就是“直接寫(direct write”)。您可以想像,這會(huì)使 LOB/LONG 的 insert 比“一般” 的 insert 要慢得多: test 91中使用了一個(gè) CLOB 列,這種情況比基線測(cè)試(test 11,有一個(gè) CHAR 列)要慢 9 倍以上。有這樣一些優(yōu)化的可能性:

    • 將 LOB 或 LONG 改為 VARCHAR。這允許發(fā)生緩沖池的緩存。這可能要求將表放入到一個(gè)頁寬較大(例如 32 K)的表空間中,因?yàn)轫搶挶仨毚蟮阶阋匝b下所有的非 LOB 和非 LONG 列。
    • 使用 SMS 或 DMS 文件表空間,這樣便允許操作系統(tǒng)的緩存抵消某些性能上的降低。
    • 為 LOB/LONG 使用最佳的存儲(chǔ)/硬件配置。
    • 嘗試為 LOB 列使用 COMPACT 和 NOT LOGGED 屬性。這兩個(gè)屬性對(duì)于我這個(gè)小測(cè)試來說沒有多大提高,但是當(dāng)使用了大量數(shù)據(jù)的時(shí)候,效果就出來了。

     

    優(yōu)化級(jí)別


    對(duì)于沒有約束的簡(jiǎn)單插入,將優(yōu)化級(jí)別從缺省值(5)改為 1 的測(cè)試雖然將優(yōu)化器的算法變得更廉價(jià),但是并沒有對(duì)性能產(chǎn)生很大的變化。如果要經(jīng)常準(zhǔn)備插入語句,插入牽涉到約束,或者有選擇部分,那么使用較低的優(yōu)化級(jí)別可能會(huì)有好處。相反,如果插入部分很小,而選擇部分比較復(fù)雜,那么將優(yōu)化級(jí)別從 5 增至更大將帶來好處。

    利用源表(source table)插入/更新目標(biāo)表(MERGE 語句)


    一個(gè)相當(dāng)常見的數(shù)據(jù)庫任務(wù)就是利用一個(gè)源表更新一個(gè)目標(biāo)表。舉個(gè)特定的例子,比如取源表中的每一行,如果該行不在目標(biāo)表中,那么就將該行插入到目標(biāo)表,否則就更新目標(biāo)行。在 V8 中可以使用 MERGE 語句獨(dú)立完成上述任務(wù),而不必多次執(zhí)行不同的語句,從而性能也就更好一些。





    回頁首


    監(jiān)視和調(diào)優(yōu) insert

    當(dāng)您試圖監(jiān)視和調(diào)優(yōu) insert 時(shí),基本任務(wù)跟大多數(shù)其他的性能分析沒什么不同:找出瓶頸所在,然后直接處理瓶頸。欲確定瓶頸,首先就是利用操作系統(tǒng)工具查看 CPU、I/O、內(nèi)存和網(wǎng)絡(luò)消耗。這樣應(yīng)該就可以讓您排除某些方面,而將注意力放在一兩個(gè)方面。對(duì)操作系統(tǒng)實(shí)用程序的深入討論超出了本文的范圍。

    盡量不要被非必要的問題轉(zhuǎn)移了視線。例如,如果 CPU 利用率是 100%,那么這時(shí)減少 I/O 很可能無法提高性能,而當(dāng)以后 CPU 瓶頸已經(jīng)解除時(shí),這樣的更改本來可能非常有用,但是您可能會(huì)因?yàn)樯弦淮蔚氖《辉僮鬟@樣的更改了。

    應(yīng)該盡量讓應(yīng)用程序在操作期間的不同時(shí)刻報(bào)告插入的速率。例如,如果知道在運(yùn)行后的第 10 分鐘與第 5 分鐘時(shí)各自的每秒插入次數(shù)是否相同,是很有用的。通常,當(dāng) insert 開始的時(shí)候,有一小段較慢的啟動(dòng)時(shí)間,然后當(dāng)緩沖池填充了內(nèi)容并且沒有數(shù)據(jù)頁的 I/O 時(shí),就有一段速度比較快的時(shí)期。接著,當(dāng)開始將數(shù)據(jù)頁往外寫的時(shí)候,速率又會(huì)慢下來,如果頁清除或者 I/O 子系統(tǒng)不是最優(yōu)的,則更是如此。

    對(duì)于非常大的批量插入,通常在某一時(shí)刻插入的速率會(huì)趨于平穩(wěn)。如果不是這樣,那么通常是因?yàn)橐诓迦肫陂g創(chuàng)建索引,使得需要?jiǎng)?chuàng)建越來越多的索引頁,并且可能還要進(jìn)行隨機(jī)的 I/O 操作,讀取已有的索引頁以便更新它們。如果的確是上述情況,那么使用更大的緩沖池是最好的解決辦法,但是為索引頁增加更多的自由空間也有所幫助。

    現(xiàn)在讓我們看看可以幫助您監(jiān)視和調(diào)優(yōu) insert 的關(guān)鍵 DB2 實(shí)用程序:Snapshots(快照監(jiān)視)、Event Monitoring(事件監(jiān)視)和 Explain。要了解關(guān)于快照監(jiān)視和事件監(jiān)視的更多信息,請(qǐng)參閱 System Monitor Guide and Reference;至于 Explain,請(qǐng)參閱 Administration Guide: Performance。

    快照監(jiān)視


    快照監(jiān)視可以提供大量信息片斷來描述在插入的處理期間發(fā)生了什么事情。您可以使用以下步驟獲得所有可以得到的信息(或者也可以選擇獲得信息的子集):

    • 使用 UPDATE MONITOR SWITCHES 命令打開所有開關(guān)。
    • 運(yùn)行 RESET MONITOR ALL重設(shè)計(jì)數(shù)器。這樣更易于在一次測(cè)試的過程中比較不同快照并找出不同。
    • 等一段標(biāo)準(zhǔn)長(zhǎng)度的時(shí)間,例如 1 分鐘或者 5 分鐘,然后發(fā)出 GET SNAPSHOT FOR ALL ON <database>。 重復(fù)前兩步,以獲得多個(gè)用于比較的快照。

     

    大多數(shù)與 insert 相關(guān)的信息都可以在數(shù)據(jù)庫快照中找到,并且大部分的這些信息也都會(huì)在適當(dāng)?shù)木彌_池、應(yīng)用程序和表空間快照中以更大的粒度提供。

    以下是數(shù)據(jù)庫快照中最相關(guān)的幾行:

    Buffer pool data writes                    = 500            Asynchronous pool data page writes         = 500            Buffer pool index writes                   = 0            Asynchronous pool index page writes        = 0            Total buffer pool write time (ms)          = 25000            Total elapsed asynchronous write time      = 25000            LSN Gap cleaner triggers                   = 21            Dirty page steal cleaner triggers          = 0            Dirty page threshold cleaner triggers      = 0            Update/Insert/Delete statements executed   = 100000            Rows inserted                              = 100000            

    請(qǐng)注意連續(xù)快照中的“Rows inserted”,看看在批量插入期間插入的速率是否有變化。大多數(shù)其他的值都反映了 I/O 量和頁清除的效力。至于后者,理想情況下您可以看到,所有數(shù)據(jù)寫都是同步的,而所有緩沖池寫時(shí)間都是異步的(就像上面輸出的那樣);如果不是這樣,嘗試降低 CHNGPGS_THRESH 和/或增加 NUM_IOCLEANERS。

    通常在動(dòng)態(tài) SQL 快照中可以找到關(guān)于 insert 的附加信息??纯纯偤臅r(shí),并將其與用戶和系統(tǒng) CPU 時(shí)間相比較,這樣做是十分有用的。耗時(shí)與 CPU 之間的差值大部分在于 I/O 部分,所以,哪個(gè)地方占去了大部分的時(shí)間以及哪個(gè)地方需要調(diào)優(yōu)也就很清楚了。

    以下是一個(gè) 100,000 行的 CLI 數(shù)組插入的動(dòng)態(tài) SQL 快照條目的一個(gè)子集。注意,盡管應(yīng)用程序只發(fā)送那個(gè)數(shù)量的 1/10 那么多的數(shù)組,“Number of executions”仍是對(duì)于每一行都有一個(gè)。

    Number of executions               = 100000            Number of compilations             = 1            Rows written                       = 100000            Buffer pool data logical reads     = 102120            Total execution time (sec.ms)      = 13.830543            Total user cpu time (sec.ms)       = 10.290000            Total system cpu time (sec.ms)     = 0.130000            Statement text                     = INSERT into test1 values(?, ?, ?, ?, ?)            

    快照輸出中的其他信息:

    • “Lock waits”和“Time database waited on locks” -- 使用它們來查看插入的行上的鎖是否引起其他應(yīng)用程序的并發(fā)問題。
    • Table Snapshot --“Rows Written”將反映插入(或更新)的行的數(shù)目。

     

    事件監(jiān)視


    當(dāng)事件在服務(wù)器上發(fā)生時(shí),通過 DB2 事件監(jiān)視器可以獲得關(guān)于事件的性能信息。為了分析 insert 的性能,需要為語句創(chuàng)建一個(gè)事件監(jiān)視器,并在 insert 執(zhí)行期間激活該事件監(jiān)視器。雖然有點(diǎn)過分,但是事件監(jiān)視器的輸出會(huì)顯示每條 insert 語句的耗時(shí)。對(duì)于 OLTP 型的應(yīng)用程序,對(duì)語句運(yùn)行事件監(jiān)視的開銷相當(dāng)高,其輸出也十分冗長(zhǎng),所以應(yīng)注意不要讓事件監(jiān)視運(yùn)行太長(zhǎng)的時(shí)間。即使是幾秒鐘也會(huì)產(chǎn)生數(shù)兆的輸出。您可以將監(jiān)視器信息寫入到一個(gè)表中,以便于對(duì)結(jié)果的分析,例如性能趨勢(shì)。

    下面是一個(gè)濃縮的示例語句事件,在一系列的 10,000 個(gè) CLI 數(shù)組插入中每 10 行對(duì)應(yīng)一次這樣的語句事件。每個(gè)數(shù)組只有一個(gè)事件,在這里就是每 10 行有一個(gè)事件。

    17) Statement Event ...            Appl Handle: 9            Appl Id: *LOCAL.wilkins.0953D9033443            -------------------------------------------            Type     : Dynamic            Operation: Execute            Section  : 4            Creator  : NULLID            Package  : SYSSH200            Text     : INSERT into test1 values(?, ?)            -------------------------------------------            Start Time: 01-28-2004 22:34:43.918444            Stop Time:  01-28-2004 22:34:43.919763            Exec Time:  0.001319 seconds            Number of Agents created: 1            User CPU: 0.000000 seconds            System CPU: 0.000000 seconds            Fetch Count: 0            Rows read: 0            Rows written: 10            Internal rows deleted: 0            Internal rows updated: 0            Internal rows inserted: 0            Bufferpool data logical reads: 10            SQLCA:            sqlcode: 0            sqlstate: 00000            

    Explain


    如果 insert 的性能不像預(yù)期的那么好,則有可能是因?yàn)橛?#8220;隱藏”的處理發(fā)生。前面已經(jīng)討論過,這種處理可能以不同的形式出現(xiàn),例如索引維護(hù)、約束驗(yàn)證或者觸發(fā)器執(zhí)行。對(duì) insert 運(yùn)行某種形式的 Explain (例如 Visual Explain,或者 Explain 語句加上 db2exfmt),就可以揭示大多數(shù)額外的處理(除了索引維護(hù))。如果額外的處理可能是性能問題的起因,那么可以消除這種額外的處理。

    作為一個(gè)簡(jiǎn)單的例子,下面的圖(由 db2exfmt 產(chǎn)生)展示了“Insert into test1 values (?, ?, ...)”語句的訪問計(jì)劃。您可以猜出這里的額外處理是什么嗎?回答就在下面。

                                   Rows            RETURN            (   1)            Cost            I/O            |            0.333333            TBSCAN            (   2)            28.2956            1            /----+---\            0.04            1            FILTER   TABFNC: SYSIBM            (   3)        GENROW            28.2266            1            |            1            NLJOIN            (   4)            28.1268            1            /---------+--------\            1                       1            INSERT                  IXSCAN            (   5)                  (   7)            25.5248                 2.60196            1                       0            /---+--\\                   |            1          116               120            TBSCAN  TABLE: WILKINS    INDEX: SYSIBM            (   6)       test1       SQL0401290925513            0.0048            0            |            1            TABFNC: SYSIBM            GENROW            

    在我給出答案之前:Explain 對(duì)于 INSERT ... SELECT 語句也十分有用。insert 本身是非??斓?,但是 SELECT 可能存在一個(gè)訪問計(jì)劃問題,這個(gè)問題會(huì)拖慢整個(gè)語句。通過 Explain 就可以揭示這一切。
    答案:
    上述訪問計(jì)劃是針對(duì)將行插入到擁有外鍵關(guān)系的子表的 insert 的。其中有一個(gè) insert(步驟 5)與索引掃描(步驟 7)之間的嵌套循環(huán)連接(NLJOIN,步驟 4 )。索引掃描實(shí)際上是對(duì)父表進(jìn)行主鍵查找,并完成外鍵約束驗(yàn)證。在這種情況下,來自額外處理的開銷相對(duì)來說就比較小:索引掃描的成本估計(jì)只有 2.60196 timerons(timerons 是(主要)結(jié)合了 CPU 和 I/O 代價(jià)的成本單位),而 insert 本身的成本是 25.5248 timerons。例如,如果有一個(gè)觸發(fā)器的話,那么就會(huì)在訪問計(jì)劃中的一個(gè)或多個(gè) insert、Update 或 Delete 條目中反映出來。

    順便提一下,當(dāng)您在訪問計(jì)劃中看到“GENROW”時(shí),其實(shí)就是一個(gè)“generate row”步驟。這代表用于后續(xù)步驟的臨時(shí)行的創(chuàng)建,這里不需要擔(dān)心。





    回頁首


    結(jié)束語

    在本文中,我們給出了多種提高 insert 性能的方法。請(qǐng)參閱 附錄 B以了解這些方法的完整清單。下面是最重要的一部分方法,每種方法在某些情況下可以使性能快上兩倍:

    • 盡可能使用 Load。
    • 使用參數(shù)標(biāo)記,以避免對(duì)于每一行都有 Prepare 成本。
    • 每 N 行發(fā)出一次 Commit,其中 N 是一個(gè)比較大的數(shù),例如 1000。千萬不要每一行都提交,因此要小心自動(dòng)提交情況。
    • 一次插入一組行。
    • 將 insert 期間出現(xiàn)約束、索引和觸發(fā)器的機(jī)會(huì)降至最少。
    • 如果使用 SMS 表空間,則運(yùn)行 db2empfa。
    • 優(yōu)化“special features”的使用:帶分區(qū)表的緩沖插入,用于 Identity 和 Sequence 值的大的緩存。

     

    我們希望本文可以讓您很好地了解在 DB2 insert 處理期間所發(fā)生的事情,以及如何監(jiān)視和提高其性能。





    回頁首


    附錄 A -- 性能評(píng)測(cè)

    該附錄舉例說明了在本文中討論的那些優(yōu)化技術(shù)可以帶來的一些好處(以及對(duì)非優(yōu)化方法的沖擊)。每次測(cè)試都至少運(yùn)行 2 次,并且結(jié)果一致,但是該結(jié)果跟您在自己環(huán)境中看到的結(jié)果不一定完全吻合。特別地,這些結(jié)果是在一個(gè) I/O 強(qiáng)度比理想情況下更大的一個(gè)系統(tǒng)上得到的,因?yàn)橛脩舯砜臻g和日志被放在相同的文件系統(tǒng)中,并且是在相同的兩個(gè)磁盤上。因此,能減少 CPU 開銷的改進(jìn)策略通常還有比這里提到的更多的好處。

    下面幾條適用于所有這些測(cè)試,如有例外則會(huì)另外注明:

    • 小型 RS/6000 系統(tǒng)上的 DB2 V8 FixPak 4。
    • 使用了本地客戶機(jī)。
    • 為用戶數(shù)據(jù)使用了 SMS 表空間,并對(duì)數(shù)據(jù)庫運(yùn)行了 db2empfa 命令以建立多頁文件分配。表空間頁寬/區(qū)段大小/預(yù)取大小采用缺省值(4K,32,32)。 要插入到的那個(gè)表一開始為空,并且沒有索引。
    • 除了 test 1-2、8、61-68 和 79 以外,所有測(cè)試都是在 CLI 中使用數(shù)組插入來完成的。
    • 每 1000 行有一次 Commit。

     

    表 1. 準(zhǔn)備語句和使用數(shù)組的效果


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    1 CLI -- 對(duì)每一行執(zhí)行 SQLExecDirect 10,000 180.63
    2 CLI -- 預(yù)處理語句,但是每次只插入 1 行 10,000 92.05
    3 CLI -- 預(yù)處理語句,并且是數(shù)組插入(每次插入 10 行) 10,000 12.85

    test 1-3 中自動(dòng)提交是處于啟用狀態(tài)的,因而對(duì)每一次 insert 都有一次 Commit。也就是說,在 test 1 和 test 2 中,對(duì)于每一行都有一次 Commit,在 test 3 中對(duì)于每 10 行有一次 Commit。至于 Commit 的性能影響,請(qǐng)參考 test 61-78。

    表 2. 用一個(gè)表的內(nèi)容填充另一個(gè)表


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    6 帶日志記錄的 INSERT ... SELECT 100,000 15.66
    7 帶 NOT LOGGED INITIALLY 選項(xiàng)的 INSERT ... SELECT 100,000 16.43
    8 與 test 7 一樣,但是 CHNGPGS_THRESH = 5 100,000 11.80
    9 從游標(biāo)裝載(與 test 6-8 相同的 Select) 100,000 12.95

    test 6-9 計(jì)算了利用一個(gè)表填充另一個(gè)表的時(shí)間,包括 Commit 的時(shí)間。test 7 表明,使用 NOT LOGGED INITIALLY (NLI) 實(shí)際上會(huì)導(dǎo)致性能下降,因?yàn)樾枰?Commit 時(shí)將新頁寫到磁盤上:Commit 占了大半的時(shí)間。然而,當(dāng) test 8 中采用了更積極的頁清除時(shí),性能就大大提高了,這主要是因?yàn)?Commit 的時(shí)間縮短了超過 5 秒鐘。test 9 表明,通過使用 Load 而不是 insert ... Select,性能提高了 17%,并且沒有 test 6 中那樣的 NLI 風(fēng)險(xiǎn)。

    表 3. 檢查約束、外鍵和觸發(fā)器的影響


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    11 CLI -- 與 test 3 相同,但是有 100K 的行,提交 1000 次 100,000 31.51
    12 CLI -- 與 test 11 相同,但是有 1 個(gè)檢查約束 100,000 33.65
    13 CLI -- 與 test 11 相同,但是有 2 個(gè)檢查約束 100,000 36.63
    14 CLI -- 與 test 11 相同,但是有 1 個(gè)外鍵(FK) 100,000 55.37
    15 CLI -- 與 test 11 相同,但是有 2 個(gè)外鍵(FK) 100,000 72.71
    16 CLI -- 與 test 14 相同,但是 Insert 之后添加了 FK 100,000 32.84
    17 CLI -- 與 test 15 相同,但是 Insert 之后添加了 FK 100,000 38.89
    18 CLI -- 與 test 11 相同,但是有 1 個(gè) 進(jìn)行 Insert 操作的觸發(fā)器 100,000 67.57
    19 CLI -- 與 test 11 相同,但是有 2 個(gè) 進(jìn)行 Insert 操作的觸發(fā)器 100,000 175.95
    20 CLI -- 與 test 11 相同,但是有 1 個(gè) 進(jìn)行 Update 操作的觸發(fā)器 100,000 54.71
    21 CLI -- 與 test 11 相同,但是有 2 個(gè) 進(jìn)行 Update 操作的觸發(fā)器 100,000 150.18
    22 CLI -- 與 test 11 相同,但是有 1M 的行 100,000 282.02
    23 CLI -- 與 test 22 相同,但是 APPEND 的狀態(tài)是 ON 100,000 281.64

    顯然,檢查約束的使用對(duì)性能有一點(diǎn)小小的影響,但是外鍵和觸發(fā)器的影響卻非常大。在 test 18 和 test 19 中,各自的觸發(fā)器都將一行插入到基本表所在的相同表空間內(nèi)的一個(gè)不同的表中,這暴露了數(shù)據(jù)庫和日志磁盤上的 I/O 瓶頸。在 test 20 和 test 21 中,各自的觸發(fā)器在一個(gè)只有一行的表中增加了一個(gè)“insert count”列;雖然這里不像 test 18 和 test 19 那樣有其他的行,但是更新開銷和日志記錄仍導(dǎo)致 insert 運(yùn)行起來比沒有觸發(fā)器的情況下慢很多。雖然通過標(biāo)準(zhǔn)數(shù)據(jù)庫 I/O 調(diào)優(yōu)可以改進(jìn) test 18-21,但關(guān)鍵是在大規(guī)模插入期間應(yīng)盡可能避免存在約束。

    表 4. 在插入前后創(chuàng)建索引的影響


    test # 插入方法/注解 # 行數(shù) 索引個(gè)數(shù) 耗時(shí)(秒)
    31 CLI -- 與 test 3 相同,但是有 100K 行,提交 1000 次 100,000 0 31.51
    32 CLI -- 與 test 31 相同,但是有 1 個(gè)索引 100,000 1 53.73
    33 CLI -- 與 test 31 相同,但是有 2 個(gè)索引 100,000 2 83.26
    34 CLI -- 與 test 31 相同,但是有 3 個(gè)索引 100,000 3 108.21
    35 CLI -- 與 test 31 相同,但是有 4 個(gè)索引 100,000 4 141.63
    36 CLI -- 與 test 35 相同,但是在插入之后創(chuàng)建索引 100,000 4 (*) 73.75
    37 CLI -- 與 test 32 相同,但是在插入之后創(chuàng)建索引 100,000 1 39.44
    38 CLI -- 與 test 32 相同,但是索引被群集起來 100,000 1 62.93

    在 test 32-35 中,索引是在插入之前創(chuàng)建的,而在 test 36-37 中索引是在插入之后創(chuàng)建的(并且,對(duì)于后一次測(cè)試,耗時(shí)包括 insert 的時(shí)間加上 CREATE INDEX 語句的時(shí)間)。

    表 5. 使用標(biāo)識(shí)符或序列


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    41 CLI -- 與 test 31 相同(沒有 Identity 或 Sequence) 100,000 31.51
    42 CLI -- 與 test 41 相同,但是有 Identity 列,無緩存 100,000 896.61
    43 CLI -- 與 test 41 相同,但是有 Identity 列,緩存 5 100,000 212.52
    44 CLI -- 與 test 41 相同,但是有 Identity 列,緩存 20(缺省) 100,000 99.06
    45 CLI -- 與 test 41 相同,但是有 Identity 列,緩存 100 100,000 61.62
    46 CLI -- 與 test 41 相同,但是有 Identity 列,緩存 1000 100,000 37.51
    47 CLI -- 與 test 41 相同,但是有 Sequence,無緩存 100,000 896.92
    48 CLI -- 與 test 41 相同,但是有 Sequence,緩存 5 100,000 212.58
    49 CLI -- 與 test 41 相同,但是有 Sequence,緩存 20(缺省) 100,000 101.87
    50 CLI -- 與 test 41 相同,但是有 Sequence,緩存 100 100,000 66.55
    51 CLI -- 與 test 41 相同,但是有 Sequence,緩存 1000 100,000 39.55

    test 41-51 表明,如果緩存很小或者沒有緩存的話,使用 Identity 或 Sequence 對(duì)性能有很大的負(fù)面影響,但是通過使用較大的緩存可以使這種開銷幾乎可以忽略不計(jì)。

    表 6. 展示使用大型數(shù)組和每次提交很多 insert 的效果


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    1 CLI -- 對(duì)每一行執(zhí)行 SQLExecDirect(同上) 10,000 183.55
    61 CLI -- 與 test 1 相同,每 5 行一次 Commit 10,000 118.41
    62 CLI -- 與 test 1 相同,每 10 行一次 Commit 10,000 114.23
    63 CLI -- 與 test 1 相同,每 100 行一次 Commit 10,000 103.43
    64 CLI -- 與 test 1 相同,每 1000 行一次 Commit 10,000 102.86
    2 CLI -- 預(yù)處理語句,但是每次插入 1 行(同上) 10,000 92.05
    65 CLI -- 與 test 2 相同,每 5 行一次 Commit 10,000 22.34
    66 CLI -- 與 test 2 相同,每 10 行一次 Commit 10,000 20.78
    67 CLI -- 與 test 2 相同,每 100 行一次 Commit 10,000 10.11
    68 CLI -- 與 test 2 相同,每 1000 行一次 Commit 10,000 7.58
    69 CLI -- 與 test 3 相同(10x 行);數(shù)組大小為 10,每 10 行一次 Commit 10,000 118.18
    70 CLI -- 與 test 69 相同,但是數(shù)組大小為 10,每 100 行一次 Commit 10,000 56.71
    71 CLI -- 與 test 69 相同,但是數(shù)組大小為 10,每 1000 行一次 Commit 10,000 30.09
    72 CLI -- 與 test 69 相同,但是數(shù)組大小為 100,每 100 行一次 Commit 10,000 50.65
    73 CLI -- 與 test 69 相同,但是數(shù)組大小為 100,每 1000 行一次 Commit 10,000 24.27
    74 CLI -- 與 test 69 相同,但是數(shù)組大小為 1000,每 1000 行一次 Commit 10,000 23.43
    75 CLI -- 與 test 69 相同,但是數(shù)組大小為 2,每 2 行一次 Commit 10,000 471.82
    76 CLI -- 與 test 69 相同,但是數(shù)組大小為 2,每 10 行一次 Commit 10,000 155.21
    77 CLI -- 與 test 69 相同,但是數(shù)組大小為 2,每 100 行一次 Commit 10,000 74.82
    78 CLI -- 與 test 69 相同,但是數(shù)組大小為 2,每 1000 行一次 Commit 10,000 48.51
    79 CLI -- 通過 SQLSetStmtAttr(數(shù)組大小為 10)使用 Load 10,000 13.68

    前面的測(cè)試展示了使用大型數(shù)組和每次提交大量的行所帶來的好處,其中后者尤為重要。

    表 7. 使用 SMS 或 DMS


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    11 CLI -- SMS,運(yùn)行了 db2empfa(同上) 100,000 31.51
    81 與 test 11 相同,但是使用 DMS 文件表空間 100,000 25.52
    82 與 test 11 相同,但是 db2empfa 沒有運(yùn)行(SMS) 100,000 62.87
    83 與 test 11 相同,但是 extentsize = 4 (并且 prefetchsize = 4) 100,000 38.37
    83 與 test 11 相同,但是 extentsize = 4 (并且 prefetchsize = 4) 100,000 38.37
    84 與 test 11 相同,但是 extentsize = 8 (并且 prefetchsize = 8) 100,000 34.61
    85 與 test 11 相同,但是 extentsize = 16 (并且 prefetchsize = 16) 100,000 31.75

    前面的測(cè)試表明,對(duì)于 SMS,運(yùn)行 db2empfa 是取得良好的 insert 性能的關(guān)鍵,而使用小于 32 頁的區(qū)段是有害的。DMS 比 SMS 更好一些。

    表 8. 使用 CLOB 列的效果


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    11 CLI -- (同上) 100,000 31.51
    91 與 test 11 相同,但是 CHAR(10) 列現(xiàn)在是 CLOB(10) 100,000 286.49

    前面的比較展示了使用 CLOB 列(雖然非常短)帶來的巨大影響。我們還對(duì) test 91 的變種做過嘗試,但是結(jié)果仍然十分接近,對(duì)于 CLOB 列不管選擇作不作日志記錄,或者是否進(jìn)行壓縮,都影響不大。

    表 9. 使用表鎖而不是使用行鎖


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    11 CLI -- (同上) 100,000 31.51
    101 與 test 11 相同,但是使用了 LOCKSIZE TABLE 100,000 30.58

    上述比較表明,使用表鎖而不是行鎖可以節(jié)省大約 3% 的耗時(shí)。

    表 10. 改變 LOGBUFSZ


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    104 與 test 71 相同,但是每次提交 10K 的行(LOGBUFSZ=8) 100,000 28.53
    105 與 test 104 相同,但是 LOGBUFSZ = 256 100,000 24.91

    test 105 表明,如果將 LOGBUFSZ 升至足夠高,以免在 Insert 期間因?yàn)槿罩揪彌_區(qū)被填滿而被迫將日志寫到磁盤,這樣做可以提高大約 13% 的性能。

    表 11. 使用多種優(yōu)化


    test # 插入方法/注解 # 行數(shù) 耗時(shí)(秒)
    22 CLI -- (同上) 1,000,000 282.02
    111 與 test 22 相同,但是使用了所有優(yōu)化(見下面) 1,000,000 160.45
    1(*) CLI -- (與 test 1 相同,但是增加到了 1,000,000 行) 1,000,000 ~18000.00

    test 111 使用了前面試過的所有優(yōu)化技術(shù)來替代基線:使用 DMS 而不是 SMS,使用 1000 行的數(shù)組(而不是 10 行),每 10000 行提交一次(而不是 1000 行),使用 LOCKSIZE TABLE,使 APPEND 的狀態(tài)為 ON,并且 LOGBUFSZ 設(shè)為 256。注意,與最初的基線(test 1)相比,test 22 已經(jīng)做了一些優(yōu)化。這里將 test 1 擴(kuò)展到了 1,000,000 行,以表明僅僅更改少量因素就可能造成性能上的巨大差異。





    回頁首


    附錄 B -- 對(duì)于批量插入的優(yōu)化建議清單

    下面是在本文前面詳細(xì)討論過的一些建議。請(qǐng)記住,這些建議的影響程度千差萬別,有些建議對(duì)于除 Insert 之外的其他任務(wù)還可能有負(fù)面影響。

    1. 只準(zhǔn)備 Insert 語句一次,在語句中使用參數(shù)標(biāo)記,然后多次執(zhí)行該語句。如果經(jīng)常要準(zhǔn)備語句,那么可以試著調(diào)低優(yōu)化級(jí)別。
    2. 通過 CLI 數(shù)組或 JDBC 批處理操作,在每次 Insert 中包括多行,并盡可能地將處理工作從數(shù)組/批處理循環(huán)中移出來,以優(yōu)化應(yīng)用程序。
    3. 將多條 Insert 組到一起(Compound SQL)。
    4. 讓客戶機(jī)應(yīng)用程序和數(shù)據(jù)庫使用相同的代碼頁。
    5. 避免客戶機(jī)與數(shù)據(jù)庫之間的數(shù)據(jù)類型轉(zhuǎn)換。
    6. 避免使用 LOB 和 LONG 列;如果無法避免的話,請(qǐng)參考優(yōu)化對(duì)這兩種列的使用的建議。
    7. 為表使用 APPEND 模式,或者將 DB2MAXFSCRSEARCH (注冊(cè)表)設(shè)置成一個(gè)較低的值。
    8. 盡可能避免表上有任何索引,尤其是不要有群集索引。
    9. 在數(shù)據(jù)頁上預(yù)留適當(dāng)大小的自由空間(如果使用了 APPEND 模式,則預(yù)留的空間為 0)。
    10. 在索引頁上預(yù)留適當(dāng)大小的自由空間(如果是隨機(jī) insert,則預(yù)留的自由空間應(yīng)大于 10%)。
    11. DMS 表空間是最好的,但是如果使用了 SMS,那么運(yùn)行 db2empfa 并使用 32 頁或更大的區(qū)段。
    12. 使用大的緩沖池,如果必須在 Insert 期間建立索引的話,更應(yīng)如此。
    13. 通過降低 CHNGPGS_THRESH 和增加 NUM_IOCLEANERS (DB CFG),確保有效的頁清除。在 V8 FP4 中,要考慮 DB2_USE_ALTERNATE_PAGE_CLEANING(注冊(cè)表)。
    14. 將數(shù)據(jù)、索引和日志散布在多個(gè)磁盤上,日志使用的磁盤應(yīng)不同于其他東西使用的磁盤。對(duì)于數(shù)據(jù)、索引和日志,應(yīng)避免 RAID 5。
    15. 使用 LOCK TABLE 或 ALTER TABLE 建立表一級(jí)的鎖。如果存在并發(fā)問題,那么可能需要增加 LOCKLIST 和/或 MAXLOCKS,以確保不會(huì)出現(xiàn)獨(dú)占鎖升級(jí)的現(xiàn)象。
    16. 考慮 DB2_EVALUNCOMMITTED (注冊(cè)表,V8 FP4),以減少鎖對(duì)其他應(yīng)用程序的影響。同時(shí)還為其他應(yīng)用程序使用隔離級(jí)別 UR,以減少鎖對(duì)它們的影響。
    17. 增加 LOGBUFSZ(DB CFG)。
    18. 為 Insert 表使用 ACTIVATE NOT LOGGED INITIALLY,但是要清楚恢復(fù)問題。
    19. 每過 N 行之后再 Commit,其中 N 是一個(gè)較大的數(shù),比如 1000。
    20. 盡量減少表上的觸發(fā)器、檢查和外鍵約束以及生成的列。
    21. 盡可能避免 IDENTITY 和 SEQUENCE,如果不能避免,則使用較大的緩存。
    22. 避免將行插入到帶有“refresh immediate”MQT 的表中。
    23. 在 DPF 環(huán)境中,使用緩沖插入并避免分區(qū)間的傳輸。
    24. 使用 Load 和 staging 表或多線程應(yīng)用程序,以實(shí)現(xiàn) Insert 的并行性。
    25. 對(duì)于 Insert ... Select,確保對(duì) Select 部分進(jìn)行優(yōu)化。
    26. 使用 Explain 來發(fā)現(xiàn)“隱藏”的處理。
    27. 并且使用盡可能快的 CPU 和磁盤,這一點(diǎn)是不用說的。
    本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
    打開APP,閱讀全文并永久保存 查看更多類似文章
    猜你喜歡
    類似文章
    mysql 觸發(fā)器
    觸發(fā)器(Trigger)(三)
    mysql-操作觸發(fā)器
    SQL詳解
    轉(zhuǎn)貼:SQL SERVER面試題1
    新手MySQL工程師必備命令速查手冊(cè)
    更多類似文章 >>
    生活服務(wù)
    分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
    綁定賬號(hào)成功
    后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
    如果VIP功能使用有故障,
    可點(diǎn)擊這里聯(lián)系客服!

    聯(lián)系客服