SQL Server 記憶體資料庫原理解析

發表於2016-08-05

前言

關係型資料庫發展至今,細節上以做足文章,在尋求自身突破發展的過程中,記憶體與分散式資料庫是當下最流行的主題,這與效能及擴充套件性在大資料時代的需求交相輝映.SQL Server作為傳統的資料庫也在最新發布版本SQL Server 2014中提供了新利器 SQL Server In-Memory OLTP(Hekaton),使得其在OLTP系統中的效能有了幾十倍甚至上百倍的效能提升,本篇文章為大家探究一二.

大資料時代的資料如何組織應用?這恐怕眾口不一.但不可否認,關係型資料依舊是當下世界最有效的應用方式.作為應用技術,也必將伴隨著應用的需求而不斷演化.資訊爆炸對資訊處理提出了更為嚴苛的需求,單從傳統的OLTP系統來看,效能和擴充套件性便是應用者最為關注的方面.假如應用者告訴你我需要當下資料庫訪問量100倍的計算資源,單純硬體?顯然新的技術應用呼之而出.

傳統關係型資料庫自誕生起自身不斷完善的同時也伴隨著硬體的飛速發展,效能提升上伴隨處理器神奇的摩爾定律,TPC-C,TPC-E等指標不斷提升,而隨著今年來處理器物理工藝接近極限,CPU的主頻速度幾乎不再提升,這時計算機朝著多核方向進展,同時記憶體成本也線上性降低,不再如此昂貴,目前記憶體的成本已經低於10$/GB. 而固態硬碟(SSD)的廣泛應用也使得傳統資料庫在效能上有更多的延伸.面對這些新的硬體環境傳統的關係型資料庫自然也有其設計之初不可避免的自身效能瓶頸.

SQL Server 2014的傳統引擎中引入緩衝池擴充套件(Buffer Pool Extension)功能利用SSD的高IOPS作為緩衝池的有利延伸,構成了熱,活,冷三層資料體系,有效緩解磁碟的壓力.我們可以把更多的資料放入記憶體,SSD中,但即便如此資料庫的效能還是被自身的一些架構和處理方式所約束著. 就著前面的假設,我們要把事務處理能力提升100倍.假設我們現在的處理能力是100 TPS,而這時每個事務所以得平均CPU指令為100萬個,以此提升10倍1000 TPS,每個事務的CPU指令就需降為10萬個,而再提升10倍10000 TPS每個事務的CPU指令就需降為1萬個,這在現有的資料庫系統中是不可能實現的,所以我們依舊需要新的處理方式.

傳統資料庫引擎面臨的問題

有的朋友可能會說把所有資料都放入記憶體中就是記憶體資料庫,就不存在短板了,但即便如此我們仍面臨如下主要問題:

1:保護記憶體中的資料結構而採用的閂鎖(latch)引起的熱點 (hot spots) 問題.

2:使用鎖機制控制多版本併發帶來的阻塞等問題.

3:使用解釋型(interpretation)語言的執行計劃的執行效率問題.

我們簡單看下上述問題的由來

1:假設我有一個查詢Q1需要訪問一個資料頁 頁號7,此時資料頁不在Buffer Pool(BP)中,為此係統為其分配了記憶體架構,並去磁碟取相關資料頁置入BP中此過程正常大概10-20ms,而此時恰好另一個查詢Q2需訪問資料頁號7,由於BP中已經存在應該頁架構,如果此時允許Q2讀取,則Q2將會髒讀.因此引入閂鎖,當Q1去磁碟讀取資料時BP中的相應架構被閂鎖保護,Q2讀相應的頁時將被阻塞,知道Q1完成相應操作並釋放閂鎖,如下圖1-1所示

現在有資料庫系統中為保證多執行緒下的共享資料一致性,記憶體任何資料結構都需被閂鎖保護.而當大量併發程式同時訪問一個資料頁(結構)就造成了熱點問題.消耗了大量CPU的同時影響了併發吞吐.

1-1

圖1-1

2:假設有如下兩個操作,都對資料庫中的某個值進行修改

A=1000

Q1: A = A + 100 Q2: A = A + 500

在資料庫中的操作為

Q1: Read A, A=A+100, Write A

Q2: Read A, A=A+500, Write A

如果是序列先後執行,則沒有問題,但如果同時執行則可以出現資料的不一致情形.

Q1,Q2同時讀取了A的原始值後,進行修改,則資料不一致如圖1-2

1-2

圖1-2

為了解決此問題,已故的業界大神,圖靈獎的獲得者Jim Gray提出了兩階段鎖概念 (Two-Phase Locking),合理地解決了併發一致性問題,並被絕大多數資料庫系統應用並改進(如SQL Server中資料不同粒度下併發相容情形引入的意向鎖).

本例中當Q1讀取A時,對A加排他鎖,當Q2試圖讀取時就會被阻塞,需等待Q1的事務完成後釋放鎖資源後才能繼續讀取.如圖1-3

1-3

圖1-3

但也正因為鎖的引入,使得事務間可能出現相互阻塞,並且需要特定的進行管理鎖資源,且需對死鎖等問題即時檢測,而這些問題自然地會影響併發效能.

3:熟悉SQL Server的人都知道一條語句在SQL Server中執行,現有進行繫結,語義分析,基於成本的優化等一些列過程然後生成相應的解釋性語言執行計劃,而引擎在執行相應的執行計劃時會呼叫相應的資料庫函式,執行每一個運算子,如果資料在硬碟上則會去硬碟上取資料…這些情形使得執行解釋性語言時高時間消耗的同時也打斷CPU流水,使得CPU的效率無法充分發揮,而如果資料均在記憶體中,則可以採用更高效的方式處理.而絕大多數關係型資料庫系統的執行計劃均為解釋性語言.

面對這些問題,巨頭資料庫廠商們都提供了相應的記憶體資料庫解決方案,如Oracle的Timesten,還有最新圖靈獎獲得者Michael Stonebraker教授的研究H-store演化出的商業產品VoltDB等.而微軟的SQL Server 2014也推出了記憶體資料庫SQL Server In-Memory OLTP(開發代號Hekaton),接下來我們就簡要的看下Hekaton如何應對上面的問題,使得效能得到新的昇華.

SQL Server Hekaton的應對方式

SQL Server Hekaton是一個基於記憶體優化的高效能的OLTP資料庫引擎,且資料是可持久化的,他完全整合於SQL Server內(可與傳統引擎,基於列儲存引擎混合透明使用 如圖2-1),且是基於現代多核CPU架構設計.

2-1

圖2-1

應對上述三點效能瓶頸,熱點上Hekaton採用”Bw-tree”資料結構實現Latch-free,併發鎖上採用樂觀併發中多版本時間戳資料行控制實現無鎖事務,解釋性語言執行效率採用截執行計劃編譯為機器程式碼(DLL)提升CPU效率.下面針對這三點來簡要說明下.

Hekaton中的資料頁大小是彈性的,以便於增量更新Delta update,因為現有傳統的update in place會使得現有的CPU Cache失效,在多核架構下會使得效能受限.資料頁在記憶體中通過對映表管理,將每個資料頁的邏輯ID與實體地址一一對映.如圖2-2

2-2

圖 2-2

在對資料進行更新時採用Compare and Swap(CAS)實現無鎖(Latch free)操作

CAS:通過比對實體地址的值與攜帶值是否匹配,匹配則可操作,不匹配則拒絕操作.

如某個程式在攜帶的地址M的值為20,匹配地址M的實際值,如果為20則可以修改,否則拒絕如圖2-3

2-3

圖2-3

在對資料頁進行增量更新時每次操作均會在資料上生成一個新的增量地址作為資料頁的訪問入口,並採用CAS完成對映表中(mapping table)物理新地址的對映(delta address),並對針對同一資料頁可能出現的同時更新進行仲裁,此時勝出者將進行更新,而失敗者可以進行重試,遺憾的是目前SQL Server只會對失敗操作丟擲錯誤資訊,需要我們自己捕捉錯誤資訊並重試,具體可參考聯機文件.具體如圖2-4所示

2-4

圖2-4

這樣的操作方式下,當更新鏈過長時訪問資料會造成時間複雜度提升從而影響效能,SQL server會在合適的情形下進行整理,生成新的資料頁,並將實體地址指向新的資料頁,而老的資料頁連結串列將會作為垃圾回收釋放記憶體.如圖2-5

2-5

圖2-5

由於資料頁是彈性的,所以可能造成資料頁過大或是過程,Hekaton中會在其認為合適的情形下進行頁分裂或是合併.限於篇幅這裡就不在詳細敘述了,在實現Latch-free中所有記憶體中的操作都是通過一個或多個原子操作完成.感興趣的朋友可以參考微軟的相關文獻.

有的朋友可能會說閂鎖本身是保護記憶體結構的輕量級鎖,況且不同型別的閂鎖可能相容,Latch-free對效能幫助能有多大呢?實際SQL Server在訪問記憶體中資料時,閂鎖本身用作控制資料訪問時成本很高,為此會在資料上加自旋鎖(Spin lock)供執行緒探測資料是否可以訪問,Spin lock實現即一個Bit位(0或1),執行緒會一直探測記憶體中的這個Bit位以試圖獲得自旋鎖,如果可以訪問則訪問,否則自旋,如果幾千次的探測仍無法訪問則停下”休息”這個稱作一次碰撞.但是在自旋的過程CPU負荷狀態,因此也就造成CPU資源白白浪費.生產中我們可能看到CPU高啟,而併發卻上不去,訪問變慢,其中的一個原因就是大量程式訪問熱點資料下大量自旋鎖徵用使得效能受限.而在Hekaton中無閂鎖的情況下就不存在這樣問題,單從這個角度來看隨著執行緒的增加效能也是線性放大.當然除了Latch-free,其他的兩個方面Hekaton同樣表現出色.

前文中敘述可知,關係型資料庫中事務是靠鎖來保證多版本併發控制的,由此帶來的阻塞死鎖等問題相信所有的DBA都印象深刻.而Hekaton中採用樂觀併發下多版本資料加時間戳的形式實現.下面來簡要解下.

Hekaton中將一個事務分為三個階段,正常事務處理步驟用於我們的資料操作DML則建立新的版本行.驗證提交階段驗證這個事務是否可以安全提交(根據版本資料).提交處理階段用於寫日誌,並將新的版本行資料對其它事務可見.如圖2-6

2-6

圖2-6

我們通過一個例項簡要說明下:事務過程採用Timestamps(時間戳(全域性時鐘))標記事務和行版本,每個事務開始時賦予開始時間戳Begin_TS,用於讀取正確的行版本(資料行同樣均具有時間戳),行版本資料結束時間戳End_TS一般為正無窮(+∞),當進行資料更新時建立新的版本行,並將舊的版本行End_TS修改為事務ID Xb(此處非時間戳),新的版本行的Begin_TS同樣標記為事務ID (Xb).然後獲取事務的End_TS (唯一),確認可提交後,提交事務,並將新舊版本的事務ID(Xb)替換成獲取的End_TS.至此完成一次操作.未涉及任何鎖,閂鎖,阻塞.如圖2-7

2-7

圖2-7

有的同學看到上圖可能回想,這樣Xa讀取的版本行是正確的嗎?他為什麼不能讀到Xb的新行資料.我們來簡單分析下

Xa開始時分配的時間戳為25,Xb為35,這就意味著Xb的結束時間戳一定大於35此時Xa讀取資料,時間戳範圍應為Begin_TS-20, End_TS-+∞,而Xa的Begin_TS小於Xb的Begin_TS,所以讀取正確如圖2-8.

2-8

2-8

實際上Hekaton中規定 查詢的可見值區間必須覆蓋此查詢的開始時間戳比如一個查詢事務的開始時間戳為30,他可見的行版本可以包括10至+∞,20至150,但不能看到40至+∞

如圖2-9

2-9

圖2-9

有的同學可能會想,隨著訪問,DML的增加,會累積大量的無用資料佔用記憶體,實際上根據查詢自身的事務時間戳,如上當最古老的事務開始時間戳大於等於50時,舊版本的資料就可以安全的清除釋放記憶體了. 清除工作可以使多執行緒並行執行,對新能影響很小.

從圖2-6中可以看到,並不是每個事務都可以安全提交的,在驗證階段,Hekaton會根據使用者設定的隔離級別進行驗證.Hekaton為樂觀併發,提供三種隔離級別的支援分別為快照隔離級別(Snapshot Isolation),可重複讀隔離級別(Repeatable Reads Isolation)及序列化隔離級別(Serializable),這與傳統的關係型資料類似, Snapshot中是無需驗證的,而可重複則需在提交前再次驗證與事務開始時的資料是否一致,如一致則可提交,否則不可提交.而序列化中顧名思義讀取的區間資料都需一致,否則失敗.有同學可能會想序列化中將匹配多少資料啊,成本是不是太高了,別忘了這是在記憶體中,依然比傳統的序列化成本要低很多.熟悉樂觀級別的同學都知道,傳統的樂觀併發級別下回滾成本是非常高的,而Hekaton中採用驗證的方式有效的規避了這項成本.提交就是寫日誌記錄變化,並將資料行中事務ID替換成獲取的時間戳,對其他事務可見.

