天天看點

解讀SQL 記憶體資料庫的細節

相信大家對記憶體資料庫的 概念并不陌生,之前園子裡也有多位大牛介紹過SQL記憶體資料庫的建立方法,我曾仔細 拜讀過,有了大緻了解,不過仍有很多細節不清晰,比如:

(1)記憶體資料庫是把整個資料庫放到記憶體中的嗎?

(2)資料都在記憶體裡面,那當機或者斷電了,資料不是沒有了嗎?

(4)記憶體資料庫号稱無鎖式設計,SQL是如何處理并發沖突的呢?

相信這些疑問也是大家在思考記憶體資料庫時經常遇到的難題,下文将為大家一一揭開這些問題的面紗,如有不對之處,還請各位看官幫我指出。

一、記憶體資料庫是如何存儲的,隻放在記憶體嗎?是把整個資料庫放在記憶體嗎?

答案:不是。

如果你還沒有看過宋大俠的部落格,建議現在就看看。

http://www.cnblogs.com/CareySon/p/3155753.html

在這篇文章中,我想着重引用如下兩個資訊:

(2)記憶體資料庫用檔案流的方式組織磁盤中的資料檔案;

我再補充一個資訊

(3)記憶體資料庫的資料檔案分data file和delta file,而且是成對出現;

1、記憶體資料庫其實就是将指定的表放到記憶體中,而不是整個資料庫;

從宋大俠的部落格中可以知道,記憶體資料庫的建立過程其實就是将表存放到記憶體中,而不是整個資料庫。下圖展示 了建立記憶體優化表的文法,紅色框标注了記憶體與傳統表建立時文法不相同的地方。

解讀SQL 記憶體資料庫的細節

記憶體優化表不僅僅是把資料存放到記憶體中,要不然跟傳統資料的緩存沒有差別。在記憶體資料庫中,記憶體優化表也叫為" natively compile memory-optimized tables",翻譯過來就是本地編譯記憶體優化表,記憶體優化表在建立的同時被編譯成本地機器代碼裝載到記憶體中,本地機器代碼包含了能被CPU直接執行的機器指令,是以對記憶體優化表的通路和操作将非常快。

解讀SQL 記憶體資料庫的細節

記憶體優化表分兩類,持久性表和非持久性表,對持久性表的改動會記錄日志,即使資料庫重新開機,資料也不會丢失;對非持久性表的操作不會記錄日志,這些操作結果隻保留在記憶體中,資料庫重新開機後資料會丢失。

上文隻是介紹了建立一張表的情況,在正常的業務環境中我們不可能對一個業務系統資料庫的每張表都去create,那對于已經存在的表,有沒有配置方法呢?答案恐怕不太令人滿意,目前SQL暫不支援遷移現有表到記憶體中,是以要想使用記憶體資料庫,現有的業務資料表必須重新建立。

2、記憶體資料庫用檔案流的方式組織磁盤中的資料檔案

在記憶體資料庫中,磁盤上存儲的資料檔案不在是區、頁的存儲方式,而是基于檔案流存儲。檔案流存儲的一個特點之一就是支援快速的讀操作,這在資料庫重新開機時将檔案流中的資料load到記憶體中時很能提高效率。

3、記憶體資料庫的資料檔案分data file和delta file,而且是成對出現;

記憶體資料庫中插入、更新的資料和删除的資料實體分開存儲的,分别用data file和delta file儲存。

(1)Data file

Data file用來儲存"插入"或者"更新"的資料行,data file中資料行的存儲順序嚴格按照事務執行的順序組織,比如data file中第一行的資料來自于事務1,第二行資料來自于事務2,這兩行可以是同一個表的資料,也可以是不同表的資料,取決于這兩個連續的事務操作的記憶體優化表是否相同。 這種方式的好處是保證了磁盤IO的連續性,避免随機IO。

