從KingbaseES V9的自研優(yōu)化器算子談起
9月30號發(fā)布的第二批數(shù)據(jù)庫國測結(jié)果中,電科金倉通過了兩款數(shù)據(jù)庫,算上第一批通過的KingbaseES V8(以下簡稱KES),電科金倉目前有3款數(shù)據(jù)庫在國測清單中。本次國測結(jié)果對于數(shù)據(jù)庫廠商來說是生死攸關(guān)的,因為大規(guī)模數(shù)據(jù)庫國產(chǎn)化替代工作馬上就要展開,這會讓通過國測的企業(yè)在市場上肯定會擁有一定的優(yōu)勢。
KES V8/V9兩個版本都過了國測,這讓電科金倉的新老用戶在國產(chǎn)化替代工作中省了不少力氣。V8老用戶不必急著升級,新用戶可以大膽地選擇功能和性能更加優(yōu)秀的V9版本。之前我聽一些同學(xué)吐槽過,說因為PG內(nèi)核升級了,所以KES V9的性能就比V8好了。事實是這樣嗎?有些東西道聽途說總是不太靠譜,還是眼見為實才好。
圖片
上面的信息是D-SMART從KES V8R6中采集出來的,可以看出服務(wù)器版本是12.1。
圖片
上面是V9的信息,服務(wù)器版本并未升級??礃幼覸9在某些SQL上的性能提升并不是如坊間傳聞的那樣,是因為使用了較新版本的內(nèi)核。通過對KES V9的初步分析,我個人的推測是,電科金倉在KES數(shù)據(jù)庫內(nèi)核可能上已經(jīng)走上了自主分支的道路,不一定會緊跟PG社區(qū)內(nèi)核升級了。在核心上脫離社區(qū),構(gòu)建自主的獨(dú)立分支,同時關(guān)注社區(qū)的技術(shù)發(fā)展,不斷把社區(qū)版本中的優(yōu)秀的方案搬到自主內(nèi)核上。既保證了對用戶需求的更好支撐,又可以不斷吸取社區(qū)的先進(jìn)思想,從而確保技術(shù)演進(jìn)高效的前提下成本最低,這對于目前研發(fā)資金不太足夠的國產(chǎn)數(shù)據(jù)庫來說至關(guān)重要。
目前國產(chǎn)化替代中,用戶遇到的最主要問題有兩方面,一方面是如何在最小改動的情況下將企業(yè)中原來在國外商用數(shù)據(jù)庫上跑得很好的應(yīng)用遷移到國產(chǎn)數(shù)據(jù)庫上,這方面很多國產(chǎn)數(shù)據(jù)庫做得都不錯。比如達(dá)夢、電科金倉、神通這些老牌數(shù)據(jù)庫廠商,經(jīng)過十多年的技術(shù)積累,在Oracle、MySQL、PG、DB2、SQL SERVER等數(shù)據(jù)庫的兼容性上做得都相當(dāng)不錯了。另外一方面是遷移過來的應(yīng)用性能不能太差,起碼能夠接近原有數(shù)據(jù)庫的水平或者相差不是太大。
第二方面的問題也是目前大多數(shù)國產(chǎn)數(shù)據(jù)庫在用戶現(xiàn)場遇到的最多的,就是一些SQL的執(zhí)行計劃不如Oracle優(yōu)秀,導(dǎo)致系統(tǒng)遷移后應(yīng)用性能無法被用戶接受。其中很重要的原因是因為國產(chǎn)數(shù)據(jù)庫的優(yōu)化器功能不足,某些Oracle支持的執(zhí)行算子自身不支持。要解決這些問題,就需要數(shù)據(jù)庫廠商在內(nèi)核上多下點(diǎn)功夫,提升優(yōu)化器的能力。
還有一種情況是某些用戶的SQL的寫法并不常規(guī),數(shù)據(jù)庫產(chǎn)品經(jīng)理沒有想到會有這樣的SQL存在,所以在生成執(zhí)行計劃時rewrite出來的等價SQL不夠合理,從而導(dǎo)致隨后生成的執(zhí)行計劃性能不佳。這類問題往往是因為我們的國產(chǎn)數(shù)據(jù)庫實戰(zhàn)的應(yīng)用場景還不夠豐富,因此沒有發(fā)現(xiàn)這類問題。如果這類問題能夠被發(fā)現(xiàn)的話,作為具有一定自主核心研發(fā)能力的數(shù)據(jù)庫廠商可以很快就解決掉這些問題的。
最近研究KES V9,發(fā)現(xiàn)雖然內(nèi)核中優(yōu)化器方面的功能提升還是挺明顯的,特別是自研算子和SQL REWRITE規(guī)則的豐富程度方面。舉個例子,在PG數(shù)據(jù)庫上遇到NOT IN子查詢的語句還是挺頭疼的,PG在大多數(shù)情況下會使用FILTER算子。我們來看下面的測試用例:
DROP TABLE JOIN1;
DROP TABLE JOIN2;
create table join1 (id integer,name varchar(300),k1 integer);
create table join2 (id integer,name varchar(300),score integer);
insert into join1 values ( generate_series(1,20000),'aaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',10);
insert into join1 values ( generate_series(1,20000),'aaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',10);
insert into join1 values ( generate_series(1,20000),'aaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',10);
insert into join1 values ( generate_series(50201,50300),'aaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAASSSSSAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',10);
insert into join1 values ( generate_series(50201,50300),'aaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAASSSSSAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',10);
insert into join1 values ( generate_series(150201,1350300),'aaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAASSSSSAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',10);
insert into join2 values ( generate_series(1,40000),'aaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',1);
insert into join2 values ( generate_series(1,40000),'aaaaaaaaaaaaaaaaAAAAAAABBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',2);
insert into join2 values ( generate_series(20001,22000),'aaaaaaaaaaaaaaaaAACCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',3);
insert into join2 values ( generate_series(150201,950300),'aaaaaaaaaaaaaaaaAACCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaAAAAAAAAAAAAAAAAAAAAaaaaaaaAAAAAAAAAAAAAAAAAAAAA',3);
create index idx_j1 on join1(id);
create index idx_j2 on join2(id);
VACUUM ANALYZE JOIN1;
VACUUM ANALYZE JOIN2;
首先我們在一套PG 14上測試一下下面的一個帶有NOT IN子查詢的SQL:
圖片
這是PG典型的過濾器算子。子查詢掃描出來的數(shù)據(jù)做HASH,然后對外表的每行計算HASH值,進(jìn)行否定過濾。這種執(zhí)行計劃與HASH ANTI JION相比存在一定的缺陷,無法更好選擇左表,而且當(dāng)子計劃返回的數(shù)據(jù)超過WORK_MEM限制的時候,無法使用HASH表,會極大影響SQL的執(zhí)行效率。以前在優(yōu)化PG數(shù)據(jù)庫上的應(yīng)用時,遇到此類情況,只能改寫SQL了。
圖片
我們再來看一下KES V9,它使用了Hash Anti LSNA Jion算子,效率也高了不少。Oracle、SQL SERVER等數(shù)據(jù)庫都支持Hash Anti Jion算子,這對于NOT IN等類型的SQL消除子查詢是十分有效的,特別對于數(shù)據(jù)量很大的情況。KES在算子方面從O記借鑒了很多,對于HASH ANTI JOIN,設(shè)計了NA ,LSNA,RSNA等多種算子,分別針對不同的場景。
上面的例子中,PG數(shù)據(jù)庫做Filter的subplan返回的數(shù)據(jù)集還不算很大,我們設(shè)置的32M的WORK_MEM還能夠放得下整個HASH表,PG可以采用Hash算法來做Filter,此時的性能與HASH ANTI JOIN差別還不算大。如果返回的數(shù)據(jù)集比較大,PG的執(zhí)行計劃就會惡化。通過一個簡單的測試,把T2的數(shù)據(jù)加大,再做一次測試看看。
圖片
上面是KES V9的執(zhí)行計劃,可以看出KES依然使用了Hash Anti Jion,因為我去掉了子查詢中的>條件,返回的結(jié)果集可能帶有空值,所以無法使用更加高效的LSNA算子,使用了NA算子。從響應(yīng)時間上看是可以接受的,644毫秒相對數(shù)據(jù)量的增長還算線性。接下來再來看看PG 14的執(zhí)行情況。
圖片
因為WORK_MEM不足,因此按照PG優(yōu)化器的限制無法使用HASH,改為使用Materialize,所以這條SQL的執(zhí)行時間惡化到75146毫秒。
圖片
當(dāng)然我們也可以通過設(shè)置更大的WORK_MEM來優(yōu)化這條SQL,上面是我們把WORK_MEM加大到64M后的執(zhí)行效果。不過能夠在不需要調(diào)整WORK_MEM的情況下,通過優(yōu)化器去解決這些問題,是不是對用戶更加友好呢?而實際生產(chǎn)環(huán)境中,很多情況下,子查詢的結(jié)果集可能會更大,我們也不能總是通過加大WORK_MEM來解決問題吧。
圖片
對于此類查詢,Hash Anti Jion算子并不一定是最優(yōu)的選擇,如果子查詢能夠等價轉(zhuǎn)換為JOIN,那么在不同的情況下,可能需要使用其他的算子來解決問題。修改一下查詢條件,讓外表掃描返回的數(shù)據(jù)量更少,在這個案例里KES V9優(yōu)化器認(rèn)為走Nested Loop Anti Jion最佳,看上圖的結(jié)果,確實如此,執(zhí)行時間降低到50毫秒。除此之外,適當(dāng)調(diào)整數(shù)據(jù)量,我們還能看到這條SQL使用了MERGE ANTI JOIN算子,這些算子都是KES為了提升此類表連接的性能自研的。
圖片
PG 14則還是使用祖?zhèn)鞯腇ilter: (NOT (hashed SubPlan 1))算子,執(zhí)行時間的差距拉得更大了。
實際上目前數(shù)據(jù)庫國產(chǎn)化替代工作中遇到的最麻煩的事情就是替換后很多執(zhí)行計劃變差,而且無法優(yōu)化,只能通過修改SQL來解決問題,這給數(shù)據(jù)庫國產(chǎn)化替代工作帶來了額外的成本。
KES V9版本里,多了很多面向用戶應(yīng)用場景的優(yōu)化器功能增強(qiáng),比如參數(shù)kdb_rbo.enable_push_joininfo_to_union可以控制優(yōu)化器的行為,讓一個帶有UNION操作的子查詢參與連接操作,該特性可以將連接的條件下推到UNION連接的各子查詢中,從而優(yōu)化nested loop算子,從而提高SQL的性能。
另外一個例子是針對大表做count distinct這個算子的優(yōu)化 ,在數(shù)據(jù)重復(fù)度比較高的情況下,KES通過等價變換邏輯變換,將select count(distinct name) from t1; 轉(zhuǎn)換成select count(name) from (select name from t1 group by name);的形式,可以大大提高SQL的效率。當(dāng)然這種優(yōu)化和數(shù)據(jù)的分布關(guān)系很大,因此并不是通用性的,通過調(diào)整kdb_rbo.attribute_distinct_value_threshold參數(shù),用戶可以根據(jù)自己應(yīng)用的數(shù)據(jù)分布特點(diǎn),在普通情況下使用傳統(tǒng)的方式去處理,而達(dá)到參數(shù)規(guī)定的閾值后,自動啟用SQL改寫,從而能夠更加靈活地解決SQL的性能問題。
其實DB2、Oracle的優(yōu)化器中就有大量的這樣的開關(guān),這些開關(guān),都是不斷地在解決用戶的實際問題的時候不斷積累出來的。聽電科金倉的同學(xué)說,目前他們正針對數(shù)百個客戶現(xiàn)場遇到的與執(zhí)行計劃相關(guān)的性能問題,設(shè)計了大量的優(yōu)化補(bǔ)丁 ,正在一個一個地投入研發(fā)解決。這些針對優(yōu)化器的PATCH將會在未來的V9版本中陸續(xù)發(fā)布。
對于電科金倉的用戶來說,這是個福音,這比簡單地通過升級數(shù)據(jù)庫內(nèi)核獲得某些方面的性能和功能的提升有價值得多。其實企業(yè)應(yīng)用系統(tǒng)所需要的數(shù)據(jù)庫功能與并發(fā)處理能力,目前的絕大多數(shù)數(shù)據(jù)庫都已經(jīng)夠用了。用戶最急迫需要的是無論自己的應(yīng)用寫得多爛,數(shù)據(jù)庫廠商都能夠通過對優(yōu)化器的改進(jìn)讓用戶的應(yīng)用能夠跑起來。在這方面,電科金倉的KES做得確實不錯。