終極事務處理(XTP,Hekaton)——萬能大招?


在SQL Server 2014里,微軟引入了終極事務處理(Extreme Transaction Processing),即大家熟知的Hekaton。我在網上圍觀了一些文檔,寫這篇文章,希望可以讓大家更好的理解Hekaton,它的局限性,還有它驚艷的全新內存數據庫技術。這篇文章會通過下面幾個方面來講解Hekaton:

  1. 概況
  2. 可擴展性(Scalability)
  3. 局限性(Limitations)

1.概況

讓我們從XTP的簡潔概況開始。像XTP這樣的內存數據庫技術首要目標非常明確:盡可能高效的使用我們現有的服務器硬件。我們來看當下的現代服務器系統硬件,你會發現下列問題/局限性:

  • 傳統存儲(機械硬盤)非常緩慢,企業若准備SSD存儲非常昂貴。另一方面主要內存(RAM)卻非常便宜,只要花100美元就可以配置到64GB內存,這可是標准版的SQL Server的最大支持內存數。
  • CPU速度很難再提升。現在我們困在了3-4GHZ,再快點不太可能(當你嘗試超頻時你就會有這個體會)。
  • 傳統的關系型數據庫管理系統(RDBMS)不能線性擴展,主要是因為內部的鎖,阻塞和封鎖(Locking, Blocking, and Latching)機制(數據結構的內存里鎖,當它們被讀寫訪問時)。

因此為了克服這些限制,你需要這樣的技術:

  1. 使用RAM將數據全部存在內存里來克服傳統旋轉硬盤(機械硬盤)的速度限制。
  2. 盡可能划算的使用當下有速度限制的CPU,使用盡可能少的CPU指令數,來實現近可能快的去執行關系數據庫管理系統(RDBMS)的查詢。
  3. 當對你的關系數據庫管理系統(RDBMS)執行讀/寫操作時,完全避免鎖/阻塞,和閂鎖(Locking/Blocking, and Latching)。

這3點就是SQL Server 2014里終極事務處理(Extreme Transaction Processing )的3大支柱:

 

使用XTP你可以在內存里緩存整個表(即所謂的內存優化表(Memory Optimized Tables)),存儲過程可以編譯為本機C代碼,而且對於內存優化表,鎖/阻塞和閂鎖(Locking/Blocking, and Latching)這些機制都是完全沒有的,因為XTP是基於樂觀的多版本並發控制(Multi Version Concurrency Control ,MVCC)的。我們來詳細看下這3大支柱。

內存中的存儲(In-Memory Storage)

對於服務器系統,內存越來越便宜了。你只要花幾百美元就可以裝備你的服務器為64G內存,64GB內存可是SQL Server標准版本支持的最大內存。因此XTP表(即內存優化表)是完全存在內存里的。從SQL Server角度來看,內存表的所有數據存在於FILESTREAM文件組,在SQL Server啟動時,它們從文件組讀取,然后在起飛時重建你的所有索引。

這也給你的目標恢復時間(Recovery Time Objective,RTO)帶來巨大壓力,因為一啟動你所有索引都被重建完成后,你的數據庫才是在線狀態。你的FILESTREAM文件組所存儲的存儲系統速度,因此也會直接影響目標恢復時間。因此你可以在FILESTREAM文件組里放置多個容器,這樣的話你可以在啟動期間分散I/O到多個存儲系統,從而讓你的數據庫盡快進入在線狀態。

在CTP1里,XTP只支持所謂的Hash-Indexes,它是在內存里完全存儲在哈希表里。SQL Server目前能查找和掃描Hash-Indexes。從CTP2起,微軟會引入所謂的Range Indexes,這樣可以是你的范圍查詢非常,非常快。Range Indexes是基於所謂的Bw-Tree

每個內存優化表也是編譯為本地C代碼。對每個表你都會得到一個DLL,這個是通過cl.exe編譯的(微軟C語言編譯器,SQL Server 2014 自帶)。生成的DLL然后載入sqlservr.exe 的進程空間,這個可以在sys.dm_os_loaded_modules里看到。編譯本身在獨立的線程里完成,這就是說你眼疾手快的話,你可以在任務管理器里看到cl.exe。下面代碼給你展示了描述你的表的典型C語言代碼: 

 1 struct hkt_277576027
 2 {
 3     struct HkSixteenByteData hkc_1;
 4     __int64 hkc_5;
 5     long hkc_2;
 6     long hkc_3;
 7     long hkc_4;
 8 };
 9 struct hkis_27757602700002
