天天看點

細說Oracle資料庫與作業系統存儲管理二三事

作者介紹

楊建榮,【dbaplus社群】聯合發起人。現就職于搜狐暢遊,oracle ace-a、yep成員,超7年資料庫開發和運維經驗,擅長電信資料業務、資料庫遷移和性能調優。持oracle 10g ocp,ocm,mysql ocp認證,《oracle dba工作筆記》作者。

在上大學的時候,學習作業系統感覺特别枯燥,都是些條條框框的知識點,感覺和實際應用的關聯不大。發現越是工作以後,在工作中越想深入了解,發現作業系統知識越發重要。在實踐中結合理論還是不錯的一種學習方法。自從接觸資料庫以後,越來越感覺到很多東西其實都是相通的,作業系統中的很多設計思想在資料庫中也有借鑒和改進之處。

說到存儲管理,是作業系統中最重要的資源之一。因為任何程式和資料等都需要占有一定的存儲空間,存儲管理會直接影響到系統的性能。

存儲器是由主存和外存組成。對于外存,可能覆寫面更廣,像硬碟,移動硬碟,CD光牒,錄音帶,ssd等等都是外存的覆寫範圍。主存大家很熟悉,這些年主存的大小也有了極高的提升,現在的伺服器配置中幾百gb的記憶體都是很正常的。 

關于存儲的管理技術,先讨論以下兩個部分。

固定分區 

先來點作業系統的知識。

關于固定分區管理技術,就是把主存分為若幹個固定大小的存儲區,每個分區提供給某一個作用使用,如果作業完成會把相應的存儲區歸還。

在多道作業系統中,主存中分區的個數是固定不變的,而且每個分區的大小也是固定不變的。如果分區總是大于作業,那麼就有很多分區沒有充分使用,産生碎片。

來結合資料庫來看一看(shared pool中的free list) 

在資料庫中,shared pool中的free list(bucket)管理和固定分區管理很相似。 

shared pool中存儲機關是chunk,多個chunk組成一個連結清單,也叫做bucket,每個bucket都對chunk的大小都有一定的範圍,是一個連續的值,沒有交叉。 

在10g,11g中都設定了255個bucket,可以通過trace檔案來了解一下。 

[ora11g@rac1 trace]$ grep bucket *18155*.trc

 bucket 0 size=32

 bucket 1 size=40

 bucket 2 size=48

 bucket 3 size=56

 bucket 4 size=64

 bucket 5 size=72

...

 bucket 250 size=12352

 bucket 251 size=12360

 bucket 252 size=16408

 bucket 253 size=32792

 bucket 254 size=65560

可能對于bucket的大小沒有一個直覺的感受,可以生成一個圖來看看就很清楚了。

細說Oracle資料庫與作業系統存儲管理二三事

随着bucket的增長,對應的chunk大小都在遞增,絕大多數的bucket中,chunk的大小都在5k以内。隻有很小的一部分bucket的支援的chunk size很大,這個也是oracle在不斷的改進中得到的一個最優值,按照比例來劃分,保證每次通路需要的chunk大小都能夠合理的配置設定,盡量減少備援。

同時不是每個bucket裡面都是有chunk的,這個chunk的配置設定還是根據進入shared pool以後申請chunk大小緊密相關,bucket中的chunk數目可不是平均的。

oracle在早期的版本中也碰到了不少的問題,在10g,11g中都對bucket的數目做了提升(目前都是255個),而且分區的大小也做了調整。這是一個比較均衡的比例,能夠保證每次請求的大小都在bucket的範圍之内,盡量提高效率。 

回到作業系統中,我們再補充幾點。

在存儲的管理中,存儲的配置設定和釋放都需要根據分區來說明。在固定分區中采用了一個存儲分塊表(mbt)來維護而存儲的區的資訊,存儲區的資訊在作業系統中有一個專有名詞叫做資料基,資料基聽起來挺抽象,其實了解起來還是蠻簡單的。

我們用下面的圖示來說明。我們假設下面的這個表格就是存儲分塊表,其中資料基就包括,存儲的分區大小,存儲位置還有狀态。

分區

大小

存儲位置

狀态

1

8k

xxxxx

used

2

free

3

16k

4

5

6

32k

猛一看,上面的方式還是比較簡單而且可行的。但是還是固定分區的硬傷,主存使用率不高,對于進入主存中的作業大小我們也沒法預知,而且對于mbt表的管理感覺還是不夠清晰。如果需要查找哪些分區可用,需要重新配置設定的時候,就得周遊整個表,周遊了已經使用的分區,這樣配置設定的過程就比較長了。 

這個時候可以參考一下:

可變分區的多道管理技術

這種技術在一定程度上解決了固定分區帶來的問題,可變分區在主存中不會事先建立一個個分區,而是在作業進入主存的時候按照作業大小再來建立分區。 

這樣的話,分區個數不固定,分區大小不固定,在oracle中也有一些相似之處。 

oracle中的deferred_segment_creation

比如說對于分區的不固定,在11g中有一個參數deferred_segment_creation,如果我們設定為true,那麼在建立之初是不會配置設定對應的分區的,直到開始插入資料之後,它才會根據插入的資料來建立分區。

oracle中的interval partitoning

如果根據需要動态的建立分區,而且分區的大小也不固定。

比如在資料庫的表空間管理中,我們可以指定分區的。

對于可變分區的資料基管理,是采用了兩個存儲分區表來管理的,已使用分區表(ubt)和空閑分區表(fbt),這樣就可以減少存儲配置設定和釋放的性能。

在這點上,oracle表空間中的資料字典管理方式是一緻的。

oracle中早期是采用fet$,uet$ 兩個資料字典表來維護分區的資訊的。隻是在資料基上會有一定的差别。

fet$和uet$的結構如下:

細說Oracle資料庫與作業系統存儲管理二三事

這種方式在早期的oracle版本中采用,這種表空間管理方式叫資料字典管理。

但是在oracle的不斷改進中,發現這種方式還是存在一定的問題,資源消耗還是比較高的。對于這兩種資料字典表的dml操作,會産生較多的遞歸sql來間接完成對兩個資料字典表的更新,在更新的過程中也會存在事務,存在事務也就會産生一定的undo和redo。最後就是對于相鄰空閑空間的合并, 在oracle中是通過smon程序來實作的。

回到作業系統,作業系統中對于資料基的管理還有一種方式,就是空閑存儲連結清單。

這種方式就是把空閑分區通過連結清單的形式串起來,形成了一條空閑存儲塊鍊。

這種技術在資料庫中可有一個很響亮的名字,在buffer cache中叫做lru連結清單。

在buffer cache中的實作方式也是類似的。當然在oracle中會采用其它的算法和政策。oracle中是把buffer按照被使用的先後順序挂在lru連結清單 上,先被使用的buffer放在了連結清單的後面,後被使用的buffer挂載lru連結清單的前面,如果buffer被修改的時候,buffer就會從lru鍊 表上取出。這樣始終保持lru連結清單中都是可用的資料塊。

可變分區的存儲算法 

然後來簡單說一下可變分區的存儲算法。

目前主要有以下幾種:

最佳适用算法

這種方式就是從所有未配置設定的分區中挑選一個最接近于作業尺寸且大于或者等于作業大小的分區配置設定。

最先适應法

按照分區序号從存儲分塊表中的第一個表目找找,把最先找到且大約等于作業大小的分區配置設定。

最壞适應法

把所有未配置設定的分區中挑選最大的且大于等于作業大小的分區配置設定。

位圖法

把所有的分區使用一個位來表示狀态,1表示塊已經被使用,0表示分區空閑。

在oracle中的存儲算法可能更接近于最佳适應算法,唯一的不同的是在oracle中采用了hash來該配置設定到哪個bucket。但是都會保證配置設定的空間是大于等于請求的大小。

而位圖法在表空間管理中也有相似的使用方式。

表空間的管理有兩種方式,資料字典管理和本地管理。

本地管理中會在資料檔案的頭部采用多個位來存放。這個bitmap類似下面的形式。

11110111001110100.....

1代表分區已被使用,0代表分區還是空閑,當程序需要分區的時候,隻要掃描資料檔案的頭部的bitmap,就可以找到值為0的分區。配置設定了分區之後把它修 改為1,釋放空間就會從1修改為0. 修改資料檔案頭部的操作速度快且不存在事務,就沒有redo,undo,更不會有遞歸sql。對于相鄰分區的合并來說,兩個連續的0就能說明是連續的空閑 分區,是以也不需要再合并相鄰的可用分區了。

前面讨論了固定分區和可變分區管理的一些情況,它們的主要缺點就是主存使用的低效率和存儲配置設定釋放的低速。固定分區是分區内部的碎片造成主存使用率低,而可變分區是分區外部的碎片,往往小到無法使用,進而主存使用率不高。對于這個問題,分頁是一種很有效的方法。

分頁技術 

分頁技術主要是把主存分為許多同樣大小的存儲塊,并以這種存儲塊作為存儲配置設定機關。oracle資料庫中實體存儲機關有段,區,資料塊,這個時候所說的資料塊和作業系統資料塊存在着映射,一般都比作業系統塊要大。資料庫中預設為8k,資料的存儲都是以8k的基本機關來存儲的。如果把這一點繼續延伸,oracle中的區(extent)就和分頁技術中所說的頁很類似。

分頁存儲中的基本實作過程,有以下幾點:

把主存分為相同大小的存儲塊,叫做頁架,頁架從0開始,編号依次是0,1,2....

使用者邏輯位址的分頁,使用者邏輯位址可以劃分為和頁架大小相同的部分,叫做頁。頁号從0開始,依次為0,1,2...

邏輯位址的表示,既然說到了邏輯位址,表示方法也很重要。每一個邏輯位址都是相對位址,用一個數對(p,d)來表示,p代表頁号,d代表邏輯位址在也好為p的頁中相對的位址,也叫偏移量。

聽起來挺枯燥啊,可以簡單舉個例子,我們常看的書就是一個很好的例子,書有很多大小,四開,八開,十六開,可以了解為頁架,書中的每一頁就是我們所說的頁,邏輯位址可以這麼了解,一本書有很多章節,小結,比如第二章第3頁,我們就能夠很快找到,這個時候,頁号就是2,偏移量就是3,用(p,d)來表示就 是(2,3)

舉一個嚴謹的例子,比如給定一個虛位址3456,假設頁面大小為1000b,則第0頁對應的位址為0-999,第1頁為1000-1999,則虛位址3456=(3,456)

這一點和oracle中建立表空間時指定的extent management管理方式很相似,比如我們建立一個表空間test指定分區大小為1m,表空間大小為100m,則語句如下:

create tablespace test add datafile '/u01/app/db/test01/data01/test01.dbf' size 100m extent management local uniform size 1m ;

這樣我們指定分區大小為1m,如果存儲了100m的資料,這樣100m就會分為100個分區。如果資料大于分區1m,則可以存儲在相應的分區上,不一定連續。

可以用下面的表格來說明。

位址

程序

頁号

0-999

程序1

1000-1999

2000-2999

程序2 

3000-3999

程序3

4000-4999

5000-5999

對應到每個程序對應的位址,就是我們所說的邏輯位址,比如程序1對應的邏輯位址就是

2000-3999

是以在分頁思想中的難點就是對于位址的表示,我們已經說使用(p,d)來表示,但是這個數在機器指令的位址場中表示還有不同,首先會把位址分為兩部分,一部分表示頁号,一部分表示頁内位址。

這種方式每次通路一個主存單元都用一次除法得到頁号和頁内位址就很繁瑣,實際上效率要更差。這個時候相比前人也是考慮了很多招數,最後還是使用二進制來搞定,指定頁面尺寸是2的幂,這樣就會省去很多額外的轉換。

最後一個例子很關鍵,如果看懂了說明你對分頁思想算是明白了。

假設頁的大小為1kb,計算邏輯地位址為4101的頁号,頁内位址。

按照二進制的思想,4101可以這樣表示 4101=2^12+2^1+2^1+2^0

用0,1來表示就是

0001000000000101

頁的大小是1kb=2^10,則在二進制串中,後10位就是對應的頁内位址,二進制0101代表的是5,表示頁内位址為5

頁号對應的二進制串000100表示頁号為4

是以4101對應的邏輯位址表示為(4,5)

這種方法可以省去除法運算,硬體層面會自動把邏輯位址拆分為兩部分,對應頁号和頁内位址。

問題來了,位址能夠表示了,那使用的時候是怎麼轉換的呢,首先會把邏輯位址抽取出來,像上面的例子,頁号是4,然後根據頁号為索引找到該頁存放的主存頁架号。比如存放的位址為2000-2999,則頁架号為2,然後把頁架号取代邏輯位址,和右邊的頁内位址組成了最終的實體位址去通路記憶體。

這種思想還是需要些時間去消化一下,優點也是很明顯的,基本上沒有頁内碎片,同時也不會存在小到無法再用的頁外碎片。因為每個碎片都是頁架的整數倍。

分頁中使用的二進制方式處理位址是一種很值得借鑒的方式,可以減少很多額外的開銷,和oracle中的rowid存儲方式也很類似。

分段式存儲 

分段式存儲管理系統中,會為每個段配置設定一個連續的分區,而程序中的各個段可以離散地移入記憶體中不同的分區中,說起分段就會聯想到分頁,我們來聊聊分頁與分段的主要差別。

分頁和分段有許多相似之處,比如兩者都不要求作業連續存放。但在概念上兩者完全不同,主要表現在以下幾個方面:

頁是資訊的實體機關,分頁是為了實作非連續配置設定,以便解決記憶體碎片問題,或者說分頁是由于系統管理的需要。段是資訊的邏輯機關,它含有一組意義相對完整的資訊,分段的目的是為了更好地實作共享,滿足使用者的需要。

頁的大小固定,由系統确定,将邏輯位址劃分為頁号和頁内位址是由機器硬體實作的。而段的長度卻不固定,決定于使用者所編寫的程式,通常由編譯程式在對源程式進行編譯時根據資訊的性質來劃分。

分頁的作業位址空間是一維的,分段的位址空間是二維的。

從資料庫的角度來看,感覺和資料庫中的段概念還是比較類似的。資料庫中段包含多個分區。各個分區也可以在不相鄰的分區中。

找一個圖來說明。

在分段情況下,會要求每個程序的位址空間劃分為若幹個段,每個段都有自己的段名,對應到下圖中就是一個段号。每個段的地位址空間都是從0開始,是一個連續的位址空間。

從位址的存儲情況來說,段和頁的存儲方式都是類似的,都會包含兩部分。分段存儲中是段号和段内位址,和分頁存儲中的頁号和頁内位址類似。

細說Oracle資料庫與作業系統存儲管理二三事

由于一個程序由很多段組成,而且各個段可能被配置設定在主存中的多個不相鄰的分區中,為了将程序的邏輯位址轉換為實體位址,需要有一個短标來指出程序的某段放在主存中的位置以及段長。

這一點從資料庫層面來說有類似的方面,首先是程序由多個段組成,資料庫中可以了解為一個表包含多個段,資料段,索引段,lob段,lob索引段等等。這些都是獨立的段,在存儲的時候也可能分布在不同的表空間中,是以可能不是一個相鄰的分區。

而段的資訊在作業系統層面是通過段表來維護的,資料庫層面則是通過資料字典,user_segments,user_extents來維護的,每個表包含的段,每個段包含的區都是很詳實的。

從分段和分頁的優點來說,因為它們涉及的層面和應用方向不同,但是還是有一定的可比性,在段共享方面,分段存儲還是很有優勢,誰讓它是段共享呢。

從作業系統層面舉個例子就是一個多使用者系統,有一個應用程式可能包含的程式段是100k,資料段是40k,按理說需要40k*40+100k*40=1600+4000=5600k

在分段存儲中則需要100k+40k*40=1700k,從這一點上來說還是很大的改進。

從這一點上來說,資料庫中的同義詞就有點分段存儲的味道,每個同義詞都可以通路源表,相當于共享了資料,同義詞占用的存儲空間很小,幾乎可以忽略。

可能分段存儲和分頁存儲都各有千秋,但是都是在不斷的使用和改進中主鍵發展起來的,分段存儲沒有段内碎片,隻有外部碎片,簡單分段技術也是基于多重分區技 術的發展而來。另外簡單分頁對于使用者是不可見的,使用者無法了解程序被分頁或者分頁的細節,但是簡單分段對于使用者基本是可見的,當程序被交換出記憶體的時候, 對應的頁表和段表也需要随着程序一起撤出記憶體。

當然分頁分段方式還在不斷的發展中,要不怎麼有後續的段頁式存儲呢,很多時候類比作業系統方面的知識,就會讓我們對于很多事物有了全新的認識和了解。

當然順帶幫大家複習了作業系統的基礎知識,我的目的也算達到了。

<b>本文來自雲栖社群合作夥伴"dbaplus",原文釋出時間:2016-07-26</b>