研究《DAX查询计划 白皮书》中的案例(五)优化方案3

这是本系列的最后一个优化方案,这一篇的优化方案3是所有方案中最优秀的,它解决了优化方案2中产生的[RowNumber] 问题,使整个底层计算过程中全部步骤都没有出现数据量超过万行的情况,使性能获得了极大的提升!


研究《DAX查询计划 白皮书》中的案例(一)初始方案
研究《DAX查询计划 白皮书》中的案例(二)优化方案1
研究《DAX查询计划 白皮书》中的案例(三)优化方案(番外)
研究《DAX查询计划 白皮书》中的案例(四)优化方案2
研究《DAX查询计划 白皮书》中的案例(四)优化方案3


优化方案3

DAX 查询

// 优化方案3(修正)
EVALUATE
ADDCOLUMNS (
    VALUES ( 'Calendar'[Date] ),
    "Open Orders",
        SUMX (
            FILTER (
                GENERATE (
                    SUMMARIZE (
                        Sales,
                        Sales[OrderDate],
                        Sales[ShipDate],
                        "Rows", COUNTROWS ( Sales )
                    ),
                    DATESBETWEEN ( 
                        'Calendar'[Date], 
                        Sales[OrderDate] + 1,  // 修正此处,原版是 Sales[OpenDate]
                        Sales[ShipDate] - 1 )
                ),
                'Calendar'[Date] = EARLIER ( 'Calendar'[Date] )
            ),
            [Rows]
        )
)

运行效率

以连续5次冷启动的结果进行统计,将各方案的情况列写在表格里,数字下方括号里记录的是相对前一个方案的性能变化情况。

优化方案3的表现非常亮眼,计算效率超过了之前全部的方案,总耗时已经接近个位数!

环境参数初始方案优化方案1优化方案2优化方案(番外)优化方案3
环境1平均总耗时(毫秒)4389.4290.4
(↑ 93.38%)
79.6
(↑ 72.59%)
56.8
(↑ 28.64%)
SE耗时
(毫秒)
14.44.8
13.6
4.8
SE占比0.328%1.653%
(↑ 403.83%)
17.09%
(↑ 933.67%)
8.45%
(↓50.54%)
环境2平均总耗时(毫秒)4017.0300.60
(↑ 92.52%)
76.4
(↑ 74.58%)
53.8
(↑ 29.58%)
12.8
(↑ 83.25%)
SE耗时
(毫秒)
17.25.8
(↑ 66.28%)
18.2
5
3.4
SE占比0.428%1.929%
(↑ 350.62%)
23.82%
(↑ 1134.64%)
9.29%
(↓ 60.99%)
26.56%
(↑ 11.50%)

白皮书中没有对此方案进行任何分析,留给读者实践,下面是我对该方案进行分析的详细记录。

存储引擎部分

依然首先查看存储引擎执行了哪些 xmSQL 查询

image

左侧窗口记录 SE Cache 为 1,表明有一个 xmSQL 被复用。右侧窗口记录了 4 行 Scan,其中 Line2 和 Line 4 是完全相同的,Line4 复用了 Line2 。

存储引擎一共执行 3 个 xmSQL:

1、VQ1

Line2 和 Line4,从 Calendar 表中获取 [Date] 的不重复值,包含 1 列,共 1081 行

‘Calendar’[Date]

// xmSQL VQ1
SELECT
    'Calendar'[Date]
FROM 'Calendar';

2、VQ2

Line6,对 Sales 表以 Sales[OrderDate]、Sales[ShipDate] 分组并计算行数,包含 2 个数据列和 1 个值列,共 1081 行

Sales[OrderDate], Sales[ShipDate], COUNT

// xmSQL VQ2
SELECT
    'Sales'[OrderDate],
    'Sales'[ShipDate],
    COUNT ( )
FROM 'Sales';

部分结果类似

image

3、VQ3

Line8,对 Sales 表以 Sales[OrderDate]、Sales[ShipDate] 分组,即获取 Sales 表中{Sales[OrderDate], Sales[ShipDate]} 的不重复值对,共 1081 行

Sales[OrderDate], Sales[ShipDate]

// xmSQL VQ3
SELECT
    'Sales'[OrderDate],
    'Sales'[ShipDate]
FROM 'Sales';

部分结果为:

image

存储引擎部分 3 个 xmSQL 查询都没有出现 [RowNumber]。

公式引擎部分

逻辑查询计划

AddColumns: RelLogOp DependOnCols()() 0-1 RequiredCols(0, 1)('Calendar'[Date], ''[Open Orders])
├── Scan_Vertipaq: RelLogOp DependOnCols()() 0-0 RequiredCols(0)('Calendar'[Date])
└── SumX: ScaLogOp DependOnCols(0)('Calendar'[Date]) Integer DominantValue=BLANK
    ├── Filter: RelLogOp DependOnCols(0)('Calendar'[Date]) 1-4 RequiredCols(0, 1, 2, 3, 4)('Calendar'[Date], 'Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows], 'Calendar'[Date])
    │   ├── CrossApply: RelLogOp DependOnCols()() 1-4 RequiredCols(1, 2, 3, 4)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows], 'Calendar'[Date])
    │   │   ├── AddColumns: RelLogOp DependOnCols()() 1-3 RequiredCols(1, 2, 3)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows])
    │   │   │   ├── GroupBy_Vertipaq: RelLogOp DependOnCols()() 1-15 RequiredCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate])
    │   │   │   │   └── Scan_Vertipaq: RelLogOp DependOnCols()() 1-15 RequiredCols(6, 7)('Sales'[OrderDate], 'Sales'[ShipDate])
    │   │   │   └── Count_Vertipaq: ScaLogOp DependOnCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) Integer DominantValue=BLANK
    │   │   │       └── Scan_Vertipaq: RelLogOp DependOnCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) 16-30 RequiredCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate])
    │   │   └── DatesBetween: RelLogOp DependOnCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) 4-4 RequiredCols(1, 2, 4)('Sales'[OrderDate], 'Sales'[ShipDate], 'Calendar'[Date])
    │   │       ├── Add: ScaLogOp DependOnCols(1)('Sales'[OrderDate]) DateTime DominantValue=NONE
    │   │       │   ├── 'Sales'[OrderDate]: ScaLogOp DependOnCols(1)('Sales'[OrderDate]) DateTime DominantValue=NONE
    │   │       │   └── Constant: ScaLogOp DependOnCols()() Integer DominantValue=1
    │   │       └── Subtract: ScaLogOp DependOnCols(2)('Sales'[ShipDate]) DateTime DominantValue=NONE
    │   │           ├── 'Sales'[ShipDate]: ScaLogOp DependOnCols(2)('Sales'[ShipDate]) DateTime DominantValue=NONE
    │   │           └── Constant: ScaLogOp DependOnCols()() Integer DominantValue=1
    │   └── EqualTo: ScaLogOp DependOnCols(0, 4)('Calendar'[Date], 'Calendar'[Date]) Boolean DominantValue=false
    │       ├── 'Calendar'[Date]: ScaLogOp DependOnCols(4)('Calendar'[Date]) DateTime DominantValue=NONE
    │       └── 'Calendar'[Date]: ScaLogOp DependOnCols(0)('Calendar'[Date]) DateTime DominantValue=NONE
    └── ''[Rows]: ScaLogOp DependOnCols(3)(''[Rows]) Integer DominantValue=NONE

代码中每一行的 DependOnCols 表示该操作依赖哪些列,RequiredCols 表示生成哪些列,根据这些信息在每个块上添加更多内容。上一篇讲了同一列可以有多个来源,这次直接在各列后面用数字标明。

在这里插入图片描述

逻辑查询计划基本与 DAX 查询代码保持了相同的结构和计算顺序。

物理查询计划

在这里插入图片描述

AddColumns: IterPhyOp LogOp=AddColumns IterCols(0, 1)('Calendar'[Date], ''[Open Orders])
├── Spool_Iterator<SpoolIterator>: IterPhyOp LogOp=Scan_Vertipaq IterCols(0)('Calendar'[Date]) #Records=1081 #KeyCols=20 #ValueCols=0
│   └── ProjectionSpool<ProjectFusion<>>: SpoolPhyOp #Records=1081
│       └── Cache: IterPhyOp #FieldCols=1 #ValueCols=0
└── SpoolLookup: LookupPhyOp LogOp=SumX LookupCols(0)('Calendar'[Date]) Integer #Records=1079 #KeyCols=1 #ValueCols=1 DominantValue=BLANK
    └── AggregationSpool<Sum>: SpoolPhyOp #Records=1079
        └── Extend_Lookup: IterPhyOp LogOp=Extend_Lookup''[Rows] IterCols(3)(''[Rows])
            ├── Filter: IterPhyOp LogOp=Filter IterCols(0, 1, 2, 3, 4)('Calendar'[Date], 'Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows], 'Calendar'[Date])
            │   └── CrossApply: IterPhyOp LogOp=EqualTo IterCols(0, 4)('Calendar'[Date], 'Calendar'[Date])
            │       ├── ApplyRemap: IterPhyOp LogOp=CrossApply LookupCols(4)('Calendar'[Date]) IterCols(1, 2, 3)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows])
            │       │   └── Spool_MultiValuedHashLookup: IterPhyOp LogOp=CrossApply LookupCols(4)('Calendar'[Date]) IterCols(1, 2, 3)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows]) #Records=6444 #KeyCols=4 #ValueCols=0
            │       │       └── AggregationSpool<GroupBy>: SpoolPhyOp #Records=6444
            │       │           └── CrossApply: IterPhyOp LogOp=CrossApply IterCols(1, 2, 3, 4)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows], 'Calendar'[Date])
            │       │               ├── AddColumns: IterPhyOp LogOp=AddColumns LookupCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) IterCols(3)(''[Rows])
            │       │               │   ├── Spool_UniqueHashLookup: IterPhyOp LogOp=GroupBy_Vertipaq LookupCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) #Records=1081 #KeyCols=20 #ValueCols=0
            │       │               │   │   └── ProjectionSpool<ProjectFusion<>>: SpoolPhyOp #Records=1081
            │       │               │   │       └── Cache: IterPhyOp #FieldCols=2 #ValueCols=0
            │       │               │   └── SpoolLookup: LookupPhyOp LogOp=Count_Vertipaq LookupCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) Integer #Records=1081 #KeyCols=20 #ValueCols=1 DominantValue=BLANK
            │       │               │       └── ProjectionSpool<ProjectFusion<Copy>>: SpoolPhyOp #Records=1081
            │       │               │           └── Cache: IterPhyOp #FieldCols=2 #ValueCols=1
            │       │               └── DatesBetween: IterPhyOp LogOp=DatesBetween IterCols(1, 2, 4)('Sales'[OrderDate], 'Sales'[ShipDate], 'Calendar'[Date])
            │       │                   ├── Extend_Lookup: IterPhyOp LogOp=Add IterCols(1)('Sales'[OrderDate])
            │       │                   │   ├── Spool_Iterator<SpoolIterator>: IterPhyOp LogOp=AddColumns IterCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) #Records=1081 #KeyCols=2 #ValueCols=0
            │       │                   │   │   └── AggregationSpool<GroupBy>: SpoolPhyOp #Records=1081
            │       │                   │   │       └── AddColumns: IterPhyOp LogOp=AddColumns IterCols(1, 2, 3)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows])
            │       │                   │   │           ├── Spool_Iterator<SpoolIterator>: IterPhyOp LogOp=GroupBy_Vertipaq IterCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) #Records=1081 #KeyCols=20 #ValueCols=0
            │       │                   │   │           │   └── ProjectionSpool<ProjectFusion<>>: SpoolPhyOp #Records=1081
            │       │                   │   │           │       └── Cache: IterPhyOp #FieldCols=2 #ValueCols=0
            │       │                   │   │           └── SpoolLookup: LookupPhyOp LogOp=Count_Vertipaq LookupCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) Integer #Records=1081 #KeyCols=20 #ValueCols=1 DominantValue=BLANK
            │       │                   │   │               └── ProjectionSpool<ProjectFusion<Copy>>: SpoolPhyOp #Records=1081
            │       │                   │   │                   └── Cache: IterPhyOp #FieldCols=2 #ValueCols=1
            │       │                   │   └── Add: LookupPhyOp LogOp=Add LookupCols(1)('Sales'[OrderDate]) DateTime
            │       │                   │       ├── ColValue<'Sales'[OrderDate]>: LookupPhyOp LogOp=ColValue<'Sales'[OrderDate]>'Sales'[OrderDate] LookupCols(1)('Sales'[OrderDate]) DateTime
            │       │                   │       └── Constant: LookupPhyOp LogOp=Constant Integer 1
            │       │                   └── Subtract: LookupPhyOp LogOp=Subtract LookupCols(2)('Sales'[ShipDate]) DateTime
            │       │                       ├── ColValue<'Sales'[ShipDate]>: LookupPhyOp LogOp=ColValue<'Sales'[ShipDate]>'Sales'[ShipDate] LookupCols(2)('Sales'[ShipDate]) DateTime
            │       │                       └── Constant: LookupPhyOp LogOp=Constant Integer 1
            │       └── InnerHashJoin: IterPhyOp LogOp=EqualTo IterCols(0, 4)('Calendar'[Date], 'Calendar'[Date])
            │           ├── Extend_Lookup: IterPhyOp LogOp=Extend_Lookup'Calendar'[Date] IterCols(0)('Calendar'[Date])
            │           │   ├── Spool_Iterator<SpoolIterator>: IterPhyOp LogOp=Scan_Vertipaq IterCols(0)('Calendar'[Date]) #Records=1081 #KeyCols=20 #ValueCols=0
            │           │   │   └── ProjectionSpool<ProjectFusion<>>: SpoolPhyOp #Records=1081
            │           │   │       └── Cache: IterPhyOp #FieldCols=1 #ValueCols=0
            │           │   └── ColValue<'Calendar'[Date]>: LookupPhyOp LogOp=ColValue<'Calendar'[Date]>'Calendar'[Date] LookupCols(0)('Calendar'[Date]) DateTime
            │           └── HashLookup: IterPhyOp LogOp=HashLookup'Calendar'[Date] IterCols(4)('Calendar'[Date]) #Recs=1079
            │               └── HashByValue: SpoolPhyOp #Records=1079
            │                   └── Extend_Lookup: IterPhyOp LogOp=Extend_Lookup'Calendar'[Date] IterCols(4)('Calendar'[Date])
            │                       ├── Spool_Iterator<SpoolIterator>: IterPhyOp LogOp=CrossApply IterCols(4)('Calendar'[Date]) #Records=1079 #KeyCols=1 #ValueCols=0
            │                       │   └── AggregationSpool<GroupBy>: SpoolPhyOp #Records=1079
            │                       │       └── ApplyRemap: IterPhyOp LogOp=CrossApply IterCols(1, 2, 3, 4)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows], 'Calendar'[Date])
            │                       │           └── CrossApply: IterPhyOp LogOp=CrossApply IterCols(1, 2, 3, 4)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows], 'Calendar'[Date])
            │                       │               ├── AddColumns: IterPhyOp LogOp=AddColumns LookupCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) IterCols(3)(''[Rows])
            │                       │               │   ├── Spool_UniqueHashLookup: IterPhyOp LogOp=GroupBy_Vertipaq LookupCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) #Records=1081 #KeyCols=20 #ValueCols=0
            │                       │               │   │   └── ProjectionSpool<ProjectFusion<>>: SpoolPhyOp #Records=1081
            │                       │               │   │       └── Cache: IterPhyOp #FieldCols=2 #ValueCols=0
            │                       │               │   └── SpoolLookup: LookupPhyOp LogOp=Count_Vertipaq LookupCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) Integer #Records=1081 #KeyCols=20 #ValueCols=1 DominantValue=BLANK
            │                       │               │       └── ProjectionSpool<ProjectFusion<Copy>>: SpoolPhyOp #Records=1081
            │                       │               │           └── Cache: IterPhyOp #FieldCols=2 #ValueCols=1
            │                       │               └── DatesBetween: IterPhyOp LogOp=DatesBetween IterCols(1, 2, 4)('Sales'[OrderDate], 'Sales'[ShipDate], 'Calendar'[Date])
            │                       │                   ├── Extend_Lookup: IterPhyOp LogOp=Add IterCols(1)('Sales'[OrderDate])
            │                       │                   │   ├── Spool_Iterator<SpoolIterator>: IterPhyOp LogOp=AddColumns IterCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) #Records=1081 #KeyCols=2 #ValueCols=0
            │                       │                   │   │   └── AggregationSpool<GroupBy>: SpoolPhyOp #Records=1081
            │                       │                   │   │       └── AddColumns: IterPhyOp LogOp=AddColumns IterCols(1, 2, 3)('Sales'[OrderDate], 'Sales'[ShipDate], ''[Rows])
            │                       │                   │   │           ├── Spool_Iterator<SpoolIterator>: IterPhyOp LogOp=GroupBy_Vertipaq IterCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) #Records=1081 #KeyCols=20 #ValueCols=0
            │                       │                   │   │           │   └── ProjectionSpool<ProjectFusion<>>: SpoolPhyOp #Records=1081
            │                       │                   │   │           │       └── Cache: IterPhyOp #FieldCols=2 #ValueCols=0
            │                       │                   │   │           └── SpoolLookup: LookupPhyOp LogOp=Count_Vertipaq LookupCols(1, 2)('Sales'[OrderDate], 'Sales'[ShipDate]) Integer #Records=1081 #KeyCols=20 #ValueCols=1 DominantValue=BLANK
            │                       │                   │   │               └── ProjectionSpool<ProjectFusion<Copy>>: SpoolPhyOp #Records=1081
            │                       │                   │   │                   └── Cache: IterPhyOp #FieldCols=2 #ValueCols=1
            │                       │                   │   └── Add: LookupPhyOp LogOp=Add LookupCols(1)('Sales'[OrderDate]) DateTime
            │                       │                   │       ├── ColValue<'Sales'[OrderDate]>: LookupPhyOp LogOp=ColValue<'Sales'[OrderDate]>'Sales'[OrderDate] LookupCols(1)('Sales'[OrderDate]) DateTime
            │                       │                   │       └── Constant: LookupPhyOp LogOp=Constant Integer 1
            │                       │                   └── Subtract: LookupPhyOp LogOp=Subtract LookupCols(2)('Sales'[ShipDate]) DateTime
            │                       │                       ├── ColValue<'Sales'[ShipDate]>: LookupPhyOp LogOp=ColValue<'Sales'[ShipDate]>'Sales'[ShipDate] LookupCols(2)('Sales'[ShipDate]) DateTime
            │                       │                       └── Constant: LookupPhyOp LogOp=Constant Integer 1
            │                       └── ColValue<'Calendar'[Date]>: LookupPhyOp LogOp=ColValue<'Calendar'[Date]>'Calendar'[Date] LookupCols(4)('Calendar'[Date]) DateTime
            └── ColValue<''[Rows]>: LookupPhyOp LogOp=ColValue<''[Rows]>''[Rows] LookupCols(3)(''[Rows]) Integer