10 {
11     struct HkSixteenByteData hkc_1;
12 };
13 struct hkif_27757602700002
14 {
15     struct HkSixteenByteData hkc_1;
16 };
17 __int64 CompareSKeyToRow_27757602700002(
18     struct HkSearchKey const* hkArg0,
19     struct HkRow const* hkArg1)
20 {
21     struct hkis_27757602700002* arg0 = ((struct hkis_27757602700002*)hkArg0);
22     struct hkt_277576027* arg1 = ((struct hkt_277576027*)hkArg1);
23     __int64 ret;
24     ret = (CompareKeys_guid((arg0->hkc_1), (arg1->hkc_1)));
25     return ret;
26 }
27 __int64 CompareRowToRow_27757602700002(
28     struct HkRow const* hkArg0,
29     struct HkRow const* hkArg1)
30 {
31     struct hkt_277576027* arg0 = ((struct hkt_277576027*)hkArg0);
32     struct hkt_277576027* arg1 = ((struct hkt_277576027*)hkArg1);
33     __int64 ret;
34     ret = (CompareKeys_guid((arg0->hkc_1), (arg1->hkc_1)));
35     return ret;
36 }

 可以看到,代碼本身並不直觀,但你可以通過C的結構使用來看出你的表結構是如何被描述的。XTP的好處是內存優化表是完全自然集成到SQL Server關系引擎的其余部分。因此你可以使用傳統的T-SQL代碼查詢這些表,可以進行備份/還原,還有集成HA/DR技術——微軟在集成領域做了大量的偉大工作。除了內存中存儲外,內存優化表也完全鎖/阻塞,和閂鎖,因為XTP是基於樂觀的多版本並發控制(MVCC)原則。在無鎖/閂鎖數據結構部分我們會繼續討論這個。

你需要注意的最重要事實是:你應該將性能最重要的表移入內存,不是你所有的數據庫。在接下來可擴展性部分里,我們會談到哪些情況使用XTP是有意義的。通常你會把你數據庫的95%使用基於傳統磁盤的表存儲,剩下的5%可以用內存優化表存儲。 

本地編譯(Native Compilation)

微軟申明當前硬件系統的第一個問題是:傳統的,旋轉的存儲太慢。對此將表數據在內存中存儲。第2個問題需要申明的是:當下處理器的時鍾頻率卡在了3-4GHz。我們不能再快了,因為會引入散熱問題。因此當前時鍾周期必須盡可能有效的管理。這是當下T-SQL實現的巨大問題,因為T-SQL只是一個解釋性的語言。

在查詢優化期間,SQL Server的查詢優化器生成所謂的查詢樹,在執行期間,查詢樹從頭運算符到所有的樹節點被解讀。這會引入大量的額外CPU指令,會在SQL Server里的每個執行計划里執行。另外每個運算符(即所謂的迭代器(Iterator))是以C++類實現的,這就意味在執行各個運算符時,會用到所謂的虛擬函數調用(Virtual Function Calls )虛擬方法調用根據需要執行的CPU指令又是很占資源的。總之,在運行期間,執行計划被解讀時,生成大量的CPU指令,即意味着當前的CPU沒有高效的使用。你在浪費寶貴的CPU周期,可以用另外更好的方式來使用,從而提速你的整個工作。

因為查詢引擎內的這些原因和限制,SQL Server引入XTP所謂的 本機編譯的存儲過程(Natively Compiled Stored Procedures)。背后的思路很簡單:存儲過程的整體編譯為本地C語言代碼,結果又是生成DLL,然后載入sqlservr.exe 的進程空間。因此在執行期間不需要解讀,虛擬函數調用完全消除。這樣的話做同樣數量的工作卻需要很少的CPU指令,這就意味着你的工作輸出量會更高,因為在可用的CPU周期里可以做更多的工作。

