開發與維運

AnalyticDB for MySQL最佳實踐總結

表設計的最佳實踐:

ADB做為一個分佈式的,追求實時分析海量數據的極致性能,需要充分發揮分佈式數據庫的優勢,滿足ADB達到最佳性能的特徵要求,對錶的設計時,需要注意以下幾點規則。

1.選擇合適的表類型(維度表or普通表):

維度表:又稱廣播表,數據倉庫中的一個概念,一般存儲一些維度數據。在ADB中建表語句中有DISTRIBUTED BY BROADCAST 的關鍵字,這些表會在集群的每個節點存儲一份數據,因此建議維度表的數據量不宜太大,每張維度表存儲的數據不超過10萬行。
普通表:也叫作分區表、事實表,一般存儲業務的主題數據。普通表可存儲的數據量通常比較大,可以存儲千萬條甚至萬億條數據,可以對其設置一級分區對數據做sharding或者二級分區進行數據的生命週期管理。

注意:維度表如果太大會導致數據存儲空間的膨脹,節點越多膨脹越大,同時也會導致實時寫入時性能下降,iops會比較高。

2.選擇合適的分佈鍵(一級分區鍵):

ADB中創建普通表時,默認需要通過DISTRIBUTED BY HASH(column_name,...)指定分佈鍵,按照column_name的HASH值進行分區。ADB支持將多個字段作為分佈鍵。
分佈鍵的選擇依據:
儘可能選擇參與JOIN的字段作為分佈鍵,例如按照用戶維度透視或者圈人,可以選擇user_id作為分佈鍵。
儘可能選擇值分佈均勻的字段作為分佈鍵,例如交易ID、設備ID、用戶ID或者自增列作為分佈鍵。
注意:分佈鍵不均勻容易導致數據分佈不均,嚴重影響寫入和查詢的效率,此外也容易導致單節點磁盤寫滿導致整個集群鎖定不可用。一般情況數據均勻是第一優先級,然後才考慮JOIN KEY對齊的問題,除非有業務就是想定製化。

3.選擇合適的分區鍵(二級分區鍵):

如果業務明確有增量數據導入需求,創建普通表時可以同時指定分佈鍵和分區,分區可以實現數據的增量同步。
1)直接用ds的值來做分區 PARTITION BY VALUE(ds)
2)ds轉換後的天做分區 PARTITION BY VALUE(DATE_FORMAT(ds, '%Y%m%d'))
3)ds轉換後的月做分區 PARTITION BY VALUE(DATE_FORMAT(ds, '%Y%m'))
4)ds轉換後的年做分區 PARTITION BY VALUE(DATE_FORMAT(ds, '%Y'))
注意:一個實例能承載的最大二級分區數目是有限的,當前限制是10240。請提前規劃後這個實例的所有表二級分區鍵,儘量充分利用二級分區,不要讓每個二級分區的數據量過小,如:你使用天做二級分區,但是每天數據量很小,這時可考慮用月來做二級分區。否則會導致數據庫中需要保存分區數據的元數據特別多,這些元數據是需要存放在內存中,會佔據內存較多的空間,容易導致系統的GC或者OOM,同時也會導致實時寫入時的iops比較高。

二級分區的過期策略:
目前二級分區過期策略是依據大小排序,只保留最大的N個二級分區,其中N為生命週期的大小。假設表A定義的生命週期個數為3,目前存在的二級分區為202001,202002,202003。當分區值為202004的數據寫入進來時202001分區就會被淘汰。需要注意的是分區淘汰是延遲進行的,不保證202004的數據寫入後立即會淘汰202001。此外在使用二級分區時也要注意髒數據帶來的誤淘汰問題,如果此時表A分別寫入了分區值為300001,300002,300003的三條髒數據,那麼分區淘汰策略也會被觸發,整表將只剩下分區值最大的三條髒數據。

4.選擇合適的主鍵:

在表中定義主鍵可以去實現數據消重(Replace into)和數據更新操作(Delete、Update)。只有定義過主鍵的表支持數據更新操作(DELETE和UPDATE)。
主鍵的選擇依據:
1)儘可能選擇單數字類型字段作為主鍵,表的性能相對更好。ADB支持將字符串或者多字段組合作為主鍵。
2)主鍵中必須包含分佈鍵和分區鍵,如果有二級分區鍵的話,需要包含二級分區鍵。
注意:設置的主鍵的字段不宜太大,或者某個字段的長度不宜過長,否則的話,會導致數據庫的IOPS很高。

5.選擇合適聚集索引:

聚集索引會將該列或者多列排序,保證該列相同或者相近的數據存在磁盤的相同或相近位置,當以聚集列做為查詢條件時,查詢結果保持在磁盤的相同位置,這樣可以減少磁盤的IO。
聚集索引的選擇依據:
查詢一定會攜帶的字段可以作為聚集索引。例如,電商賣家透視平臺中每個賣家只訪問自己的數據,賣家ID可以定義為聚集索引,保證數據的局部性,提升數據查詢性能。

注意:目前聚集索引只支持一個,但該聚集索引可以有多列。目前除非對非常分散的數據進行點查,否則聚集索引對性能的幫助很少。

6.設計合適的數據類型:

原理:
ADB處理數值類型的性能遠好於處理字符串類型。原因在於:
1)數值類型定長,佔用內存少,存儲空間小。
2)數值類型計算更快,尤其是join時。
3)從內部索引機制上,字符串類型適合等值查詢和範圍查詢情況,而時間,數值類型性能更高,建議用戶儘可能- - 使用數值類型,減少使用字符串類型。
4)選擇儘可能小的列,列類型要儘可能選擇匹配的列,比如性別就可以用boolean或者byte類型,數據長度不大的可以用int
5)在同一個業務模型內,相同字段設計成相同的數據類型和字段長度,字段命名也保持一致,特別是涉及到主外鍵關聯的字段更要注意,避免表在關聯時不同的數據類型的字段關聯導致隱式轉換。

方法:
常見將字符串轉換為數值類型方法:
1)包含字符前綴或後綴,例如E12345,E12346等。可以直接去掉前綴或者將前綴映射為數字。
2)該列只有少數幾個值,例如國家名。可以對每個國家編碼,每個國家對應一個唯一數字。
時間/日期類型數據,避免使用varchar字符類型存儲,3)儘量使用date,timestamp或者int類型存儲時間類型。
4)對於地理經度維度的使用,需要通過地理函數查詢情況,數據類型採用double數據類型。

數據寫入方面的最佳實踐:

實時寫入:

1.批量打包的方式提交:

向表中寫入數據時,可以通過批量打包方式INSERT INTO和REPLACE INTO提高數據寫入性能。建議如下:
1)通過每條INSERT或者REPLACE語句寫入的數據行數大於1000行,但寫入的總數據量不宜太大,不要超過16MB。
2)通過批量打包方式寫入數據時,單個批次的寫入延遲相對會高一些,但是整體的性能會提升。
3)寫入報錯時,需要做重試確保數據被寫入,重試導致的數據重複可以通過表的主鍵來消除。
4)如果不需要對原始的數據進行修改,直接使用insert into比replace into效率會高3倍以上。 樣例:

(id, name,sex,age,login_time) 
values
(1,'dcs',0,23,'2018-03-02 10:00:00'),
(2,'hl',0,23,'2018-03-02 10:01:00'),
(3,'xx',0,23,'2018-03-02 10:02:00')
......;

2.更新數據

數據更新有多種方式,使用區別如下:
• 高頻基於主鍵的行級覆蓋更新, 且應用可以補齊所有列,請使用replace into values批量打包
• 高頻基於主鍵的行級覆蓋更新, 應用不能補齊所有列,請使用update into values批量打包
• 低頻基於主鍵更新,可以使用replace into或者update into單條數據
• 低頻任意條件更新,請使用 update set where
注意:update需要查表來填補更新中缺失的舊值,因此比replace into多一次查詢,性能較低,不建議做高頻、大批量的update操作。如果線上update性能無法滿足需求,需考慮替換成Replace into,由應用端補舊值。

3.刪除數據

數據刪除有多種方式,使用區別如下:
• 低頻主鍵條件刪除,請使用 delete from where pk = xxx
• 低頻任意條件刪除,請使用 delete from where
• 刪除單個二級分區,請使用 truncate partition
• 刪除單表(包括所有二級分區,如有),請使用truncate table或drop table

批量導入:

1.如何選擇是批量導入還是實時導入

1)從ODPS、OSS導入ADB,推薦使用insert overwrite select做批量導入,原因有二: 一方面,批量導入適合大數據量導入,性能好 二方面,批量導入適合數倉語義,即導入過程中舊數據可查,導入完成一鍵切換新數據。如果導入失敗,新數據會回滾,不影響舊數據的查詢。
2)從RDS、MySQL、ADB等導入ADB,看數據量情況,數據量不大的(百萬級別的表),推薦使用insert into select做實時導入,數據量大的,推薦使用insert overwrite select做批量導入。

2.導入併發和資源說明

1)單張表的導入會在系統內部串行,不同表之間的導入任務會並行,默認並行度是2;從ODPS分區表導入到ADB時,每次導入橫跨的分區數不要超過30個,同一張表的不同分區導入是排隊串行,不同表的導入,同時提交,有並行度n個任務同時導入,出於資源控制,超出的任務也會排隊。
2)導入使用的是ADB內部的資源,與查詢一樣,屬於同一個實例的資源。推薦導入任務在查詢qps比較低的時候進行,比如凌晨0點以後,並推薦用戶配置d2等定時任務,錯峰做導入。

高效查詢的最佳實踐

ADB的優勢是能在海量數據場景下,面對複雜查詢,做到實時的在線分析。ADB的SQL調優需要充分發揮分佈式計算優勢,以及ADB本身的一些特徵,同時對於通用的數據庫優化的方法論同樣是適用。

查詢優化的通用法則:

按照鬥佛早些年在《ORACLE DBA手記》上寫的文章,數據訪問優化滿足以下漏斗法則:
image.png
1、 減少數據訪問(減少磁盤訪問)
例如:儘量多的使用過濾條件,儘早的提前過濾數據,減少參與計算的數據量,能在子查詢裡面把數據先過濾的提前過濾。
2、 返回更少數據(減少網絡傳輸或磁盤訪問)
例如:避免select * 的查詢,特別的在OLAP數據庫下,往往表的列數比較多,同時由於基於列存或者行列混存,對於這種select * 的操作,需要請求的IO回更多。
3、 減少交互次數(減少網絡傳輸)
例如:上文提到的批量提交。
4、 減少服務器CPU開銷(減少CPU及內存開銷)
例如:
A. 減少不必要的排序和分頁,特別是在子查詢中的排序
B. 在滿足業務前提下,儘量減少count distinct操作
C. 在滿足業務前提下,特別是在海量數據下,採用類似Hyperloglog的近似計算代替準確計算。
5、 利用更多資源(增加資源)
例如:
A. 設計表的時候,儘量避免分區傾斜,導致存儲和計算壓在某一個節點上,儘量把數據都均勻的散列到所有的節點上,充分利用所有機器的能力,最大發揮分佈式的數據庫的效能
B. ADB本身就是MPP大規模並行處理的典型系統,在內核層面也做了大量的優化處理,充分利用更多的資源。

ADB特殊場景的優化:

外表的查詢最佳實踐

不要嘗試對外表進行較為複雜的計算,這樣會導致比較嚴重的GC,因為外表的計算是全部把數據拖過來算的,且網絡帶寬的壓力也會變大。
同時,外部不支持DML操作(delete,update,truncate),如果要修改外表的數據,需要在源頭表裡面操作。

巧妙的使用聚集索引:

當查詢條件一定包含某列時,特別是該列的數據在存儲上非常分散時,對該列建立聚集索引,性能會有明顯的提升,可以採用類似如下的sql語句添加聚集索引:
alter table table_name ADD CLUSTERED INDEX index_cls (d_fdbid);
注意:如果表裡面的數據已經有了,直接add cluster index不會對存量的數據排序,需要重建表,在建表的時候加上聚集列關鍵字。

減少節點間的數據交互:

分佈式數據庫,在充分發揮分佈式計算的同時,有時也會加大跨節點間的網絡開銷,特別是請求的數據散列在各個節點上時,請求的數據量有比較少,且節點個數又比較多情況下,跨網絡開銷的情況就非常明顯,因此可以採用以下幾個思路:

1)如果能採用本地計算,在各個節點內join或者聚合分析時,儘量在本節點內計算,具體做法就是,如果在滿足業務前提下,能用戶一級分區鍵關聯的,採用一級分區鍵關聯;能對一級分區鍵進行group by的,採用一級分區鍵group by,這樣可以儘量採用localjoin,大大減少跨網絡的訪問。
2)合理的控制節點數量,並不是節點越多越好,當數據庫規模不大,且每次查詢的數據量很少,且跨網絡訪問很嚴重的情況下,節點越多,問題越嚴重。

合理的使用索引:

合理使用索引在數據庫調優中,非常重要,在ADB中也不例外。在ADB中,默認每列都會創建索引。但是也有例外情況,如果某列的cardinality值比較少時,通過索引查詢可能會更慢,因為他需要多查一次索引再回表,且索引的選擇性又不高,性能就會很差,這時可以在建表時把這些disable掉建索引的功能,這樣就不會在建表後自動建索引了,如果索引已經創建了,可以把索引刪除掉,或者通過hint 不走索引訪問:
alter table table_name drop index index_name 把枚舉列的索引刪除掉。
或者使用/+no_index_columns=[t_order_content.fdelete;fdbid]/ 類似這樣的hint把索引去掉不走。

ADB連接的最佳實踐

ADB在使用方式上做到和99%以上和mysql兼容,支持多種連接方式,比如mysql命令行,JDBC連接,Python連接,c#連接,PHP連接等等。整體請參考官方文檔 。
例如:
採用Druid連接池的配置,請參考。

FAQ:

1. 磁盤佔用大小包含哪些數據?為什麼會觸發磁盤滿鎖定?
磁盤佔用量主要包括數據和索引兩部分。索引在構建過程中,會臨時額外佔用少量空間,期間可能會有少量數據膨脹。
用戶可以使用如下sql查詢使用空間:(延遲統計的,1小時統計一次)
select (sum(data_length)+sum(index_length))/1024/1024/1024 as '數據空間(GB)' from information_schema.tables;
使用如下sql查詢當前日誌使用空間:
show binary logs
其中,adb-bin.log表示binlog,adb-system.log表示系統日誌。
單節點磁盤使用量超過80%則會觸發鎖定.
有2種可能原因:一是一級分區鍵選擇不合理導致某些節點數據傾斜,二是數據分佈比較平均,總體使用量過大。是否存在表有分區傾斜可以在控制檯頁面查看存儲的整體水位達到多少。

2. 是否支持磁盤大小擴縮,是否支持節點數擴縮?節點數擴縮需要多久?
目前磁盤使用ecs雲盤,只支持擴容,不支持縮容。節點數支持擴縮,數量範圍與實例初始規格相關,控制檯變配頁面可以看到當前實例節點數變配範圍。節點數擴縮會在節點間進行部分數據遷移,正常情況下最大耗時為最大單節點磁盤使用量/40(MB/s) + 20min。

3. 如何進一步提高寫入性能?
數據寫和導入儘可能使用批量寫入的方式,使用dataworks進行數據同步可關注是否併發任務數和寫入批大小設置過小。主鍵選擇儘可能精簡。寫入表的分區鍵選擇儘可能均衡。

