网络
虚拟私有云 VPC
弹性公网IP EIP
弹性负载均衡 ELB
NAT网关 NAT
云专线 DC
虚拟专用网络 VPN
云连接 CC
VPC终端节点 VPCEP
企业路由器 ER
企业交换机 ESW
全球加速 GA
企业连接 EC
云原生应用网络 ANC
安全与合规
安全技术与应用
Web应用防火墙 WAF
企业主机安全 HSS
云防火墙 CFW
安全云脑 SecMaster
DDoS防护 AAD
数据加密服务 DEW
数据库安全服务 DBSS
云堡垒机 CBH
数据安全中心 DSC
云证书管理服务 CCM
威胁检测服务 MTD
态势感知 SA
认证测试中心 CTC
边缘安全 EdgeSec
应用中间件
微服务引擎 CSE
分布式消息服务Kafka版
分布式消息服务RabbitMQ版
分布式消息服务RocketMQ版
API网关 APIG
分布式缓存服务 DCS
多活高可用服务 MAS
事件网格 EG
管理与监管
统一身份认证服务 IAM
消息通知服务 SMN
云监控服务 CES
应用运维管理 AOM
应用性能管理 APM
云日志服务 LTS
云审计服务 CTS
标签管理服务 TMS
配置审计 Config
应用身份管理服务 OneAccess
资源访问管理 RAM
组织 Organizations
资源编排服务 RFS
优化顾问 OA
IAM 身份中心
云运维中心 COC
资源治理中心 RGC
解决方案
高性能计算 HPC
SAP
混合云灾备
开天工业工作台 MIW
Haydn解决方案工厂
数字化诊断治理专家服务
云生态
云商店
合作伙伴中心
华为云开发者学堂
华为云慧通差旅
开发与运维
软件开发生产线 CodeArts
需求管理 CodeArts Req
流水线 CodeArts Pipeline
代码检查 CodeArts Check
编译构建 CodeArts Build
部署 CodeArts Deploy
测试计划 CodeArts TestPlan
制品仓库 CodeArts Artifact
移动应用测试 MobileAPPTest
CodeArts IDE Online
开源镜像站 Mirrors
性能测试 CodeArts PerfTest
应用管理与运维平台 ServiceStage
云应用引擎 CAE
开源治理服务 CodeArts Governance
华为云Astro轻应用
CodeArts IDE
Astro工作流 AstroFlow
代码托管 CodeArts Repo
漏洞管理服务 CodeArts Inspector
联接 CodeArtsLink
软件建模 CodeArts Modeling
Astro企业应用 AstroPro
CodeArts 盘古助手
华为云Astro大屏应用
计算
弹性云服务器 ECS
Flexus云服务
裸金属服务器 BMS
云手机服务器 CPH
专属主机 DeH
弹性伸缩 AS
镜像服务 IMS
函数工作流 FunctionGraph
云耀云服务器(旧版)
VR云渲游平台 CVR
Huawei Cloud EulerOS
网络
虚拟私有云 VPC
弹性公网IP EIP
弹性负载均衡 ELB
NAT网关 NAT
云专线 DC
虚拟专用网络 VPN
云连接 CC
VPC终端节点 VPCEP
企业路由器 ER
企业交换机 ESW
全球加速 GA
企业连接 EC
云原生应用网络 ANC
CDN与智能边缘
内容分发网络 CDN
智能边缘云 IEC
智能边缘平台 IEF
CloudPond云服务
安全与合规
安全技术与应用
Web应用防火墙 WAF
企业主机安全 HSS
云防火墙 CFW
安全云脑 SecMaster
DDoS防护 AAD
数据加密服务 DEW
数据库安全服务 DBSS
云堡垒机 CBH
数据安全中心 DSC
云证书管理服务 CCM
威胁检测服务 MTD
态势感知 SA
认证测试中心 CTC
边缘安全 EdgeSec
大数据
MapReduce服务 MRS
数据湖探索 DLI
表格存储服务 CloudTable
可信智能计算服务 TICS
推荐系统 RES
云搜索服务 CSS
数据可视化 DLV
数据接入服务 DIS
数据仓库服务 GaussDB(DWS)
数据治理中心 DataArts Studio
湖仓构建 LakeFormation
智能数据洞察 DataArts Insight
应用中间件
微服务引擎 CSE
分布式消息服务Kafka版
分布式消息服务RabbitMQ版
分布式消息服务RocketMQ版
API网关 APIG
分布式缓存服务 DCS
多活高可用服务 MAS
事件网格 EG
开天aPaaS
应用平台 AppStage
开天企业工作台 MSSE
开天集成工作台 MSSI
API中心 API Hub
云消息服务 KooMessage
交换数据空间 EDS
云地图服务 KooMap
云手机服务 KooPhone
组织成员账号 OrgID
云空间服务 KooDrive
管理与监管
统一身份认证服务 IAM
消息通知服务 SMN
云监控服务 CES
应用运维管理 AOM
应用性能管理 APM
云日志服务 LTS
云审计服务 CTS
标签管理服务 TMS
配置审计 Config
应用身份管理服务 OneAccess
资源访问管理 RAM
组织 Organizations
资源编排服务 RFS
优化顾问 OA
IAM 身份中心
云运维中心 COC
资源治理中心 RGC
区块链
区块链服务 BCS
数字资产链 DAC
华为云区块链引擎服务 HBS
解决方案
高性能计算 HPC
SAP
混合云灾备
开天工业工作台 MIW
Haydn解决方案工厂
数字化诊断治理专家服务
价格
成本优化最佳实践
专属云商业逻辑
云生态
云商店
合作伙伴中心
华为云开发者学堂
华为云慧通差旅
其他
管理控制台
消息中心
产品价格详情
系统权限
客户关联华为云合作伙伴须知
公共问题
宽限期保留期
奖励推广计划
活动
云服务信任体系能力说明
开发与运维
软件开发生产线 CodeArts
需求管理 CodeArts Req
流水线 CodeArts Pipeline
代码检查 CodeArts Check
编译构建 CodeArts Build
部署 CodeArts Deploy
测试计划 CodeArts TestPlan
制品仓库 CodeArts Artifact
移动应用测试 MobileAPPTest
CodeArts IDE Online
开源镜像站 Mirrors
性能测试 CodeArts PerfTest
应用管理与运维平台 ServiceStage
云应用引擎 CAE
开源治理服务 CodeArts Governance
华为云Astro轻应用
CodeArts IDE
Astro工作流 AstroFlow
代码托管 CodeArts Repo
漏洞管理服务 CodeArts Inspector
联接 CodeArtsLink
软件建模 CodeArts Modeling
Astro企业应用 AstroPro
CodeArts 盘古助手
华为云Astro大屏应用
存储
对象存储服务 OBS
云硬盘 EVS
云备份 CBR
高性能弹性文件服务 SFS Turbo
弹性文件服务 SFS
存储容灾服务 SDRS
云硬盘备份 VBS
云服务器备份 CSBS
数据快递服务 DES
云存储网关 CSG
专属分布式存储服务 DSS
数据工坊 DWR
地图数据 MapDS
键值存储服务 KVS
容器
云容器引擎 CCE
云容器实例 CCI
容器镜像服务 SWR
云原生服务中心 OSC
应用服务网格 ASM
华为云UCS
数据库
云数据库 RDS
数据复制服务 DRS
文档数据库服务 DDS
分布式数据库中间件 DDM
云数据库 GaussDB
云数据库 GeminiDB
数据管理服务 DAS
数据库和应用迁移 UGO
云数据库 TaurusDB
人工智能
AI开发平台ModelArts
华为HiLens
图引擎服务 GES
图像识别 Image
文字识别 OCR
自然语言处理 NLP
内容审核 Moderation
图像搜索 ImageSearch
医疗智能体 EIHealth
企业级AI应用开发专业套件 ModelArts Pro
人脸识别服务 FRS
对话机器人服务 CBS
语音交互服务 SIS
人证核身服务 IVS
视频智能分析服务 VIAS
城市智能体
自动驾驶云服务 Octopus
盘古大模型 PanguLargeModels
IoT物联网
设备接入 IoTDA
全球SIM联接 GSL
IoT数据分析 IoTA
路网数字化服务 DRIS
IoT边缘 IoTEdge
设备发放 IoTDP
企业应用
域名注册服务 Domains
云解析服务 DNS
企业门户 EWP
ICP备案
商标注册
华为云WeLink
华为云会议 Meeting
隐私保护通话 PrivateNumber
语音通话 VoiceCall
消息&短信 MSGSMS
云管理网络
SD-WAN 云服务
边缘数据中心管理 EDCM
云桌面 Workspace
应用与数据集成平台 ROMA Connect
ROMA资产中心 ROMA Exchange
API全生命周期管理 ROMA API
政企自服务管理 ESM
视频
实时音视频 SparkRTC
视频直播 Live
视频点播 VOD
媒体处理 MPC
视频接入服务 VIS
数字内容生产线 MetaStudio
迁移
主机迁移服务 SMS
对象存储迁移服务 OMS
云数据迁移 CDM
迁移中心 MGC
专属云
专属计算集群 DCC
开发者工具
SDK开发指南
API签名指南
DevStar
华为云命令行工具服务 KooCLI
Huawei Cloud Toolkit
CodeArts API
云化转型
云架构中心
云采用框架
用户服务
账号中心
费用中心
成本中心
资源中心
企业管理
工单管理
客户运营能力
国际站常见问题
支持计划
专业服务
合作伙伴支持计划
我的凭证
华为云公共事业服务云平台
工业软件
工业数字模型驱动引擎
硬件开发工具链平台云服务
工业数据转换引擎云服务
更新时间:2025-01-09 GMT+08:00
分享