Data file的大小是固定的,為128MB,當一個data file被寫滿了後,SQL會自動建立一個data file。因為資料在data file中儲存的順序是按照事務的執行順序進行的,是以一張表的資料行(來自多個事務)可能跨越了多個data file,當對多行進行更新操作時,寫操作可以配置設定到多個檔案上,并且同時進行,這樣就可以加快更新的效率。(下文介紹delta file時會介紹)

如下圖,一共有4個data files(淺藍色),第一個data file的事務範圍為100-200,第二個data file的事務範圍為200-300……(100、200表示時間戳)

解讀SQL 記憶體資料庫的細節

在Data file中,如果一行被删除或者更新了,這行不會從data file中移除,而是通過delta file(上圖黃色框)來标記删除的行,(update的本質是delete和insert的集合,是以執行update時也會有删除的動作),這樣可以消除不必要的磁盤IO。

(2) Delta file

每個data file都有一個與之比對的Delta File,這個比對是指事務範圍上的比對,兩者記錄的是同一段事務(包括一個或者多個事務)上的資料,Delta File中記錄了data file中被删除行的标記,這個标記其實就是一個關聯資訊{inserting_tx_id, row_id, deleting_tx_id }。它跟data file一樣,也是嚴格按照事務操作的順序來儲存删除的行的資訊。

解讀SQL 記憶體資料庫的細節

如上圖,該記憶體資料庫有5個data file,分别存放了事務範圍在100-200、200-300、300-400、400-500及500的資料。如果有一個時間戳為501的事務需要删除時間戳為150、250、450的事務所産生的資料和增加一些新資料時,相應的IO請求就會被配置設定到第1、2、4的 delta file上和第5的data file上。删除操作可以配置設定到多個檔案上,并且同時進行,這樣就可以加快删除的效率。

二、資料都在記憶體裡面,那當機或者斷電了,資料不是沒有了嗎?

記憶體資料庫通過兩種方式保證資料的持久性:事務日志和chcekpoint。

(1)事務日志

記憶體資料庫的"寫日志"和"寫資料"在一個事務中進行,在事務執行期間,SQL會先"寫資料"然後在才"寫日志",這點與傳統資料庫不同,在傳統資料庫中,不管是在記憶體中還是磁盤中,"寫資料"總是在"寫日志"之後,也就是通常所說的WAL(Write-Ahead Transaction Log)。但是,在事務送出時,記憶體資料庫和傳統資料庫在"寫日志"上沒有什麼差別:日志會先于資料寫入到磁盤中。

解讀SQL 記憶體資料庫的細節

是以,即使伺服器發生了當機或者斷電,下次資料庫重新開機時會按照已經儲存在磁盤中事務日志将業務redo(重做),是以不要擔心資料會丢失。

另外,需要補充的是,記憶體資料庫隻會對持久性表将已送出的事物日志儲存到磁盤中。這樣做的好處可以減少寫磁盤的次數。記憶體資料庫支援頻繁、快速的增、删、改等操作,這個強度遠遠高于傳統資料庫,資料庫需要為每筆操作寫日志,這樣就會産生大量磁盤IO,寫日志操作将有可能成為性能瓶頸,不記錄未送出的事務日志就減少寫日志的數量,進而可以提高資料庫的性能。

有同學會想,不記錄未送出事務的日志會不會導緻資料不一緻呢?

肯定不會,因為日志在寫入磁盤前不可能發生先把"髒資料"寫入到磁盤的現象(下面介紹checkpoint的時候會介紹原因)。

(2)CheckPoint

在傳統資料庫這種,Checkpoint可以将未送出的資料flush到磁盤的mdf檔案中,這個現象在記憶體資料庫中不會發生,因為記憶體資料庫隻将已送出事務的日志,而在寫日志(到磁盤)之前不可能将資料先寫到磁盤中,是以可以保證寫到磁盤中的資料一定是已送出事務的資料。