4. 如何選擇合適的一級分區列?
ADB內部將數據拆分為若干個一級分區,通常情況下一個ADB實例內部大概有100數量級左右的一級分區。在進行查詢時不同的一級分區併發進行。因此一級分區列最重要的一點是需要保證數據儘可能的均勻,否則會出現長尾查詢拖慢整體查詢進度。
不同的表如果一級分區列相同,那麼這些表在執行以一級分區列為join key的join時可以大幅度減少數據shuffle。因此在保證數據均勻的前提下,相同的一級分區列可以加速join。

5. 如何選擇合適的二級分區列?
二級分區是對一級分區的進一步拆分,一般是在時間維度上進行。大部分情況下單二級分區的數據儘量超過一百萬,達到百萬級,同時也不要達到數千萬。
下面以一張訂單表為例來選擇合適的二級分區。假設這張表單天增量百萬左右,需要保留10年的數據。由於我們單ADB集群通常情況下,有100左右的一級分區。若該表按日為分區,則單二級分區的大小約為1w左右遠低於我們的建議值。因此用月或者年作為二級分區比較合適。
二級分區的生命週期是支持修改的。如下語句: alter table lineitem partitions 12展示瞭如何將lineitem的二級分區個數修改為12。需要注意的是二級分區個數的修改是後臺異步執行的,執行build table lineitem可以加速分區修改任務。

6. 二級分區的過期策略是怎樣的?
目前二級分區過期策略是依據大小排序,只保留最大的N個二級分區,其中N為生命週期的大小。假設表A定義的生命週期個數為3,目前存在的二級分區為202001,202002,202003。當分區值為20204的數據寫入進來時202001分區就會被淘汰。需要注意的是分區淘汰是延遲進行的,不保證20204的數據寫入後立即會淘汰202001。此外在使用二級分區時也要注意髒數據帶來的誤淘汰問題,如果此時表A分別寫入了分區值為300001,300002,300003的三條髒數據,那麼分區淘汰策略也會被觸發,整表將只剩下分區值最大的三條髒數據。

7. 聚集索引是什麼,什麼情況下適合使用聚集索引?
聚集索引就是讓數據根據若干字段進行排序。對於有這相同的排序字段的數據在物理上儘可能的存儲在一起。
如果查詢一定會帶的某個字段,比如電商中賣家透視平臺,每個賣家只訪問自己的數據,那賣家id就是可以選擇為聚集索引,可以保證數據的locality,進而性能有量級的提升。
目前聚集索引只支持一個,但該聚集索引可以有多列。目前除非對非常分散的數據進行點查,否則聚集索引對性能的幫助很少,請謹慎選擇。

8. 主鍵如何選擇,是否能夠修改主鍵?
主鍵一般情況下用於數據的去重。主鍵的長度與去重的效率成反比,因此非常不建議使用較長的String如UUID作為主鍵,建議為1~3個long值。
此外需要注意的是,主鍵需要包含一級分區鍵和二級分區鍵。目前不支持主鍵的修改。

9. 如何自己指定索引?

  1. ADB默認是全字段索引,一般不需要自己維護索引。
  2. 如何查看一個表有哪些索引,跟mysql一樣使用這個語句:show index from t
  3. 如果想要drop掉某個索引可以使用:alter table t drop key key_name。其中key_name可以通過上面的語句查詢。
    注意:drop掉索引會導致查詢變慢。
  4. 如果想要自己指定索引,那跟mysql一樣,使用key關鍵字:key key_name (column_name)。如:create table t(id bigint,c1 varchar,key id_idx(id))DISTRIBUTE BY HASH(id)

10直接用mysql的建表DDL可以在adb中執行建表嗎?
可以的,具體行為是這樣的:
• 如果DDL中有主鍵,用主鍵做distribute key
• 如果DDL中沒有主鍵,會自動給他添加一個字段:__adb_auto_id__,然後用__adb_auto_id__做主鍵和分區鍵。

12. 可以直接用adb2.0的建表語句在adb3.0中執行嗎?
ADB3.0已經兼容了ADB2.0的建表語句。

Leave a Reply

Your email address will not be published. Required fields are marked *