範文齋

有關sqlserver2014內存數據庫特性介紹

相信大家對內存數據庫的概念並不陌生,之前也有多位大牛介紹過SQL內存數據庫的創建方法,我曾仔細 拜讀過,有了大致瞭解,不過仍有很多細節不清晰,比如:

有關sqlserver2014內存數據庫特性介紹

(1)內存數據庫是把整個數據庫放到內存中的嗎?

(2)數據都在內存裏面,那宕機或者斷電了,數據不是沒有了嗎?

(3)據在內存是怎麼存放的,還是按照頁的方式嗎,一行的大小有限制嗎?

(4)內存數據庫號稱無鎖式設計,SQL是如何處理併發衝突的呢?

相信這些疑問也是大家在思考內存數據庫時經常遇到的難題,下文將爲大家一一揭開這些問題的面紗,如有不對之處,還請各位看官幫我指出。

一、內存數據庫是如何存儲的,只放在內存嗎?是把整個數據庫放在內存嗎?

答案:不是。

sql server 2014提供了衆多激動人心的新功能,但其中我想最讓人期待的特性之一就要算內存數據庫了。去年我再西雅圖參加SQL PASS Summit 2012的開幕式時,微軟就宣佈了將在下一個SQL Server版本中附帶代號爲Hekaton的內存數據庫引擎。現在隨着2014CTP1的到來,我們終於可以一窺其面貌。

內存數據庫

在傳統的數據庫表中,由於磁盤的物理結構限制,表和索引的結構爲B-Tree,這就使得該類索引在大併發的OLTP環境中顯得非常乏力,雖然有很多辦法來解決這類問題,比如說樂觀併發控制,應用程序緩存,分佈式等。但成本依然會略高。而隨着這些年硬件的發展,現在服務器擁有幾百G內存並不罕見,此外由於NUMA架構的成熟,也消除了多CPU訪問內存的瓶頸問題,因此內存數據庫得以出現。

內存的學名叫做Random Access Memory(RAM),因此如其特性一樣,是隨機訪問的,因此對於內存,對應的數據結構也會是Hash-Index,而併發的隔離方式也對應的變成了MVCC,因此內存數據庫可以在同樣的硬件資源下,Handle更多的併發和請求,並且不會被鎖阻塞,而SQL Server 2014集成了這個強大的功能,並不像Oracle的TimesTen需要額外付費,因此結合SSD AS Buffer Pool特性,所產生的效果將會非常值得期待。

SQL Server內存數據庫的表現形式

在SQL Server的Hekaton引擎由兩部分組成:內存優化表和本地編譯存儲過程。雖然Hekaton集成進了關係數據庫引擎,但訪問他們的方法對於客戶端是透明的,這也意味着從客戶端應用程序的角度來看,並不會知道Hekaton引擎的存在。如圖1所示。

圖1.客戶端APP不會感知Hekaton引擎的存在

首先內存優化表完全不會再存在鎖的概念(雖然之前的版本有快照隔離這個樂觀併發控制的概念,但快照隔離仍然需要在修改數據的時候加鎖),此外內存優化表Hash-Index結構使得隨機讀寫的速度大大提高,另外內存優化表可以設置爲非持久內存優化表,從而也就沒有了日誌(適合於ETL中間結果操作,但存在數據丟失的危險)

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

(1)內存數據庫其實就是將指定的表放到內存中,而不是整個數據庫;

(2)內存數據庫用文件流的方式組織磁盤中的數據文件;

我再補充一個信息

(3)內存數據庫的數據文件分data file和delta file,而且是成對出現;

1、內存數據庫其實就是將指定的表放到內存中,而不是整個數據庫;

內存數據庫的創建過程其實就是將表存放到內存中,而不是整個數據庫。下圖展示 了創建內存優化表的語法,紅色框標註了內存與傳統表創建時語法不相同的地方。

內存優化表不僅僅是把數據存放到內存中,要不然跟傳統數據的緩存沒有區別。在內存數據庫中,內存優化表也叫爲"natively compile memory-optimized tables",翻譯過來就是本地編譯內存優化表,內存優化表在創建的同時被編譯成本地機器代碼裝載到內存中,本地機器代碼包含了能被CPU直接執行的機器指令,所以對內存優化表的訪問和操作將非常快。

內存優化表分兩類,持久性表和非持久性表,對持久性表的改動會記錄日誌,即使數據庫重啓,數據也不會丟失;對非持久性表的操作不會記錄日誌,這些操作結果只保留在內存中,數據庫重啓後數據會丟失。

上文只是介紹了新建一張表的情況,在正常的業務環境中我們不可能對一個業務系統數據庫的每張表都去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表示時間戳)

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

如果data file的數據永不刪除,那文件豈不是無限制的增大,以後備份不是得用很大的磁盤才行?當然不是,SQL在處理這個問題用到方法其實很簡單——"合併",根據合併策略,將多個data file和delta file合併起來,依據delta file的內容刪除data file中的多餘記錄,然後將多個data file合併成一個文件,從而減小數據文件佔用的磁盤空間大小。

(2) Delta file

每個data file都有一個與之匹配的Delta File,這個匹配是指事務範圍上的匹配,兩者記錄的是同一段事務(包括一個或者多個事務)上的數據,Delta File中記錄了data file中被刪除行的標記,這個標記其實就是一個關聯信息{ing_tx_id, row_id, deleting_tx_id }。它跟data file一樣,也是嚴格按照事務操作的順序來保存刪除的行的信息。

如上圖,該內存數據庫有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)。但是,在事務提交時,內存數據庫和傳統數據庫在"寫日誌"上沒有什麼區別:日誌會先於數據寫入到磁盤中。

因此,即使服務器發生了宕機或者斷電,下次數據庫重啓時會按照已經保存在磁盤中事務日誌將業務redo(重做),所以不要擔心數據會丟失。

另外,需要補充的是,內存數據庫只會對持久性表將已提交的'事物日誌保存到磁盤中。這樣做的好處可以減少寫磁盤的次數。內存數據庫支持頻繁、快速的增、刪、改等操作,這個強度遠遠高於傳統數據庫,數據庫需要爲每筆操作寫日誌,這樣就會產生大量磁盤IO,寫日誌操作將有可能成爲性能瓶頸,不記錄未提交的事務日誌就減少寫日誌的數量,從而可以提高數據庫的性能。

有同學會想,不記錄未提交事務的日誌會不會導致數據不一致呢?

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

(2)CheckPoint

在內存數據庫中,CheckPoint的主要目的就是將內存中的"數據"寫入到磁盤中,從而在數據庫崩潰或者重啓時減少數據恢復的時間。不需要數據庫逐條讀取所有的日誌來恢復數據。默認情況下Checkpoint是週期性進行的,當日志至上次checkpoint後增加了512M時會觸發新一輪CheckPoint。

在傳統數據庫這種,Checkpoint可以將未提交的數據flush到磁盤的mdf文件中,這個現象在內存數據庫中不會發生,因爲內存數據庫只將已提交事務的日誌,而在寫日誌(到磁盤)之前不可能將數據先寫到磁盤中,因此可以保證寫到磁盤中的數據一定是已提交事務的數據。

三、數據在內存是怎麼存放的,還是按照頁的方式嗎,一行的大小有限制嗎?

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

內存優化表是基於行版本存儲的,同一行在內存中會有多個版本,可以將內存優化表的存儲結構看作是該表中 所有行的多個行版本的集合。

內存優化表中的行跟傳統數據庫的行結構是不一樣的,下圖描述了內存優化表中一行的數據結構:

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

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

Row body記錄了一行的實際數據。

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

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

既然同一行在內存中存在這麼多的行版本,那數據庫在訪問時是怎麼控制的呢?

在傳統數據庫中,表中每一行都是唯一的,一個事務如想找到一行,通過文件號、頁號、槽位就可以了。

在內存數據庫中,每一行有多個行版本,一個事務不可能對將每個行版本都操作一遍,實際上,一個事物只能操作同一行的一個行版本,至於它能對哪個行版本進行操作,取決於事務執行時間是否在這行的兩個時間戳之間。除此之外的其他行版本對該事務而言是不可見的。

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

四、內存數據庫號稱無鎖式設計,那如果發生了併發衝突怎麼辦,SQL是如何處理衝突的呢?

答案:內存數據庫用行版本來處理衝突。

鎖的一個重要作用就是避免多個進程同時修改數據,從而造成數據不一致。常見的衝突現象包括讀寫互鎖和寫寫互鎖。那內存數據庫是如何通過行版本來解決這兩種鎖定現象的呢?

(1)讀寫互鎖

在內存數據庫中,所有對內存優化表的事務隔離都是基於快照的,準確的說是基於行的快照。從上文行的 結構可以知道,每行的行頭包括開始時間戳和結束時間戳的,一個事務能不能訪問到這行關鍵在於事務的啓動時間是不是在這行的兩個時間戳內。

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

(2)寫寫互鎖

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

內存數據庫衝突發生的概率比傳統數據庫小很多,但如果實在遇到了衝突,只能調整應用程序,在應用程序中加入"重試邏輯"(等待一會,然後再重新發起事務)來解決。

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