三、資料在記憶體是怎麼存放的,還是按照頁的方式嗎,一行的大小有限制嗎?

       答案:不是按照頁的方式,一行的限制大小為8060Bytes。

記憶體優化表是基于行版本存儲的,同一行在記憶體中會有多個版本,可以将記憶體優化表的存儲結構看作是該表中 所有行的多個行版本的集合。

記憶體優化表中的行跟傳統資料庫的行結構是不一樣的,下圖描述了記憶體優化表中一行的資料結構:

解讀SQL 記憶體資料庫的細節

在記憶體優化表中,一行有兩個大部分組成:Row header和Row body,

Row header記錄這個行的有效期(開始時間戳和結束時間戳)和索引指針

Row body記錄了一行的實際資料。

在記憶體優化表中,行版本的數量是由針對該行的操作次數決定的,比如:每更新一次,就會新産生一行,增加一個行版本,新行有新的開始時間戳,新行産生後,原來的資料行會自動填充結束時間戳,意味這行已經過期。

解讀SQL 記憶體資料庫的細節

備注:上圖實際上隻有3行,第1行有3個行版本,第2行有2個行版本,第3行有4個行版本。

既然同一行在記憶體中存在這麼多的行版本,那資料庫在通路時是怎麼控制的呢?

在傳統資料庫中,表中每一行都是唯一的,一個事務如想找到一行,通過檔案号、頁号、槽位就可以了。

在記憶體資料庫中,每一行有多個行版本,一個事務不可能對将每個行版本都操作一遍,實際上,一個事物隻能操作同一行的一個行版本,至于它能對哪個行版本進行操作,取決于事務執行時間是否在這行的兩個時間戳之間。除此之外的其他行版本對該事務而言是不可見的。

由于一行可能存在多個行版本,大家可能會提出這樣一個疑問:每行都有這麼多行版本,一張上百萬行的表,記憶體哪夠呀。不用擔心,前文介紹過了,每個行實際上是有時間戳的,對于已經打上結束時間戳且沒有活動事務通路的行,SQL Server會通過garbage collection機制回收它占用的記憶體,進而節省記憶體。是以不要擔心記憶體不夠。

四、記憶體資料庫号稱無鎖式設計,那如果發生了并發沖突怎麼辦,SQL是如何處理沖突的呢?

答案:記憶體資料庫用行版本來處理沖突。

鎖的一個重要作用就是避免多個程序同時修改資料,進而造成資料不一緻。常見的沖突現象包括讀寫互鎖和寫寫互鎖。那記憶體資料庫是如何通過行版本來解決這兩種鎖定現象的呢?

(1)讀寫互鎖

在記憶體資料庫中,所有對記憶體優化表的事務隔離都是基于快照的,準确的說是基于行的快照。從上文行的 結構可以知道,每行的行頭包括開始時間戳和結束時間戳的,一個事務能不能通路到這行關鍵在于事務的啟動時間是不是在這行的兩個時間戳内。

如果某個事務正在修改一行(快照),但還未送出到記憶體優化表中,也就是說"新行"還沒有結束時間戳,對"讀事務"而言,它讀還是是原來行(快照),是以不會存在髒讀的現象。

(2)寫寫互鎖

        兩個事務同時更新一行時,就會發生寫寫互鎖。

記憶體資料庫沖突發生的機率比傳統資料庫小很多,但如果實在遇到了沖突,隻能調整應用程式,在應用程式中加入"重試邏輯"(等待一會,然後再重新發起事務)來解決。

或許有同學覺得這種方式好像也沒有什麼大的性能改變。其實不然,舉個例子,在傳統資料庫中一個鎖可能将整個表都管住了,在表鎖期間隻能等待這個事務做完才能執行其他事務,而實際上這個事務可能隻是修改了小部分行,因為表鎖的存在,其他行那些不需要被這個事務操作的行。但記憶體資料庫中寫寫沖突總是發生在行級别的,這個粒度小多了,影響沒這麼大。