这次的物理查询计划流程结构图如下:

在这里插入图片描述

底层计算过程

本系列的重点在于 “理解DAX 在底层是怎么算的”,而不是“怎么通过阅读物理查询计划了解 DAX 底层计算过程”,在上一篇尝试了用模拟小数据表来演示每一步的计算过程,比干巴巴解释物理查询计划的代码更形象,这一篇就不逐一去解释代码了。
根据对物理查询计划的理解梳理底层计算过程,用一个小数据表做模拟。

image

1、

将 Sales 表按 Sales[OrderDate]、Sales[ShipDate] 分组,并计算出每个{[OrderDate], [ShipDate]}组合包含的数据行数量

image

2、

将 Sales 表中按 Sales[OrderDate]、Sales[ShipDate] 分组,按照每一个 {[OrderDate], [ShipDate]} 值对,从 Calendar[Date] 筛选出 [OrderDate] +1 < [Date] < [ShipDate] -1 的数据,与对应的 {[OrderDate], [ShipDate]} 交叉成表

image

3、

将 2# 的结果表与Calendar[Date](0)​以 [Date] 进行匹配查询合并,得到

image

这一步的目的是在切换 [Date](4)​到 [Date](0)​,上一篇已经详细讲过这种情况

4、

3# 以 [Date](0)​分组,将每组内的 [Row] 累加

image

5、

Calendar[Date](0)​为基准,从 4# 中查询对应的 [SUM] 作为 [Open Orders] 列,得到最终结果

image

各步骤与物理查询结构流程图的对应关系可下载文件查看

性能分析

优化方案3取得如此明显的性能优化,它与优化方案2一样,没有出现优化方案1中由 FILTER 产生的任何交叉表,而比优化方案2更优秀的是,这一次甚至没有出现 [RowNumber],全程所有步骤都没有生成超过万级的数据表,全在处理千级数据。

image

优化方案3的思路,来自优化方案2的物理查询计划,实际就是把优化方案2的底层计算过程用 DAX 写了一遍。在上一篇中特意做了每个步骤的模拟计算,就是为了这里做铺垫,如果仔细阅读并理解了优化方案2的底层计算过程,应该还记得它主要用了这样几个步骤:

1、对于 Sales 表的每一行,从 Calendar 表中找出 [OrderDate]、[ShipDate] 之间的 [Date] 并与 Sales 表合并

image

2、从 1# 的结果中,按 [OrderDate]、[ShipDate] 分组,并更换 Date(16)​到 Date(0)

image

3、将 2# 与 Sales表以 [OrderDate]、[ShipDate] 进行匹配查询,目的是获取各个{[OrderDate],[ShipDate]}值对包含的全部 [RowNumber],因为最后的 COUNTROWS​需要使用 [RowNumber] 来对行进行计数。

image

这一个结果看着与 1# 很相似,区别在 1# 的结果表中是 Date(16)​,现在是 Date[0]​,在上一篇已经讲过 DAX 底需要这样做的原因了。

4、最后从以 Calendar[Date](0)​为基准,从 3# 中根据 Date(0)​分组并对 [RowNumber] 进行记数,得到每个 Calendar[Date] 有多少行 [RowNumber]

image

在 DAX 代码层面,优化方案3采取了以下改进:

  1. 不使用 Sales 整表,而用SUMMARIZE(Sales,Sales[OrderDate],Sales[ShipDate])​取得分组数据,减少数据量
  2. 按照优化方案2底层创建 Sales 与 Calendar 合并表的情况,主动以 Sales 的分组表与 Calendar 创建合并表
  3. 在给 Sales 分组时便计算出各个分组的数据量,最后该用 SUM 替代 优化方案2中的 COUNTROWS(Sales) ,避免出现 [RowNumber]

image

最后的总结

本系列参照《白皮书》的结构,分析了若干个 DAX 查询代码的物理查询计划,梳理其底层计算过程,逐个分析性能瓶颈,熟悉了 DAX 性能优化的一般步骤和一些优化思路,了解了可能影响计算性能的因素,比如中间步骤产生的大数据量,交叉表的存在,[RowNumber] 的影响等。DAX 性能优化并不是一件简单的事,阅读物理查询计划、绘制流程结构图、进行精简、梳理底层计算步骤确实需要耗费许多精力,而且官方对这一块的支持力度不是很大,并没有提供很多关于查询计划操作符的详细解释,笔者也是连蒙带猜地结合实验进行了一些探索,难免会出现错误的理解,希望对 DAX 性能优化感兴趣、愿意了解物理查询计划或者正在研究这个领域的朋友,能够一起交流探讨。

  • 34
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值