PolarDB-X是一款以TP為主的HTAP資料庫,也支援一定情境的分析需求。而典型的分析情境一般有以下幾類特徵:
少量的寫或者更新要求,大多數是讀請求;
每次查詢都從資料庫中讀取大量的行,但是同時又僅需要少量的列;
大多數查詢都是比較複雜的查詢,查詢的並發不會很大,但單個查詢需要高輸送量;
對於簡單查詢,允許一定的延遲;
分析情境上分散式交易可能不是必須的;
大部分查詢中往往會涉及到事實表和維表的關聯,是典型的大小表關聯情境;
查詢結果明顯小於來源資料,即資料被過濾或彙總後能夠被盛放在單台伺服器的記憶體中;
分析的資料往往是最近的業務資料,歷史資料可以被清理或者被歸檔。
依據上述對分析情境的歸納,分析情境做效能最佳化除了要沿用TP資料庫的最佳化思路,還會有自身不一樣的最佳化思路。主要體現在結構設計和查詢最佳化兩個方面。
結構設計
結構設計主要包括如何選擇表類型、分區鍵、主鍵以及聚簇索引,使表的效能達到最優。
設計為分區表或者廣播表
廣播表會在叢集的每個資料節點都儲存一份資料,建議廣播表的資料量不宜太大,每張廣播表格儲存體的資料不超過20萬行,這樣在大表和廣播表做關聯時,可以計算下推,讓關聯貼近資料層做計算,避免大表資料拉取到計算節點做計算。
其他業務資料儘可能做成分區表,可以充分利用分布式系統的查詢能力。理論上表的分區數量越多越好,這樣多個分區表可以做並行掃描。儲存層更易做到水平擴充,儲存千萬條甚至上億條資料。實際使用中建議一個分區表的數量在500w~5000w之間。
選擇合適的分區鍵
PolarDB-X預設按照主鍵做分拆,主要為了降低分散式資料庫的使用成本。同時我們也支援通過指定分區鍵建分區表,在分析情境中建議您根據如下依據選擇分區鍵:
儘可能選擇參與JOIN的欄位作為分區鍵,這樣做的目的是為了關聯條件下推,避免資料被拉取到計算層做計算。
儘可能選擇值分布均勻的欄位作為分區鍵,這樣可以避免由於分布式不均導致出現計算長尾現象,嚴重拖慢大查詢效能。
合理設計二級分區
PolarDB-X的DRDS模式資料庫支援二級分區。當資料量過大或者有資料扭曲時,二級分區的選擇至關重要,如果資料量大的表中沒有二級分區或者二級分區切分不合理,也會影響效能。如果業務明確有增量資料匯入需求,主要是對最近資料的報表分析,那麼建議用日期格式做二級分區,避免對歷史到期資料的掃描。
//直接用col先做一級分區
PARTITION BY HASH(col)
SUBPARTITION BY LIST (ds)
//ds轉換後的月做二級分區
SUBPARTITION TEMPLATE (
PARTITION p1 VALUES LESS THAN ('2021-08-00'),
PARTITION p2 VALUES LESS THAN ('2021-09-00'),
)合理設計索引
如果業務已經按照關聯欄位,合理的設計了分區鍵。但依然還有部分複雜查詢涉及到對該表的其他列做關聯,無法做到關聯查詢下推,此時可以考慮基於該非分區鍵的列做全域二級索引。這樣複雜查詢對該表做關聯,可以轉化成與該全域二級索引做關聯。同時了為了避免回表的代價,對於分析情境建議所有的全域二級索引都建成聚簇索引。
查詢最佳化
在分析情境中,由於會涉及比較大的資料,且對簡單查詢的延遲有一定的容忍度,推薦您採用MPP執行模式,既利用多個計算節點(CN)的計算資源承擔複雜計算。一般只在唯讀執行個體預設開啟MPP能力,如果您可以允許在主執行個體做分析需求,請聯絡阿里雲支援人員。
在查詢過程中,PolarDB-X首先會基於最佳化器選擇合適的分布式執行計畫,然後將計劃調度到各個計算節點,充分發揮整個叢集的計算資源加速查詢。這個過程產生的分布式執行計畫完全是基於統計資訊做代價選擇,因此及時的資訊採集至關重要;同時由於最佳化器產生的計劃不一定是最優的,本文也提供了一些SQL編寫和最佳化的經驗。
收集統計資訊
PolarDB-X會及時定時收集統計資訊,如果發現PolarDB-X產生的分布式執行計畫不是最優的。可以通過ANALYZE TABLE手動對某個表做統計資訊收集。
SQL編寫技巧
去掉不必要的列
由於分析情境大多數是高吞吐的,所以應該去除返回過程中不必要的列,減少對頻寬的壓力。在編寫SQL時一定要確認業務需要返回的列,不要直接使用星號(*)進行查詢。
//不合適寫法 select * from T1 where a1>100 and a2<1000; //更合適寫法,只需要返回業務關心的列 select a1, a2 from T2 where a1>100 and a2<1000;基於局部索引做過濾
很多分析情境都期望用時間做二級分區,這樣做巨量資料掃描的時候可以把時間做過濾條件,過濾掉絕大多數歷史資料。
select a1,c2 from T1 where time >='2010-01-01 00:00:00';為了避免全部掃描,目前預設會在這個分區列上做局部索引。同樣的在很多高吞吐的掃描情境下,可以考慮基於過濾條件做局部索引。
避免低效的SQL文法
如果表記錄數非常大,掃描會很慢,直接導致查詢緩慢。在SQL編寫過程中我們需要注意以下幾點:
避免索引失效
不在索引列上做任何操作,例如計算、函數、類型轉換(自動或手動),會導致索引失效而轉向全表掃描。
explain execute select * from staffs where name= 'hu'; +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+ | 1 | SIMPLE | staffs | NULL | ref | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74 | const | 1 | 100 | NULL | +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+ 1 row in set , 1 warning (0.00 sec) //在索引列上做了其他動作,導致索引失效 explain execute select * from staffs where left(name,4)= 'hu'; +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 198 | 100 | Using where | +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set , 1 warning (0.00 sec)在使用不等於(!=或<>)的時候,無法使用索引導致全表掃描。
is null,is not null也無法使用索引。
explain execute select * from staffs where name is null ; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+ 1 row in setlike以萬用字元開頭,MySQL索引失效會進行全表掃描的操作。
explain exeucte select * from staffs where name like '%hu' ; +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 198 | 11.11 | Using where | +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set explain execute select * from staffs where name like 'hu%' ; +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+ | 1 | SIMPLE | staffs | NULL | range | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74 | NULL | 1 | 100 | Using index condition | +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+ 1 row in set
盡量少用like,like操作一般不會很高效,盡量使用範圍條件到達目的。比如between...and...。
多表關聯情境下:
盡量包含分區列條件。如果不包含,則盡量通過WHERE條件過濾掉多餘的資料。
outer join的on和where範圍不同。on是作用於join的過程,where是作用於join之後的結果,所以應該將能在join的時候提前過濾的條件寫在on上,也可以寫在join表的子查詢裡,這樣可以減少join原始表的資料量。
資料扭曲的檢查和處理
如果出現查詢異常緩慢,或者資源使用率不均勻的情況,則需要確認是否出現了資料扭曲。一般解決傾斜有三種策略:
通過
show info from table檢查某個分區在各個節點上的資料分布情況,如果各節點上的資料分布明顯不均勻,則可以考慮對該表的分區鍵進行調整。如果是出現了嚴重Join Key熱點問題,將傾斜的Key用單獨的邏輯來處理。例如兩邊的Key中有大量NULL資料導致了傾斜,則需要在Join前先過濾掉NULL資料或者補上隨機數,然後再進行Join,樣本如下。
SELECT * FROM A JOIN B ON CASE WHEN A.value IS NULL THEN CONCAT('value',RAND() ) ELSE A.value END = B.value;在實際情境中,如果您發現已經資料扭曲,但無法擷取導致資料扭曲的Key資訊,可以使用如下方法查看資料扭曲。
--執行如下語句查詢資料扭曲。 SELECT * FROM a JOIN b ON a.key=b.key; --您可以執行如下SQL,查看Key的分布,判斷執行Join操作時是否會有資料扭曲。 SELECT left.key, left.cnt * right.cnt FROM (select key, count(*) AS cnt FROM a GROUP BY key) LEFT JOIN (SELECT key, COUNT(*) AS cnt FROM b GROUP BY key) RIGHT ON left.key=right.key;如果Group By Key出現了熱點問題,可以考慮對SQL進行改寫,添加隨機數,把長Key進行拆分。例如:
SELECT Key,COUNT(*) AS Cnt FROM TableName GROUP BY Key; //最佳化成以下SQL,先對熱點做打散預彙總,再做最終彙總 -- 假設長尾的Key已經找到是KEY001。 SELECT a.Key , SUM(a.Cnt) AS Cnt FROM ( SELECT Key , COUNT(*) AS Cnt FROM TableName GROUP BY Key, CASE WHEN Key = 'KEY001' THEN rand() % 50 ELSE 0 END ) a GROUP BY a.Key;
調整執行策略
按照上述策略調整後,查詢效能依然不理想且計算和儲存資源都未到達瓶頸,這個時候可以調整下執行策略。主要有兩種方式去調整: