1. 程式人生 > 其它 >一條SQL語句提交後,db2都做了什麼?

一條SQL語句提交後,db2都做了什麼?

一條SQL語句提交後,db2都做了什麼?

somenzz 微信公眾號「Python七號」和你一起精進。  

一直在做 db2 資料倉庫的運維工作,對一些常用操作已經非常熟悉,但是總感覺自己學到是仍然是操作的細節,而不是真正的知識。如果你問我,一條 SQL 語句提交後,db2 都做了哪些工作,我可能會有點慌,因為我不能肯定的回答出來。於是,我就搜尋一些資料,結合自己的理解,總結一下關於 db2 體系結構,db2 記憶體模型,SQL語句的執行過程,希望對正在使用 db2 的你有所幫助。

為什麼要學習架構?

如果僅滿足於 select * from where 這種簡單的查詢,對於業務人員可能夠用了,但對於程式設計師,這遠遠不夠,系統初建成之後,應用的效能還可以,但隨著資料的累積,一些查詢會非常低效,會影響前端使用者體驗,如果不懂資料庫架構和原理,是無法有效的調優的,也無法從根本上解決問題。相反如果瞭解資料庫的架構,那麼在最初設計資料庫,設計表時就可以高瞻遠矚,把效能惡化從源頭上消滅。

db2 體系結構

db2 是 c/s 架構,客戶端發起 SQL 請求,伺服器返回相應結果。 體系結構如下圖所示: 

 

在本地連線 db2 服務時使用共享記憶體和訊號通訊,遠端連線 db2 伺服器,則使用協議(例如命名管道 (NPIPE) 或 TCP/IP)進行通訊。

可以這樣形象地理解:如果把資料庫比作大型超市,那麼客戶機就是消費者,請求的資料即是商品,緩衝區就是超市的貨架,整個超市的空間就是資料庫所用的記憶體,而倉庫是資料的最終儲存,即磁碟。

上圖中的圓圈或圓圈組表示引擎分派單元(EDU),你可以理解為導購員,在計算機中叫程序或執行緒。如果消費者請求的商品(資料)在超市的貨架上(緩衝區)中,則稱為緩衝區命中,直接從貨架(緩衝區)將商品拿給消費者,購物結束(花費時間較少)。否則,服務員(預取程式)需要根據倉庫清單(索引)去後臺倉庫(磁碟)為消費者查詢並取出商品,商品仍會先放在超市的貨架上,再拿給消費者(花費時間較長)。

與實際情況不同的是,消費者購完商品後,商品並不真正的從貨架上移出,而仍保留,一旦有人購買相同的商品時,可以直接從貨架上取走從而節省時間,這是因為資料只要不刪除,是可以重複讀取的。

還有一種情況就是:如果兩個消費者都想買同一型別的商品,恰好空間有限,同時只能有一個人佔據商品所在的空間,他要檢查商品來確定要不要購買,那麼另一個人只能等前者確定購或者不購(回退)之後才能佔據相應空間來做同樣的事情,這就是死鎖等待,如果超過規定的時間(死鎖等待時間 dlchktime)那麼這個消費者可能要報警了,這就是 911 死鎖。

以上任何一個環節都有可能造成購物時間過長,消費者等待,從而導致前端使用者體驗極差,也就是我們常見的資料庫效能下降問題。

超市的內部空間設計的好不好,直接影響購物的整體流程,因此要想打造高效地超市,就要先對超市的內部空間設計要有足夠的瞭解,這就是 db2 的記憶體模型。

db2 的記憶體模型

理解 DB2 如何使用記憶體,可以防止過度分配記憶體,並有助於對記憶體的使用進行調優,從而獲得更好的效能。下圖為官網提供的 db2 記憶體模型: 

 

db2 在 4 種不同的記憶體集(memory set)內拆分和管理記憶體。如上圖所示,從上到下依次為: 1. 例項共享記憶體(instance shared memory) 2. 資料庫共享記憶體(database shared memory) 3. 應用程式組共享記憶體(application group shared memory) 4. 代理私有記憶體(agent private memory)

每種記憶體集由各種不同的記憶體池(亦稱堆)組成,下面依次做介紹。

1、例項共享記憶體

首先要理解什麼是例項,從 DB2 的體系結構方面來看,例項實際上就是DB2 的執行程式碼和資料庫物件的中間邏輯層。例項可以看成是關於所有的資料庫及其物件的邏輯集合,例項為資料庫執行提供一個環境。一般地,我們會那一個數據庫例項使用者如 xxxx_inst 使用者,然後使用這個使用者來安裝資料庫,並使用這個例項使用者來啟動或停止資料庫服務。

DB2 資料庫和例項之間的區別:資料庫是物理的,我們的表、索引存放在資料庫中要佔物理儲存的;而例項是邏輯的,是共享記憶體、程序和一些配置檔案(例項目錄)的集合。

每個 DB2 例項都有一個例項共享記憶體。例項共享記憶體是在資料庫管理器啟動(db2start)時分配的,並隨著資料庫管理器的停止(db2stop)而釋放。這種記憶體集用於例項級的任務,例如監控、審計和節點間通訊。下面的資料庫管理器配置(dbm cfg)引數控制著對例項共享記憶體以及其中個別記憶體池的限制: - 例項記憶體( INSTANCE_MEMORY)。 - 監視器堆( MON_HEAP_SZ):用於監控。 - Audit Buffer( AUDIT_BUF_SZ):用於 db2audit 實用程式。 - Fast Communication buffers (FCM_NUM_BUFFERS):用於分割槽之間的節點間通訊。僅適用於分割槽的例項。 下面為某資料庫通過 db2 get dbm cfg 輸出的相應資訊:

Database monitor heap size (4KB)          (MON_HEAP_SZ) = AUTOMATIC(90)
 Java Virtual Machine heap size (4KB)     (JAVA_HEAP_SZ) = 2048
 Audit buffer size (4KB)                  (AUDIT_BUF_SZ) = 0
 Size of instance shared memory (4KB)  (INSTANCE_MEMORY) = AUTOMATIC(7367929)
 Agent stack size                       (AGENT_STACK_SZ) = 1024
 Sort heap threshold (4KB)                  (SHEAPTHRES) = 0
...
 No. of int. communication buffers(4KB)(FCM_NUM_BUFFERS) = AUTOMATIC(4096)

INSTANCE_MEMORY 引數指定為例項管理預留的記憶體數量。預設值是 AUTOMATIC。這意味著 DB2 將根據監視器堆、審計緩衝區和 FCM 緩衝區的大小計算當前配置所需的例項記憶體數量。此外,DB2 還將分配一些額外的記憶體,作為溢位緩衝區。每當某個堆超出了其配置的大小時,便可以使用溢位緩衝區來滿足例項共享記憶體區內任何堆的峰值需求。在這種情況下,個別堆的設定是軟限制的,它們可以在記憶體使用的峰值期間進一步增長。

如果 INSTANCE_MEMORY 被設定為某一個數字,則採用 INSTANCE_MEMORY 與 MON_HEAP_SZ、AUDIT_BUF_SZ 和 FCM_NUM_BUFFERS 的和之間的較大者。這時,對例項記憶體就施加了一個硬性的限制,而不是軟限制。當達到這個限制時,就會收到記憶體分配錯誤。出於這個原因,建議將 INSTANCE_MEMORY 的設定保留為 AUTOMATIC。

如果 instance_memory被設為 AUTOMATIC,則可以使用下面的命令來確定它的值:

$db2 attach to instance_name #(其中 instance_name是例項的名稱)
$db2 get dbm cfg show detail | grep -i instance_memory
 Size of instance shared memory (4KB)  (INSTANCE_MEMORY) = AUTOMATIC(7367929)         AUTOMATIC(7367929) 
$ py
Python 3.6.1 (default, Jun 19 2017, 11:40:25) [C] on aix5
Type "help", "copyright", "credits" or "license" for more information.
>>> 7367929*4/1024/1024
28.10641860961914

上面的輸出表明有 28 GB 的記憶體被預留給例項共享記憶體集。

INSTANCE_MEMORY 引數只是設定了例項共享記憶體的限制。它並沒有說出當前使用了多少記憶體。要查明一個例項的記憶體使用情況,可以使用 DB2 記憶體跟蹤器工具 db2mtrk。例如,

$ db2mtrk -i -v
Tracking Memory on: 2018/11/28 at 21:39:20

Memory for instance

   Other Memory is of size 107741184 bytes
   Database Monitor Heap is of size 524288 bytes
   FCMBP Heap is of size 74055680 bytes
   Total: 182321152 bytes

上面的例子表明,雖然預留給例項共享記憶體集的記憶體有 28 GB,但在 db2mtrk 執行時只用到了大約 173.875 MB。 注意:在某些情況下,db2mtrk 顯示的大小會大於指定給配置引數的值。在這種情況下,賦予配置引數的值被作為一種軟限制,記憶體池實際使用的記憶體可能會增長,從而超出配置的大小。

2、資料庫共享記憶體

每個資料庫有一個數據庫共享記憶體集。資料庫共享記憶體是在資料庫被啟用或者第一次被連線上的時候分配的。該記憶體集將在資料庫處於非啟用狀態時釋放(如果資料庫先前是處於啟用狀態)或者最後一個連線被斷開的時候釋放。這種記憶體用於資料庫級的任務,例如備份/恢復、鎖定和 SQL 的執行。

 

 

上圖中完整的綠色方框意味著,在資料庫啟動的時候,該記憶體池是完全分配的,否則,就只分配部分的記憶體。例如,當一個數據庫第一次啟動時,不管 util_heap_sz 的值是多少,只有大約 16 KB 的記憶體被分配給實用程式堆。當一個數據庫實用程式(例如備份、恢復、匯出、匯入和裝載)啟動時,才會按 util_heap_sz 指定的大小分配全額的記憶體。

主緩衝池(Main Bufferpool(s)):資料庫緩衝池通常是資料庫共享記憶體中最大的一塊記憶體。DB2 在其中操縱所有常規資料和索引資料。 一個數據庫必須至少有一個緩衝池,可以有多個緩衝池,這要視工作負載的特徵、資料庫中使用的資料庫頁面大小等因素而定。例如,頁面大小為 8KB 的表空間只能使用頁面大小為 8KB 的緩衝池。 可以通過 CREATE BUFFERPOOL 語句中的 EXTENDED STORAGE 選項“擴充套件”緩衝池。擴充套件的儲存(ESTORE)充當的是從緩衝池中被逐出的頁的輔助快取,這樣可以減少 I/O。ESTORE 的大小由 num_estore_segs 和 estore_seg_sz 這兩個資料庫配置引數來控制。如果使用 ESTORE,那麼就要從資料庫共享記憶體中拿出一定的記憶體,用於管理 ESTORE,這意味著用於其他記憶體池的記憶體將更少。 這時您可能要問,為什麼要這麼麻煩去使用 ESTORE?為什麼不分配一個更大的緩衝池呢?答案跟可定址記憶體(而不是實體記憶體)的限制有關。

隱藏的緩衝池(Hidden Bufferpools): 當資料庫啟動時,要分配 4 個頁寬分別為 4K、8K、16K 和 32K 的小型緩衝池。這些緩衝池是“隱藏”的,因為在系統編目中看不到它們(通過 SELECT * FROM SYSCAT.BUFFERPOOLS 顯示不出)。 如果主緩衝池配置得太大,則可能出現主緩衝池不適合可定址記憶體空間的情況。(我們在後面會談到可定址記憶體。)這意味著 DB2 無法啟動資料庫,因為一個數據庫至少必須有一個緩衝池。如果資料庫沒有啟動,那麼就不能連線到資料庫,也就不能更改緩衝池的大小。由於這個原因,DB2 預先分配了 4 個這樣的小型緩衝池。這樣,一旦主緩衝池無法啟動,DB2 還可以使用這些小型的緩衝池來啟動資料庫。(在此情況下,使用者將收到一條警告(SQLSTATE 01626))。這時,應該連線到資料庫,並減少主緩衝池的大小。

排序堆的閾值( sheapthres, sheapthres_shr): 如果沒有索引滿足所取的行的要求順序,或者優化器斷定排序的代價低於索引掃描,那麼就需要進行排序。DB2 中有兩種排序,一種是私有排序,一種是共享排序。私有排序發生在代理的私有代理記憶體(在下一節討論)中,而共享排序發生在資料庫的資料庫共享記憶體中。 對於私有排序,資料庫管理器配置引數 sheapthres 指定了私有排序在任何時刻可以消耗的記憶體總量在例項範圍內的軟限制。如果一個例項總共消耗的私有排序記憶體達到了這一限制,那麼為額外傳入的私有排序請求所分配的記憶體將大大減少。這樣就會在 db2diag.log 中看到如下訊息: "Not enough memory available for a (private) sort heap of size size of sortheap. Trying smaller size..." 如果啟用了內部分割槽並行性(intra-partition parallelism)或者集中器(concentrator),那麼當 DB2 斷定共享排序比私有排序更有效時,DB2 就會選擇執行共享排序。如果執行共享排序,那麼就會在資料庫共享記憶體中分配用於這種排序的排序堆。用於共享排序的最大記憶體量是由 sheapthres_shr 資料庫引數指定的。這是對共享排序在任何時刻可以消耗的記憶體總量在資料庫範圍內的硬限制。當達到這個限制時,請求排序的應用程式將收到錯誤 SQL0955 (rc2)。之後,在共享記憶體總消耗量回落到低於由 sheapthres_shr 指定的限制之前,任何共享排序記憶體的請求都得不到允許。

下面的公式可以計算出資料庫共享記憶體集大致需要多少記憶體: 資料庫共享記憶體 = (主緩衝池 + 4 個隱藏的緩衝池 + 資料庫堆 +實用程式堆 + locklist + 包快取 + 編目快取) + (estore 的頁數 * 100 位元組) + 大約 10% 的開銷 對於啟用了 intra_parallel 或集中器情況下的資料庫,共享排序記憶體必須作為資料庫共享記憶體的一部分預先分配,因而上述公式變為: 資料庫共享記憶體 = (主緩衝池 + 4 個隱藏的緩衝池 + 資料庫堆 +實用程式堆 + locklist + 包快取 + 編目快取 + sheapthres_shr) + (estore 的頁數 * 100 位元組) + 大約 10% 的開銷。提示: 為了發現分配給主緩衝池的記憶體有多少,可以發出: 

SELECT * FROM SYSCAT.BUFFERPOOLS

雖然大多數記憶體池的大小是由它們的配置引數預先確定的,但下面兩種記憶體池的大小在預設情況下卻是動態的:

包快取: pckcachesz = maxappls * 8
編目快取: catalogcache_sz = maxappls * 4
活動應用程式的最大數量: maxappls = AUTOMATIC

將 maxappls設為 AUTOMATIC的效果是,允許任意數量的連線資料庫的應用程式。DB2 將動態地分配所需資源,以支援新的應用程式。因此,包快取和編目的大小可以隨著 maxappls的值而變化。 除了上述引數以外,還有一個引數也會影響資料庫共享記憶體的數量。這個引數就是 database_memory。該引數的預設值是 AUTOMATIC。這意味著 DB2 將根據以上列出的各記憶體池的大小來計算當前配置所需的資料庫記憶體量。此外,DB2 還將為溢位緩衝區分配一些額外的記憶體。每當某個堆超出了其配置的大小時,便可以使用溢位緩衝區來滿足例項共享記憶體區內任何堆的峰值需求。 如果 database_memory被設為某個數字,則採用 database_memory與各記憶體池之和這兩者之間的較大者。 如果 database_memory被設為 AUTOMATIC,則可以使用以下命令來顯示它的值:

db2 connect to dbnameuser useridusing pwd
db2 get db cfg for dbnameshow detail

3、應用程式組共享記憶體

這種共享記憶體集僅適用於以下環境。(對於其他環境,這種記憶體集不存在。) - 多分割槽(multi-partitioned)資料庫。 - 啟用了內部並行(intra-parallel)處理的未分割槽(non-partitioned)資料庫。 - 支援連線集中器的資料庫。 注意:當 max_connections 的值大於 max_coordagents 的值時,連線集中器便被啟用。這兩個引數可以在資料庫管理器配置中找到。(使用 GET DBM CFG 顯示資料庫管理器配置。)

在以上環境中,應用程式通常需要不止一個的代理來執行其任務。允許這些代理之間能夠彼此通訊(相互發送/接收資料)很有必要。為了實現這一點,我們將這些代理放入到一個稱作應用程式組的組中。屬於相同應用程式組的所有 DB2 代理都使用應用程式組共享記憶體進行通訊。 應用程式組記憶體集是從資料庫共享記憶體集中分配的。其大小由 appgroup_mem_sz 資料庫配置引數決定。 多個應用程式可以指派給同一個應用程式組。一個應用程式組內可以容納的應用程式數可以這樣計算:

appgroup_mem_sz / app_ctl_heap_sz

在應用程式組內,每個應用程式都有其自己的應用程式控制堆。此外,應用程式組共享記憶體中有一部分要預留給應用程式組共享堆。如下圖所示:

 

 

考慮以下資料庫配置:

  • 最大應用程式記憶體集大小 (4KB) (APPGROUP_MEM_SZ) = 40000
  • 最大應用程式控制堆大小 (4KB) (APP_CTL_HEAP_SZ) = 512
  • 用於應用程式組堆的記憶體所佔百分比 (GROUPHEAP_RATIO) = 70

可以計算出下面的值: - 應用程式組共享記憶體集是: 40000 頁 * 4K/頁 = 160 MB - 應用程式組共享堆的大小是: 40000 * 70% = 28000 4K 頁 = 114MB - 該應用程式組內可容納的應用程式數為: 40000/512 = 78 - 用於每個應用程式的應用程式控制堆為: (100-70)% * 512 = 153 4K 頁 = 0.6MB

不要被 app_ctrl_heap_sz 引數迷惑。這個引數不是一個應用程式組內用於每個應用程式的各應用程式控制堆的大小。它只是在計算這個應用程式組內可容納多少應用程式時用到的一個值。每個應用程式的實際應用程式控制堆大小都是通過 圖 3中給出的公式計算的,這個公式就是 ((100 - groupheap_ratio)% * app_ctrl_heap_sz)。

因此,groupheap_ratio 越高,應用程式組共享堆就越大,從而用於每個應用程式的應用程式控制堆就越小。

4、代理私有記憶體

每個 DB2 代理程序都需要獲得記憶體,以執行其任務。代理程序將代表應用程式使用記憶體來優化、構建和執行訪問計劃,執行排序,記錄遊標資訊(例如位置和狀態),收集統計資訊,等等。為響應並行環境中的一個連線請求或一個新的 SQL 請求,要為一個 DB2 代理分配代理私有記憶體。

代理的數量受下面兩者中的較低者限制:

  • 所有活動資料庫的資料庫配置引數 maxappls 的總和,這指定了允許的活動應用程式的最大數量。
  • 資料庫管理器配置引數 maxagents 的值,這指定了允許的最大代理數。

代理私有記憶體集由以下記憶體池組成。這些記憶體池的大小由括號中的資料庫配置引數指定:

  • Application Heap ( applheapsz)
  • Sort Heap ( sortheap)
  • Statement Heap ( stmtheap)
  • Statistics Heap ( stat_heap_sz)
  • Query Heap ( query_heap_sz)
  • Java Interpreter Heap ( java_heap_sz)
  • Agent Stack Size ( agent_stack_sz) (僅適用於 Windows)

我們曾提到,私有記憶體是在一個 DB2 代理被“指派”執行任務時分配給該代理的。那麼,私有記憶體何時釋放呢?答案取決於 dbm cfg 引數 num_poolagents 的值。該引數的值指定任何時候可以保留的閒置代理的最大數目。如果該值為 0,那麼就不允許有限制代理。只要一個代理完成了它的工作,這個代理就要被銷燬,它的記憶體也要返回給作業系統。如果該引數被設為一個非零值,那麼一個代理在完成其工作後不會被銷燬。相反,它將被返回到閒置代理池,直到閒置代理的數目到達 num_poolagents 指定的最大值。當傳入一個新的請求時,就要呼叫這些閒置代理來服務該新請求。這樣就減少了建立和銷燬代理的開銷。

當代理變成閒置代理時,它仍然保留了其代理的私有記憶體。這樣設計是為了提高效能,因為當代理被再次呼叫時,它便有準備好的私有記憶體。如果有很多的閒置代理,並且所有這些閒置代理都保留了它們的私有記憶體,那麼就可能導致系統耗盡記憶體。為了避免這種情況,DB2 使用一個登錄檔變數來限制每個閒置代理可以保留的記憶體量。這個變數就是 DB2MEMMAXFREE。它的預設值是 8 388 608 位元組。這意味著每個閒置代理可以保留最多 8MB 的私有記憶體。如果有 100 個閒置代理,那麼這些代理將保留 800MB 的記憶體,因此它們很快就會耗盡 RAM。您可能希望降低或增加這一限制,這取決於 RAM 的大小。

上述介紹了db2 例項共享記憶體、資料庫共享記憶體和應用程式組共享記憶體以及代理私有記憶體,與其他非 db2 程序相比,他們在記憶體中的位置如下圖所示: 

 

 

 

db2 的主要執行緒

這些執行緒好比超市中的各種服務員,他們各司其職,相互配合,高效地為資料庫服務。db2 資料庫啟動後,可以看到程序列表中有個 db2sysc 的程序,它是 db2 資料庫服務的主程序。可以使用 "db2pd -edus" 顯示工作執行緒。主要的工作執行緒及功能如下: - db2tcpcm TCP 監聽 - db2ipccm IPC 監聽 - db2pfchr 預讀執行緒,從磁碟讀頁面到 bufferpool - db2pclnr 將修改後的資料頁(髒頁)寫入磁碟 - db2loggw、db2loggr 日誌寫入、讀取執行緒 - db2dlock 死鎖檢測執行緒

一條SQL語句提交後,db2都做了什麼?

前面說了那麼多,都是為了做鋪墊。先看下圖select 語句的執行過程: 

 

圖中的文字還是太過簡單,重述如下: (1)select語句通過網路傳送給代理執行緒; (2)SQL語句經過重寫及編譯,將編譯結果存放在 Package cache 中; (3)協調代理執行緒(coordinating agent)按照執行計劃執行語句,將預取請求傳送給預取執行緒; (4)預取執行緒在容器間並行執行非同步I/O,將資料頁放入緩衝池中(如果沒有發生預取,也就是緩衝池命中,則略過第4步); (5)將容器中的資料頁放入緩衝池中; (6)將需要排序的資料移動到排序堆中; (7)如果排序堆不夠,則將排序資料放到臨時表空間中; (8)排序完成的行被子代理送回客戶端。 執行過程中要注意以下幾個細節,這些細節也是影響效能的關鍵因素: (1)SQL語句的執行計劃可能會極端影響效能; (2)如果發生預取,預取執行緒會從磁碟中取出連續的資料頁,此時代理執行緒處於等待狀態; (3)如果沒發生預取,則協調代理會並行地從磁碟中取出資料。

到此為止,一條select語句就徹底執行完了,我們可以看到,一條最基本的查詢語句在 DB2 中經過各個元件的協調,歷經了 8 個步驟最終完成。在遇到一個性能問題時,任何一個環節都可能成為效能瓶頸。

 

可以看出,insert 後,資料庫在緩衝區寫入成功,同時記錄日誌到 log buffer ,返回客戶端寫入成功,但並不急於寫回磁碟,原因是寫磁碟太慢了,影響效能。此時如果用記要查詢新插入的資料,則緩衝區直接命中,效率也會非常高。當緩衝區不夠用時,可以將髒頁寫回磁碟,從而釋放緩衝區記憶體空間。基本上所有的資料庫,如 oracle,mysql 都有這種機來避免頻繁地讀寫磁碟。當然,使用更好的磁碟,如RAID10(一般都很貴)也可以提高資料庫的效能。

 

commit 語句就比較簡單了,如上圖所示。

小結:db2 還是很強大的,IBM 也不愧是資料庫理論誕生的公司,本文參考官網詳細地介紹了 db2 的記憶體模型,也簡單介紹了體系結構和 SQL 語句地執行過程,瞭解這些有助於運維工程師根據記憶體使用情況對資料庫調優。後續會介紹 db2 的鎖,索引等,敬請關注。

公眾號 somenzz 堅持原創,和你一起學習技術。