在2013年的北美TechEd上指出,對於一些特定的存儲過程,需要的CPU指令可以從1000000下降到近4000。想象下這個性能提升:25倍的性能提升!當我們談到當前XPT里的局限時,你會發現,這個提升並不是免費的……下面的代碼展示的是一個簡單存儲過程的典型C語言代碼: 

  1 HRESULT hkp_309576141(
  2     struct HkProcContext* context,
  3     union HkValue valueArray[],
  4     unsigned char* nullArray)
  5 {
  6     unsigned long yc = 0;
  7     long var_2 = (-2147483647 - 1);
  8     unsigned char var_isnull_2 = 1;
  9     HRESULT hr = 0;
 10     {
 11         var_2 = 0;
 12         var_isnull_2 = 0;
 13     }
 14     yc = (yc + 1);
 15     {
 16         while (1)
 17         {
 18             unsigned char result_7;
 19             unsigned char result_isnull_7;
 20             result_7 = 0;
 21             result_isnull_7 = 0;
 22             if ((! var_isnull_2))
 23             {
 24                 result_7 = (var_2 < 10000);
 25             }
 26             else
 27             {
 28                 result_isnull_7 = 1;
 29             }
 30             if ((result_isnull_7 || (! result_7)))
 31             {
 32                 goto l_5;
 33             }
 34             hr = (YieldCheck(context, yc, 18));
 35             if ((FAILED(hr)))
 36             {
 37                 goto l_1;
 38             }
 39             yc = 0;
 40             {
 41                 long expr_9;
 42                 long expr_10;
 43                 long expr_11;
 44                 __int64 expr_12;
 45                 struct hkt_277576027* rec2_17 = 0;
 46                 unsigned char freeRow_17 = 0;
 47                 short rowLength;
 48                 static wchar_t const hkl_18[] = 
 49                 {
 50                     73,
 51                     78,
 52                     83,
 53                     69,
 54                     82,
 55                     84,
 56                 };
 57                 static wchar_t const hkl_19[] = 
 58                 {
 59                     91,
 60                     79,
 61                     114,
 62                     100,
 63                     101,
 64                     114,
 65                     115,
 66                     93,
 67                 };
 68                 static wchar_t const hkl_20[] = 
 69                 {
 70                     91,
 71                     79,
 72                     114,
 73                     100,
 74                     101,
 75                     114,
 76                     73,
 77                     68,
 78                     93,
 79                 };
 80                 static wchar_t const hkl_21[] = 
 81                 {
 82                     73,
 83                     78,
 84                     83,
 85                     69,
 86                     82,
 87                     84,
 88                 };
 89                 static wchar_t const hkl_22[] = 
 90                 {
 91                     91,
 92                     79,
 93                     114,
 94                     100,
 95                     101,
 96                     114,
 97                     115,
 98                     93,
 99                 };
100                 static wchar_t const hkl_23[] = 
101                 {
102                     91,
103                     67,
104                     117,
105                     115,
106                     116,
107                     111,
108                     109,
109                     101,
110                     114,
111                     73,
112                     68,
113                     93,
114                 };
115                 static wchar_t const hkl_24[] = 
116                 {
117                     73,
118                     78,
119                     83,
120                     69,
121                     82,
122                     84,
123                 };
124                 static wchar_t const hkl_25[] = 
125                 {
126                     91,
127                     79,
128                     114,
129                     100,
130                     101,
131                     114,
132                     115,
133                     93,
134                 };
135                 static wchar_t const hkl_26[] = 
136                 {
137                     91,
138                     80,
139                     114,
140                     111,
141                     100,
142                     117,
143                     99,
144                     116,
145                     73,
146                     68,
147                     93,
148                 };
149                 static wchar_t const hkl_27[] = 
150                 {
151                     73,
152                     78,
153                     83,
154                     69,
155                     82,
156                     84,
157                 };
158                 static wchar_t const hkl_28[] = 
159                 {
160                     91,
161                     79,
162                     114,
163                     100,
164                     101,
165                     114,
166                     115,
167                     93,
168                 };
169                 static wchar_t const hkl_29[] = 
170                 {
171                     91,
172                     81,
173                     117,
174                     97,
175                     110,
176                     116,
177                     105,
178                     116,
179                     121,
180                     93,
181                 };
182                 static wchar_t const hkl_30[] = 
183                 {
184                     73,
185                     78,
186                     83,
187                     69,
188                     82,
189                     84,
190                 };
191                 static wchar_t const hkl_31[] = 
192                 {
193                     91,
194                     79,
195                     114,
196                     100,
197                     101,
198                     114,
199                     115,
200                     93,
201                 };
202                 static wchar_t const hkl_32[] = 
203                 {
204                     91,
205                     80,
206                     114,
207                     105,
208                     99,
209                     101,
210                     93,
211                 };
212                 goto l_16;
213             l_16:;
214                 expr_9 = 1;
215                 expr_10 = 1;
216                 expr_11 = 1;
217                 expr_12 = 2045;
218                 goto l_15;
219             l_15:;
220                 rowLength = sizeof(struct hkt_277576027);
221                 hr = (HkRowAlloc((context->Transaction), (Tables[0]), rowLength, ((struct HkRow**)(&rec2_17))));
222                 if ((FAILED(hr)))
223                 {
224                     goto l_8;
225                 }
226                 freeRow_17 = 1;
227                 if ((! (nullArray[1])))
228                 {
229                     (rec2_17->hkc_1) = ((valueArray[1]).SixteenByteData);
230                 }
231                 else
232                 {
233                     hr = -2113929186;
234                     if ((FAILED(hr)))
235                     {
236                         {
237                             CreateError((context->ErrorObject), hr, 5, 18, hkl_20, 16, hkl_19, hkl_18);
238                         }
239                         if ((FAILED(hr)))
240                         {
241                             goto l_8;
242                         }
243                     }
244                 }
245                 (rec2_17->hkc_2) = expr_9;
246                 (rec2_17->hkc_3) = expr_10;
247                 (rec2_17->hkc_4) = expr_11;
248                 (rec2_17->hkc_5) = expr_12;
249                 freeRow_17 = 0;
250                 hr = (HkTableInsert((Tables[0]), (context->Transaction), ((struct HkRow*)rec2_17)));
251                 if ((FAILED(hr)))
252                 {
253                     goto l_8;
254                 }
255                 goto l_13;
256             l_13:;
257                 goto l_14;
258             l_14:;
259                 hr = (HkRefreshStatementId((context->Transaction)));
260                 if ((FAILED(hr)))
261                 {
262                     goto l_8;
263                 }
264             l_8:;
265                 if ((FAILED(hr)))
266                 {
267                     if (freeRow_17)
268                     {
269                         HkTableReleaseUnusedRow(((struct HkRow*)rec2_17), (Tables[0]), (context->Transaction));
270                     }
271                     SetLineNumberForError((context->ErrorObject), 18);
272                     goto l_1;
273                 }
274             }
275             yc = (yc + 1);
276             {
277                 __int64 temp_34;
278                 if ((! var_isnull_2))
279                 {
280                     temp_34 = (((__int64)var_2) + ((__int64)1));
281                     if ((temp_34 < (-2147483647 - 1)))
282                     {
283                         hr = -2113929211;
284                         {
285                             hr = (CreateError((context->ErrorObject), hr, 2, 23, 0));
286                         }
287                         if ((FAILED(hr)))
288                         {
289                             goto l_33;
290                         }
291                     }
292                     if ((temp_34 > 2147483647))
293                     {
294                         hr = -2113929212;
295                         {
296                             hr = (CreateError((context->ErrorObject), hr, 2, 23, 0));
297                         }
298                         if ((FAILED(hr)))
299                         {
300                             goto l_33;
301                         }
302                     }
303                     var_2 = ((long)temp_34);
304                     var_isnull_2 = 0;
305                 }
306                 else
307                 {
308                     var_isnull_2 = 1;
309                 }
310             l_33:;
311                 if ((FAILED(hr)))
312                 {
313                     SetLineNumberForError((context->ErrorObject), 28);
314                     goto l_1;
315                 }
316             }
317             yc = (yc + 1);
318         }
319     l_5:;
320     }
321     yc = (yc + 1);
322     ((valueArray[0]).SignedIntData) = 0;
323 l_1:;
324     return hr;
325 }

 你可以看到有很多的GOTO語句,很容易讓人想到是面條式代碼(spaghetti code)。但這個不是我們討論的范圍……

