程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> DB2數據庫 >> DB2教程 >> DB2中INSERT的優化方法

DB2中INSERT的優化方法

編輯:DB2教程

DB2數據庫中INSERT的效率如何提高,是很多人都在思考的問題,本文將為您介紹DB2數據庫中INSERT 處理過程,及對應優化方法,供您參考,希望對您有所啟迪。

語句准備

動態

自動(例如在 CLP 中,或者在一次 CLI SQLExecDirect 調用中) 
顯式(例如,通過一條 SQL Prepare、CLI SQLPrepare 或 JDBC prepareStatement 語句) 
靜態 一次編譯

發送列值到服務器

在 VALUES 子句中包含多行的內容,示例程序 sqllib/samples/cli/tbload.c 
在 JDBC 中使用批處理操作 
使用 load 將數據快速地裝入到一個 staging 表中,然後使用 INSERT ... SELECT 填充主表。 
將多條語句組合成一條語句可以通過 Compound SQL 來實現 ??

讓客戶機與要存取的數據庫使用相同的代碼頁 
減少自動執行數據類型轉換 
將應用程序中與插入相關的設置開銷最小化 ??

找到存儲行的地方(分配和選擇頁)

DB2 使用三種算法中的一種來確定將行插入到哪裡。(如果使用了多維群集(Multi-dimensional Clustering,MDC)

缺省模式是,DB2 搜索散布在表的各頁上的自由空間控制記錄(Free Space Control Records,FSCR),以找到有足夠自由空間存放新行的頁。DB2 提供了 DB2MAXFSCRSEARCH 注冊表變量,以便允許將搜索范圍限制為少於缺省的 5 頁 
當表是通過 ALTER TABLE 以 APPEND 模式放置時,就要使用第二種算法。這樣就完全避免了 FSCR 搜索,因為只需簡單地將行直接放到表的末尾。 
當表有群集索引(clustering index)時,就要用到最後一種算法。在這種情況下,DB2 試圖將每一行插入到有相似鍵值的一頁中。如果那一頁沒有空間了,DB2 就會嘗試附近的頁,如果附近的頁也沒有空間,DB2 就進行 FSCR 搜索。

如果只考慮插入時間的優化,那麼使用 APPEND 模式對於批量插入是最快的一種方法,但是這種方法的效果遠不如我們這裡討論的很多其他方法那麼成效顯著。第二好的方法應該是采用缺省算法,但是,如果在最 佳環境中,更改 DB2MAXFSCRSEARCH 的值影響很小,而在一個 I/O 約束較少的環境中,這種更改所造成的影響就比較可觀了。

如果有群集索引,則對 insert 的性能會有很大的負面影響,這一點也不驚奇,因為使用群集索引的目的就是通過在插入時做額外的工作來提高查詢(即 select)性能的。如果的確需要群集索引,那麼可以通過確保有足夠的自由空間來使其對插入的影響降至最小:使用 ALTER TABLE 增加 PCTFREE,然後使用 REORG 預留自由空間。不過,如果允許太多自由空間的存在,則可能導致查詢時需要讀取額外的頁,這反而大大違反了使用群集索引的本意。另一種選擇是,在批量插入之 前先刪除群集索引,而後再重新創建群集索引,也許這是最優的方法(創建群集索引的開銷跟創建常規索引的開銷差不多,都不是很大,只是在插入時有額外的開銷)。

緩沖池、I/O 和頁清除

一旦像前面討論的那樣指定了頁,那麼在將行添加到該頁之前,該頁必須已經在緩沖池中。對於批量插入,大部分頁都是最新指派給表的,因此讓我們關注一下對新頁的處理。

SMS表空間中,當需要新頁時,缺省情況下是從文件系統中分別為每一頁分配空間。但是,如果對數據庫運行了 db2empfa 命令,那麼每個 SMS 表空間就會為新頁一次性分配一個區段。

DMS 時,更改區段大小並沒有明顯的效果。

如果表上有索引,則對於每個插入的行,都要添加一個條目到每條索引。這要求在緩沖池中存在適當的索引頁。

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

一般來說,對於批量插入,您會希望積極地進行 異步頁清除(asynchronous page cleaning),這樣在緩沖池中就總有可用於新頁的空余位置。

如果在同一情況下進行了積極的頁清除,則批量插入過程可能要花更長的時間,但是此後緩沖池中的髒頁要少一些,從 而使得隨後的任務執行起來性能更佳。至於那些結果中到底哪個要更好些,我們並不是總能分得清,但是通常來說,將所有髒頁都存儲在緩沖池中是不可能的,所以 為了取得最佳性能,采取有效的頁清除是有必要的。

為了盡可能好地進行頁清除:將 CHNGPGS_THRESH 數據庫配置參數的值從缺省的 60 減少到 5 這麼低。這個參數決定緩沖池中髒頁的阈值百分比,當髒頁達到這個百分比時,就會啟動頁清除。

嘗試啟用注冊表變量 DB2_USE_ALTERNATE_PAGE_CLEANING(在 DB2 V8 FixPak 4 中最新提供)。通過將這個變量設置成 ON,可以為頁清除提供一種比缺省方法(基於 CHNGPGS_THRESH 和 LSN 間隙觸發器)更積極的方法。

至於 I/O 本身,當需要建立索引時,可以通過使用盡可能大的緩沖池來將 I/O 活動減至最少。如果不存在索引,則使用較大的緩沖池幫助不大,而只是推遲了 I/O。也就是說,它允許所有新頁暫時安放在緩沖池中,但是最終仍需要將這些頁寫到磁盤上。

當發生將頁寫到磁盤的 I/O 時,通過一些常規的 I/O 調優步驟可以加快這一過程,例如:

將表空間分布在多個容器(這些容器映射到不同磁盤)。

盡可能使用最快的硬件和存儲管理配置,這包括磁盤和通道速度、寫緩存以及並行寫等因素。

避免 RAID5(除非是與像 Shark 這樣有效的存儲設備一起使

為獲得和釋放鎖而產生的 CPU 開銷。
由獲得每一行之上的一個 X 鎖以及後來釋放該鎖引起的 CPU 開銷是比較可觀的。對於每個新行之上的鎖,惟一可以替代的是表鎖(DB2 中沒有頁鎖)。當使用表鎖時,耗時減少了 3%。

運行 ALTER TABLE LOCKSIZE TABLE。這將導致 DB2 為隨後使用該表的所有 SQL 語句使用一個表鎖,直到 locksize 參數改回到 ROW。 
運行 LOCK TABLE IN EXCLUSIVE MODE。這將導致表上立即上了一個 X 鎖。注意,在下一次提交(或回滾)的時候,這個表將被釋放,因此,如果您要運行一個測試,測試中每 N 行提交一次,那麼就需要在每次提交之後重復執行 LOCK TABLE。 
使用缺省鎖,但是讓 LOCKLIST 和 MAXLOCKS 數據庫配置參數的值比較小。當獲得少量的行鎖時,行鎖就會自動地逐漸升級為表鎖。

可能由於鎖沖突而導致的並發問題。

對於 V8 FixPak 4,或許也可以通過 DB2_EVALUNCOMMITTED 注冊表變量來減少鎖沖突:如果將該變量設置為 YES,那麼在很多情況下,只能獲得那些符合某個謂詞的行上的鎖,而並不是獲得被檢查的所有行上的鎖。

發出一個 COMMIT 命令以釋放鎖,因此如果更頻繁地提交的話就足以減輕鎖沖突的負擔。

日志記錄

缺省情況下,每條 insert 都會被記錄下來,以用於恢復。日志記錄首先被寫到內存中的日志緩沖池,然後再寫到日志文件,通常是在日志緩沖池已滿或者發生了一次提交時寫到日志文件的。 對批量插入的日志記錄的優化實際上就是最小化日志記錄寫的次數,以及使寫的速度盡可能快。

這裡首先考慮的是日志緩沖池的大小,這由數據庫配置參數 LOGBUFSZ 來控制。該參數缺省值為 8 頁或 32 K,這與大多數批量插入所需的理想日志緩沖池大小相比要小些。

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

如果有一條語句失敗,那麼這個表將被標記為不可訪問的,並且需要被刪除掉。這與其他恢復問題(請參閱 SQL Reference 關於 Create Table 的討論)一起使得 NLI 在很多情況下不能成為可行的方法。 
在工作單元最後進行的提交,必須等到在此工作單元內涉及的所有髒頁都被寫到磁盤之後才能完成。這意味著這種提交 要占用大量的時間。如果沒有積極地進行頁清除,那麼在使用 NLI 的情況下,Insert 加上提交所耗費的總時間要更長一些。將 NLI 與積極的頁清除一起使用的時候,可以大大減少耗時。如果使用 NLI,就要瞪大眼睛盯緊提交操作所耗費的時間。

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

將日志與新行所要插入到的表分別放在不同的磁盤上。

在操作系統層將日志分放到多個磁盤。

考慮為日志使用原始設備(raw device),但是要注意,這樣管理起來要更困難些。

避免使用 RAID 5,因為它不適合於寫密集型(write-intensive)活動

提交

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

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

索引維護

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

遍歷每個索引樹,在樹的每一層搜索一個頁,以確定新條目必須存儲在哪裡(索引條目總是按鍵順序存儲的),這一過程所引起的 CPU 開銷;

將所有搜索到的頁讀入緩沖池,並最終將每個更新後的頁寫到磁盤上的 I/O 開銷。

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved