全部產品
Search
文件中心

ApsaraDB for SelectDB:資料模型

更新時間:Nov 23, 2024

當您需要在不同業務情境中使用雲資料庫 SelectDB 版時,深入瞭解其資料模型對於您的業務設計具有重要協助。本文檔將指導您瞭解其資料模型,以協助您設計出更優的資料存放區方案。

基本概念

雲資料庫 SelectDB 版中,資料以表(Table)的形式進行邏輯上的描述。 一張表包括行(Row)和列(Column),Row即您資料表中的一行資料,Column用於描述一行資料中不同的欄位。

Column可以分為兩大類:Key和Value。從業務角度看,Key和Value可以分別對應維度列和指標列。在SelectDB建表語句的列中,關鍵字DUPLICATE KEYAGGREGATE KEYUNIQUE KEY指定的列即是Key列,其他列是Value列。

上述關鍵字對應SelectDB中的3種資料模型,本文將對這些資料模型進行詳細介紹:

  • Aggregate模型

  • Unique模型

  • Duplicate模型

Aggregate模型

對於寫入的資料,SelectDB會根據不同資料模型,根據建表所選不同模型的Key列中,資料相同的行(Row)進行不同的處理。對Aggregate模型而言,指定的所有Key列資料相同的行,多行資料會進行合并,Value列按照建表時欄位定義中設定的AggregationType進行預彙總,最終只保留一行資料。

這意味著Aggregate模型可以通過預彙總,極大地降低彙總查詢時所需掃描的資料量和查詢的計算量,非常適合報表類統計分析情境。該模型對count(*)查詢不友好,因為固定了Value列上的彙總方式,在進行其他類型的彙總查詢時,需要考慮語義正確性。

通過以下樣本來闡述什麼是彙總模型,以及如何正確地使用Aggregate彙總模型。

樣本1:匯入資料彙總

資料表example_tbl1的結構定義表。

ColumnName

Type

AggregationType

Comment

user_id

LARGEINT

使用者ID

date

DATE

資料寫入日期

city

VARCHAR(20)

使用者所在城市

age

SMALLINT

使用者年齡

sex

TINYINT

使用者性別

last_visit_date

DATETIME

REPLACE

使用者最後一次訪問時間

cost

BIGINT

SUM

使用者總消費

max_dwell_time

INT

MAX

使用者最大停留時間

min_dwell_time

INT

MIN

使用者最小停留時間

建立表example_tbl1(省略建表語句中的Partition和Distribution資訊),樣本如下。

CREATE TABLE IF NOT EXISTS test.example_tbl1
(
    `user_id` LARGEINT NOT NULL COMMENT "使用者id",
    `date` DATE NOT NULL COMMENT "資料寫入日期時間",
    `city` VARCHAR(20) COMMENT "使用者所在城市",
    `age` SMALLINT COMMENT "使用者年齡",
    `sex` TINYINT COMMENT "使用者性別",
    `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "使用者最後一次訪問時間",
    `cost` BIGINT SUM DEFAULT "0" COMMENT "使用者總消費",
    `max_dwell_time` INT MAX DEFAULT "0" COMMENT "使用者最大停留時間",
    `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "使用者最小停留時間"
)
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;

這是一個典型的使用者資訊和訪問行為的事實表。在一般星型模型中,使用者資訊和訪問行為一般分別存放在維度資料表和事實表中。這裡我們為了更加方便的解釋SelectDB的資料模型,將兩部分資訊統一存放在一張表中。

表中的列按照是否設定AggregationType,分為Key(維度列)和Value(指標列)。沒有設定AggregationType的,如user_iddateage 等稱為Key,而設定了AggregationType的稱為Value。當我們匯入資料時,對於指定的所有Key列值完全相同時,多行資料會進行合并,而Value列會按照設定的AggregationType進行彙總,最終只保留一行資料。

AggregationType目前有如下彙總方式。

彙總方式參數

參數說明

SUM

求和。適用數實值型別。

MIN

求最小值。適合數實值型別。

MAX

求最大值。適合數實值型別。

REPLACE

替換。對於維度列相同的行,指標列會按照匯入的先後順序,後匯入的替換先匯入的。

REPLACE_IF_NOT_NULL

非空值替換。和REPLACE的區別在於對於null值,不做替換。這裡要注意的是欄位預設值要給NULL,而不能是Null 字元串,如果是Null 字元串,會給你替換成Null 字元串。

HLL_UNION

HLL類型的列的彙總方式,通過HyperLogLog演算法彙總。

BITMAP_UNION

BITMAP類型的列的彙總方式,進行位元影像的並集彙總。

example_tbl1表中寫入如下資料。

user_id

date

city

age

sex

last_visit_date

cost

max_dwell_time

min_dwell_time

10000

2017-10-01

北京

20

0

2017-10-01 06:00:00

20

10

10

10000

2017-10-01

北京

20

0

2017-10-01 07:00:00

15

2

2

10001

2017-10-01

北京

30

1

2017-10-01 17:05:45

2

22

22

10002

2017-10-02

上海

20

1

2017-10-02 12:59:12

200

5

5

10003

2017-10-02

廣州

32

0

2017-10-02 11:20:00

30

11

11

10004

2017-10-01

深圳

35

0

2017-10-01 10:00:15

100

3

3

10004

2017-10-03

深圳

35

0

2017-10-03 10:20:22

11

6

6

通過SQL匯入資料,樣本如下。

INSERT INTO example_db.example_tbl_agg1 VALUES
(10000,"2017-10-01","北京",20,0,"2017-10-01 06:00:00",20,10,10),
(10000,"2017-10-01","北京",20,0,"2017-10-01 07:00:00",15,2,2),
(10001,"2017-10-01","北京",30,1,"2017-10-01 17:05:45",2,22,22),
(10002,"2017-10-02","上海",20,1,"2017-10-02 12:59:12",200,5,5),
(10003,"2017-10-02","廣州",32,0,"2017-10-02 11:20:00",30,11,11),
(10004,"2017-10-01","深圳",35,0,"2017-10-01 10:00:15",100,3,3),
(10004,"2017-10-03","深圳",35,0,"2017-10-03 10:20:22",11,6,6);

資料寫入到SelectDB後,SelectDB中最終儲存結果如下。

user_id

date

city

age

sex

last_visit_date

cost

max_dwell_time

min_dwell_time

10000

2017-10-01

北京

20

0

2017-10-01 07:00:00

35

10

2

10001

2017-10-01

北京

30

1

2017-10-01 17:05:45

2

22

22

10002

2017-10-02

上海

20

1

2017-10-02 12:59:12

200

5

5

10003

2017-10-02

廣州

32

0

2017-10-02 11:20:00

30

11

11

10004

2017-10-01

深圳

35

0

2017-10-01 10:00:15

100

3

3

10004

2017-10-03

深圳

35

0

2017-10-03 10:20:22

11

6

6

通過最終儲存結果分析,使用者ID為10000的使用者只剩下了一行彙總後的資料。而其餘使用者的資料和未經處理資料保持一致。使用者ID為10000的使用者資料彙總後的資料解釋如下。

前5列沒有變化,從第6列last_visit_date開始。

  • 2017-10-01 07:00:00:因為last_visit_date列的彙總方式為REPLACE,所以2017-10-01 07:00:00替換了2017-10-01 06:00:00儲存了下來。

    說明

    在同一個匯入批次中的資料,對於REPLACE這種彙總方式,替換順序不做保證。如在這個例子中,最終儲存下來的,也有可能是2017-10-01 06:00:00。而對於不同匯入批次中的資料,可以保證,後一批次的資料會替換前一批次。

  • 35:因為cost列的彙總類型為SUM,所以由20+15累加獲得35。

  • 10:因為max_dwell_time列的彙總類型為MAX,所以10和2取最大值,獲得10。

  • 2:因為min_dwell_time列的彙總類型為MIN,所以10和2取最小值,獲得2。

經過彙總,SelectDB中最終只會儲存彙總後的資料。即詳細資料將丟失,您將無法再查詢到彙總前的詳細資料。

樣本2:匯入資料與已有資料彙總

建立表example_tbl2(省略建表語句中的Partition和Distribution資訊),樣本如下。

CREATE TABLE IF NOT EXISTS test.example_tbl2
(
    `user_id` LARGEINT NOT NULL COMMENT "使用者id",
    `date` DATE NOT NULL COMMENT "資料寫入日期時間",
    `city` VARCHAR(20) COMMENT "使用者所在城市",
    `age` SMALLINT COMMENT "使用者年齡",
    `sex` TINYINT COMMENT "使用者性別",
    `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "使用者最後一次訪問時間",
    `cost` BIGINT SUM DEFAULT "0" COMMENT "使用者總消費",
    `max_dwell_time` INT MAX DEFAULT "0" COMMENT "使用者最大停留時間",
    `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "使用者最小停留時間"
)
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;

example_tbl2表中寫入如下資料。

user_id

date

city

age

sex

last_visit_date

cost

max_dwell_time

min_dwell_time

10000

2017-10-01

北京

20

0

2017-10-01 07:00:00

35

10

2

10001

2017-10-01

北京

30

1

2017-10-01 17:05:45

2

22

22

10002

2017-10-02

上海

20

1

2017-10-02 12:59:12

200

5

5

10003

2017-10-02

廣州

32

0

2017-10-02 11:20:00

30

11

11

10004

2017-10-01

深圳

35

0

2017-10-01 10:00:15

100

3

3

10004

2017-10-03

深圳

35

0

2017-10-03 10:20:22

11

6

6

通過SQL匯入資料,樣本如下。

INSERT INTO test.example_tbl2 VALUES
(10000,"2017-10-01","北京",20,0,"2017-10-01 06:00:00",20,10,10),
(10000,"2017-10-01","北京",20,0,"2017-10-01 07:00:00",15,2,2),
(10001,"2017-10-01","北京",30,1,"2017-10-01 17:05:45",2,22,22),
(10002,"2017-10-02","上海",20,1,"2017-10-02 12:59:12",200,5,5),
(10003,"2017-10-02","廣州",32,0,"2017-10-02 11:20:00",30,11,11),
(10004,"2017-10-01","深圳",35,0,"2017-10-01 10:00:15",100,3,3),
(10004,"2017-10-03","深圳",35,0,"2017-10-03 10:20:22",11,6,6);

再向表example_tbl2中寫入如下資料。

user_id

date

city

age

sex

last_visit_date

cost

max_dwell_time

min_dwell_time

10004

2017-10-03

深圳

35

0

2017-10-03 11:22:00

44

19

19

10005

2017-10-03

長沙

29

1

2017-10-03 18:11:02

3

1

1

通過SQL匯入資料,樣本如下。

INSERT INTO test.example_tbl2 VALUES
(10004,"2017-10-03","深圳",35,0,"2017-10-03 11:22:00",44,19,19),
(10005,"2017-10-03","長沙",29,1,"2017-10-03 18:11:02",3,1,1);

資料寫入到SelectDB後,SelectDB中最終儲存結果如下。

user_id

date

city

age

sex

last_visit_date

cost

max_dwell_time

min_dwell_time

10000

2017-10-01

北京

20

0

2017-10-01 07:00:00

35

10

2

10001

2017-10-01

北京

30

1

2017-10-01 17:05:45

2

22

22

10002

2017-10-02

上海

20

1

2017-10-02 12:59:12

200

5

5

10003

2017-10-02

廣州

32

0

2017-10-02 11:20:00

30

11

11

10004

2017-10-01

深圳

35

0

2017-10-01 10:00:15

100

3

3

10004

2017-10-03

深圳

35

0

2017-10-03 11:22:00

55

19

6

10005

2017-10-03

長沙

29

1

2017-10-03 18:11:02

3

1

1

通過最終儲存結果分析,使用者ID為10004的使用者的已有資料和新匯入的資料發生了彙總。同時新增了使用者ID為10005使用者資料。

資料的彙總,在SelectDB中發生在如下三個階段。

  1. 每一批次資料匯入的ETL階段。該階段會在每一批次匯入的資料內部進行彙總。

  2. 計算叢集進行資料Compaction的階段。該階段,計算叢集會對已匯入的不同批次的資料進行進一步的彙總。

  3. 資料查詢階段。在資料查詢時,對於查詢涉及到的資料,會進行對應的彙總。

資料在不同時間彙總的程度可能不一致。比如一批資料剛匯入時,可能還未與之前已存在的資料進行彙總。但是對於您而言,您只能查詢到彙總後的資料。即不同的彙總程度對於您的查詢而言是透明的。您只需要始終認為資料以最終完成的彙總程度存在,而不需要假設某些彙總還未發生。

樣本3:保留詳細資料

資料表example_tbl3的結構定義表如下。

ColumnName

Type

AggregationType

Comment

user_id

LARGEINT

使用者ID

date

DATE

資料寫入日期

timestamp

DATETIME

資料寫入時間,精確到秒

city

VARCHAR(20)

使用者所在城市

age

SMALLINT

使用者年齡

sex

TINYINT

使用者性別

last_visit_date

DATETIME

REPLACE

使用者最後一次訪問時間

cost

BIGINT

SUM

使用者總消費

max_dwell_time

INT

MAX

使用者最大停留時間

min_dwell_time

INT

MIN

使用者最小停留時間

增加了一列timestamp,記錄精確到秒的資料寫入時間。 同時,將AGGREGATE KEY設定為AGGREGATE KEY(user_id, date, timestamp, city, age, sex)

建立表example_tbl3(省略建表語句中的Partition和Distribution資訊),樣本如下。

CREATE TABLE IF NOT EXISTS test.example_tbl3
(
    `user_id` LARGEINT NOT NULL COMMENT "使用者id",
    `date` DATE NOT NULL COMMENT "資料寫入日期時間",
    `timestamp` DATETIME NOT NULL COMMENT "資料寫入時間,精確到秒",
    `city` VARCHAR(20) COMMENT "使用者所在城市",
    `age` SMALLINT COMMENT "使用者年齡",
    `sex` TINYINT COMMENT "使用者性別",
    `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "使用者最後一次訪問時間",
    `cost` BIGINT SUM DEFAULT "0" COMMENT "使用者總消費",
    `max_dwell_time` INT MAX DEFAULT "0" COMMENT "使用者最大停留時間",
    `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "使用者最小停留時間"
)
AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;

向表example_tbl3中寫入如下資料。

user_id

date

timestamp

city

age

sex

last_visit_date

cost

max_dwell_time

min_dwell_time

10000

2017-10-01

2017-10-01 08:00:05

北京

20

0

2017-10-01 06:00:00

20

10

10

10000

2017-10-01

2017-10-01 09:00:05

北京

20

0

2017-10-01 07:00:00

15

2

2

10001

2017-10-01

2017-10-01 18:12:10

北京

30

1

2017-10-01 17:05:45

2

22

22

10002

2017-10-02

2017-10-02 13:10:00

上海

20

1

2017-10-02 12:59:12

200

5

5

10003

2017-10-02

2017-10-02 13:15:00

廣州

32

0

2017-10-02 11:20:00

30

11

11

10004

2017-10-01

2017-10-01 12:12:48

深圳

35

0

2017-10-01 10:00:15

100

3

3

10004

2017-10-03

2017-10-03 12:38:20

深圳

35

0

2017-10-03 10:20:22

11

6

6

通過SQL匯入資料,樣本如下。

INSERT INTO test.example_tbl3 VALUES
(10000,"2017-10-01","2017-10-01 08:00:05","北京",20,0,"2017-10-01 06:00:00",20,10,10),
(10000,"2017-10-01","2017-10-01 09:00:05","北京",20,0,"2017-10-01 07:00:00",15,2,2),
(10001,"2017-10-01","2017-10-01 18:12:10","北京",30,1,"2017-10-01 17:05:45",2,22,22),
(10002,"2017-10-02","2017-10-02 13:10:00","上海",20,1,"2017-10-02 12:59:12",200,5,5),
(10003,"2017-10-02","2017-10-02 13:15:00","廣州",32,0,"2017-10-02 11:20:00",30,11,11),
(10004,"2017-10-01","2017-10-01 12:12:48","深圳",35,0,"2017-10-01 10:00:15",100,3,3),
(10004,"2017-10-03","2017-10-03 12:38:20","深圳",35,0,"2017-10-03 10:20:22",11,6,6);

資料寫入到SelectDB後,SelectDB中最終儲存結果如下。

user_id

date

timestamp

city

age

sex

last_visit_date

cost

max_dwell_time

min_dwell_time

10000

2017-10-01

2017-10-01 08:00:05

北京

20

0

2017-10-01 06:00:00

20

10

10

10000

2017-10-01

2017-10-01 09:00:05

北京

20

0

2017-10-01 07:00:00

15

2

2

10001

2017-10-01

2017-10-01 18:12:10

北京

30

1

2017-10-01 17:05:45

2

22

22

10002

2017-10-02

2017-10-02 13:10:00

上海

20

1

2017-10-02 12:59:12

200

5

5

10003

2017-10-02

2017-10-02 13:15:00

廣州

32

0

2017-10-02 11:20:00

30

11

11

10004

2017-10-01

2017-10-01 12:12:48

深圳

35

0

2017-10-01 10:00:15

100

3

3

10004

2017-10-03

2017-10-03 12:38:20

深圳

35

0

2017-10-03 10:20:22

11

6

6

儲存的資料和匯入的資料完全一樣,沒有發生任何彙總。因為這批資料中加入了timestamp列,導致每一行的Key都不完全相同。只要確保匯入的資料中,每一行的Key都不完全相同,即使在彙總模型下,也可以儲存完整的詳細資料。

Unique模型

在某些多維分析情境下,更關注的是如何保證Key的唯一性,即如何獲得主鍵唯一性限制式。因此,雲資料庫 SelectDB 版引入了Unique表引擎。在早期版本中,該模型本質上是彙總模型的一個特例,也是一種簡化的表結構表示方式,由於彙總模型的實現方式是讀時合并MOR(Merge on Read),因此在一些彙總查詢上效能不佳。在3.0版本SelectDB引入了Unique模型新的實現方式,寫時合并MOW(Merge on Write),通過在寫入時做一些額外的工作,實現最優的查詢效能。

對Unique模型而言,指定的所有Key列資料相同時多行資料會進行覆蓋,僅保留最新匯入的行,提供類似關係型資料庫中的唯一性限制式。

Unique模型針對需要唯一性限制式的情境,提供了主鍵唯一性限制式,可用於滿足訂單等關係型資料分析情境。對於彙總查詢有較高效能需求的情境,推薦使用新版本引入的寫時合并實現,但是該表引擎無法利用 ROLLUP等預彙總帶來的查詢優勢。

說明

在Unique資料模型中,推薦使用寫時合并的實現方式

我們將通過以下樣本來闡述兩種不同的實現方式

寫時合并(MOW)

Unqiue表引擎的寫時合并實現,與彙總表引擎就是完全不同的兩種模型,查詢效能更接近於上文的Duplicate模型,在有主鍵約束需求的情境上相比彙總模型有較大的查詢效能優勢,尤其是在彙總查詢以及需要用索引過濾大量資料的查詢中。

寫時合并在3.0版本中,作為一個新的特性,預設關閉,您可以通過如下屬性property來開啟。

"enable_unique_key_merge_on_write" = "true"

資料表example_tbl6的結構定義表。

ColumnName

Type

AggregationType

Comment

user_id

BIGINT

使用者ID

username

VARCHAR(50)

使用者暱稱

city

VARCHAR(20)

NONE

使用者所在城市

age

SMALLINT

NONE

使用者年齡

sex

TINYINT

NONE

使用者性別

phone

LARGEINT

NONE

使用者電話

address

VARCHAR(500)

NONE

使用者住址

register_time

DATETIME

NONE

使用者註冊時間

建立表example_tbl6(省略建表語句中的Partition和Distribution資訊),修改表property屬性。樣本如下。

CREATE TABLE IF NOT EXISTS test.example_tbl6
(
    `user_id` LARGEINT NOT NULL COMMENT "使用者id",
    `username` VARCHAR(50) NOT NULL COMMENT "使用者暱稱",
    `city` VARCHAR(20) COMMENT "使用者所在城市",
    `age` SMALLINT COMMENT "使用者年齡",
    `sex` TINYINT COMMENT "使用者性別",
    `phone` LARGEINT COMMENT "使用者電話",
    `address` VARCHAR(500) COMMENT "使用者地址",
    `register_time` DATETIME COMMENT "使用者註冊時間"
)
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16
PROPERTIES (
    "enable_unique_key_merge_on_write" = "true"
);

這種建表語句所建立的表結構與彙總表引擎完全不同。

在開啟了寫時合并選項的Unique表上,資料在匯入階段就會去將被覆蓋和被更新的資料進行標記刪除,同時將新的資料寫入新的檔案。在查詢的時候,所有被標記刪除的資料都會在檔案層級被過濾掉,讀取出來的資料就都是最新的資料。這消除掉了讀時合并中的資料彙總過程,並且能夠在很多情況下支援多種謂詞的下推。因此在許多情境都能帶來比較大的效能提升,尤其是在有彙總查詢的情況下。

說明
  • 新的Merge-on-Write實現預設關閉,且只能在建表時通過指定屬性property的方式開啟。

  • 舊的Merge-on-Read的實現無法無縫升級到新版本的實現(資料群組織方式完全不同),如果需要改為寫時合并的實現,需要手動執行INSERT INTO unique-mow-table SELECT * FROM source_table

  • 在Unique引擎上專屬的delete sign和sequence col,在寫時合并的新版實現中仍可以正常使用,用法沒有變化。

讀時合并(MOR)

資料表example_tbl4的結構定義表。

ColumnName

Type

IsKey

Comment

user_id

BIGINT

Yes

使用者ID

username

VARCHAR(50)

Yes

使用者暱稱

city

VARCHAR(20)

No

使用者所在城市

age

SMALLINT

No

使用者年齡

sex

TINYINT

No

使用者性別

phone

LARGEINT

No

使用者電話

address

VARCHAR(500)

No

使用者住址

register_time

DATETIME

No

使用者註冊時間

這是一個典型的使用者基礎資訊表。這類資料沒有彙總需求,只需保證主鍵唯一性。(這裡的主鍵為user_id+username)。

建立表example_tbl4(省略建表語句中的Partition和Distribution資訊),樣本如下。

CREATE TABLE IF NOT EXISTS test.example_tbl4
(
    `user_id` LARGEINT NOT NULL COMMENT "使用者id",
    `username` VARCHAR(50) NOT NULL COMMENT "使用者暱稱",
    `city` VARCHAR(20) COMMENT "使用者所在城市",
    `age` SMALLINT COMMENT "使用者年齡",
    `sex` TINYINT COMMENT "使用者性別",
    `phone` LARGEINT COMMENT "使用者電話",
    `address` VARCHAR(500) COMMENT "使用者地址",
    `register_time` DATETIME COMMENT "使用者註冊時間"
)
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;

資料表example_tbl5的結構定義表。

ColumnName

Type

AggregationType

Comment

user_id

BIGINT

使用者ID

username

VARCHAR(50)

使用者暱稱

city

VARCHAR(20)

REPLACE

使用者所在城市

age

SMALLINT

REPLACE

使用者年齡

sex

TINYINT

REPLACE

使用者性別

phone

LARGEINT

REPLACE

使用者電話

address

VARCHAR(500)

REPLACE

使用者住址

register_time

DATETIME

REPLACE

使用者註冊時間

建立表example_tbl5(省略建表語句中的Partition和Distribution資訊),樣本如下。

CREATE TABLE IF NOT EXISTS test.example_tbl5
(
    `user_id` LARGEINT NOT NULL COMMENT "使用者id",
    `username` VARCHAR(50) NOT NULL COMMENT "使用者暱稱",
    `city` VARCHAR(20) REPLACE COMMENT "使用者所在城市",
    `age` SMALLINT REPLACE COMMENT "使用者年齡",
    `sex` TINYINT REPLACE COMMENT "使用者性別",
    `phone` LARGEINT REPLACE COMMENT "使用者電話",
    `address` VARCHAR(500) REPLACE COMMENT "使用者地址",
    `register_time` DATETIME REPLACE COMMENT "使用者註冊時間"
)
AGGREGATE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;

上述表example_tbl4的表結構,完全等同於使用彙總表引擎example_tbl5的表結構。

即Unique引擎的讀時合并實現完全可以用彙總模型中的REPLACE方式替代。其內部的實現方式和資料存放區方式也完全一樣。

Duplicate模型

在某些多維分析情境下,資料既沒有主鍵,也沒有彙總需求。因此,引入Duplicate資料模型來滿足這類需求。

Duplicate模型而言,指定的所有Key列資料相同時多行資料同時儲存在系統中,互不影響,沒有預彙總、唯一性限制式。

Duplicate模型適合任意維度Ad-hoc查詢,可用於滿足日誌等詳細資料分析情境。該表引擎無法利用預彙總帶來的效能提升、唯一性限制式帶來的自動更新便利性。

我們將通過以下樣本來闡述Duplicate模型的實現方式

資料表example_tbl7的結構定義表。

ColumnName

Type

SortKey

Comment

timestamp

DATETIME

Yes

日誌時間

type

INT

Yes

日誌類型

error_code

INT

Yes

錯誤碼

error_msg

VARCHAR(1024)

No

錯誤詳細資料

op_id

BIGINT

No

負責人ID

op_time

DATETIME

No

處理時間

建立表example_tbl7(省略建表語句中的Partition和Distribution資訊)。樣本如下。

CREATE TABLE IF NOT EXISTS test.example_tbl7
(
    `timestamp` DATETIME NOT NULL COMMENT "日誌時間",
    `type` INT NOT NULL COMMENT "日誌類型",
    `error_code` INT COMMENT "錯誤碼",
    `error_msg` VARCHAR(1024) COMMENT "錯誤詳細資料",
    `op_id` BIGINT COMMENT "負責人id",
    `op_time` DATETIME COMMENT "處理時間"
)
DUPLICATE KEY(`timestamp`, `type`, `error_code`)
DISTRIBUTED BY HASH(`type`) BUCKETS 16;

這種表模型區別於Aggregate和Unique模型,資料完全按照匯入檔案中的資料進行儲存,不會有任何彙總。即使兩行資料完全相同,也都會保留。 而在建表語句中指定的DUPLICATE KEY,只是用來指明底層資料按照那些列進行排序。在DUPLICATE KEY的選擇上,建議適當的選擇前2-4列即可。

資料模型比較

Aggregate表引擎的局限性

在彙總表引擎中,模型對外展現的是最終彙總後的資料。也就是說,任何還未彙總的資料(比如說兩個不同匯入批次的資料),必須通過某種方式保證對外展示的一致性。通過以下樣本來進行說明。

example_tbl8結構如下。

ColumnName

Type

AggregationType

Comment

user_id

LARGEINT

使用者ID

date

DATE

資料寫入日期

cost

BIGINT

SUM

使用者總消費

向表example_tbl8中分兩個批次寫入如下資料。

  • 第一次大量匯入的資料如下。

user_id

date

cost

10001

2017-11-20

50

10002

2017-11-21

39

  • 第二次大量匯入的資料如下。

user_id

date

cost

10001

2017-11-20

1

10001

2017-11-21

5

10003

2017-11-22

22

可以看到,使用者ID為10001的資料在兩個批次中匯入,在SelectDB內部的合并任務未執行完畢前,底層儲存會包含上述5條未經處理資料。但是為了保證使用者只能查詢到如下最終彙總後的資料,我們會在查詢引擎中自動加入了彙總運算元,來保證資料對外的一致性。使用者查詢結果如下。

user_id

date

cost

10001

2017-11-20

51

10001

2017-11-21

5

10002

2017-11-21

39

10003

2017-11-22

22

例如,執行如下SQL,得到的結果是5,而不是1。

SELECT MIN(cost) FROM example_tbl8;

同時,這種一致性保證,在某些查詢中,會極大的降低查詢效率。我們以最基本的count(*)查詢為例。

SELECT COUNT(*) FROM example_tbl8;

在其他資料庫中,這類查詢都會很快的返回結果。因為,可以通過如“匯入時對行進行計數,儲存count的統計資訊”,或者在查詢時“僅掃描某一列資料,獲得count值”的方式,只需很小的開銷,即可獲得查詢結果。但是在彙總表引擎中,這種查詢的開銷非常大。

以上述的資料為例。

執行SQLselect count(*) from example_tbl8;的結果應該為4。但如果只掃描user_id這一列,且加上查詢時彙總,最終得到的結果是3(10001, 10002, 10003)。而不加查詢時彙總,則得到的結果是5(兩批次一共5行資料)。可見這兩個結果都是不對的。

因為當彙總列非常多時,count()查詢需要掃描大量的資料。為了得到正確的結果,我們必須同時讀取user_iddate這兩列的資料,再加上查詢時彙總,才能返回4這個正確的結果。即在count()查詢中,必須掃描所有的AGGREGATE KEY列(這裡就是user_iddate),並且彙總後,才能得到正確的結果

因此,當業務上有頻繁的count(*)查詢時,我們建議通過增加一個值恒為1、彙總類型為SUM的列來類比count(*)。例如在example_tbl8的表結構中添加列count,修改後結構定義表如下。

ColumnName

Type

AggregateType

Comment

user_id

BIGINT

使用者ID

date

DATE

資料寫入日期

cost

BIGINT

SUM

使用者總消費

count

BIGINT

SUM

用於計算count

增加一個count列,並且匯入資料中,該列值恒為1。則select count(*) from table;的結果等價於select sum(count) from table;。而後者的查詢效率將遠高於前者。不過這種方式也有使用限制,就是需要您自行保證,不會重複匯入AGGREGATE KEY列都相同的行。否則,select sum(count) from table;只能表述原始匯入的行數,而不是select count(*) from table;的語義。

另一種方式,就是將如上的count列的彙總類型改為REPLACE,且依然值恒為1。那麼select sum(count) from table;select count(*) from table;的結果將是一致的。並且這種方式,沒有匯入重複行的限制。

Unique表引擎的寫時合并實現

Unique引擎的寫時合并實現沒有彙總引擎的局限性,還是以上方的資料為例,寫時合并為每次匯入的rowset增加了對應的delete bitmap,來標記哪些資料被覆蓋。第一批資料匯入後狀態如下。

  • 第一次大量匯入的結果資料如下。

user_id

date

cost

delete bit

10001

2017-11-20

50

false

10002

2017-11-21

39

false

當第二批資料匯入完成後,第一批次匯入的資料中重複的行就會被標記為已刪除,此時兩批資料狀態如下。

  • 第一批次匯入的資料被標記後結果如下。

user_id

date

cost

delete bit

10001

2017-11-20

50

true

10002

2017-11-21

39

false

  • 第二批次匯入的資料結果如下。

user_id

date

cost

delete bit

10001

2017-11-20

1

false

10001

2017-11-21

5

false

10003

2017-11-22

22

false

在查詢時,所有在delete bitmap中被標記刪除的資料都不會讀出來,因此也無需進行做任何資料彙總,上述資料中有效行數為4行,查詢出的結果也應該是4行,也就可以採取開銷最小的方式來擷取結果,即前面提到的“僅掃描某一列資料,獲得count值”的方式。

在測試環境中,count(*)查詢在Unique引擎的寫時合并實現上的效能,相比彙總模型有10倍以上的提升。

Duplicate表引擎的局限性

Duplicate 模型沒有彙總引擎的這個局限性。因為該引擎不涉及彙總語意,在做count(*)查詢時,任意選擇一列查詢,即可得到語意正確的結果。

Key列

Duplicate、Aggregate、Unique模型在使用過程中,都會在建表時指定Key列,然而實際上有所區別。

對於Duplicate模型,表的Key列,可以認為只是 “排序列”,並非起到唯一標識的作用。

對於Aggregate、Unique模型,在這種彙總類型的表裡,Key列兼顧 “排序列” 和 “唯一識別欄位”,是真正意義上的“Key列”。

資料模型的選擇建議

因為資料模型在建表時就已經確定,且無法修改。所以,選擇一個合適的資料模型非常重要。

  1. Aggregate模型可以通過預彙總,極大地降低彙總查詢時所需掃描的資料量和查詢的計算量,從而大幅提升查詢效能。適合有固定查詢模式或需要彙總分析的報表類情境。該模型對count(*)查詢不友好;同時因為固定了Value列的彙總方式,在進行其他類型的彙總查詢時,需要考慮語義正確性。

  2. Unique模型針對需要唯一性限制式的情境,可以保證主鍵唯一性。適合訂單、交易等關係型資料的分析情境。該模型無法利用ROLLUP等預彙總帶來的查詢優勢。當您對於彙總查詢有較高效能需求,推薦使用自1.2版本加入的寫時合并實現。

  3. Duplicate模型雖然同樣無法利用預彙總的特性,但是不受彙總模型的約束,可以充分發揮列存的優勢(唯讀取相關列,而不需要讀取所有Key列)。適合針對日誌資料、詳細資料等進行任意維度Ad-hoc查詢。

  4. 如果有部分列更新的需求,請查閱文檔部分列更新擷取相關使用建議。