無鎖/閂鎖數據結構(Lock/Latch Free Data Structures)

 在我們剛才討論XTP里的內存存儲數據時,我已經說過對內存優化表,SQL Server是以無鎖/閂鎖數據結構實現的。這意味着當想要讀寫你的數據時,沒有鎖/閂鎖涉及到的等待。在傳統的像SQL Server這樣關系數據庫管理系統(RDBMS)里,寫操作需要排它鎖(Exclusive Locks (X) ),讀操作需要共享鎖(Shared Locks (S) )。2個鎖是互斥的。

這就是說讀阻塞寫,寫阻塞讀。共享鎖能把持多久是通過不同的事務隔離級別控制的。這個方式稱為悲觀並發控制(Pessimistic Concurrency)。隨着SQL Server 2005的發布,微軟引入了新的並發模式:樂觀並發控制(Optimistic Concurrency)。使用樂觀並發控制,讀操作不再需要共享鎖。直接從TempDb永駐的版本存儲里讀取。

使用新的隔離級別Read Committed Snapshot Isolation (RCSI) ,你回退到語句開始后不再有效的記錄版本;使用隔離級別Snapshot Isolation,你回退到事務開始后不再有效的記錄版本,這意味這在Snapshot Isolation里你可以重復的讀。

 

設置這些新的隔離級別會大大促進你的整個工作量。但還有問題必須解決:

