大資料分析中使用關係型資料庫的關鍵點
相當一部分大資料分析處理的原始資料來自關係型資料庫,處理結果也存放在關係型資料庫中。原因在於超過99%的軟體系統採用傳統的關係型資料庫,大家對它們很熟悉,用起來得心應手。
在我們正式的大資料團隊,數倉(資料倉庫Hive+HBase)的資料收集同樣來自Oracle或MySql,處理後的統計結果和明細,儘管儲存在Hive中,但也會定時推送到Oracle/MySql,供前臺系統讀取展示,生成各種報表。
在這種場景下,資料庫的讀寫效能就顯得尤為重要!
一、資料庫定位
有大神說,給我足夠強的資料庫硬體,一個GroupBy就可以滿足各種統計分析場景。
這話不假,我們一臺數百萬的金融級別Oracle一體機證明了GroupBy可以做得很強大,同時也證明了它有天花板,就是當資料更大的時候,它依然得趴下!
於是,我們需要有設計原則,有優化技巧。
核心原則:資料庫只是資料儲存的載體,在大資料中難以利用它的計算能力!
有了這個原則,就意味著資料庫將會用得“純粹”:
- 資料表獨立性很強,大表間很少join(這讓我想起有同學在Hive裡對兩張大表做笛卡爾乘積產生270T資料)
- 資料表很大,單表幾十億行很常見
- 索引很少,一般按主鍵查單行或者按時間查一段
二、分割槽儲存
在這裡,資料庫就是儲存資料的倉庫,海量資料需要拆分儲存,不可能全都擠一塊。
根據業務不同,一般有兩種拆分方式:
- 單表分割槽。常見於Oracle,每月做一個分割槽,資料連續方便業務處理,但要求單機效能強勁。
- 分表分庫。常見於MySql,分個128張表乃至4096張表也都是很平常的事情,可以用很多效能較差的機器組建叢集,但因資料不連續不便於業務處理。
具體採用哪一種拆分方式,由使用場景決定。
如果以後還要整體抽出來去做統計分析,比如原始資料和中間資料,那麼優先考慮做分割槽。既方便連續抽取,又方便按月刪除歷史資料,對海量資料Delete很痛苦。分割槽內還可以建立子分割槽和分割槽內索引。
如果用於業務資料或者最終統計結果,那麼考慮分庫後分表,按照業務維度把資料“均勻”存在不同表上。比如對單號取CRC,然後對資料表數取模。
有很多資料,屬於時序資料性質,或者日誌型,都是隻有插入,只有少量或者完全沒有Update,幾乎沒有Delete。
這種資料有個很關鍵的時間欄位,確定資料什麼時候到來,比如InputDate/CreateTime/UpdateTime,可以藉助觸發器給這個欄位填充當前時間。
基於時間維度抽取時序資料進行分析時,必須確保時間欄位升序能夠查到所有資料,不會漏過也不會重複查某些行。
三、高效查詢
海量資料查詢,必須100%確定命中索引。要麼是code=xxx,要麼是 updatetime>=:start and updatetime<:end。
根據主鍵查詢,命中單行或少量資料;
根據時間查詢,必須合理選擇時間區間(start, end),讓查詢結果控制在10000~20000行左右較好。
比如考慮到高峰時段,我們一般取5秒的區間進行查詢,一般得到10000~40000行。
使用資料時,可能有很多查詢條件,但其中最重要的一般是時間區間。
因為資料很大,DBMS本身的統計資訊收集工作可能很不及時,導致執行計劃選擇錯誤的索引方案,這種情況下需要手工收集資訊,甚至在查詢語句裡面強制指定索引。
四、批量寫入
藉助記憶體計算,我們往往可以在很短的時間內計算得到數十萬乃至數百萬資料,需要寫入資料庫。
一般資料庫的Insert/Update效能只有3000~5000tps,帶著索引的負擔,難以快速把資料寫入其中。
這裡以Oracle為例,它的OracleCommand有一個超強功能ArrayBindCount,可以對一次引數化寫入操作繫結多組(例如5000組/行)。
該方法能夠讓它得到最高寫入效能,實際業務使用得到30000tps左右。
var count = 1_000_000; var connectStr = "User Id=scott;Password=tiger;Data Source="; var conn = new OracleConnection(connectStr); var command = new OracleCommand { Connection = conn, ArrayBindCount = count, CommandText = "insert into dept values(:deptno, :deptname, :loc)" }; conn.Open(); var deptNo = new Int32[count]; var dname = new String[count]; var loc = new String[count]; var deptNoParam = new OracleParameter("deptno", OracleDbType.Int32) { Direction = ParameterDirection.Input, Value = deptNo }; command.Parameters.Add(deptNoParam); var deptNameParam = new OracleParameter("deptname", OracleDbType.Varchar2) { Direction = ParameterDirection.Input, Value = dname }; command.Parameters.Add(deptNameParam); var deptLocParam = new OracleParameter("loc", OracleDbType.Varchar2) { Direction = ParameterDirection.Input, Value = loc }; command.Parameters.Add(deptLocParam); var sw = Stopwatch.StartNew(); for (var i = 0; i < count; i++) { deptNo[i] = i; dname[i] = i.ToString(); loc[i] = i.ToString(); } command.ExecuteNonQuery(); sw.Stop(); Debug.WriteLine("批量插入:" + count + "所佔時間:" + sw.ElapsedMilliseconds);
MySql和SQLite都有它獨特的批量寫入功能,並且支援netcore。
SqlServer也有批量寫入功能,但是目前還不支援netcore。
MySql方案另起一篇文章專門寫。
五、總結
關係型資料庫儲存大資料,要點就是:簡單儲存、分割槽分表、高效索引、批量寫入!
End.