體系結構---實例

實例

**
我們來看一下單實例進程的交互流程:
**用戶進程:**可以是一般的客戶端軟件,像Oracle的sqlplus,sql developer,或者是一些驅動程序等等都屬於用戶進程
**服務器進程:**服務器進程有時會稱爲前臺進程,當然是相對於後臺進程(後面會提到的數據庫寫入器,日誌寫入器等)來說的,服務器進程的主要作用就是處理連接到當前實例的用戶進程的請求,對客戶端發來的sql進行執行並返回執行結果,感覺服務器進程就是講數據讀出來,放在緩存中,而後臺進程主要是把數據寫入物理文件中
流程:
首先:用戶與用戶進程進行交互,然後用戶進程再與服務器進程進行交互,在然後服務器進程與實例進程進行交互,最後,實例與數據庫進程進行交互

舉個例子,用戶在淘寶買東西,選好相應的條件之後,數據會發送到web服務器上,而web服務器會向實例發送很多連接,這些連接叫做
連接池,oracle實例針對web服務器的每一個連接建立一個相應的服務器進程(前臺進程)例如用戶想買鞋子,將條件發送給web服務器,Web服務器會選中一個連接,通過這個連接給實例發送相應的SQL語句。實例接到SQL語句之後,解析,執行並將結果返給Web服務器,web服務器將結果通過界面的形式返回給用戶

實例的進程和內存結構:
內存結構和進程結構關係較緊密,進程會作用到對應的內存區域,比如數據庫寫入器作用到數據庫緩衝區緩存中,日誌寫入器會作用到日誌緩衝區,所以內存結構和進程結構會相互配合地進行描述
oracle實例內存結構由兩部分組成SGA(系統全局區)和PGA(用戶全局區)組成,SGA是一塊共享的內存區域,也是最大的一塊內存區域;PGA則是用戶會話專有的內存區域,每個會話在服務器端都有一塊專有的內存區域就是PGA。
oracle所有的後臺進程都共享SGA內存
每個進程都有自己的進程空間,這個空間是PGA提供的。PGA有一塊大的內存空間,每當進程運行,PGA都會拿出一小塊內存空間供進程使用,PGA進程主要供前臺進程使用,但是後臺進程也有自己的PGA內存
本文主要對SGA進行分析描述。SGA組成如下
Shared Pool/共享池
Database Buffer Cache/數據庫緩衝區高速緩存
Redo Log Buffer/重做日誌緩衝區
Large Pool/大池
Java Pool/JAVA池
Streams Pool/流池
在這裏插入圖片描述
在這裏插入圖片描述

SGA–緩存區緩存

我們舉個簡單的例子,來詳細的說一下緩存的機制
一條SQL語句的執行流程:客戶端輸入一條SQL語句,通過連接送給了服務器進程,oracle單獨分配一個PGA空間供服務器進程使用。服務器進程接收到SQL語句之後,將SQL語句解析成執行計劃,然後拿着執行計劃去執行,去數據文件中取數據,返回給用戶。

解析過程:服務器首先判斷SQL語句語法有沒有錯誤,然後在判斷SQL中的表或者視圖有沒有權限,存不存在。還要去判斷這表SQL語句怎麼執行(選取最優執行方案),最後生成執行計劃。解析過程中會消耗很多資源:CPU,IO。但是有很多時候,可能很多用戶都在執行相同的業務,這種情況下會同時消耗CPO,但是如果相同的SQL語句解析出執行計劃,緩存起來,下一個相同的業務執行的時候,就可以直接拿着緩存的執行計劃去執行,這樣就會避免重複解析消耗不必要的CPU資源。oracle提供這種機制,會把首次執行的執行計劃放進共享池。這樣下次有重複業務的時候就可以直接拿共享池中的執行計劃去執行。
取數據
服務器進程拿着執行計劃去數據文件中取出數據,返給用戶,數據庫緩衝區高速緩存是用來緩存服務器進程取出來的數據,我們完全有必要進行這樣的緩存,假如說a用戶訪問數據文件中一個表的數據,但是其他用戶訪問這個表的數據可能性也非常的大,沒有數據庫緩衝區高速緩存的話,每次訪問一個表中數據,都需要重新訪問數據文件,發生物理IO,降低性能。但是如果把這個表中數據緩存到數據庫緩衝區高速緩存中的話,再有用戶訪問這個表,直接在緩存中獲取數據,性能就會大大提高。所以獲取數據的時候,首先去數據庫緩衝區高速緩存找,如果沒有再會去數據文件中去找,找到之後,也並不是直接返回給用戶,而是先放在數據庫緩衝區高速緩存中,從這裏面返回給用戶
邏輯IO:服務器進程讀取數據時,先去數據庫緩衝區高速緩存讀取數據,這樣叫做邏輯讀取(內存讀)
物理IO:數據庫緩衝區高速緩存沒有數據,去數據文件讀取時,叫做物理讀取(磁盤讀)
命中率:邏輯/邏輯+物理,命中率越高,邏輯讀越多,速度越快