寫還是需要排它鎖,意味這並行寫操作還是會相互阻塞。

當訪問內存中的數據時(數據頁,索引頁),這些結構必須被閂鎖,意味着它們只能被單線程訪問。那是傳統多線程並發問題,需要以此方式(閂鎖)來解決。

因此XTP引入了基於多版本並發控制(Multi Version Concurrency Control ,MVCC)原則。使用MVCC就沒有鎖(甚至沒有排它鎖)和閂鎖。寫不會相互阻塞,因為哈希索引不是建立在頁上的,數據訪問是無閂鎖的(內部是用哈希桶的哈希表來存儲的)。在內存里不再有阻塞。當你使用XTP時,意味着卓越的吞吐量保證。但你的內存里訪問沒有閂鎖時,你的性能瓶頸將轉移,主要移向事務日志,在下個討論XTP擴展性時我們會談到。

MVCC的一個副作用是所謂的寫-寫沖突(Writer-Writer conflict。你可以在XTP里對同個記錄進行多個寫操作,它們不會阻塞。第一個寫會勝出,其他所有並發的寫事務會失敗。這意味着你需要修改你的代碼來捕獲這個特定錯誤,然后重試你的事務。這和死鎖處理是一樣的。如果在你程序里已經有死鎖的實現方式,應該很容易對你的終端用戶處理寫-寫沖突。

2.可擴展性(Scalability)

現在你應該大致理解了XTP的主要概念和背后的原因,但最大的問題是,在哪些情況下才可以使用XTP呢。我認為XTP不是一個隨處可以部署的技術。你需要一個特殊情景來使用XTP才有意義。請相信我:我們現在所面臨的大多數SQL Server問題,基本是索引問題,或者是硬件的錯誤配置問題(尤其是SANS領域)。

當你面對這樣的問題時,我絕不推薦升級到XTP。一定要先分析下根源,在第一步就從根源解決問題。XTP應該是你最后才考慮的解決方法,因為當你的部分數據庫使用XTP時,你的問題分析方法就會完全不一樣了。對於XTP,你會遇到大量的各種限制。XTP是賊快(我可以說是TMD的快),但不是個歷史奇跡(all-time wonder),不是每個地方都適用的。

微軟推出XTP主要是為了克服加鎖競爭(Latch Contention)問題。剛才你已經看到,當你訪問數據頁進行讀寫活動時,加鎖競爭在內存里總會發生。當你的工作量越來越大,到了某個時間點,你就引入了加鎖競爭,因為單線程訪問內存里的這些頁。這里最常見的例子就是最后頁插入加鎖競爭(Last Page Insert Latch Contention)

這個問題很容易重現:按照最佳實踐創建一個聚集鍵,這個鍵使用自增長來避免在聚集索引里的硬頁分裂。你的工作量不會延伸——相信我!這里的問題是:在INSERT語句期間,在你的聚集索引里只有一個熱塊——最后頁。下圖展示了這個現象:

Last Page Insert

 

從圖中可以看到,SQL Server需要橫穿聚集索引的右手,從索引根頁下至葉子層來在聚集索引的機翼后緣插入新的記錄。因此在葉子層你有單線程訪問葉子頁,這就意味着單線程插入(Single-Thread INSERT)操作。這會大大傷及你的性能。下圖展示了使用INT IDENTITY列的表里的簡單INSERT語句(自增長,導致最后頁插入加鎖競爭!),我使用ostress.exe程序(來自微軟RML工具的一部分)模擬不同用戶在16核的機器上的不同性能表現。

 

從圖中可以看到,隨着用戶的增加,工作量在逐步下降,你的閂鎖等待增加——這就是使用自增長值的最后頁插入加鎖競爭(Last Page Insert Latch Contention)。當訪問在內存里的索引頁和數據頁時,這個競爭就會發生,因為閂鎖。

有幾個方式可以克服最后頁插入加鎖競爭:

  • 使用隨機聚集鍵,例如UNIQUEIDENTIFIER 在整個聚集索引葉子層分布式插入
  • 實行哈希分區(Implement Hash Partitioning)

當你使用UNIQUEIDENTIFIER 作為你的聚集鍵時,首先你就會覺得自己做錯了,但你的工作吞吐量卻大幅度上升了。哈希分區是你另一個可以部署的選項。哈希分區意味着你為每個CPU內核創建不同的分區,使用取模運算符在不同的分區間,你的配分函數(Partition Function)分發記錄。下圖展示了這個方法:

 

你通過在不同分區里的不同B-Tree結構分發INSERT語句,因此你就可以並行在表里執行INSERT語句。但這個也是有缺點的,你需要SQL Server的企業版,用了這個分區,你的表就不能重新分區,你就不能有效使用分區消除(Partition Elimination)。下圖是對應的性能提升展示:

 

從圖中可以看到,當用戶達到64個前都是平穩延伸的,在128個用戶后,再次發生最后頁插入加鎖競爭,吞吐量再次下降。

現在假設我們啟用內存優化表,性能會發生如何的改變?表的生產力會賊快——XTP真是的TMD的快!因為沒有鎖和閂鎖!我們可以看下SQL Server批量請求時資源使用情況:

 

在這個情況下,我可以執行200個用戶的本地編譯存儲過程里的INSERT語句,可以接收25500批量請求/秒——0工作等待,0數據I/O!所有的一切都在內存里發生。但是:這次測試都是在虛擬機里執行。虛擬機有8個內核,分配了20G的內存,虛擬機和相關的SQL Server文件都存儲在PCI-E上的SSD上。

我現在碰到的XTP的生產力瓶頸在哪里呢?這個不容易馬上知道答案。首先你要考慮的是還是你的聚集鍵。使用XTP並不支持IDENTITY列。因此微軟建議使用序列對象(Sequence Object)。序列是完美的,你可以使用緩存,但是XTP是TMD的太快了,你馬上就碰上了SQL Server里的序列生成器(Sequence Generator)的競爭。SQL Server在你主數據文件的第132頁保存序列值。第132也是系統表sysobjvalues的一部分。當你讀寫那個頁時,SQL Server又會閂鎖那個頁,你的閂鎖競爭又回來了,但只在你的數據酷的不同領域上。你不能避免這個頁的閂鎖競爭,因為系統表還是存儲在傳統磁盤的表上。因此從這個角度來說序列並不是XTP的最佳解決方法,如果你想無限延伸你的工作量。

因此讓我們再次回到老朋友UNIQUEIDENTIFIER 這里。當生成UNIQUEIDENTIFIER 不會有任何競爭,因為生成是通過算法的。不好的是:函數NEWID()在SQL Server 2014的CTP1里並不支持。但這個也沒關系,你可以在存儲過程里寫入,在存儲過程里生成UNIQUEIDENTIFIER ,通過變量值傳給本地編譯的存儲過程。問題解決,這樣的話我可以把工作量提升至25500 批量請求/秒。從我的測試可以看出,UNIQUEIDENTIFIER 比序列更好,因為沒有需要協調和閂鎖的共享資源。這個是我的最大收獲。

因此現在的問題就是什么限制了25500 批量請求/秒的工作量?2個主要東西:事務日志和CPU使用率!我們首先來看看事務日志。在XTP里,微軟對此做了大量的優化工作,例如沒有UNDO記錄。微軟嘗試使事務日志量最小化來優化事務日志的寫入。寫得少,做得快。為了克服事務日志限制,XTP提供給你2類不同的內存優化表:

  • SCHEMA_AND_DATA
  • SCHEMA_ONLY

 SCHEMA_AND_DATA意味着表架構和數據都永駐,因此只要你的事務一提交,XTP就要把事務日志記錄寫入事務日志。在事務執行期間,XTP從不把事務日志記錄寫入事務日志,因為你隨時可能回滾事務。SCHEMA_ONLY表數據的修改不進行日志記錄,並且表中的數據不保留在磁盤上:當你重啟你的SQL Server,只有空表,嗯???你要慎重考慮使用這個選項,什么時候是可以用的,什么時候是不行的。微軟建議下列2個場景可以使用這個選項:

  • ASP.NET會話狀態數據庫
  • 提取轉換加載(ETL)場景

ASP.NET會話狀態數據庫可以使用SCHEMA_ONLY選項,因為你這里不保存關鍵數據。會話狀態是關於你網站用戶的信息。對於ETL場景也同樣可以使用SCHEMA_ONLY選項,因為即使失敗也很容易重建數據。使用SCHEMA_ONLY選項,我可以獲得25500 批量請求/秒的工作量。使用SCHEMA_AND_DATA就成為了主要瓶頸,只能獲得15000 批量請求/秒。我說過,我的測試環境的虛擬機是運行在PCI-E上的SSD上(事務日志,數據文件,虛擬機),換做實體的純金屬服務器會更好。

當你使用SCHEMA_AND_DATA部署你的內存優化表,你還有一個東西:極快的事務日志——和往常一樣!當你使用SCHEMA_AND_DATA,CPU會稱為你的瓶頸,因為沒別的了。從剛才的圖中你可以看到虛擬機里CPU基本運行在85%。當我把所有都部署在實體服務器上是,我覺得會加倍它的工作量。因為我只分配16核中的8核給虛擬機。在主機有50%的平均CPU占用,因此加倍工作量應該不是問題。那應該是在很低成本機器上卻有50000批量請求/秒的工作量……

3.局限性(Limitations)

到目前位置,XTP的一切都看起來很棒,它應該是SQL Server里特定問題的最佳解決方案。但是XTP也有很大的代價——一大堆的局限性,尤其是現在的CTP1。下面列出部分,更多可以查看微軟在線幫助:

  • 差異備份不支持
  • 行大小限制為8kb
  • 內存優化表不能truncate
  • NEWID()尚未實現
  • 用SCHEMA_AND_DATA部署的內存優化表必須要有主鍵
  • ALTER TABLE/ALTER PROCEDURE不支持
  • 外鍵(Foreign-Keys)不支持
  • LOB數據類型不支持
  • 本地編譯的存儲過程不會重編譯。由於統計信息改變,不重編譯會導致很糟的性能
  • 在內存優化的存儲過程里不能訪問存放在傳統硬盤(機械硬盤)里的表
  • 整個表的定義只能在一個CREATE TABLE里描述(包括索引和約束)
  • 你不能從別的數據庫往內存優化表里直接插入數據,你需要一個中間表。這在剛才提到的ETL場景里會是個大問題
  • ……

除了這些局限性外,目前的CTP1版本里的XTP還是有BUG的,數據庫居然崩潰了,也不能進行還原……不要問我如何重現這個BUG。

結論

 XTP在SQL Server里的確是快速發展起來的技術。你需要考慮的唯一事情就是:當你基於XTP部署解決方案時,為你的事務日志准備盡可能快的存儲系統吧,這個會大幅度降低系統瓶頸!希望這篇文章可以幫你很好的了解XTP,也希望你在閱讀的時候,和我一樣享受這個撰寫過程。感謝您的閱讀!

參考文章:

https://www.sqlpassion.at/archive/2013/08/12/extreme-transaction-processing-xtp-hekaton-the-solution-to-everything/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM