国产成人精品亚洲777人妖,欧美日韩精品一区视频,最新亚洲国产,国产乱码精品一区二区亚洲

您的位置:首頁技術文章
文章詳情頁

DB2數據庫中提高INSERT性能詳解(1)

瀏覽:4日期:2023-11-08 08:51:54
INSERT 處理過程概述首先讓我們快速地看看插入一行時的處理步驟。這些步驟中的每一步都有優化的潛力,對此我們在后面會一一討論。在客戶機預備 語句。對于動態 SQL,在語句執行前就要做這一步,此處的性能是很重要的;對于靜態 SQL,這一步的性能實際上關系不大,因為語句的預備是事先完成的。 在客戶機,將要插入的行的各個 列值組裝起來,發送到 DB2 服務器。 DB2 服務器確定將這一行插入到哪一頁中。DB2 在 用于該頁的緩沖池中預留一個位置。假如 DB2 選定的是一個已有的頁,那么就需要讀磁盤;假如使用一個新頁,則要在表空間(假如是SMS,也就是系統治理存儲的表空間)中為該頁物理地分配空間。插入了新行的每一頁最后都要從緩沖池寫入到磁盤。在目標頁中對該行進行格式化,并獲得該行上的一個X(exclusive,獨占的) 行鎖。將反映該 insert 的一條記錄寫入到日志緩沖區中。最后提交包含該 insert 的事務,假如這時日志緩沖區中的記錄還沒有被寫入日志文件的話,則將這些記錄寫到日志文件中。此外,還可能發生很多類型的附加處理,這取決于數據庫配置,例如,索引或觸發器的存在。這種額外的處理對于性能來說也是意義重大的,我們在后面會討論到。INSERT 的替代方案在具體討論 insert 的優化之前,讓我們先考慮一下 insert 的兩種替代方案:load 和 import。import 實用程序實際上是 SQL INSERT 的一個前端,但它的某些功能對于您來說也是有用的。load 也有一些有用的額外功能,但是我們使用 load 而不使用 insert 的主要原因是可以提高性能。load 直接格式化數據頁,而避免了由于插入導致的對每一行進行處理的大部分開銷(例如,日志記錄在這里實際上是消除了)。而且,load 可以更好地利用多處理器機器上的并行性。在 V8 load 中有兩個新功能,它們對于 load 成為 insert 的替代方案有著非凡的功效,這兩個功能是:從游標裝載和從調用層接口(CLI)應用程序裝載。 12345678910下一頁 從游標裝載這種方法可用于應用程序的程序代碼(通過 db2Load API),或用于 DB2 腳本。下面是后一種情況的一個例子:declare staffcursor cursor forselect * from staff;load from staffcursor of cursor insert into myschema.new_staff;這兩行可以用下面一行替代:insert into myschema.new_staff select * from staff同等效的 INSERT ... SELECT 語句相比,從游標裝載幾乎可以提高 20% 的性能。從 CLI 裝載這種方法顯然只限于調用層接口(CLI)應用程序,但是它非常快。這種技巧非常類似于數組插入,DB2 附帶了這樣的示例,使用 load 時的速度是使用經過完全優化的數組插入時的兩倍,幾乎要比未經優化的數組插入快 10 倍。所有INSERT可以改進的地方讓我們看看插入處理的一些必要步驟,以及我們可以用來優化這些步驟的技巧。1. 語句預備作為一條 SQL 語句,INSERT 語句在執行之前必須由 DB2 進行編譯。這一步驟可以自動發生(例如在 CLP 中,或者在一次 CLI SQLExecDirect 調用中),也可以顯式地進行(例如,通過一條 SQL Prepare、CLI SQLPrepare 或 JDBC prepareStatement 語句)。該編譯過程牽涉到授權檢查、優化,以及將語句轉化為可執行格式時所需的其他一些活動。在編譯語句時,語句的訪問計劃被存儲在包緩存中。假如重復地執行相同的 INSERT 語句,則該語句的訪問計劃(通常)會進入到包緩存中,這樣就免除了編譯的開銷。然而,假如 insert 語句對于每一行有不同的值,那么每一條語句都將被看成是惟一的,必須單獨地進行編譯。因此,將像下面這樣的重復語句:insert into mytable values (1, 'abc')insert into mytable values (2, 'def') 上一頁1234567下一頁 等等,換成帶有參數標記的語句,一次預備,重復執行,這樣做是十分可取的:insert into mytable values (?, ?)使用參數標記可以讓一系列的 insert 的運行速度提高數倍。(在靜態 SQL 程序中使用主機變量也可以獲得類似的好處。)2. 發送列值到服務器可以歸為這一類的優化技巧有好幾種。最重要的一種技巧是在每條 insert 語句中包括多行,這樣就可以避免對于每一行都進行客戶機-服務器通信,同時也減少了 DB2 開銷。可用于多行插入的技巧有:在 VALUES 子句中包含多行的內容。例如,下面的語句將插入三行:INSERT INTO mytable VALUES (1, 'abc'), (2, 'def'), (3, 'ghi') 在 CLI 中使用數組插入(array insert)。這需要預備一條帶參數標記的 INSERT 語句,定義一個用于存儲要插入的值的數組,將該數組綁定到參數標記,以及對于每個數組中的一組內容執行一次 insert。而且,示例程序 sqllib/samples/cli/tbload.c 提供了數組插入的基本框架(但是執行的是 CLI LOAD)。從不使用數組改為使用包含 100 行的數組,可以將時間縮短大約 2.5 倍。所以應該盡可能地使用包含至少 100 行的數組。在 JDBC 中使用批處理操作。這跟 CLI 中的數組插入一樣,基于相同的概念,但是實現細節有所不同。當通過 prepareStatement 方法預備了 insert 語句之后,剩下的步驟是針對每一列調用適當的 setXXXX 方法(例如,setString 或 setInt),然后是 addBatch。對于要插入的每一行,都要重復這些步驟,然后調用 executeBatch 來執行插入。要查看這方面的例子,請參閱“參考資料一節中的 JDBC Tutorial。使用 load 將數據快速地裝入到一個 staging 表中,然后使用 INSERT ... SELECT 填充主表。(通過這種方法節省下來的代價源于 load 的速度非??欤偌由?INSERT ... SELECT 是在 DB2 內(在服務器上)傳輸數據的,從而消除了通信上的代價。一般情況下我們不會使用這種方法,除非在 INSERT ... SELECT 中還要另外做 load 無法完成的處理。 上一頁12345678下一頁 假如不可能在一條 insert 語句中傳遞多行,那么最好是將多條 insert 語句組成一組,將它們一起從客戶機傳遞到服務器。(不過,這意味著每條 insert 都包含不同的值,都需要預備,因而其性能實際上要比使用參數標記情況下的性能更差一些。)將多條語句組合成一條語句可以通過 Compound SQL 來實現。在 SQL 中,復合語句是通過 BEGIN ATOMIC 或 BEGIN COMPOUND 語句創建的。在 CLI 中,復合語句可以通過 SQLExecDirect 和 SQLExecute 調用來建立。對于 DB2 V8 FixPak 4,另一種生成復合語句的方法是在(對一條預處理語句)發出多個 SQLExecute 調用之前設置語句屬性 SQL_ATTR_CHAINING_BEGIN,并在調用之后設置語句屬性 SQL_ATTR_CHAINING_END。下面是關于該話題的其他一些建議:假如可能的話,讓客戶機與要存取的數據庫使用相同的代碼頁,以避免在服務器上的轉換代價。數據庫的代碼頁可以通過運行“get db cfg for 來確定。在某些情況下,CLI 會自動執行數據類型轉換,但是這樣同時也會帶來看不見的(小小的)性能損耗。因此,盡量使插入值直接處于與相應列對應的格式。將應用程序中與插入相關的設置開銷最小化。例如,當在 CLI 中使用數組插入時,對于整個一組插入,應該盡量保證對于每一列只執行一次 SQLBindParameter,而不是對每一組數組內容都執行一次。對于個體來說,這些調用的代價并不高,但是這些代價是累積的。3. 找到存儲行的地方DB2 使用三種算法中的一種來確定將行插入到哪里。(假如使用了多維群集(Multi-dimensional Clustering,MDC),則另當別論,我們在這里不予討論。)缺省模式是,DB2 搜索散布在表的各頁上的自由空間控制記錄(Free Space Control Records,FSCR),以找到有足夠自由空間存放新行的頁。顯然,假如每頁上的自由空間都比較少的話,就要浪費很多的搜索時間。為了應付這一點, DB2 提供了 DB2MAXFSCRSEARCH 注冊表變量,以便答應將搜索范圍限制為少于缺省的 5 頁。 上一頁123456789下一頁 當表是通過 ALTER TABLE 以 APPEND 模式放置時,就要使用第二種算法。這樣就完全避免了 FSCR 搜索,因為只需簡單地將行直接放到表的末尾。當表有群集索引(clustering index)時,就要用到最后一種算法。在這種情況下,DB2 試圖將每一行插入到有相似鍵值的一頁中。假如那一頁沒有空間了,DB2 就會嘗試四周的頁,假如四周的頁也沒有空間,DB2 就進行 FSCR 搜索。假如只考慮插入時間的優化,那么使用 APPEND 模式對于批量插入是最快的一種方法,但是這種方法的效果遠不如我們這里討論的很多其他方法那么成效顯著。第二好的方法應該是采用缺省算法,但是,假如在最佳環境中,更改 DB2MAXFSCRSEARCH 的值影響很小,而在一個 I/O 約束較少的環境中,這種更改所造成的影響就比較可觀了。假如有群集索引,則對 insert 的性能會有很大的負面影響,這一點也不驚異,因為使用群集索引的目的就是通過在插入時做額外的工作來提高查詢(即 select)性能的。假如的確需要群集索引,那么可以通過確保有足夠的自由空間來使其對插入的影響降至最?。菏褂?ALTER TABLE 增加 PCTFREE,然后使用 REORG 預留自由空間。不過,假如答應太多自由空間的存在,則可能導致查詢時需要讀取額外的頁,這反而大大違反了使用群集索引的本意。另一種選擇是,在批量插入之前先刪除群集索引,而后再重新創建群集索引,也許這是最優的方法(創建群集索引的開銷跟創建常規索引的開銷差不多,都不是很大,只是在插入時有額外的開銷)。4. 緩沖池、I/O 和頁清除每一條 insert 在執行時,都是先將新行存儲在一個頁中,并最終將那個頁寫到磁盤上。一旦像前面討論的那樣指定了頁,那么在將行添加到該頁之前,該頁必須已經在緩沖池中。對于批量插入,大部分頁都是最新指派給表的,因此讓我們關注一下對新頁的處理。 上一頁12345678910下一頁 假如表在系統治理存儲的(System Managed Storage,SMS)表空間中,當需要新頁時,缺省情況下是從文件系統中分別為每一頁分配空間。但是,假如對數據庫運行了 db2empfa 命令,那么每個 SMS 表空間就會為新頁一次性分配一個區段。我們建議運行 db2empfa 命令,并使用 32 頁的區段。對于數據庫治理的存儲(Database Managed Storage,DMS)表空間,空間是在創建表空間時就預先分配的,但是頁的區段則是在插入處理過程中指派給表的。與 SMS 相比,DMS 對空間的預分配可以提高大約 20% 的性能 -- 使用 DMS 時,更改區段大小并沒有明顯的效果。假如表上有索引,則對于每個插入的行,都要添加一個條目到每條索引。這要求在緩沖池中存在適當的索引頁。晚些時候我們將討論索引的維護,但是現在只需記住,插入時對緩沖池和 I/O 的考慮也類似地適用于索引頁,對于數據頁也是一樣。隨著插入的進行,越來越多的頁中將填入被插入的行,但是,DB2 不要求在 insert 或 Commit 后將任何新插入的或更新后的數據或索引寫入到磁盤。(這是由于 DB2 的 writeahead 日志記錄算法。但是有一個例外,這將在關于日志記錄的小節中論述到。)然而,這些頁需要在某一時刻寫到磁盤上,這個時刻可能會在數據庫關閉時才會輪到。一般來說,對于批量插入,您會希望積極地進行 異步頁清除(asynchronous page cleaning),這樣在緩沖池中就總有可用于新頁的空余位置。頁清除率,或者說總缺頁率,可能導致計時上的很大不同,使得性能比較輕易產生誤解。例如,假如使用 100,000 頁的緩沖池,并且不存在頁清除,則批量插入在結束前不會有任何新的或更改過的(“臟的)頁寫到磁盤上,但是隨后的操作(例如選擇,甚至乎關閉數據庫)都將被大大推遲,因為這時有至多 100,000 個在插入時產生的臟頁要寫到磁盤上。另一方面,假如在同一情況下進行了積極的頁清除,則批量插入過程可能要花更長的時間,但是此后緩沖池中的臟頁要少一些,從而使得隨后的任務執行起來性能更佳。至于那些結果中到底哪個要更好些,我們并不是總能分得清,但是通常來說,將所有臟頁都存儲在緩沖池中是不可能的,所以為了取得最佳性能,采取有效的頁清除是有必要的。 上一頁234567891011下一頁 為了盡可能好地進行頁清除:將 CHNGPGS_THRESH 數據庫配置參數的值從缺省的 60 減少到 5 這么低。這個參數決定緩沖池中臟頁的閾值百分比,當臟頁達到這個百分比時,就會啟動頁清除。嘗試啟用注冊表變量 DB2_USE_ALTERNATE_PAGE_CLEANING(在 DB2 V8 FixPak 4 中最新提供)。通過將這個變量設置成 ON,可以為頁清除提供一種比缺省方法(基于 CHNGPGS_THRESH 和 LSN 間隙觸發器)更積極的方法。我沒有評測過其效果。請參閱 FixPak 4 Release Notes 以了解這方面的信息。確保 NUM_IOCLEANERS 數據庫配置參數的值至少等于數據庫中物理存儲設備的數量。至于 I/O 本身,當需要建立索引時,可以通過使用盡可能大的緩沖池來將 I/O 活動減至最少。假如不存在索引,則使用較大的緩沖池幫助不大,而只是推遲了 I/O。也就是說,它答應所有新頁暫時安放在緩沖池中,但是最終仍需要將這些頁寫到磁盤上。當發生將頁寫到磁盤的 I/O 時,通過一些常規的 I/O 調優步驟可以加快這一過程,例如:將表空間分布在多個容器(這些容器映射到不同磁盤)。盡可能使用最快的硬件和存儲治理配置,這包括磁盤和通道速度、寫緩存以及并行寫等因素。避免 RAID5(除非是與像 Shark 這樣有效的存儲設備一起使用)。5. 鎖缺省情況下,每一個插入的行之上都有一個 X 鎖,這個鎖是在該行創建時就開始有的,一直到 insert 被提交。有兩個跟 insert 和鎖相關的性能問題:為獲得和釋放鎖而產生的 CPU 開銷??赡苡捎阪i沖突而導致的并發問題。對于經過良好優化的批量插入,由獲得每一行之上的一個 X 鎖以及后來釋放該鎖引起的 CPU 開銷是比較可觀的。對于每個新行之上的鎖,惟一可以替代的是表鎖(DB2 中沒有頁鎖)。當使用表鎖時,耗時減少了 3%。有 3 種情況可以導致表鎖的使用,在討論表鎖的缺點之前,我們先用一點時間看看這 3 種情況: 上一頁34567891011下一頁 運行 ALTER TABLE LOCKSIZE TABLE。這將導致 DB2 為隨后使用該表的所有 SQL 語句使用一個表鎖,直到 locksize 參數改回到 ROW。運行 LOCK TABLE IN EXCLUSIVE MODE。這將導致表上立即上了一個 X 鎖。注重,在下一次提交(或回滾)的時候,這個表將被釋放,因此,假如您要運行一個測試,測試中每 N 行提交一次,那么就需要在每次提交之后重復執行 LOCK TABLE。使用缺省鎖,但是讓 LOCKLIST 和 MAXLOCKS 數據庫配置參數的值比較小。當獲得少量的行鎖時,行鎖就會自動地逐漸升級為表鎖。當然,所有這些的缺點就在于并發的影響:假如表上有一個 X 鎖,那么其他應用程序除非使用了隔離級別 UR(未提交的讀),否則都不能訪問該表。假如知道獨占訪問不會導致問題,那么就應該盡量使用表鎖。但是,即使您堅持使用行鎖,也應記住,在批量插入期間,表中可能存在數千個有 X 鎖的新行,所以就可能與其他使用該表的應用程序產生沖突。通過一些方法可以將這些沖突減至最少:確保鎖的升級不會無故發生。您可能需要加大 LOCKLIST 和/或 MAXLOCKS 的值,以答應插入應用程序有足夠的鎖。對于其他的應用程序,使用隔離級別 UR。對于 V8 FixPak 4,或許也可以通過 DB2_EVALUNCOMMITTED 注冊表變量來減少鎖沖突:假如將該變量設置為 YES,那么在很多情況下,只能獲得那些符合某個謂詞的行上的鎖,而并不是獲得被檢查的所有行上的鎖。發出一個 COMMIT 命令以釋放鎖,因此假如更頻繁地提交的話就足以減輕鎖沖突的負擔。注重在 V7 中,存在涉及 insert 和鍵鎖的并發問題,但是在 V8 中,由于提供了 type-2 索引,這些問題實際上已經不見了。假如要遷移到 V8 中來,那么應該確保使用帶 CONVERT 要害字的 REORG INDEXES 命令,以便將索引從 type-1 轉換為 type-2。 上一頁4567891011下一頁 在 V7 中,插入過程中可能使用 W 或 NW 鎖,但是在 V8 中只有在使用了 type-1 索引或者隔離級別為 RR 的情況下才會出現這兩種鎖。因此,應盡可能避免這兩種情況。一條 insert 所據有的鎖(通常是一個 X 鎖)通常不會受隔離級別的影響。例如,使用隔離級別 UR 不會阻止從插入的行上獲得鎖。然而,假如使用了 INSERT ... SELECT,則隔離級別將影響從 SELECT 獲得的鎖。6. 日志記錄缺省情況下,每條 insert 都會被記錄下來,以用于恢復。日志記錄首先被寫到內存中的日志緩沖池,然后再寫到日志文件,通常是在日志緩沖池已滿或者發生了一次提交時寫到日志文件的。對批量插入的日志記錄的優化實際上就是最小化日志記錄寫的次數,以及使寫的速度盡可能快。這里首先考慮的是日志緩沖池的大小,這由數據庫配置參數 LOGBUFSZ 來控制。該參數缺省值為 8 頁或 32 K,這與大多數批量插入所需的理想日志緩沖池大小相比要小些。舉個例子,對于一個批量插入,假設對于每一行的日志內容有 200 字節,則在插入了 160 行之后,日志緩沖池就將被填滿。假如要插入 1000 行,因為日志緩沖池將被填滿幾次,再加上提交,所以大概有 6 次日志寫。假如將 LOGBUFSZ 的值增加到 64 頁(256K)或者更大,緩沖池就不會被填滿,這樣的話對于該批量插入就只有一次日志寫(在提交時)。通過使用更大的 LOGBUFSZ 可以獲得大約 13% 的性能提升。較大日志緩沖池的不利之處是,緊急事故恢復所花的時間可能要稍微長一點。減少日志寫的另一種可能性是對新行要插入到的那個表使用“ALTER TABLE ACTIVATE NOT LOGGED INITIALLY(NLI)。假如這樣做了,那么在該工作單元內不會記錄任何 insert 操作,但是這里存在兩個與 NLI 有關的重要問題: 上一頁567891011下一頁 假如有一條語句失敗,那么這個表將被標記為不可訪問的,并且需要被刪除掉。這與其他恢復問題(請參閱 SQL Reference 關于 Create Table 的討論)一起使得 NLI 在很多情況下不能成為可行的方法。在工作單元最后進行的提交,必須等到在此工作單元內涉及的所有臟頁都被寫到磁盤之后才能完成。這意味著這種提交要占用大量的時間。假如沒有積極地進行頁清除,那么在使用 NLI 的情況下,Insert 加上提交所耗費的總時間要更長一些。將 NLI 與積極的頁清除一起使用的時候,可以大大減少耗時。假如使用 NLI,就要瞪大眼睛盯緊提交操作所耗費的時間。至于提高日志寫的速度,有下面一些可能性:將日志與新行所要插入到的表分別放在不同的磁盤上。在操作系統層將日志分放到多個磁盤??紤]為日志使用原始設備(raw device),但是要注重,這樣治理起來要更困難些。避免使用 RAID 5,因為它不適合于寫密集型(write-intensive)活動。7. 提交提交迫使將日志記錄寫到磁盤上,以保證提交的插入肯定會存在于數據庫中,并且釋放新行上的鎖。這些都是有價值的活動,但是因為 Commit 總是要牽涉到同步 I/O(對于日志),而 insert 則不會,所以 Commit 的開銷很輕易高于 insert 的開銷。因此,在進行批量插入時,每一行都提交一次的做法對于性能來說是很糟糕的,所以應確保不使用自動提交(對于 CLI 和 CLP 來說缺省情況正是如此)。建議大約每 1000 行提交一次:當每 1000 行而不是一兩行提交一次時,性能可以提高大概 10 倍。不過,一次提交多于 1000 行只能節省少量的時間,但是一旦出現失敗,恢復起來所花的時間要更多。對上述方法的一種修正:假如 MINCOMMIT 數據庫配置參數的值大于 1 (缺省值),則 DB2 就不必對每次 commit 都進行一次同步 I/O,而是等待,并試圖與一組事件一起共享日志 I/O。對于某些環境來講,這樣做是有好處,但是對于批量插入經常沒有作用,甚至有負作用,因此,假如要執行的要害任務是批量插入,就應該讓 MINCOMMIT 的值保持為 1。 上一頁67891011下一頁
標簽: DB2 數據庫
主站蜘蛛池模板: 澄城县| 南江县| 安岳县| 凌源市| 东宁县| 宁化县| 尖扎县| 临洮县| 开化县| 大渡口区| 汝城县| 新化县| 昌江| 比如县| 汉川市| 九龙县| 乳源| 扶绥县| 房产| 呼玛县| 乾安县| 武汉市| 宜城市| 阿尔山市| 收藏| 安远县| 黄龙县| 阿合奇县| 吉林省| 界首市| 商河县| 思茅市| 利津县| 龙泉市| 天津市| 南城县| 临城县| 青铜峡市| 淮南市| 周口市| 东莞市|