数据模型

Doris的数据模型主要分为3类:

Aggregate模型

以实际的例子来说明什么是聚合模型,以及如何正确的使用聚合模型。

  • 示例1:导入数据聚合

    假设业务有以下模式:

    表1 参数说明

    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

    用户最小停留时间

    转换成建表语句,如下所示。

    CREATE TABLE IF NOT EXISTS demo.example_tbl
    (
        `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 1
    PROPERTIES (
        "replication_allocation" = "tag.location.default: 1"
    );

    可以看到,这是一个典型的用户信息和访问行为的事实表。在一般星型模型中,用户信息和访问行为一般分别存放在维度表和事实表中。这里我们为了更加方便的解释Doris的数据模型,将两部分信息统一存放在一张表中。

    表中的列按照是否设置了AggregationType,分为Key(维度列)和Value(指标列)。没有设置AggregationType的,如user_id、date、age、sex称为Key,而设置了AggregationType的称为Value。

    当导入数据时,对于Key列相同的行会聚合成一行,而Value列会按照设置的AggregationType进行聚合。AggregationType目前有以下四种聚合方式:
    • SUM:求和,多行的Value进行累加。
    • REPLACE:替代,下一批数据中的Value会替换之前导入过的行中的Value。
    • MAX:保留最大值。
    • MIN:保留最小值。
      表2 原始数据

      user_id

      date

      city

      age

      sex

      last_visit_date

      cost

      max_dwell_time

      min_dwell_time

      10000

      2017-10-01

      A

      20

      0

      2017-10-01 06:00:00

      20

      10

      10

      10000

      2017-10-01

      A

      20

      0

      2017-10-01 07:00:00

      15

      2

      2

      10001

      2017-10-01

      A

      30

      1

      2017-10-01 17:05:45

      2

      22

      22

      10002

      2017-10-02

      B

      20

      1

      2017-10-02 12:59:12

      200

      5

      5

      10003

      2017-10-02

      C

      32

      0

      2017-10-02 11:20:00

      30

      11

      11

      10004

      2017-10-01

      D

      35

      0

      2017-10-01 10:00:15

      100

      3

      3

      10004

      2017-10-03

      D

      35

      0

      2017-10-03 10:20:22

      11

      6

      6

    我们假设这是一张记录用户访问某商品页面行为的表。我们以第一行数据为例,解释如下:

    表3 参数说明

    数据

    说明

    10000

    用户id,每个用户唯一识别id

    2017-10-01

    数据入库时间,精确到日期

    A

    用户所在城市

    20

    用户年龄

    0

    性别男(1 代表女性)

    2017-10-01 06:00:00

    用户本次访问该页面的时间,精确到秒

    20

    用户本次访问产生的消费

    10

    用户本次访问,驻留该页面的时间

    10

    用户本次访问,驻留该页面的时间(冗余)

    那么当这批数据正确导入到Doris中后,Doris中最终存储如下:

    表4 插入数据

    user_id

    date

    city

    age

    sex

    last_visit_date

    cost

    max_dwell_time

    min_dwell_time

    10000

    2017-10-01

    A

    20

    0

    2017-10-01 07:00:00

    35

    10

    2

    10001

    2017-10-01

    A

    30

    1

    2017-10-01 17:05:45

    2

    22

    22

    10002

    2017-10-02

    B

    20

    1

    2017-10-02 12:59:12

    200

    5

    5

    10003

    2017-10-02

    C

    32

    0

    2017-10-02 11:20:00

    30

    11

    11

    10004

    2017-10-01

    D

    35

    0

    2017-10-01 10:00:15

    100

    3

    3

    10004

    2017-10-03

    D

    35

    0

    2017-10-03 10:20:22

    11

    6

    6

    可以看到,用户10000只剩下了一行聚合后的数据。而其余用户的数据和原始数据保持一致。这里先解释下用户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。

      经过聚合,Doris中最终只会存储聚合后的数据。换句话说,即明细数据会丢失,用户不能够再查询到聚合前的明细数据了。

  • 示例2:保留,明细数据。

    接示例1,将表结构修改如下:

    表5 参数说明

    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)。

    导入数据如下:

    表6 原始数据

    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

    A

    20

    0

    2017-10-01 06:00:00

    20

    10

    10

    10000

    2017-10-01

    2017-10-01 09:00:05

    A

    20

    0

    2017-10-01 07:00:00

    15

    2

    2

    10001

    2017-10-01

    2017-10-01 18:12:10

    A

    30

    1

    2017-10-01 17:05:45

    2

    22

    22

    10002

    2017-10-02

    2017-10-02 13:10:00

    B

    20

    1

    2017-10-02 12:59:12

    200

    5

    5

    10003

    2017-10-02

    2017-10-02 13:15:00

    C

    32

    0

    2017-10-02 11:20:00

    30

    11

    11

    10004

    2017-10-01

    2017-10-01 12:12:48

    D

    35

    0

    2017-10-01 10:00:15

    100

    3

    3

    10004

    2017-10-03

    2017-10-03 12:38:20

    D

    35

    0

    2017-10-03 10:20:22

    11

    6

    6

    那么当这批数据正确导入到Doris中后,Doris中最终存储如下:

    表7 数据结果

    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

    A

    20

    0

    2017-10-01 06:00:00

    20

    10

    10

    10000

    2017-10-01

    2017-10-01 09:00:05

    A

    20

    0

    2017-10-01 07:00:00

    15

    2

    2

    10001

    2017-10-01

    2017-10-01 18:12:10

    A

    30

    1

    2017-10-01 17:05:45

    2

    22

    22

    10002

    2017-10-02

    2017-10-02 13:10:00

    B

    20

    1

    2017-10-02 12:59:12

    200

    5

    5

    10003

    2017-10-02

    2017-10-02 13:15:00

    C

    32

    0

    2017-10-02 11:20:00

    30

    11

    11

    10004

    2017-10-01

    2017-10-01 12:12:48

    D

    35

    0

    2017-10-01 10:00:15

    100

    3

    3

    10004

    2017-10-03

    2017-10-03 12:38:20

    D

    35

    0

    2017-10-03 10:20:22

    11

    6

    6

  • 示例3:导入数据与已有数据聚合。

    接示例1中的参数列,插入以下表中数据。

    表8 原始数据

    user_id

    date

    city

    age

    sex

    last_visit_date

    cost

    max_dwell_time

    min_dwell_time

    10000

    2017-10-01

    A

    20

    0

    2017-10-01 07:00:00

    35

    10

    2

    10001

    2017-10-01

    A

    30

    1

    2017-10-01 17:05:45

    2

    22

    22

    10002

    2017-10-02

    B

    20

    1

    2017-10-02 12:59:12

    200

    5

    5

    10003

    2017-10-02

    C

    32

    0

    2017-10-02 11:20:00

    30

    11

    11

    10004

    2017-10-01

    D

    35

    0

    2017-10-01 10:00:15

    100

    3

    3

    10004

    2017-10-03

    D

    35

    0

    2017-10-03 10:20:22

    11

    6

    6

    在导入一批新的数据:

    表9 新数据

    user_id

    date

    city

    age

    sex

    last_visit_date

    cost

    max_dwell_time

    min_dwell_time

    10004

    2017-10-03

    D

    35

    0

    2017-10-03 11:22:00

    44

    19

    19

    10005

    2017-10-03

    E

    29

    1

    2017-10-03 18:11:02

    3

    1

    1

    那么当这批数据正确导入到Doris中后,Doris中最终存储如下:

    表10

    user_id

    date

    city

    age

    sex

    last_visit_date

    cost

    max_dwell_time

    min_dwell_time

    10000

    2017-10-01

    A

    20

    0

    2017-10-01 07:00:00

    35

    10

    2

    10001

    2017-10-01

    A

    30

    1

    2017-10-01 17:05:45

    2

    22

    22

    10002

    2017-10-02

    B

    20

    1

    2017-10-02 12:59:12

    200

    5

    5

    10003

    2017-10-02

    C

    32

    0

    2017-10-02 11:20:00

    30

    11

    11

    10004

    2017-10-01

    D

    35

    0

    2017-10-01 10:00:15

    100

    3

    3

    10004

    2017-10-03

    D

    35

    0

    2017-10-03 11:22:00

    55

    19

    6

    10005

    2017-10-03

    E

    29

    1

    2017-10-03 18:11:02

    3

    1

    1

    可以看到,用户10004的已有数据和新导入的数据发生了聚合。同时新增了10005用户的数据。

    数据的聚合,在Doris中有如下三个阶段发生:

    • 每一批次数据导入的ETL阶段。该阶段会在每一批次导入的数据内部进行聚合。
    • 底层BE进行数据Compaction的阶段。该阶段,BE会对已导入的不同批次的数据进行进一步的聚合。
    • 数据查询阶段。在数据查询时,对于查询涉及到的数据,会进行对应的聚合。

    数据在不同时间,可能聚合的程度不一致。例如一批数据刚导入时,可能还未与之前已存在的数据进行聚合。但是对于用户而言,用户只能查询到聚合后的数据。即不同的聚合程度对于用户查询而言是透明的。用户需始终认为数据以最终的完成的聚合程度存在,而不应假设某些聚合还未发生。

Unique模型

在某些多维分析场景下,用户更关注的是如何保证Key的唯一性,即如何获得Primary Key唯一性约束。因此,我们引入了Unique的数据模型。该模型本质上是聚合模型的一个特例,也是一种简化的表结构表示方式。举例说明:

Unique模型表,不推荐开启merge-on-write属性,默认使用merge-on-read。

表11 参数说明

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)。那么我们的建表语句如下:

CREATE TABLE IF NOT EXISTS example_db.expamle_tbl
(
    `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 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

而这个表结构,完全同等于以下使用聚合模型描述的表结构:

表12 参数说明

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

用户注册时间

建表语句。

CREATE TABLE IF NOT EXISTS example_db.expamle_tbl
(
    `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 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

即Unique模型完全可以用聚合模型中的REPLACE方式替代。其内部的实现方式和数据存储方式也完全一样。

Duplicate模型

在某些多维分析场景下,数据既没有主键,也没有聚合需求。因此,我们引入Duplicate数据模型来满足这类需求。
表13 数据

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

处理时间

建表语句。

CREATE TABLE IF NOT EXISTS example_db.expamle_tbl
(
    `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`)
DISTRIBUTED BY HASH(`type`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

这种数据模型区别于Aggregate和Unique模型。数据完全按照导入文件中的数据进行存储,不会有任何聚合。即使两行数据完全相同,也都会保留。 而在建表语句中指定的DUPLICATE KEY,只是用来指明底层数据按照哪些列进行排序。(更贴切的名称应该为“Sorted Column”,这里取名“DUPLICATE KEY”只是用以明确表示所用的数据模型。在DUPLICATE KEY的选择上,我们建议适当地选择前2-4列就可以。

这种数据模型适用于既没有聚合需求,又没有主键唯一性约束的原始数据的存储。更多使用场景,可参见聚合模型的局限性。

聚合模型的局限

  • Aggregate模型&Unique模型。

    这里我们针对Aggregate模型(包括Unique模型),来介绍下聚合模型的局限性。

    在聚合模型中,模型对外展现的,是最终聚合后的数据。也就是说,对于任何还未聚合的数据(例如说两个不同导入批次的数据),必须通过某种方式保证对外展示的一致性。

    假设表结构如下:

    表14 数据

    ColumnName

    Type

    AggregationType

    Comment

    user_id

    LARGEINT

    -

    用户ID

    date

    DATE

    -

    数据导入日期

    cost

    BIGINT

    SUM

    用户总消费

    假设存储引擎中有如下两个已经导入完成的批次的数据。

    batch1

    表15 数据

    user_id

    date

    cost

    10001

    2017-11-20

    50

    10002

    2017-11-21

    39

    batch2

    表16 数据

    user_id

    date

    cost

    10001

    2017-11-20

    1

    10001

    2017-11-21

    5

    10003

    2017-11-22

    22

    可以看到,用户10001分属在两个导入批次中的数据还没有聚合。但是为了保证用户只能查询到如下最终聚合后的数据。

    表17 数据

    user_id

    date

    cost

    10001

    2017-11-20

    51

    10001

    2017-11-21

    5

    10002

    2017-11-21

    39

    10003

    2017-11-22

    22

    建数据表。

    CREATE TABLE IF NOT EXISTS example_db.expamle_tb2
    (
        `user_id` LARGEINT NOT NULL COMMENT "用户id",
        `date` DATE NOT NULL COMMENT "数据导入日期时间",
        `cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费"
    )
    AGGREGATE KEY(`user_id`, `date`)
    DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
    PROPERTIES (
    "replication_allocation" = "tag.location.default: 1"
    );

    插入表15表16的数据。

    INSERT INTO example_db.expamle_tb2 (user_id,date,cost) VALUES('10001','2017-11-20','50'),('10002','2017-11-21','39'),('10001','2017-11-20','1'),('10001','2017-11-21','5'),('10003','2017-11-22','22');

    我们在查询引擎中加入了聚合算子,来保证数据对外的一致性。

    另外,在聚合列(Value)上,执行与聚合类型不一致的聚合类查询时,要注意语意。例如我们在如上示例中执行如下查询:

    mysql> SELECT MIN(cost) FROM example_db.expamle_tb2;
    +-------------+
    | min(`cost`) |
    +-------------+
    |           5 |
    +-------------+
    1 row in set (0.02 sec)

    得到的结果是5,而不是1。

    同时,这种一致性保证,在某些查询中,会极大降低查询效率。

    我们以最基本的count(*)查询为例:

    SELECT COUNT(*) FROM table;

    在其他数据库中,这类查询都会很快返回结果。因为在实现上,我们可以通过如导入时对行进行计数,保存count的统计信息,或者在查询时仅扫描某一列数据,获得count值的方式,只需很小的开销,即可获得查询结果。但是在Doris的聚合模型中,这种查询的开销非常大。以刚才的数据为例。

    batch1

    表18 数据

    user_id

    date

    cost

    10001

    2017-11-20

    50

    10002

    2017-11-21

    39

    batch2

    表19 数据

    user_id

    date

    cost

    10001

    2017-11-20

    1

    10001

    2017-11-21

    5

    10003

    2017-11-22

    22

    最终聚合结果如下表所示。

    表20 数据

    user_id

    date

    cost

    10001

    2017-11-20

    51

    10001

    2017-11-21

    5

    10002

    2017-11-21

    39

    10003

    2017-11-22

    22

    查询结果。

    mysql> SELECT COUNT(date) FROM example_db.expamle_tb2;
    +---------------+
    | count(`date`) |
    +---------------+
    |             4 |
    +---------------+
    1 row in set (0.01 sec)

    所以,select count(*) from table; 的正确结果应该为4。但如果我们只扫描user_i 这一列,如果加上查询时聚合,最终得到的结果是3(10001, 10002, 10003)。而如果不加查询时聚合,则得到的结果是5(两批次一共5行数据)。可见这两个结果都是不对的。

    为了得到正确的结果,我们必须同时读取user_id和date这两列的数据,再加上查询时聚合,才能返回4这个正确的结果。也就是说,在count( * ) 查询中,Doris必须扫描所有的AGGREGATE KEY列(这里就是user_id 和 date),并且聚合后,才能得到语意正确的结果。当聚合列非常多时,count( * )查询需要扫描大量的数据。

    因此,当业务上有频繁的count( * )查询时,我们建议用户通过增加一个值恒为1的,聚合类型为SUM的列来模拟count( * )。如刚才的例子中的表结构,我们修改如下:

    表21 参数说明

    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; 的结果将是一致的。并且这种方式,没有导入重复行的限制。

  • Duplicate

    Duplicate模型没有聚合模型的这个局限性。因为该模型不涉及聚合语意,在做count(*)查询时,任意选择一列查询,即可得到语意正确的结果。

相关文档