修改過程同樣,執行修改,刪除同樣需要先讀取數據,服務器進程從數據文件中讀取,放在數據庫緩衝區高速緩存中,然後在數據庫緩衝區高速緩存修改數據,修改的時候會產生日誌,日誌是服務器進程產生的 。這個時候內存裏面的數據就和數據文件中的數據不一致了,
這就需要把緩存區的最新數據寫入到數據文件中了,寫入到數據文件中需要另一個後臺進程DBWn。由重做日誌緩存區寫入到log日誌文件中去需要lgwr進程

select * from caozyxx where caozyxm = 'admin';

我們來看這麼一條簡單的查詢語句,oracle是如何處理的。首先,當用戶提交了該條sql語句,由對應的用戶進程(比如我們常用的sql developer)將其發送給服務器,監聽程序監聽到該條請求,會爲其建立一個對應的服務器進程,然後服務器進程會先掃描緩衝區中有沒有包含關鍵行(“admin”)的數據塊,如果有,這就算一次緩存命中了,然後相關行會傳輸到PGA進行進一步處理,最終經過格式化後展示給用戶;如果沒有命中,那麼服務器進程會首先將對應行復制到緩衝區內,然後再返回給客戶端。
  DML(insert,update,delete)操作同理,加入用戶發送一條update語句,服務進程依然先去掃描緩衝區,如果緩存命中,則直接更新,數據變髒;如果沒有命中,由服務器進程將對應數據塊先從磁盤上覆制到緩衝區內,再進行更新操作。

數據緩存區就是我們將數據庫文件的數據存放的緩存。它用來保存從數據文件裏讀取近期的數據塊信息。當中的數據被全部用戶享用。數據緩存區有很多大小同樣的緩存塊組成,這些緩存塊大致能夠分爲3類:
一、 空暇緩存塊
當我們又一次啓動數據庫後。系統就會爲數據庫分配一些空暇的緩存塊。
空暇緩存塊中是沒有不論什麼數據的,他們在等待後臺進程或server進程向當中寫入數據。當Oracle 數據庫從數據文件裏讀取數據後,數據庫就會尋找是否有空暇的緩存塊,以便將數據寫入當中。
一般來說。數據庫在啓動的時候,就會在內存中預先分配這些緩存塊。
所以,Oracle數據庫在啓動的時候,會佔用比較多的內存(這個內存空間是能夠設置的)。可是,這能夠免去在實際須要時向內存申請的時間。所以,有時候Oracle數據庫儘管已啓動,內存的佔用率就非常高。可是。其興許仍然能夠正常執行的原因。而其它數據庫儘管剛啓動的時候內存佔用率不是非常高,可是。但系統內存到達80%以上時,在進行數據處理就會受到明顯的影響。
所以,當我們利用SELECT語句從數據庫文件裏讀取文件的時候,數據庫首先會尋找是否有空暇的緩存。

二、命中緩存塊
命中緩存塊保存那些正在使用的數據。
當select語句先從數據庫文件裏讀取數據後,會把取得的數據放入到這個命中緩存塊中。
直到快速緩存消耗完成等原因,這個空間纔會被釋放。如此下次假設再次訪問同樣的數據的時候就能夠從這裏進行查找,節省時間(由於僅僅是被select。因此這裏的數據是不會換出內存)。

三、髒緩存塊
髒緩存塊保存已經被改動可是還沒有被寫入數據庫文件的數據。當訪問完數據之後。由空暇緩存塊標誌轉化爲命中緩存塊標誌。當我們運行update這類帶侵略性的操作的時候,我們要先去命中緩存區去尋找數據,假設存在就行直接操作。而且此時命中緩存區標誌被轉爲髒緩存塊標誌。這樣就行實現數據的一致性。當滿足一定的條件時。這些髒緩存塊中的數據內容會被寫入到數據庫文件裏去,以便永久性的保留數據庫改動記錄。
當寫入數據庫之後髒緩存塊標誌就會轉化爲空暇緩存塊。

那Oracle數據庫關於實現三大緩存塊標誌轉化的原理:
實現以上機制主要靠兩個列表:1.近期最少使用列表(LRU列表); 2.寫入列表(DIRTY表).當中LRU列表保存着所有空暇緩存塊、命中緩存塊和所有還沒有被移入到DIRTY列表中的髒緩存塊。
當Oracle數據庫用戶在查詢數據的時候。可能會遇到例如以下情況:
1、查詢數據時。數據庫首先在LRU列表中查詢是否有空暇緩存塊。其查詢的數據是從尾部開始查找。當查找有空暇的緩存塊時,數據庫就會把查到的數據寫入到這個空暇緩存中。
2、若數據庫在查詢的時候。首先查到的是髒緩存的話,則會把這個髒緩存移動到DIRTY列表中。然後再繼續查詢。直到查詢到合適的空暇緩存塊爲止(查詢的時候數據發生變動)。
3、若數據庫在LRU列表中,從尾到頭查了一遍(忽略oracle的查找算法)。沒有找到空暇緩存塊,或者儘管有空暇緩存塊,可是其容量不符合要求時,數據庫就會臨時結束這一次查找。然後,系統就會觸發數據庫寫進程,把DIRTY列表中的髒緩存塊寫入到數據庫中去。已經被寫入到數據庫文件裏去的髒緩存塊將又被數據庫標記爲空暇緩存塊。並插入到LRU列表中。當數據庫運行完成這個動作之後。數據庫又會對LRU列表進行搜索,找到合適的數據快速空暇緩存之後,就會把讀取的數據寫入到這個空暇緩存中。

髒緩衝區
  如果緩衝區存儲的塊和磁盤上的塊不一致,該緩衝區就叫做“髒緩衝區”,髒緩衝區最終會由數據庫寫入器(DBWn)寫入到磁盤中去。
  
數據庫寫入器(DBWn)
  數據庫寫入器是Oracle的一個後臺進程,所謂後臺進程是相對於前臺進程(服務器進程)來講的。DBWn的"n"意味着一個實例是可以有多個數據庫寫入器的。
  作用:簡而言之,DBWn的作用就是將變髒了的緩衝區從數據庫緩衝區緩存中寫入到磁盤中的數據文件中去。
  
  數據庫緩衝區緩存這塊內存區域和數據庫寫入器這塊是比較重要的概念,別的數據庫產品像mySql也都有對應的實現,只不過叫法不一樣罷了。瞭解這塊的時候,要時刻意識到會話是不會直接更新磁盤數據的,會話的更新,插入,刪除包括查詢等都是先作用到緩衝區上,隨後,DBWn會將其中的髒緩衝區轉儲到磁盤上去。 
  DBWn什麼時候寫入?
DBWn是個比較懶的進程,它會盡可能少的進行寫入,在以下四種情況它會執行寫入:

a.沒有任何可用緩衝區(不得不寫)

b.髒緩衝區過多

c.3秒超時(最晚3秒會執行一次寫入)

d.遇到檢查點,即checkPoint(檢查點),檢查點是個Oracle事件,遇到檢查點,DBWn會執行寫入。比如實例有序關閉的時候會有檢查點,DBWn會將所有髒緩衝區寫入到磁盤上去的,這很容易理解,要保持數據文件的一致性。
 注意:

從上述DBWn的幾個寫入時機,我們能意識到,DBWn的寫入不是直接依賴於會話的更新操作的。不是一有髒緩衝區,它就執行寫入。而且,DBWn執行寫入跟commit操作也沒有任何關係,不要以爲commit操作的影響結果會實時流入到磁盤中去。

DBWn採用極懶算法進行寫入,原因我們應該要清楚:頻繁的磁盤IO對系統的壓力很大,如果DBWn很積極地去寫入磁盤,那對系統性能的影響就太大了,換個角度想,如果DBWn很勤快的寫磁盤,那麼數據庫緩衝區存在的意義也就不大了。

