前言
關系型資料庫發展至今,細節上以做足文章,在尋求自身突破發展的過程中,記憶體與分布式資料庫是當下最流行的主題,這與性能及擴充性在大資料時代的需求交相輝映。
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倍10000tps每個事務的cpu指令就需降為1萬個,這在現有的資料庫系統中是不可能實作的,是以我們依舊需要新的處理方式。
一、傳統資料庫引擎面臨的問題
有的朋友可能會說把所有資料都放入記憶體中就是記憶體資料庫,就不存在短闆了,但即便如此我們仍面臨如下主要問題:
1.保護記憶體中的資料結構而采用的闩鎖(latch)引起的熱點 (hot spots)問題。
2.使用鎖機制控制多版本并發帶來的阻塞等問題。
3.使用解釋型(interpretation)語言的執行計劃的執行效率問題。
我們簡單看下上述問題的由來
1.假設我有一個查詢q1需要通路一個資料頁 頁号7,此時資料頁不在bufferpool(bp)中,為此系統為其配置設定了記憶體架構,并去磁盤取相關資料頁置入bp中此過程正常大概10-20ms,而此時恰好另一個查詢q2需通路資料頁号7,由于bp中已經存在應該頁架構,如果此時允許q2讀取,則q2将會髒讀。
是以引入闩鎖,當q1去磁盤讀取資料時bp中的相應架構被闩鎖保護,q2讀相應的頁時将被阻塞,知道q1完成相應操作并釋放闩鎖,如下圖1-1所示:
圖1-1
現在有資料庫系統中為保證多線程下的共享資料一緻性,記憶體任何資料結構都需被闩鎖保護。而當大量并發程序同時通路一個資料頁(結構)就造成了熱點問題。消耗了大量cpu的同時影響了并發吞吐。
2.假設有如下兩個操作,都對資料庫中的某個值進行修改:
a=1000
q1:a = a + 100q2: 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
為了解決此問題,已故的業界大神,圖靈獎的獲得者jimgray提出了兩階段鎖概念 (two-phase locking),合理地解決了并發一緻性問題,并被絕大多數資料庫系統應用并改進(如sql server中資料不同粒度下并發相容情形引入的意向鎖)。
本例中當q1讀取a時,對a加排他鎖,當q2試圖讀取時就會被阻塞,需等待q1的事務完成後釋放鎖資源後才能繼續讀取。如圖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
在對資料進行更新時采用compareand swap(cas)實作無鎖(latch free)操作
cas:通過比對實體位址的值與攜帶值是否比對,比對則可操作,不比對則拒絕操作。
如某個程序在攜帶的位址m的值為20,比對位址m的實際值,如果為20則可以修改,否則拒絕如圖2-3:
圖2-3
在對資料頁進行增量更新時每次操作均會在資料上生成一個新的增量位址作為資料頁的通路入口,并采用cas完成映射表中(mapping table)實體新位址的映射(delta address),并對針對同一資料頁可能出現的同時更新進行仲裁,此時勝出者将進行更新,而失敗者可以進行重試,遺憾的是目前sql server隻會對失敗操作抛出錯誤資訊,需要我們自己捕捉錯誤資訊并重試,具體可參考聯機文檔。
具體如圖2-4所示:
圖2-4
這樣的操作方式下,當更新鍊過長時通路資料會造成時間複雜度提升進而影響性能,sql server會在合适的情形下進行整理,生成新的資料頁,并将實體位址指向新的資料頁,而老的資料頁連結清單将會作為垃圾回收釋放記憶體。
如圖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
我們通過一個執行個體簡要說明下:
事務過程采用timestamps(時間戳(全局時鐘))标記事務和行版本,每個事務開始時賦予開始時間戳begin_ts,用于讀取正确的行版本(資料行同樣均具有時間戳),行版本資料結束時間戳end_ts一般為正無窮(+∞),當進行資料更新時建立新的版本行,并将舊的版本行end_ts修改為事務id xb(此處非時間戳),新的版本行的begin_ts同樣标記為事務id (xb)。然後擷取事務的end_ts (唯一),确認可送出後,送出事務,并将新舊版本的事務id(xb)替換成擷取的end_ts。至此完成一次操作。未涉及任何鎖,闩鎖,阻塞。
如圖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
實際上hekaton中規定 查詢的可見值區間必須覆寫此查詢的開始時間戳比如一個查詢事務的開始時間戳為30,他可見的行版本可以包括10至+∞,20至150,但不能看到40至+∞。如圖2-9:
圖2-9
有的同學可能會想,随着通路,dml的增加,會累積大量的無用資料占用記憶體,實際上根據查詢自身的事務時間戳,如上當最古老的事務開始時間戳大于等于50時,舊版本的資料就可以安全的清除釋放記憶體了。清除工作可以使多線程并行執行,對新能影響很小。
從圖2-6中可以看到,并不是每個事務都可以安全送出的,在驗證階段,hekaton會根據使用者設定的隔離級别進行驗證。
hekaton為樂觀并發,提供三種隔離級别的支援分别為快照隔離級别(snapshot isolation),可重複讀隔離級别(repeatablereads isolation)及序列化隔離級别(serializable),這與傳統的關系型資料類似,snapshot中是無需驗證的,而可重複則需在送出前再次驗證與事務開始時的資料是否一緻,如一緻則可送出,否則不可送出。
而序列化中顧名思義讀取的區間資料都需一緻,否則失敗。有同學可能會想序列化中将比對多少資料啊,成本是不是太高了,别忘了這是在記憶體中,依然比傳統的序列化成本要低很多。
熟悉樂觀級别的同學都知道,傳統的樂觀并發級别下復原成本是非常高的,而hekaton中采用驗證的方式有效的規避了這項成本。送出就是寫日志記錄變化,并将資料行中事務id替換成擷取的時間戳,對其他事務可見。
當然提高寫日志,我們都知道磁盤終究是瓶頸,為此hekaton也有其特定的優化方式來緩解這個問題,限于篇幅這裡就不在叙述。而且針對一些特定的場景我們可以選擇隻保留schema而無需資料持久化(如遊戲的場景資料等)。
最後,針對cpu執行效率将執行計劃由解釋性語言(interpreted)替換為機器語言(native)。
優化器可以說是關系型資料庫最複雜的部分了,簡單說下sqlserver優化器處理過程:一條語句交給優化器會進行綁定解析,生成解析樹,然後進行語義分析生成邏輯執行計劃,最後優化器再為邏輯執行計劃基于成本生成實體的執行計劃。
而hekaton中,如果我們選擇native方式執行(将所執行語句通過存儲過程特殊編譯),在生成邏輯執行計劃之後将會根據不同的算法,成本預估生成不同的實體執行計劃,然後将實體執行計劃轉譯成c語言代碼再通過編譯器将其編譯成dll即機器代碼。如圖2-10:
圖2-10
曾經微網誌上有朋友問為什麼mysql重構優化器時為什麼要将parsing,optimizing, execution三個子產品分開而不是混在一起了,我想這裡可能就找到答案了,一個優秀rdbms它自身的健壯是多麼重要。
在native下,所有的執行都是“goto”,直接讀取資料,再也不用一個一個的function的調用,極大提升cpu的工作效率。有人可能會問這樣每次都編譯将是非常大的工作成本,實際上hekaton将指定查詢(存儲過程)編譯成dll檔案,隻是在第一次将其載入記憶體就可以了。對于即席查詢是不可以的。
hekaton在機器代碼下執行效率大幅提升,以下是微軟給出的測試資料:
a.interpreted與native的對比,其中分為是否為記憶體優化表,查詢單條資料所消耗的cpu指令。如圖2-11:
圖2-11
b.随機查找1000萬資料普通表與hekaton記憶體優化表查詢時間對比圖2-12:
圖2-12
c.普通表與hekaton記憶體優化表記憶體中随機更新資料對比,此時不寫日志如圖2-13:
圖2-13
三、hekaton應用案例
hekaton,古希臘語中表示百倍,雖然目前還未達到願景,我想這個出色的團隊一定能夠做到。
sql server有了這個新利器,在應對性能問題上更加出色。在微軟的官方網站上有大量案例,這裡我們列舉幾個。
bwin,歐洲最大的線上博彩公司,采用hekaton後,線上每秒批處理由15000提升到250000。
edgenet,矽谷著名的資料服務商,采用hekaton後,線上入庫資料量由7450/s提升到126665/s均由近17倍的速度提升。如圖3-1:
圖3-1
而将易車的惠買車的通路量在hekaton模拟運作時,各項性能名額都表現的很淡定。如圖3-2:
圖3-2
hekaton不僅為我們解決了不少場景下的性能問題,我想面對特定場景中的一些棘手問題也有一定的幫助。比如電商熱衷的秒殺/搶購。這裡筆者就不在叙述業内朋友研究的排隊論,批量送出等等辦法。
實際上計算機在當下普遍應用都是模拟三維空間内的人為活動,試想下,搶購的過程終究有成功或是失敗,就好像你在搶購熱銷産品時被身手矯健的大媽推到一邊你沒搶到一樣,這不正好符合hekaton中的事務機制?我們在設計網上産品活動的時候是否該想想模拟到現實中是什麼樣子的?對此,我認為我們需要的是可控,而不是控制。
四、結語
最後,這麼多帶給人驚喜振奮的資料庫,它就完美無缺嗎?當然不是。hekaton的樂觀并發級别限定使得其并不适合大量更新沖突的場景,其以空間換速度的設計要求會消耗大量記憶體,需要應用者合理規劃設計……
請牢記“任何技術都是有缺陷的”,沒有哪項技術/架構是完美無缺的,合适的場景選擇合理的技術/架構才是我們的初衷。
<b></b>
<b>本文來自雲栖社群合作夥伴"dbaplus",原文釋出時間:2016-03-08</b>