SQL管理物化視圖
實(shí)時(shí)物化視圖將對(duì)明細(xì)表的數(shù)據(jù)進(jìn)行預(yù)先聚合,存儲(chǔ)為物化視圖,通過查詢物化視圖,減少計(jì)算量,顯著提升查詢性能。本文為您介紹在Hologres中如何使用物化視圖。
背景信息
Hologres實(shí)時(shí)物化視圖不需要手動(dòng)刷新物化數(shù)據(jù),明細(xì)表實(shí)時(shí)寫入,會(huì)實(shí)時(shí)反映在對(duì)物化視圖的查詢上,寫入即可見,寫入即聚合。
在實(shí)時(shí)物化視圖中,實(shí)時(shí)寫入的表叫明細(xì)表,也稱Base Table,用戶的Insert、Update、Delete都執(zhí)行在明細(xì)表上,物化視圖基于明細(xì)表的聚合規(guī)則定義,當(dāng)明細(xì)表發(fā)生變更時(shí),變更會(huì)實(shí)時(shí)同步到物化視圖中。當(dāng)前僅支持Insert類變更,后續(xù)會(huì)逐步增加更多類型的變更。
使用限制
當(dāng)前實(shí)時(shí)物化視圖不支持對(duì)明細(xì)表進(jìn)行Delete或Update操作,所以需要將明細(xì)表設(shè)置
appendonly
屬性,當(dāng)前對(duì)明細(xì)表任何的Delete或Update操作會(huì)提示:Table XXX is append-only
。Flink實(shí)時(shí)寫入時(shí)mutateType
也只支持InsertOrIgnore。當(dāng)前不支持異步創(chuàng)建物化視圖,需要?jiǎng)?chuàng)建明細(xì)表的同時(shí)創(chuàng)建基于該表的物化視圖。
當(dāng)前僅支持單表的物化視圖,不支持CTE、多表JOIN、子查詢、不支持WHERE條件、ORDER BY、LIMIT、HAVING語句。
實(shí)時(shí)物化視圖的GROUP BY Key和Value都不支持表達(dá)式,比如不支持
SUM(CASE WHEN COND THEN A ELSE B END)
、SUM(col1 + col2)
、GROUP BY date_trunc('hour', ts)
。每張明細(xì)表最多創(chuàng)建10個(gè)物化視圖,物化視圖數(shù)量和資源消耗成正比。
如果基于分區(qū)表創(chuàng)建物化視圖,物化視圖的GROUP BY Key必須包含分區(qū)表的分區(qū)列,且不能對(duì)分區(qū)表的子表創(chuàng)建物化視圖,只能針對(duì)分區(qū)表父表創(chuàng)建。
如果基于分區(qū)表創(chuàng)建物化視圖,不支持
ATTACH PARTITION
至父表語法,支持CREATE TABLE PARTITION OF
語法。對(duì)于創(chuàng)建了物化視圖的明細(xì)表,暫不支持
DROP COLUMN
。物化視圖的底層數(shù)據(jù)與明細(xì)表的TTL一致,不可以手動(dòng)設(shè)置物化視圖的TTL,否則會(huì)出現(xiàn)物化視圖數(shù)據(jù)和明細(xì)表數(shù)據(jù)不一致的情況。
支持的聚合函數(shù)
物化視圖當(dāng)前支持如下聚合函數(shù)。
SUM
COUNT
AVG
MIN
MAX
RB_BUILD_CARDINALITY_AGG(只支持BIGINT,需創(chuàng)建Extension roaringbitmap)
SQL示例
創(chuàng)建實(shí)時(shí)物化視圖
BEGIN; CREATE TABLE base_sales( day text not null, hour int , ts timestamptz, amount float, pk text not null primary key ); CALL SET_TABLE_PROPERTY('base_sales', 'mutate_type', 'appendonly'); --當(dāng)實(shí)時(shí)物化視圖被Drop后,可以取消明細(xì)表的appendonly屬性,執(zhí)行以下命令 --CALL SET_TABLE_PROPERTY('base_sales', 'mutate_type', 'none'); CREATE MATERIALIZED VIEW mv_sales AS SELECT day, hour, avg(amount) AS amount_avg FROM base_sales GROUP BY day, hour; COMMIT; insert into base_sales values(to_char(now(),'YYYYMMDD'),'12',now(),100,'pk1'); insert into base_sales values(to_char(now(),'YYYYMMDD'),'12',now(),200,'pk2'); insert into base_sales values(to_char(now(),'YYYYMMDD'),'12',now(),300,'pk3');
分區(qū)表創(chuàng)建物化視圖
BEGIN; CREATE TABLE base_sales_p( day text not null, hour int, ts timestamptz, amount float, pk text not null, primary key (day, pk) ) partition by list(day); CALL SET_TABLE_PROPERTY('base_sales_p', 'mutate_type', 'appendonly'); --day是分區(qū)列,要出現(xiàn)在視圖的group by的條件中 CREATE MATERIALIZED VIEW mv_sales_p AS SELECT day, hour, avg(amount) AS amount_avg FROM base_sales_p GROUP BY day, hour; COMMIT; create table base_sales_20220101 partition of base_sales_p for values in('20220101');
查詢物化視圖
SELECT * FROM mv_sales WHERE day = to_char(now(),'YYYYMMDD') AND hour = 12;
刪除物化視圖
DROP MATERIALIZED VIEW mv_sales;
查詢物化視圖占用存儲(chǔ)空間
select pg_relation_size('mv_sales');
查詢所有物化視圖底層占用空間
SELECT schemaname || '.' || matviewname AS mv_full_name, pg_size_pretty(pg_relation_size('"' || schemaname || '"."' || matviewname || '"')) AS mv_size, pg_relation_size('"' || schemaname || '"."' || matviewname || '"') AS order_size FROM pg_matviews ORDER BY order_size DESC;
使用物化視圖提升精確UV計(jì)算性能
精確UV計(jì)算是計(jì)算復(fù)雜度非常高的算子,通常是系統(tǒng)的性能瓶頸部分。Hologres支持RB_BUILD_CARDINALITY_AGG
聚合函數(shù),通過利用RoaringBitmap數(shù)據(jù)結(jié)構(gòu),可以對(duì)BIGINT類數(shù)據(jù)(通常是表示業(yè)務(wù)ID字段)進(jìn)行物化視圖預(yù)聚合,實(shí)現(xiàn)UV統(tǒng)計(jì)實(shí)時(shí)去重,可按照如下方式創(chuàng)建物化視圖,當(dāng)前僅支持BIGINT類字段的聚合去重。
--UV計(jì)算依賴RoaringBitmap數(shù)據(jù)類型,需要提前創(chuàng)建RoaringBitmap extension
CREATE EXTENSION if not exists roaringbitmap;
BEGIN;
CREATE TABLE base_sales_r(
day text not null,
hour int ,
ts timestamptz,
amount float,
userid bigint,
pk text not null primary key
);
CALL SET_TABLE_PROPERTY('base_sales_r', 'mutate_type', 'appendonly');
CREATE MATERIALIZED VIEW mv_sales_r AS
SELECT
day,
hour,
avg(amount) AS amount_avg,
rb_build_cardinality_agg(userid) as user_count
FROM base_sales_r
GROUP BY day, hour;
COMMIT;
insert into base_sales_r values(to_char(now(),'YYYYMMDD'),'12',now(),100,1,'pk1');
insert into base_sales_r values(to_char(now(),'YYYYMMDD'),'12',now(),200,2,'pk2');
insert into base_sales_r values(to_char(now(),'YYYYMMDD'),'12',now(),300,3,'pk3');
select user_count as UV from mv_sales_r where day = to_char(now(),'YYYYMMDD') AND hour = 12;
通過rb_build_cardinality_agg
計(jì)算去重?cái)?shù),mv_sales_r
中user_count
代表userid
去重?cái)?shù),查詢user_count
可獲得去重?cái)?shù)。
使用物化視圖支持多維度聚合查詢
假設(shè)定義了上述的mv_sales
物化視圖,且明細(xì)表base_sales
中當(dāng)前含有以下明細(xì)數(shù)據(jù)。
Day | Hour | Amount | PK |
20210101 | 12 | 2 | pk1 |
20210101 | 12 | 4 | pk2 |
20210101 | 13 | 6 | pk3 |
直接查詢sales_mv
將會(huì)有如下結(jié)果。
postgres=> select * from mv_sales;
day | hour | amount_avg
-----------+---------+--------------
20210101 | 12 | 3
20210101 | 13 | 6
這時(shí)如果想更改查詢物化視圖的聚合維度,例如使用維度day進(jìn)行avg聚合計(jì)算,則會(huì)得到的會(huì)是一個(gè)錯(cuò)誤的結(jié)果,因?yàn)閍vg的avg不等于total的avg。
postgres=> select day, avg(amount_avg) from mv_sales group by day;
day | avg
-----------+--------
20210101 | 4.5
這時(shí)候一種辦法是再建一張以day為維度進(jìn)行聚合的物化視圖,但這樣會(huì)導(dǎo)致物化視圖的數(shù)量膨脹,Hologres提供了一種基于聚合中間狀態(tài)的實(shí)現(xiàn),使得用戶僅用一張物化視圖,實(shí)現(xiàn)不同維度聚合查詢。這里以Avg為例,修改聚合視圖的定義如下。
BEGIN;
CREATE TABLE base_sales(
day text not null,
hour int ,
ts timestamptz,
amount float,
pk text not null primary key
);
CALL SET_TABLE_PROPERTY('base_sales', 'mutate_type', 'appendonly');
CREATE MATERIALIZED VIEW mv_sales_partial AS
SELECT
day,
hour,
avg(amount) as avg,
avg_partial(amount) AS amt_avg_partial
FROM base_sales
GROUP BY day, hour;
COMMIT;
原先的avg聚合函數(shù)重新定義為avg_partial聚合函數(shù),amount_avg_partial列存儲(chǔ)的是聚合結(jié)果的中間狀態(tài),查詢時(shí)需要修改查詢函數(shù),將avg聚合函數(shù)改寫為avg_final最終聚合函數(shù),聲明是對(duì)聚合結(jié)果中間狀態(tài)的最終聚合。
postgres=> select day, avg(avg) as avg_avg, avg_final(amt_avg_partial) as real_avg from mv_sales_partial group by day;
day | avg_avg | real_avg
-----------+-----------+----------
20210101 | 4.5 | 4
目前支持以下聚合函數(shù)及對(duì)應(yīng)的partial聚合函數(shù)。
普通聚合函數(shù) | Partial聚合函數(shù) | 最終聚合函數(shù) |
AVG | AVG_PARTIAL | AVG_FINAL |
RB_BUILD_CARDINALITY_AGG | RB_BUILD_AGG | RB_OR_CARDINALITY_AGG |
TTL說明
如果明細(xì)表設(shè)置了TTL,并創(chuàng)建了物化視圖,那么在TTL臨界點(diǎn)附近的數(shù)據(jù),Hologres無法保證明細(xì)表和物化視圖查詢結(jié)果的一致性,查詢TTL臨界點(diǎn)附近的物化視圖數(shù)據(jù)的結(jié)果,是個(gè)未定義行為。下面以明細(xì)表base_sales_table
和物化視圖sales_mv
為例。
為base_sales_table
設(shè)置了TTL,如果數(shù)據(jù)由于TTL到期被回收掉,那么此時(shí)查詢明細(xì)表的結(jié)果如下所示。
postgres=> SELECT
day,
hour,
avg(amount) AS amount_avg
FROM base_sales
GROUP BY day, hour;
--查詢結(jié)果
day | hour | amount_avg
-----------+---------+--------------
20210101 | 12 | 4
20210101 | 13 | 6
但是由于被回收的數(shù)據(jù),已經(jīng)物化到了物化視圖的數(shù)據(jù)中,所以查詢物化視圖時(shí)有可能得到的結(jié)果如下。
postgres=> select * from mv_sales;
--查詢結(jié)果
day | hour | amount_avg
-----------+---------+--------------
20210101 | 12 | 3
20210101 | 13 | 6
此時(shí)查詢結(jié)果不一致,建議改進(jìn)方案如下。
明細(xì)表不要設(shè)置TTL。
明細(xì)表設(shè)置TTL,但是物化視圖的GROUP BY含有數(shù)據(jù)的時(shí)間字段,且查詢物化視圖的時(shí)候,不會(huì)去查詢?cè)赥TL臨界點(diǎn)附近的數(shù)據(jù)。
明細(xì)表建成分區(qū)表,不設(shè)置TTL,回收數(shù)據(jù)通過刪除(Drop)分區(qū)表來實(shí)現(xiàn)。
實(shí)時(shí)物化視圖使用最佳實(shí)踐
建表時(shí)建議將物化視圖的GROUP BY Key設(shè)置為明細(xì)表的Distribution Key,這樣能進(jìn)一步提升數(shù)據(jù)的壓縮率,提升查詢性能。
查詢時(shí)建議將查詢物化視圖時(shí)常用的過濾條件,放在GROUP BY Key的前列(符合Clustering Key左匹配原則)。
物化視圖的智能路由
查詢時(shí)不需要顯式指定物化視圖表名稱,可以像之前一樣基于基礎(chǔ)表進(jìn)行查詢。如果有匹配的物化視圖表,優(yōu)化器會(huì)智能路由到最佳的物化視圖表來加速查詢。在查詢時(shí),物化視圖表的選擇規(guī)則如下:
選擇包含所有查詢列或可以通過間接計(jì)算得到的物化視圖表。
選擇GROUP BY列字段包含原始查詢GROUP BY所有列的物化視圖表。
當(dāng)有多個(gè)物化視圖表符合條件時(shí),選擇GROUP BY列字段少的物化視圖表。
當(dāng)前支持智能路由的聚合函數(shù)有SUM、COUNT、AVG、MIN和MAX。