當然提高寫日誌,我們都知道磁碟終究是瓶頸,為此Hekaton也有其特定的優化方式來緩解這個問題,限於篇幅這裡就不在敘述.而且針對一些特定的場景我們可以選擇只保留Schema而無需資料持久化(如遊戲的場景資料等).

最後,針對CPU執行效率將執行計劃由解釋性語言(Interpreted)替換為機器語言(Native).

優化器可以說是關係型資料庫最複雜的部分了,簡單說下SQL Server優化器處理過程:一條語句交給優化器會進行繫結解析,生成解析樹,然後進行語義分析生成邏輯執行計劃,最後優化器再為邏輯執行計劃基於成本生成物理的執行計劃.而Hekaton中,如果我們選擇Native方式執行(將所執行語句通過儲存過程特殊編譯),在生成邏輯執行計劃之後將會根據不同的演算法,成本預估生成不同的物理執行計劃,然後將物理執行計劃轉譯成C語言程式碼再通過編譯器將其編譯成DLL即機器程式碼.如圖2-10

2-10

圖2-10

曾經微博上有朋友問為什麼Mysql重構優化器時為什麼要將parsing, optimizing, execution三個模組分開而不是混在一起了,我想這裡可能就找到答案了,一個優秀RDBMS它自身的健壯是多麼重要.

在Native下,所有的執行都是”Goto”,直接讀取資料,再也不用一個一個的function的呼叫,極大提升CPU的工作效率.有人可能會問這樣每次都編譯將是非常大的工作成本,實際上Hekaton將指定查詢(儲存過程)編譯成DLL檔案,只是在第一次將其載入記憶體就可以了.對於即席查詢是不可以的.

Hekaton在機器程式碼下執行效率大幅提升,以下是微軟給出的測試資料

a. Interpreted與Native的對比,其中分為是否為記憶體優化表,查詢單條資料所消耗的CPU指令 如圖2-11

2-11

圖2-11

b.隨機查詢1000萬資料普通表與Hekaton記憶體優化表查詢時間對比圖2-12

2-12

圖2-12

c. 普通表與Hekaton記憶體優化表記憶體中隨機更新資料對比,此時不寫日誌如圖2-13

2-13

圖2-13

Hekaton應用案例

Hekaton,古希臘語中表示百倍,雖然目前還未達到願景,我想這個出色的團隊一定能夠做到.

SQL Server有了這個新利器,在應對效能問題上更加出色.在微軟的官方網站上有大量案例,這裡我們列舉幾個.

Bwin,歐洲最大的線上博彩公司,採用Hekaton後,線上每秒批處理由15000提升到250000.

EdgeNet,矽谷著名的資料服務商,採用Hekaton後,線上入庫資料量由7450/s提升到126665/s

均由近17倍的速度提升如圖3-1

3-1

圖3-1

而將易車的惠買車的訪問量在Hekaton模擬執行時,各項效能指標都表現的很淡定.

如圖3-2

3-2

圖3-2

Hekaton不僅為我們解決了不少場景下的效能問題,我想面對特定場景中的一些棘手問題也有一定的幫助.比如電商熱衷的秒殺/搶購.這裡筆者就不在敘述業內朋友研究的排隊論,批量提交等等辦法.實際上計算機在當下普遍應用都是模擬三維空間內的人為活動,試想下,搶購的過程終究有成功或是失敗,就好像你在搶購熱銷產品時被身手矯健的大媽推到一邊你沒搶到一樣,這不正好符合Hekaton中的事務機制?…我們在設計網上產品活動的時候是否該想想模擬到現實中是什麼樣子的?對此,我認為我們需要的是可控,而不是控制.

結語

最後,這麼多帶給人驚喜振奮的資料庫,他就完美無缺嗎?當然不是.Hekaton的樂觀併發級別限定使得其並不適合大量更新衝突的場景,其以空間換速度的設計要求會消耗大量記憶體,需要應用者合理規劃設計…請牢記”任何術都是有缺陷的” 沒有哪項技術/架構時完美無缺的,合適的場景選擇合理的技術/架構才是我們的初衷.

相關文章