程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> Oracle數據庫 >> Oracle數據庫基礎 >> DB2數據庫中提高INSERT性能詳解(下)

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

編輯:Oracle數據庫基礎

一般來說,對於批量插入,您會希望積極地進行 異步頁清除(asynchronous page cleaning),這樣在緩沖池中就總有可用於新頁的空余位置。頁清除率,或者說總缺頁率,可能導致計時上的很大不同,使得性能比較容易產生誤解。例如,如果使用 100,000 頁的緩沖池,並且不存在頁清除,則批量插入在結束前不會有任何新的或更改過的(“髒的”)頁寫到磁盤上,但是隨後的操作(例如選擇,甚至乎關閉數據庫)都將被大大推遲,因為這時有至多 100,000 個在插入時產生的髒頁要寫到磁盤上。另一方面,如果在同一情況下進行了積極的頁清除,則批量插入過程可能要花更長的時間,但是此後緩沖池中的髒頁要少一些,從而使得隨後的任務執行起來性能更佳。至於那些結果中到底哪個要更好些,我們並不是總能分得清,但是通常來說,將所有髒頁都存儲在緩沖池中是不可能的,所以為了取得最佳性能,采取有效的頁清除是有必要的。

為了盡可能好地進行頁清除:將 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 開銷。

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

對於經過良好優化的批量插入,由獲得每一行之上的一個 X 鎖以及後來釋放該鎖引起的 CPU 開銷是比較可觀的。對於每個新行之上的鎖,惟一可以替代的是表鎖(DB2 中沒有頁鎖)。當使用表鎖時,耗時減少了 3%。有 3 種情況可以導致表鎖的使用,在討論表鎖的缺點之前,我們先用一點時間看看這 3 種情況:

運行 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 命令以釋放鎖,因此如果更頻繁地提交的話就足以減輕鎖沖突的負擔。

一般來說,對於批量插入,您會希望積極地進行 異步頁清除(asynchronous page cleaning),這樣在緩沖池中就總有可用於新頁的空余位置。頁清除率,或者說總缺頁率,可能導致計時上的很大不同,使得性能比較容易產生誤解。例如,如果使用 100,000 頁的緩沖池,並且不存在頁清除,則批量插入在結束前不會有任何新的或更改過的(“髒的”)頁寫到磁盤上,但是隨後的操作(例如選擇,甚至乎關閉數據庫)都將被大大推遲,因為這時有至多 100,000 個在插入時產生的髒頁要寫到磁盤上。另一方面,如果在同一情況下進行了積極的頁清除,則批量插入過程可能要花更長的時間,但是此後緩沖池中的髒頁要少一些,從而使得隨後的任務執行起來性能更佳。至於那些結果中到底哪個要更好些,我們並不是總能分得清,但是通常來說,將所有髒頁都存儲在緩沖池中是不可能的,所以為了取得最佳性能,采取有效的頁清除是有必要的。

為了盡可能好地進行頁清除:將 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 開銷。

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

對於經過良好優化的批量插入,由獲得每一行之上的一個 X 鎖以及後來釋放該鎖引起的 CPU 開銷是比較可觀的。對於每個新行之上的鎖,惟一可以替代的是表鎖(DB2 中沒有頁鎖)。當使用表鎖時,耗時減少了 3%。有 3 種情況可以導致表鎖的使用,在討論表鎖的缺點之前,我們先用一點時間看看這 3 種情況:

運行 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。

在 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 有關的重要問題:

如果有一條語句失敗,那麼這個表將被標記為不可訪問的,並且需要被刪除掉。這與其他恢復問題(請參閱 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。

可以選擇性地進行改進的地方

對於一次 insert,有幾種類型的處理將自動發生。如果您的主要目標只是減少插入時間,那麼最簡單的方法是避免所有這些處理的開銷,但是如果從總體上考慮的話,這樣做未必值得。讓我們依次進行討論。

索引維護

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

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

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

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

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

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