當然,講到這兒,我們可能會意識到一個問題,DBWn如此懶地進行數據轉儲,如果在某一時刻,數據庫緩衝區緩存內存在着大量的髒緩衝區(生產環境中,這是常態),也就是有大量的未commit和已commit的數據還在內存中,沒有持久化到磁盤中,然後突然系統斷電了,這種情況下,數據是不是就丟掉了?數據當然不會丟失,這就引出了重做日誌(redo log)的概念,接下來,我們就來談談對應重做日誌的內存結構和後臺進程。

SGA–日誌緩衝區

當我們執行一些DML操作(insert,update,delete),數據塊發生改變了,產生的變更向量則會寫入到重做日誌文件中去。有了這些記錄,當系統由於斷電等因素突然宕掉,數據庫緩衝區緩存內的大量髒數據還沒來得及寫入到數據文件中去,在重新啓動的時候,會有一個實例恢復的進程,在此過程中就應用了重做日誌記錄來使數據保持一致;或者數據庫遭遇了物理損壞,比如磁盤損壞了,此時可以通過Oracle的備份恢復工具(如RMAN)進行數據恢復,原理就是 提取備份集–>應用重做日誌文件中的變更記錄。
 日誌緩衝區
日誌緩衝區是一塊比較小的內存區域,它是用來短期存儲將寫入到磁盤中的重做日誌文件中的變更向量的。

日誌緩衝區存在的意義依然是爲了減少磁盤IO,減少用戶的等待時間,試想下,如果每一次用戶DML操作都要進行等待重做記錄被寫入到磁盤中去,體驗會有多差勁。

日誌寫入器(LGWR)
顧名思義,日誌寫入器(LGWR)就是把日誌緩衝區內的內容寫入到磁盤的重做日誌文件中去,相比數據庫寫入器(DBWn),日誌寫入器就勤快多了。

以下三種情況LGWR會執行寫入:
a.commit時寫入
  前面提過,DBWn的寫入和commit沒有任何關係,如果commit時數據庫沒有任何記錄,那數據就真的丟失了,Oracle 的重做日誌就是爲了保證數據安全而存在的,commit時,會話會先掛起,等待LGWR將這些記錄寫入到磁盤上的重做日誌文件中,纔會通知用戶提交完成。所以,LGWR在commit時執行寫入,是爲了確保事務永不丟失。

b.日誌緩衝區的佔用率達到1/3。

c.DBWn要寫入髒緩衝區前

這個寫入是爲了數據回滾考慮的。DBWn完全可能寫入還沒提交的事務(參照上面提到的寫入時機),那如何保證事務回滾呢?

首先要知道,DBWn除了寫入實際的數據,還會寫入撤銷數據,簡單說,事務回滾需要撤銷數據,在寫入撤銷數據前,會先寫入針對撤銷數據的日誌記錄(有點繞),若用戶要進行事務回滾,就可以應用這些日誌記錄來構造撤銷數據,然後進行回滾。

我們對這兩塊最重要的內存區域和對應的後臺進程做個總結:

數據庫緩衝區緩存和日誌緩衝區都是爲了提高性能,避免頻繁IO而存在的。日誌緩衝區相比數據庫緩衝區緩存要小的多,並且不能進行自動管理,對於日誌緩衝區的修改需要重啓實例,數據庫緩衝區緩存可進行自動管理。作用在數據庫緩衝區緩存上的DBWn進程,爲了避免頻繁的磁盤IO導致系統性能下降,會盡可能少地執行寫入,且DBWn的寫入和commit操作沒有任何關係;

而作用在日誌緩衝區上的LGWR進程,則會非常積極地進行寫入,一般情況下,它幾乎是實時地將重做日誌記錄轉儲到磁盤中去。LGWR是Oracle體系結構中最大的瓶頸之一。DML的速度不可能超過LGWR將變更向量寫入磁盤的速度。

我們在來看下其他的內存區域和後臺進程
**

SGA–共享池

**
shared pool
它的主要作用是緩存SQL語句以及SQL語句的執行計劃。查詢一個SQL語句分爲解析–執行–取數據
shared pool主要作用在解析階段

共享池是最複雜的SGA結構,它有許多子結構,我們來看看常見的幾個共享池組件:

**1.庫緩存(library cache):**庫緩存這塊內存區域會按已分析的格式緩存最近執行的代碼(執行計劃),這樣,同樣的sql代碼多次執行的時候,就不用重複地去進行代碼分析,可以很大程度上提高系統性能。
  硬解析:一條語句進入庫緩存中如果沒有發現執行計劃,那麼就會發生硬解析
   硬解析流程:判斷SQL語句語法有沒有錯誤,然後在判斷SQL中的表或者視圖有沒有權限,存不存在。還要去判斷這表SQL語句怎麼執行(選取最優執行方案),最後生成執行計劃。一條SQL語句有多種執行方案,有的執行方案消耗資源大,執行快,有的慢,消耗的資源多,oracle要在這些執行方案裏面挑選出一條最優的作爲執行計劃。這是硬解析裏面最重要,消耗資源最大的一步。
  軟解析:如果有會進行軟解析
   軟解析流程:判斷SQL語句語法有沒有錯誤,然後在判斷SQL中的表或者視圖有沒有權限,存不存在。軟解析沒有挑選執行計劃這一步。
   所以在實際環境中,我們希望軟解析越多越好,不管軟解析還是硬解析都需要查詢對象的信息,權限的信息等等,這些信息都是存在在數據字典裏面的。他們都會頻繁的訪問數據字典,爲了提高速度,優化,oracle將數據字典裏的信息也放在了共享池中,共享池的數據字典緩存(row cache)

SELECT * FROM v$sysstat WHERE NAME LIKE 'parse%';

庫緩存中的內存塊也是在鏈上,但是不是按照free空間的排列方式排的,而是按照sql語句中的字母轉換成ASCII值經過運算得到了一個數字編號,根據編號掛到相應的鏈上。當有想用的SQL語句時,oracle只需要解析成相應的數字編號,然後鎖住對應的編號鏈,通過遍歷找到對應的塊,然後進行軟解析。所以說free空間取得塊的操作只有硬解析纔會用
  **2.數據字典緩存(row cache):**存儲oracle中的對象定義(表,視圖,同義詞,索引等數據庫對象),這樣在分析sql代碼的時候,就不用頻繁去磁盤上讀取數據字典中的數據了
  **3、pl/sql區:**緩存存儲過程,函數,觸發器,等數據庫對象,這些對象都存儲在數據字典中,通過將其緩存到內存中,可以在重複調用的時候提高性能。
  4、free 空閒空間
   free空間內存裏面是一個一個的小的內存塊,並且由一個一個的鏈將內存塊連接起來,兩個鏈之間掛的內存塊大小不一樣
   比如,一個鏈上掛的內存塊是在1-10k的大小,下一個是10-20k的,這樣越來越大。當一個SQL語句或者執行計劃需要解析,會根據解析需要的內存大小在free空間相應的鏈上取下一個內存塊,將SQL語句或者執行計劃寫入進去,然後放到庫緩存中去。而如果沒有相同的內存大小,free會拿出個大一些的內存塊,然後分成兩塊,一塊分成相應大小供解析使用,另一塊放在相應範圍的鏈上。只有硬解析需要在free裏面獲取內存塊
   而且在硬解析獲取內存塊,產生的小的內存塊碎片,如果發生了大量的硬解析,我們的free空間裏就會有很多的小的內存塊,這個時候可能free內存還有很多,但是由於解析的語句用的的內存在free中沒有了。就會造成解析失敗。oracle就會報一個錯誤:ora-4031錯誤
   鏈:有兩個特點一是把內存串起來,二是遍歷,從鏈的第一個然後依次找,知道找到需要的內存塊
   內存裏面運用了大量的鏈

SELECT count(*) FROM x$ksmsp  --查詢內存塊數量

如何解決ora-4031錯誤
1、alter system flush shared pool;–執行之後清空所有的庫緩存和字典緩存。清空後以後在執行的語句都會發生硬解析,但是執行完後可以解決ora-4031錯誤 但只能治標不治本
2、設置shared pool 的大小也可以解決4031錯誤,我們只能設置shared pool的大小,而下面的庫緩存,字典緩存由oracle自己設置,我們無法維護。而在我們設置共享池的大小時,有兩個參數,非常重要
 查看總的SGA大小
 show parameter sga_target
 show parameter sga_max_target
 設置shared pool 大小 alter system set shared_pool_size = 150m scope =both;
 查看分配的各池的大小

select component,current_size from v$sga_dynamic_components;

sga_target 是動態的設置大小,設置完直接生效,而sga_max_target 是靜態的,設置完需要重啓數據庫,sga_max_target是制約sga_target存在的,因爲如果我們在設置sga_target的大小時,如果不小心設置的過高,這樣會把其他的內存都用來充當SGA的空間,這樣會導致系統直接掛起,很可能會導致系統死機,數據文件丟失等嚴重後果,而sga_max_target的存在限制了這一問題,我們可以在sga_max_target中設置一個大小,當設置sga_target的大小時,大小就會不允許超過sga_max_target設置的大小。
3、 共享可以解決佔用大量空間,發生硬解析出現空間都是碎片造成4031錯誤的問題,共享必須執行的語句完全一樣(空格也不行),所以要統一書寫風格。對於格式一樣,條件不一樣的語句,可以使用綁定變量解決。
4、在共享池裏面除了 上面的free,庫緩存等之外,還有一個保留區的概念,這個空間只是用來緩存大對象,也就是說,如果我們執行的一條語句的大小超過了保留區的範圍,那麼服務器進程就不會去free空間裏面找內存塊,而是直接去保留區中,這樣也能減少4031錯誤的產生。

**

SGA–大池:

大池是個可選的內存區域,前面我們提到專有服務器連接和共享服務器連接,如果數據庫採用了共享服務器連接模式,則要使用到大池;RMAN(Oracle的高級備份恢復工具)備份數據也需要大池。

**

SGA–JAVA池

**
Oracle 的很多選項使用java寫的,Java池用作實例化Java對象所需的堆空間

**

SGA–流池:

**
從重做日誌中提取變更記錄的進程 和 應用變更記錄的進程會用到流池(如實例不正常關閉,譬如斷電導致實例關閉,在重啓時,Oracle會自動執行實例恢復過程,在此過程需要提取重做日誌記錄和應用重做日誌兩個動作)

以上列舉了Oracle常見的內存結構,要注意的是,上面列舉的內存區域,除了日誌緩衝區是固定的,不能動態調整也不能進行自動管理外,其他內存區域都可以進行動態調整,也可以進行自動管理。

在說說Oracle 的幾個後臺進程(DBWn和LGWR較重要,前面已做了瞭解,在此不再贅述)

SMON(System Monitor):安裝和打開數據庫,實例恢復也是由此進程完成的

PMON(Process Monitor):進程監視器,主要監視服務器進程。前面提到過,專有服務器體系模式下,用戶進程和服務器進程是一對一的關係,如果某個會話發生異常,PMON會銷燬對應的服務器進程,回滾未提交的事務,並回收會話專有的PGA內存區域。

CKPT(Checkpoint Process):CKPT負責發起檢查點信號,手動設置檢查點的語法:

SQL>alter system checkpoint;

檢查點可強制DBWn寫入髒緩衝區,當數據庫崩潰後,由於大量髒緩衝區未寫入數據文件,在重新啓動時,需要由SMON進行實例恢復,實例恢復需要提取和應用重做日誌記錄,提取的位置就是從上次檢查點發起的位置開始的(檢查點之前的數據已經被強制寫入到數據文件中去了),這個位置稱爲RBA(redo byte address),CKPT會不斷將這個位置更新到控制文件中去(以確定實例恢復需要從哪兒開始提取日誌記錄)。

MMON(Manageability Monitor)
  數據庫的自我監視和自我調整的支持進程。實例在運行中,會收集大量有關實例活動和性能的統計數據,這些數據會收集到SGA中,MMON定期從SGA中捕獲這些統計數據,並將其寫入到數據字典中,便於後續對這些快照進行分析。(默認情況,MMON每隔一個小時收集一次快照)

ARCn(Archiver)  歸檔進程,這個進程是可選的,如果數據庫配置爲歸檔模式,這個進程就是必須的。所謂歸檔,就是將重做日誌文件永久保存(生產庫一般都會配置爲歸檔模式)到歸檔日誌文件中。歸檔日誌文件和重做日誌文件作用是一樣的,只不過重做日誌文件會不短被重寫,而歸檔日誌文件則保留了關於數據更改的完整的歷史記錄。
根據以上理解,配合內存進程交互圖,深入理解

在這裏插入圖片描述

**

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章