从数据到信息
从信息到洞察

理解物化 Materialization

Materialization,也就是物化,是列式数据库进行查询解析时的一个步骤,了解它何时发生以及如何发生是优化 DAX 非常重要的一项知识。

理解引擎计值流

为了帮助理解什么是物化,先看这个简单的查询:

EVALUATE
ROW (
"Result", COUNTROWS ( SUMMARIZE ( Sales, Sales[ProductKey] ) )
)

结果是销售表中产品键的不重复计数。即使我们还没有介绍查询引擎(将在圣经 2.0 版本中介绍),你也可以想象 VertiPaq 是如何执行这个查询的。因为唯一被查询的列是 ProductKey,所以它只能扫描该列,在列的压缩结构中查找所有值。在扫描过程中,它跟踪在位图索引中找到的值,在结束时,它只需要对标记的位进行计数。由于组的并行计算特性,这个查询可以在非常大的表上以很快的速度运行,它唯一需要分配的内存是用于对 ProductKey 进行计数的位图索引

上面的查询在压缩之后的列上运行。换句话说,不需要对列进行解压缩,也不需要重新构建原始表来解析它。这个特性可以优化查询时的内存占用,以减少内存读取。

同样的情况也发生在更复杂的查询中。请看以下代码:

EVALUATE
ROW (
    "Result", CALCULATE (
        COUNTROWS ( Sales ),
        Product[Brand] = "Contoso"
    )
)

这一次,我们使用两个不同的表:Sales 和 Product。运行这个查询需要引擎做更多的工作。因为筛选器在产品表上,而要聚合的是销售表,所以不能只扫描单个列。

如果你还不习惯列式数据库,你可能会认为公式将迭代销售表,跟踪与产品的关系,如果产品品牌是 Contoso,则求和时标记为 1,否则标记 0。因此,你可能会想到下面这种类似的算法:

EVALUATE
ROW (
    "Result", SUMX (
        Sales,
        IF (
            RELATED ( Product[Brand] ) = "Contoso",
            1,
            0
        )
    ) 
)

这种算法虽然看上去简单,但它隐藏的复杂性远远超过预期。实际上,如果仔细考虑 VertiPaq 的列式特性,这个查询涉及三个不同的列:

  • Product[Brand]用于筛选产品表。
  • Product[ProductKey]用于跟踪产品表与销售表之间的关系。
  • Sales[ProductKey]用于从销售表一端跟踪关系。

迭代 Sales[ProductKey],每行扫描 Product[ProductKey]检索对应的产品表的行号,最后在 Product[brand]中收集品牌信息,整套操作的开销会非常大,需要对内存进行大量随机读取,从而对性能产生负面影响。实际上,VertiPaq 使用了一种完全不同的算法,针对列式数据库进行了优化。

内部发生了什么?

首先,它扫描 Product[Brand]并检索值为 Contoso 的行号。如下图所示,它扫描 Brand dictionary(步骤 1),检索 Contoso 的编码,最后扫描 segment(步骤 2)搜索 ID = 0 的行号,将索引返回到所找到的行(步骤 3)。

扫描后输出品牌等于 Contoso 的行列表

此时,VertiPaq 知道产品表中的哪些行具有给定的品牌。产品表和销售表之间的关系基于 Products[ProductKey],此时 VertiPaq 只知道行号。此外,重要的是记住筛选器将用于销售表,而不是产品表。因此,实际上 VertiPaq 并不需要 Product [ProductKey]的值,它真正需要的是 Sales[ProductKey]值的集合,即销售表中的 ID,而不是产品表中的 ID。

VertiPaq 在内部将关系存储为产品表中的行号和 Sales[ProductKey]中的数据 ID 之间的配对。事实证明,这是将筛选器从产品表的行号移动到销售表中的 ProductKeys 的完美数据结构。VertiPaq 对所选行号执行查找,以确定这些行对应的 Sales[ProductKey]值:

VertiPaq 扫描 Product Key 以检索 brand 等于 Contoso 的 ID

最后一步是将筛选器应用于销售表。因为我们已经有了 Sales[ProductKey]的值列表,所以扫描 Sales[ProductKey]列就足以将这个值列表转换为行号,并最终对其进行计数。如果不是计算 COUNTROWS,而是对某列求和,那么 VertiPaq 将执行另一个步骤,将行号转换为列值来执行最后一步。

如你所见,这个过程由简单的表扫描组成,在每个步骤中,你访问单个列。然而,由于列中的数据位于相同的内存区域,VertiPaq 顺序读取内存块并对其执行简单操作,每次输出一小片数据结构在接下来的步骤中使用。

在 VertiPaq 中解析查询的过程和我们预想的有很大区别。一开始,你可能不习惯用列式思维而是按表的结构来考虑问题。VertiPaq 的算法针对列扫描进行了优化;表的概念是列式数据库中被淡化

物化的介入

然而在某些情况下,引擎不能使用这些算法,而是需要恢复到表扫描。例如,下面这个查询:

EVALUATE
ROW (
    "Result", COUNTROWS (
        SUMMARIZE (
            Sales,
            Sales[ProductKey],
            Sales[CustomerKey]
        )
    )
) 

这个查询看起来非常简单,但简单的背后也暴露了列式数据库的局限性(行数据库也面临相同挑战)。查询返回产品和客户的唯一配对的计数。此查询无法通过分别扫描 ProductKey 和 CustomerKey 来解决。这里惟一的选项是构建一个包含惟一的 ProductKey 和 CustomerKey 键值对的表,最后计算其中的行数。换句话说,这一次 VertiPaq 必须构建一个表,即使只有这两列,它也不能直接在原始存储上执行查询。

物化的定义和总结

构建一个包含部分结果的表,并在稍后扫描该表以计算最终值,称为物化(Materialization)。物化发生在很多查询中,它本身没有好坏之分。这完全取决于被物化的表的大小

物化发生在查询过程中,此时最重要的因素是时间,这意味着公式必须尽可能快的得出结果,所以物化的临时表不会被压缩,因为压缩它们将花费大量时间

值得注意的是,当你从一个表中访问多列时,不会发生物化。这完全取决于你对这些列做了什么。例如,下面这样的查询不需要任何物化,即使它访问两个不同的列:

EVALUATE
ROW (
    "Result", SUMX (
        Sales,
        Sales[Quantity] * Sales[Net Price]
    )
)

VertiPaq 在扫描两列时执行乘法然后求和,因此不需要物化带有 Quantity 和 NetPrice 的表。然而,如果表达式变得更加复杂,或者需要进一步处理该表(如前一个示例中需要 COUNTROWS 的情况),则可能需要物化。

在极端情况下,物化可能会使用大量内存(有时甚至超过整个数据库的大小)并生成非常慢的查询。当这种情况发生时,你唯一的做法是重写计算或修改模型,使 VertiPaq 不需要生成物化表来应答。在后面的章节中(2.0 版本),你将看到此技术的一些示例。

13
说点什么

1000
 
鼓掌微笑开心憧憬爱你色并不觉得吃瓜doge二哈喵喵思考笑哭捂脸悲伤大哭抓狂汗偷笑打脸捂眼黑线问号晕拜拜闭嘴衰咒骂ok作揖
4 评论数
9 被回复的评论
4 订阅评论的人数
 
查看最近回复
查看最热评论
  订阅本文评论  
最新 最旧 得票最多
提醒
成员
JohnZcat

老师你好,看完后我大概了解了一些情况下可以避免物化,一些情况下需要进行物化,物化简单来说就是创建中间表,这样理解对吗?
另外我看不懂这两个图,图1中,字典是品牌索引与品牌的键值对,而DataRows中的ID和Count是什么呢?它们DataRows表是存储什么呢?最后那个表中的from和to又表示什么呢?

微信截图_20201006175516.png
成员
JohnZcat

哦。。。图一已明白,图二中的Relationship表中,Row Num应该应该是ID在Brand列中的行号吗?ID应该是产品的索引号吗?

成员
lyliuyouyang

学习这一章需要勇气。这一章看起来真的挺头痛的,也可能是自己对于这个原理理解不到位。我自己也根据文章中的例子写了一个table1和table2。和文章中一开始提到的自我感知一样,很明显会觉得table1肯定要比table2快很多,毕竟table2又是迭代又是if,怎么也快不起来的感觉。但是经个人测试,答案确实出乎意料,table2还要更快一些。
所以就是有可能就是我们在理解迭代函数的时候,总是会想象查询引擎会真的去在整张表里去逐行迭代,判断,然后求和?实际上查询引擎是会优化这种迭代函数的计算过程,计算比我们想象的要快?
因为之前一直被告诫说是迭代函数不如calculate的简单条件模式快,所以其实在平时的应用中,能够不使用迭代函数的都会用calculate替代,但是这个例子是个例外。
我个人感觉是不是对整个表进行计数是比较影响效率的?因为我自己改成table3里只对某一列计算又会快一些。对算是做个笔记吧,因为没看的太懂,也提不出比较深刻的问题,高飞老师见笑了。

成员
lyliuyouyang

不好意思图片没上传成功,补在这一楼了

批注 2020-05-11 001900.jpg
成员
bbzhdlp

这个图看好多次没明白。请高老师指导。

0428.png
成员
bbzhdlp

感觉原理部分特别难懂

DAX 圣经

导读

初识 DAX

DAX 基础知识

DAX 原理

DAX 高级原理

基础函数类型

迭代函数

CALCULATE 函数

CALCULATE 调节器

基础表函数

条件判断函数

查找匹配函数

时间智能函数

统计类函数

投影函数

分组/连接函数

集合函数

其他函数