細(xì)說Oracle數(shù)據(jù)庫與操作系統(tǒng)存儲(chǔ)管理二三事
在上大學(xué)的時(shí)候,學(xué)習(xí)操作系統(tǒng)感覺特別枯燥,都是些條條框框的知識(shí)點(diǎn),感覺和實(shí)際應(yīng)用的關(guān)聯(lián)不大。發(fā)現(xiàn)越是工作以后,在工作中越想深入了解,發(fā)現(xiàn)操作系統(tǒng)知識(shí)越發(fā)重要。在實(shí)踐中結(jié)合理論還是不錯(cuò)的一種學(xué)習(xí)方法。自從接觸數(shù)據(jù)庫以后,越來越感覺到很多東西其實(shí)都是相通的,操作系統(tǒng)中的很多設(shè)計(jì)思想在數(shù)據(jù)庫中也有借鑒和改進(jìn)之處。
說到存儲(chǔ)管理,是操作系統(tǒng)中最重要的資源之一。因?yàn)槿魏纬绦蚝蛿?shù)據(jù)等都需要占有一定的存儲(chǔ)空間,存儲(chǔ)管理會(huì)直接影響到系統(tǒng)的性能。
存儲(chǔ)器是由主存和外存組成。對(duì)于外存,可能覆蓋面更廣,像硬盤,移動(dòng)硬盤,光盤,磁帶,SSD等等都是外存的覆蓋范圍。主存大家很熟悉,這些年主存的大小也有了極高的提升,現(xiàn)在的服務(wù)器配置中幾百GB的內(nèi)存都是很正常的。
關(guān)于存儲(chǔ)的管理技術(shù),先討論以下兩個(gè)部分。
固定分區(qū)
先來點(diǎn)操作系統(tǒng)的知識(shí)。
關(guān)于固定分區(qū)管理技術(shù),就是把主存分為若干個(gè)固定大小的存儲(chǔ)區(qū),每個(gè)分區(qū)提供給某一個(gè)作用使用,如果作業(yè)完成會(huì)把相應(yīng)的存儲(chǔ)區(qū)歸還。
在多道作業(yè)系統(tǒng)中,主存中分區(qū)的個(gè)數(shù)是固定不變的,而且每個(gè)分區(qū)的大小也是固定不變的。如果分區(qū)總是大于作業(yè),那么就有很多分區(qū)沒有充分使用,產(chǎn)生碎片。
來結(jié)合數(shù)據(jù)庫來看一看(shared pool中的free list)
在數(shù)據(jù)庫中,shared pool中的free list(bucket)管理和固定分區(qū)管理很相似。
shared pool中存儲(chǔ)單位是chunk,多個(gè)chunk組成一個(gè)鏈表,也叫做bucket,每個(gè)bucket都對(duì)chunk的大小都有一定的范圍,是一個(gè)連續(xù)的值,沒有交叉。
在10g,11g中都設(shè)置了255個(gè)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
可能對(duì)于bucket的大小沒有一個(gè)直觀的感受,可以生成一個(gè)圖來看看就很清楚了。
隨著bucket的增長(zhǎng),對(duì)應(yīng)的chunk大小都在遞增,絕大多數(shù)的bucket中,chunk的大小都在5k以內(nèi)。只有很小的一部分bucket的支持的chunk size很大,這個(gè)也是Oracle在不斷的改進(jìn)中得到的一個(gè)最優(yōu)值,按照比例來劃分,保證每次訪問需要的chunk大小都能夠合理的分配,盡量減少冗余。
同時(shí)不是每個(gè)bucket里面都是有chunk的,這個(gè)chunk的分配還是根據(jù)進(jìn)入shared pool以后申請(qǐng)chunk大小緊密相關(guān),bucket中的chunk數(shù)目可不是平均的。
Oracle在早期的版本中也碰到了不少的問題,在10g,11g中都對(duì)bucket的數(shù)目做了提升(目前都是255個(gè)),而且分區(qū)的大小也做了調(diào)整。這是一個(gè)比較均衡的比例,能夠保證每次請(qǐng)求的大小都在bucket的范圍之內(nèi),盡量提高效率。
回到操作系統(tǒng)中,我們?cè)傺a(bǔ)充幾點(diǎn)。
在存儲(chǔ)的管理中,存儲(chǔ)的分配和釋放都需要根據(jù)分區(qū)來說明。在固定分區(qū)中采用了一個(gè)存儲(chǔ)分塊表(MBT)來維護(hù)而存儲(chǔ)的區(qū)的信息,存儲(chǔ)區(qū)的信息在操作系統(tǒng)中有一個(gè)專有名詞叫做數(shù)據(jù)基,數(shù)據(jù)基聽起來挺抽象,其實(shí)理解起來還是蠻簡(jiǎn)單的。
我們用下面的圖標(biāo)來說明。我們假設(shè)下面的這個(gè)表格就是存儲(chǔ)分塊表,其中數(shù)據(jù)基就包括,存儲(chǔ)的分區(qū)大小,存儲(chǔ)位置還有狀態(tài)。
猛一看,上面的方式還是比較簡(jiǎn)單而且可行的。但是還是固定分區(qū)的硬傷,主存利用率不高,對(duì)于進(jìn)入主存中的作業(yè)大小我們也沒法預(yù)知,而且對(duì)于MBT表的管理感覺還是不夠清晰。如果需要查找哪些分區(qū)可用,需要重新分配的時(shí)候,就得遍歷整個(gè)表,遍歷了已經(jīng)使用的分區(qū),這樣分配的過程就比較長(zhǎng)了。
這個(gè)時(shí)候可以參考一下:
- 可變分區(qū)的多道管理技術(shù)
這種技術(shù)在一定程度上解決了固定分區(qū)帶來的問題,可變分區(qū)在主存中不會(huì)事先創(chuàng)建一個(gè)個(gè)分區(qū),而是在作業(yè)進(jìn)入主存的時(shí)候按照作業(yè)大小再來創(chuàng)建分區(qū)。
這樣的話,分區(qū)個(gè)數(shù)不固定,分區(qū)大小不固定,在Oracle中也有一些相似之處。
- Oracle中的deferred_segment_creation
比如說對(duì)于分區(qū)的不固定,在11g中有一個(gè)參數(shù)deferred_segment_creation,如果我們?cè)O(shè)定為true,那么在創(chuàng)建之初是不會(huì)分配對(duì)應(yīng)的分區(qū)的,直到開始插入數(shù)據(jù)之后,它才會(huì)根據(jù)插入的數(shù)據(jù)來創(chuàng)建分區(qū)。
- Oracle中的interval partitoning
如果根據(jù)需要?jiǎng)討B(tài)的創(chuàng)建分區(qū),而且分區(qū)的大小也不固定。
比如在數(shù)據(jù)庫的表空間管理中,我們可以指定分區(qū)的。
對(duì)于可變分區(qū)的數(shù)據(jù)基管理,是采用了兩個(gè)存儲(chǔ)分區(qū)表來管理的,已使用分區(qū)表(UBT)和空閑分區(qū)表(FBT),這樣就可以減少存儲(chǔ)分配和釋放的性能。
在這點(diǎn)上,Oracle表空間中的數(shù)據(jù)字典管理方式是一致的。
Oracle中早期是采用FET$,UET$ 兩個(gè)數(shù)據(jù)字典表來維護(hù)分區(qū)的信息的。只是在數(shù)據(jù)基上會(huì)有一定的差別。
FET$和UET$的結(jié)構(gòu)如下:
這種方式在早期的Oracle版本中采用,這種表空間管理方式叫數(shù)據(jù)字典管理。
但是在Oracle的不斷改進(jìn)中,發(fā)現(xiàn)這種方式還是存在一定的問題,資源消耗還是比較高的。對(duì)于這兩種數(shù)據(jù)字典表的DML操作,會(huì)產(chǎn)生較多的遞歸SQL來間接完成對(duì)兩個(gè)數(shù)據(jù)字典表的更新,在更新的過程中也會(huì)存在事務(wù),存在事務(wù)也就會(huì)產(chǎn)生一定的undo和redo。最后就是對(duì)于相鄰空閑空間的合并, 在Oracle中是通過SMON進(jìn)程來實(shí)現(xiàn)的。
回到操作系統(tǒng),操作系統(tǒng)中對(duì)于數(shù)據(jù)基的管理還有一種方式,就是空閑存儲(chǔ)鏈表。
這種方式就是把空閑分區(qū)通過鏈表的形式串起來,形成了一條空閑存儲(chǔ)塊鏈。
這種技術(shù)在數(shù)據(jù)庫中可有一個(gè)很響亮的名字,在buffer cache中叫做LRU鏈表。
在buffer cache中的實(shí)現(xiàn)方式也是類似的。當(dāng)然在Oracle中會(huì)采用其它的算法和策略。Oracle中是把buffer按照被使用的先后順序掛在LRU鏈表 上,先被使用的buffer放在了鏈表的后面,后被使用的buffer掛載LRU鏈表的前面,如果buffer被修改的時(shí)候,buffer就會(huì)從LRU鏈 表上取出。這樣始終保持LRU鏈表中都是可用的數(shù)據(jù)塊。
可變分區(qū)的存儲(chǔ)算法
然后來簡(jiǎn)單說一下可變分區(qū)的存儲(chǔ)算法。
目前主要有以下幾種:
- 最佳適用算法
這種方式就是從所有未分配的分區(qū)中挑選一個(gè)最接近于作業(yè)尺寸且大于或者等于作業(yè)大小的分區(qū)分配。
- 最先適應(yīng)法
按照分區(qū)序號(hào)從存儲(chǔ)分塊表中的第一個(gè)表目找找,把最先找到且大約等于作業(yè)大小的分區(qū)分配。
- 最壞適應(yīng)法
把所有未分配的分區(qū)中挑選最大的且大于等于作業(yè)大小的分區(qū)分配。
- 位圖法
把所有的分區(qū)使用一個(gè)位來表示狀態(tài),1表示塊已經(jīng)被使用,0表示分區(qū)空閑。
在Oracle中的存儲(chǔ)算法可能更接近于最佳適應(yīng)算法,唯一的不同的是在Oracle中采用了hash來該分配到哪個(gè)bucket。但是都會(huì)保證分配的空間是大于等于請(qǐng)求的大小。
而位圖法在表空間管理中也有相似的使用方式。
表空間的管理有兩種方式,數(shù)據(jù)字典管理和本地管理。
本地管理中會(huì)在數(shù)據(jù)文件的頭部采用多個(gè)位來存放。這個(gè)bitmap類似下面的形式。
11110111001110100.....
1代表分區(qū)已被使用,0代表分區(qū)還是空閑,當(dāng)進(jìn)程需要分區(qū)的時(shí)候,只要掃描數(shù)據(jù)文件的頭部的bitmap,就可以找到值為0的分區(qū)。分配了分區(qū)之后把它修 改為1,釋放空間就會(huì)從1修改為0. 修改數(shù)據(jù)文件頭部的操作速度快且不存在事務(wù),就沒有redo,undo,更不會(huì)有遞歸SQL。對(duì)于相鄰分區(qū)的合并來說,兩個(gè)連續(xù)的0就能說明是連續(xù)的空閑 分區(qū),所以也不需要再合并相鄰的可用分區(qū)了。
前面討論了固定分區(qū)和可變分區(qū)管理的一些情況,它們的主要缺點(diǎn)就是主存使用的低效率和存儲(chǔ)分配釋放的低速。固定分區(qū)是分區(qū)內(nèi)部的碎片造成主存利用率低,而可變分區(qū)是分區(qū)外部的碎片,往往小到無法使用,從而主存利用率不高。對(duì)于這個(gè)問題,分頁是一種很有效的方法。
分頁技術(shù)
分頁技術(shù)主要是把主存分為許多同樣大小的存儲(chǔ)塊,并以這種存儲(chǔ)塊作為存儲(chǔ)分配單位。Oracle數(shù)據(jù)庫中物理存儲(chǔ)單位有段,區(qū),數(shù)據(jù)塊,這個(gè)時(shí)候所說的數(shù)據(jù)塊和操作系統(tǒng)數(shù)據(jù)塊存在著映射,一般都比操作系統(tǒng)塊要大。數(shù)據(jù)庫中默認(rèn)為8K,數(shù)據(jù)的存儲(chǔ)都是以8K的基本單位來存儲(chǔ)的。如果把這一點(diǎn)繼續(xù)延伸,Oracle中的區(qū)(extent)就和分頁技術(shù)中所說的頁很類似。
分頁存儲(chǔ)中的基本實(shí)現(xiàn)過程,有以下幾點(diǎn):
- 把主存分為相同大小的存儲(chǔ)塊,叫做頁架,頁架從0開始,編號(hào)依次是0,1,2....
- 用戶邏輯地址的分頁,用戶邏輯地址可以劃分為和頁架大小相同的部分,叫做頁。頁號(hào)從0開始,依次為0,1,2...
- 邏輯地址的表示,既然說到了邏輯地址,表示方法也很重要。每一個(gè)邏輯地址都是相對(duì)地址,用一個(gè)數(shù)對(duì)(p,d)來表示,p代表頁號(hào),d代表邏輯地址在也好為p的頁中相對(duì)的地址,也叫偏移量。
聽起來挺枯燥啊,可以簡(jiǎn)單舉個(gè)例子,我們??吹臅褪且粋€(gè)很好的例子,書有很多大小,四開,八開,十六開,可以理解為頁架,書中的每一頁就是我們所說的頁,邏輯地址可以這么理解,一本書有很多章節(jié),小結(jié),比如第二章第3頁,我們就能夠很快找到,這個(gè)時(shí)候,頁號(hào)就是2,偏移量就是3,用(p,d)來表示就 是(2,3)
舉一個(gè)嚴(yán)謹(jǐn)?shù)睦?,比如給定一個(gè)虛地址3456,假設(shè)頁面大小為1000B,則第0頁對(duì)應(yīng)的地址為0-999,第1頁為1000-1999,則虛地址3456=(3,456)
這一點(diǎn)和Oracle中創(chuàng)建表空間時(shí)指定的extent management管理方式很相似,比如我們創(chuàng)建一個(gè)表空間test指定分區(qū)大小為1M,表空間大小為100M,則語句如下:
create tablespace test add datafile '/u01/app/db/test01/data01/test01.dbf' size 100M extent management local uniform size 1M ;
這樣我們指定分區(qū)大小為1M,如果存儲(chǔ)了100M的數(shù)據(jù),這樣100M就會(huì)分為100個(gè)分區(qū)。如果數(shù)據(jù)大于分區(qū)1M,則可以存儲(chǔ)在相應(yīng)的分區(qū)上,不一定連續(xù)。
可以用下面的表格來說明。
對(duì)應(yīng)到每個(gè)進(jìn)程對(duì)應(yīng)的地址,就是我們所說的邏輯地址,比如進(jìn)程1對(duì)應(yīng)的邏輯地址就是
- 0-999
- 1000-1999
- 2000-3999
所以在分頁思想中的難點(diǎn)就是對(duì)于地址的表示,我們已經(jīng)說使用(p,d)來表示,但是這個(gè)數(shù)在機(jī)器指令的地址場(chǎng)中表示還有不同,首先會(huì)把地址分為兩部分,一部分表示頁號(hào),一部分表示頁內(nèi)地址。
這種方式每次訪問一個(gè)主存單元都用一次除法得到頁號(hào)和頁內(nèi)地址就很繁瑣,實(shí)際上效率要更差。這個(gè)時(shí)候相比前人也是考慮了很多招數(shù),最后還是使用二進(jìn)制來搞定,指定頁面尺寸是2的冪,這樣就會(huì)省去很多額外的轉(zhuǎn)換。
最后一個(gè)例子很關(guān)鍵,如果看懂了說明你對(duì)分頁思想算是明白了。
假設(shè)頁的大小為1KB,計(jì)算邏輯地地址為4101的頁號(hào),頁內(nèi)地址。
按照二進(jìn)制的思想,4101可以這樣表示 4101=2^12+2^1+2^1+2^0
用0,1來表示就是
0001000000000101
頁的大小是1KB=2^10,則在二進(jìn)制串中,后10位就是對(duì)應(yīng)的頁內(nèi)地址,二進(jìn)制0101代表的是5,表示頁內(nèi)地址為5
0001000000000101
頁號(hào)對(duì)應(yīng)的二進(jìn)制串000100表示頁號(hào)為4
所以4101對(duì)應(yīng)的邏輯地址表示為(4,5)
這種方法可以省去除法運(yùn)算,硬件層面會(huì)自動(dòng)把邏輯地址拆分為兩部分,對(duì)應(yīng)頁號(hào)和頁內(nèi)地址。
問題來了,地址能夠表示了,那使用的時(shí)候是怎么轉(zhuǎn)換的呢,首先會(huì)把邏輯地址抽取出來,像上面的例子,頁號(hào)是4,然后根據(jù)頁號(hào)為索引找到該頁存放的主存頁架號(hào)。比如存放的地址為2000-2999,則頁架號(hào)為2,然后把頁架號(hào)取代邏輯地址,和右邊的頁內(nèi)地址組成了最終的物理地址去訪問內(nèi)存。
這種思想還是需要些時(shí)間去消化一下,優(yōu)點(diǎn)也是很明顯的,基本上沒有頁內(nèi)碎片,同時(shí)也不會(huì)存在小到無法再用的頁外碎片。因?yàn)槊總€(gè)碎片都是頁架的整數(shù)倍。
分頁中使用的二進(jìn)制方式處理地址是一種很值得借鑒的方式,可以減少很多額外的開銷,和Oracle中的rowid存儲(chǔ)方式也很類似。
分段式存儲(chǔ)
分段式存儲(chǔ)管理系統(tǒng)中,會(huì)為每個(gè)段分配一個(gè)連續(xù)的分區(qū),而進(jìn)程中的各個(gè)段可以離散地移入內(nèi)存中不同的分區(qū)中,說起分段就會(huì)聯(lián)想到分頁,我們來聊聊分頁與分段的主要區(qū)別。
分頁和分段有許多相似之處,比如兩者都不要求作業(yè)連續(xù)存放。但在概念上兩者完全不同,主要表現(xiàn)在以下幾個(gè)方面:
- 頁是信息的物理單位,分頁是為了實(shí)現(xiàn)非連續(xù)分配,以便解決內(nèi)存碎片問題,或者說分頁是由于系統(tǒng)管理的需要。段是信息的邏輯單位,它含有一組意義相對(duì)完整的信息,分段的目的是為了更好地實(shí)現(xiàn)共享,滿足用戶的需要。
- 頁的大小固定,由系統(tǒng)確定,將邏輯地址劃分為頁號(hào)和頁內(nèi)地址是由機(jī)器硬件實(shí)現(xiàn)的。而段的長(zhǎng)度卻不固定,決定于用戶所編寫的程序,通常由編譯程序在對(duì)源程序進(jìn)行編譯時(shí)根據(jù)信息的性質(zhì)來劃分。
- 分頁的作業(yè)地址空間是一維的,分段的地址空間是二維的。
從數(shù)據(jù)庫的角度來看,感覺和數(shù)據(jù)庫中的段概念還是比較類似的。數(shù)據(jù)庫中段包含多個(gè)分區(qū)。各個(gè)分區(qū)也可以在不相鄰的分區(qū)中。
找一個(gè)圖來說明。
在分段情況下,會(huì)要求每個(gè)進(jìn)程的地址空間劃分為若干個(gè)段,每個(gè)段都有自己的段名,對(duì)應(yīng)到下圖中就是一個(gè)段號(hào)。每個(gè)段的地地址空間都是從0開始,是一個(gè)連續(xù)的地址空間。
從地址的存儲(chǔ)情況來說,段和頁的存儲(chǔ)方式都是類似的,都會(huì)包含兩部分。分段存儲(chǔ)中是段號(hào)和段內(nèi)地址,和分頁存儲(chǔ)中的頁號(hào)和頁內(nèi)地址類似。
由于一個(gè)進(jìn)程由很多段組成,而且各個(gè)段可能被分配在主存中的多個(gè)不相鄰的分區(qū)中,為了將進(jìn)程的邏輯地址轉(zhuǎn)換為物理地址,需要有一個(gè)短標(biāo)來指出進(jìn)程的某段放在主存中的位置以及段長(zhǎng)。
這一點(diǎn)從數(shù)據(jù)庫層面來說有類似的方面,首先是進(jìn)程由多個(gè)段組成,數(shù)據(jù)庫中可以理解為一個(gè)表包含多個(gè)段,數(shù)據(jù)段,索引段,LOB段,LOB索引段等等。這些都是獨(dú)立的段,在存儲(chǔ)的時(shí)候也可能分布在不同的表空間中,所以可能不是一個(gè)相鄰的分區(qū)。
而段的信息在操作系統(tǒng)層面是通過段表來維護(hù)的,數(shù)據(jù)庫層面則是通過數(shù)據(jù)字典,user_segments,user_extents來維護(hù)的,每個(gè)表包含的段,每個(gè)段包含的區(qū)都是很詳實(shí)的。
從分段和分頁的優(yōu)點(diǎn)來說,因?yàn)樗鼈兩婕暗膶用婧蛻?yīng)用方向不同,但是還是有一定的可比性,在段共享方面,分段存儲(chǔ)還是很有優(yōu)勢(shì),誰讓它是段共享呢。
從操作系統(tǒng)層面舉個(gè)例子就是一個(gè)多用戶系統(tǒng),有一個(gè)應(yīng)用程序可能包含的程序段是100K,數(shù)據(jù)段是40K,按理說需要40K*40+100k*40=1600+4000=5600k
在分段存儲(chǔ)中則需要100k+40k*40=1700k,從這一點(diǎn)上來說還是很大的改進(jìn)。
從這一點(diǎn)上來說,數(shù)據(jù)庫中的同義詞就有點(diǎn)分段存儲(chǔ)的味道,每個(gè)同義詞都可以訪問源表,相當(dāng)于共享了數(shù)據(jù),同義詞占用的存儲(chǔ)空間很小,幾乎可以忽略。
可能分段存儲(chǔ)和分頁存儲(chǔ)都各有千秋,但是都是在不斷的使用和改進(jìn)中主鍵發(fā)展起來的,分段存儲(chǔ)沒有段內(nèi)碎片,只有外部碎片,簡(jiǎn)單分段技術(shù)也是基于多重分區(qū)技 術(shù)的發(fā)展而來。另外簡(jiǎn)單分頁對(duì)于用戶是不可見的,用戶無法了解進(jìn)程被分頁或者分頁的細(xì)節(jié),但是簡(jiǎn)單分段對(duì)于用戶基本是可見的,當(dāng)進(jìn)程被交換出內(nèi)存的時(shí)候, 對(duì)應(yīng)的頁表和段表也需要隨著進(jìn)程一起撤出內(nèi)存。
當(dāng)然分頁分段方式還在不斷的發(fā)展中,要不怎么有后續(xù)的段頁式存儲(chǔ)呢,很多時(shí)候類比操作系統(tǒng)方面的知識(shí),就會(huì)讓我們對(duì)于很多事物有了全新的認(rèn)識(shí)和了解。
當(dāng)然順帶幫大家復(fù)習(xí)了操作系統(tǒng)的基礎(chǔ)知識(shí),我的目的也算達(dá)到了。