优化 DAX 计算

已完成

当性能分析器识别慢速 DAX 查询时,下一步是优化 DAX 本身。 低效 DAX 是语义模型性能不佳的最常见原因之一。 对度量值定义的小更改可能会显著减少查询时间。

使用变量消除重复计算

最有效的 DAX 优化技术之一是使用变量(VAR / RETURN)。 当公式多次计算同一表达式时,引擎每次都会重新计算它。 变量只需存储一次表达式的结果并重复使用,这可以使包含重复子表达式公式的查询时间大约减半。

请考虑这个同比增长指标:

Sales YoY Growth % =
DIVIDE(
    ([Sales] - CALCULATE([Sales], PARALLELPERIOD('Date'[Date], -12, MONTH))),
    CALCULATE([Sales], PARALLELPERIOD('Date'[Date], -12, MONTH))
)

PARALLELPERIOD 表达式会被计算两次:一次在分子中,一次在分母中。 使用变量重写可消除冗余:

Sales YoY Growth % =
VAR SalesPriorYear =
    CALCULATE([Sales], PARALLELPERIOD('Date'[Date], -12, MONTH))
RETURN
    DIVIDE(([Sales] - SalesPriorYear), SalesPriorYear)

结果相同,但引擎只计算 SalesPriorYear 一次。 变量还可以提高可读性:描述性变量名称使复杂公式更易于理解和维护。

小窍门

变量还简化了调试。 可以暂时更改 RETURN 表达式以仅输出变量值,这样就可以在不重写整个公式的情况下检查中间结果。

了解 FILTER 与 KEEPFILTERS

如何在 DAX 中应用筛选器修改直接影响性能。 该 FILTER 函数逐行遍历表以检查条件。 应用于大型表时,此迭代可能很昂贵。

-- Expensive: FILTER iterates the entire Sales table
High Value Sales =
CALCULATE(
    [Total Sales],
    FILTER(Sales, Sales[Amount] > 1000)
)

FILTER在具有数百万行的表上使用时,引擎会创建逐行迭代上下文。 如果筛选器逻辑仅涉及单个列,则通常可以替换为 FILTER 更简单的布尔表达式:

-- More efficient: column filter
High Value Sales =
CALCULATE(
    [Total Sales],
    Sales[Amount] > 1000
)

KEEPFILTERS 提供不同的用途。 它保留现有的筛选器上下文,而不是替换它。 当你希望添加一个筛选条件,但又不想覆盖由切片器或其他视觉对象已施加的筛选时,请使用 KEEPFILTERS:

Online Sales =
CALCULATE(
    [Total Sales],
    KEEPFILTERS(Sales[Channel] = "Online")
)

大型表中 FILTER 和直接列谓词之间的性能差异最明显。 一般而言,当列级别的谓词能够达到相同效果时,应避免对整个表使用 FILTER。

管理迭代器函数成本

迭代器函数(例如SUMX,AVERAGEXMAXX)计算COUNTX表中每一行的表达式,然后聚合结果。 它们功能强大,有时也是必需的,但也会带来成本,尤其是在处理大型表时。

-- Iterates every row in the Sales table
Weighted Average Price =
SUMX(
    Sales,
    Sales[Quantity] * Sales[UnitPrice]
) / SUM(Sales[Quantity])

此公式正常工作,但如果 Sales 表有 5000 万行, SUMX 则计算每一行的乘法。 如果存在等效的非迭代器表达式,请使用它:

-- Non-iterator equivalent (if LineTotal is a column)
Total Revenue = SUM(Sales[LineTotal])

迭代器函数本身并不糟糕。 当你需要行级计算逻辑时,它们是正确的选择,这些逻辑不能用简单的聚合表示。 关键是了解成本:迭代器性能随表大小缩放,因此迭代器超过 1,000 行是正常的,但超过 1 亿行的迭代器可能会成为瓶颈。

避免成本高昂的模式

已知某些 DAX 模式会导致性能问题。 识别它们有助于从一开始就编写更好的公式。

在处理大型表时使用 COUNTROWS(FILTER(...))。 此模式遍历整个表以计数符合条件的行。 将其替换为 CALCULATE,并使用 COUNTROWS 作为筛选器参数:

-- Expensive
Large Orders = COUNTROWS(FILTER(Sales, Sales[Amount] > 1000))

-- Better
Large Orders = CALCULATE(COUNTROWS(Sales), Sales[Amount] > 1000)

包含复杂筛选器的嵌套 CALCULATE。 每个嵌套 CALCULATE 都会创建一个新的筛选器上下文转换。 具有多个上下文更改的深度嵌套公式难以优化。 通过将复杂度量分解为较小的组件度量,每个组件只处理一个筛选器修改,以实现简化。

混合聚合颗粒。 合并不同粒度级别的数据的度量值(例如,将单行的值与表级总计进行比较)需要上下文转换,这些转换可能很昂贵。 使用变量计算总计一次并重复使用它:

Pct of Total =
VAR TotalSales = CALCULATE([Total Sales], REMOVEFILTERS())
RETURN
    DIVIDE([Total Sales], TotalSales)

将计算移动到数据层

如果 DAX 度量在每次查询刷新时计算相同的结果,并且基础数据在刷新之间不会更改,请考虑改为在数据层中具体化该计算。

例如,一个计算列在数据刷新期间运行,将名字和姓氏连接并存储结果。 但用于度量的相同逻辑将运行在每个查询上。 对于如下所示的静态转换,有两个选项:

  • Power Query 计算列:在数据加载期间,定义 M 中的转换过程。 这些列比 DAX 计算列更高效地进行压缩,因为 VertiPaq 引擎可以在加载过程中优化存储。
  • 源级计算:如果数据源是 SQL 数据库,请将计算添加到 SQL 视图或查询。 这使用数据库引擎的优化功能。

仅在需要使用 DAX 特有函数的场景中才使用 DAX 计算列,例如对度量值进行计算,或者使用依赖于语义模型关系的时间智能函数。

重要

在上游执行计算不仅改善了查询性能,还减少了数据刷新时间,因为 DAX 计算列是在所有 Power Query 表加载完成后才进行计算的。

考虑人工智能的用户体验

DAX 性能直接影响 AI 驱动的体验。 在 Microsoft Fabric 中,IQ 数据代理和 Copilot 聊天通过在后台生成 DAX 查询来查询您的语义模型。 对人类用户需要五秒钟才能完成的度量值,Copilot 同样也需要五秒钟;而 AI 交互的超时阈值通常比交互式报表更为严格。

优化 DAX 不仅仅是为了生成更快的报表。 它旨在使数据具有足够的响应能力,以支持自然语言查询、自动化代理和实时分析。