使用 XMLA 终端实现高级增量刷新和实时数据

在启用了读/写操作的 XMLA 终结点 的高级容量中,语义模型允许通过工具、脚本和 API 支持进行更高级的刷新、分区管理和仅元数据部署。 此外,通过 XMLA 终结点的刷新作不限于 每天 48 次刷新,并且不会施加 计划的刷新时间限制

Partitions

语义模型表分区不可见,无法使用 Power BI Desktop 或 Power BI 服务进行管理。 对于分配给高级容量的工作区中的模型,可以使用 SQL Server Management Studio(SSMS)、开源表格编辑器等工具通过 XMLA 终结点管理分区,使用表格模型脚本语言(TMSL)编写脚本,并使用表格对象模型(TOM)以编程方式进行管理。

首次将模型发布到 Power BI 服务时,新模型中的每个表都有一个分区。 对于没有增量刷新策略的表,除非应用了筛选器,否则一个分区包含该表的所有数据行。 对于具有增量刷新策略的表,只有一个初始分区存在,因为 Power BI 尚未应用该策略。 在 Power BI Desktop 中,当您根据 RangeStartRangeEnd 参数定义表的日期/时间范围筛选器,以及在 Power Query 编辑器中应用任何其他筛选器时,可以配置初始分区。 此初始分区仅包含符合筛选条件的数据行。

执行 第一次 刷新操作时,没有增量刷新策略的表将刷新该表的默认单分区中包含的所有行。 在具有增量刷新策略的表中,会自动创建刷新分区和历史分区,并根据每行的日期/时间将行数据加载到相应的分区中。 如果增量刷新策略包括实时获取数据,Power BI 还会向表添加 DirectQuery 分区。

重要

将增量刷新与实时数据(混合模式)配合使用时,与混合表相关的表应使用双存储模式以避免性能损失。 此外,视觉缓存可能会延迟实时更新,直到视觉对象重新查询数据。 有关详细信息,请参阅 对增量刷新和实时数据进行故障排除

第一次刷新操作可能会需要相当长的时间,具体取决于需要从数据源加载的数据量。 模型的复杂性也可能是一个重要因素,因为刷新作必须执行更多的处理和重新计算。 可以引导此操作。 有关详细信息,请参阅 “防止初始完全刷新超时”。

分区按周期粒度创建和命名:年、季度、月和天。 最新分区( 刷新 分区)包含策略中指定的刷新周期中的行。 历史分区包含截至刷新期的完整时间段的行。 如果启用了实时功能,DirectQuery 分区将会接收刷新期间结束日期之后发生的任何数据更改。 刷新和历史分区的粒度取决于定义策略时选择的刷新和历史(存储)时间段。

例如,如果今天的日期是 2021 年 2 月 2 日,并且数据源中的 FactInternetSales 表包含截至今天的行,那么我们的策略是指定包括实时变更,刷新最后一天刷新周期中的行,并在过去三年的历史期间存储这些行。 然后,在第一次刷新操作中,将为未来的更改创建一个 DirectQuery 分区,为今天的行创建一个新的导入分区,为昨天创建一个历史分区,例如 2021 年 2 月 1 日,这是一整天的时间段。 历史分区是针对前一个月期间(2021 年 1 月)创建的,为前一年(2020 年)创建历史分区,创建 2019 年和 2018 年全年的历史分区。 没有创建整个季度分区,因为我们尚未完成 2021 年第一个完整季度。

关系图显示了文本中所述的分区命名粒度。

每次执行刷新操作时,仅刷新刷新期的分区,并且 DirectQuery 分区的日期筛选器将更新为仅包括当前刷新期之后发生的更改。 在更新的刷新周期内,对具有新日期/时间的新行创建新的刷新分区,并对刷新周期内已有日期/时间的现有行进行更新刷新。 不再刷新那些日期/时间早于刷新周期的行。

当整个阶段结束时,分区将合并。 例如,如果在策略中指定了一天刷新周期和三年历史存储期,则当月的第一天,前一个月的全天分区将合并为一个月分区。 在新季度的第一天,前三个月份的所有分区将合并为一个季度分区。 在新年的第一天,前四个季度分区都合并为一年分区。

模型始终保留整个历史存储期的分区,以及直到当前刷新期间的所有时间段分区。 在此示例中,整整三年的历史数据保留在以下分区中:2018 年、2019 年、2020 年的分区,以及 2021Q101 的月份周期分区、2021Q102 的日期周期分区和当前日期刷新的周期分区。 由于该示例将历史数据保留三 ,因此将保留 2018 分区,直到 2022 年 1 月 1 日首次刷新。

借助 Power BI 增量刷新和实时数据,服务会根据策略为你处理分区管理。 虽然该服务可以通过 XMLA 终结点使用工具来处理所有分区管理,但你可以有选择地单独、按顺序或并行刷新分区。

常见分区刷新模式

在使用 XMLA 终结点操作时,请考虑以下常见模式来管理刷新操作:

  • 频繁的小型刷新:使用 XMLA 分区命令或增强的 REST API 在工作时间运行多个小型、有针对性的刷新作,使最近的数据保持最新状态,而无需处理整个表。
  • 选择性历史回填:使用 TMSL applyRefreshPolicy: false 在空闲时间内执行较大的历史分区刷新或一次性数据更正,以重建特定的历史周期,而不会影响自动策略行为。
  • 暂存的初始负载:对于非常大的历史周期,通过增量处理分区将初始刷新分解为较小的批,以避免超时并管理资源消耗。

通过这些模式,可以将实时数据新鲜度与系统性能和资源约束进行平衡。

使用 SQL Server Management Studio 刷新管理

SQL Server Management Studio (SSMS)可用于查看和管理由增量刷新策略的应用程序创建的分区。 例如,通过使用 SSMS,可以刷新位于增量刷新时间范围之外的特定历史分区,以进行回溯更新,而无需刷新所有历史数据。 在引导以增量方式批量添加/刷新历史分区来加载大型模型的历史数据时,也可以使用 SSMS。

SSMS 中“分区”窗口的屏幕截图。

替代增量刷新行为

借助 SSMS,还可以更好地控制如何使用 表格模型脚本语言表格对象模型调用刷新。 例如,在 SSMS、对象资源管理器中,右键单击表,然后选择 “进程表 ”菜单选项,然后选择“ 脚本 ”按钮以生成 TMSL 刷新命令。

“进程表”对话框中“脚本”按钮的屏幕截图。

这些参数可与 TMSL 刷新命令一起使用,以替代默认增量刷新行为:

  • applyRefreshPolicy。 如果表定义了增量刷新策略, applyRefreshPolicy 则确定策略是否已应用。 如果未应用该策略,则完全运行的进程会使分区定义保持不变,并且表中的所有分区都会被完全刷新。 默认值为 true。

  • effectiveDate。 如果正在应用增量刷新策略,则需要知道当前日期以确定增量刷新和历史周期的滚动窗口范围。 该 effectiveDate 参数允许覆盖当前日期。 此参数适用于测试、演示和业务场景,其中数据会逐步刷新到过去或将来的某个日期,例如涉及将来预算的情况。 默认值为当前日期。

{ 
  "refresh": {
    "type": "full",

    "applyRefreshPolicy": true,
    "effectiveDate": "12/31/2013",

    "objects": [
      {
        "database": "IR_AdventureWorks", 
        "table": "FactInternetSales" 
      }
    ]
  }
}

若要详细了解如何使用 TMSL 替代默认增量刷新行为,请参阅 Refresh 命令

使用表格编辑器管理策略

除了 SSMS,还可以使用 表格编辑器 通过 XMLA 终结点直接针对语义模型创建和修改增量刷新策略。 这允许你调整策略设置(例如刷新周期、历史周期和源表达式),而无需从 Power BI Desktop 重新发布模型。 表格编辑器还可用于将刷新策略应用于现有表以及管理和RangeStartRangeEnd参数表达式。 有关详细信息,请参阅表格编辑器文档中的 增量刷新

刷新业务流程和自动化

除了使用 SSMS、TMSL 和 TOM 通过 XMLA 终结点管理刷新之外,还可以使用 Power BI REST API 协调语义模型刷新作。 增强的刷新 API 提供其他功能,包括表级和分区级刷新、重试逻辑、取消和自定义超时管理。 此方法特别适用于将刷新操作集成到自动化工作流和 CI/CD(持续集成/持续交付)管道中。 有关详细指南,请参阅 使用 Power BI REST API 实现的增强刷新

确保最佳性能

每次刷新操作时,Power BI 服务可能会向数据源发送初始化查询到每个增量刷新分区。 可以通过确保以下配置减少初始化查询的数量来提高增量刷新性能:

  • 配置增量刷新的表应从单个数据源获取数据。 如果表从多个数据源获取数据,则服务在每次刷新操作中发送的查询数会与数据源数相乘,这可能会降低刷新性能。 确保增量刷新表的查询适用于单个数据源。
  • 对于通过直接查询对导入分区进行增量刷新和实时数据的解决方案, 所有分区 都必须从单个数据源查询数据。
  • 如果安全要求允许,请将数据源隐私级别设置设置为 组织公共。 默认情况下,隐私级别为 “专用”,但此级别可能会阻止与其他云源交换数据。 若要设置隐私级别,请选择“ 更多选项 ”菜单,然后选择 “设置>数据源凭据>编辑凭据>”此数据源的“隐私级别设置”。 如果在发布到服务之前在 Power BI Desktop 模型中设置隐私级别,则发布时不会将其传输到服务。 仍必须在服务的语义模型设置中设置它。 若要了解详细信息,请参阅 隐私级别
  • 如果使用本地数据网关,请确保使用的是版本 3000.77.3 或更高版本。

防止首次完全刷新时超时

发布到 Power BI 服务后,模型的初始完全刷新操作将为增量刷新表创建分区,加载并处理增量刷新策略中定义的整个时间段内的历史数据。 对于加载和处理大量数据的某些模型,初始刷新作花费的时间可能超过服务施加的刷新时间限制或数据源施加的查询时间限制。

启动初始刷新操作允许服务为增量刷新表创建分区对象,但不会将历史数据加载或处理到任何分区。 然后,SSMS 用于有选择地处理分区。 根据要为每个分区加载的数据量,可以按顺序处理每个分区,或者按小批处理每个分区,以减少一个或多个这些分区导致超时的可能性。 以下方法适用于任何数据源。

应用刷新策略

开源 Tabular Editor 2 工具提供了一种简单的方法来启动初始刷新操作。 发布具有从 Power BI Desktop 到服务定义的增量刷新策略的模型后,请使用读取/写入模式下的 XMLA 终结点连接到模型。 对增量刷新表运行 “应用刷新策略 ”。 仅应用策略后,将创建分区,但不会将数据加载到其中。 然后,使用 SSMS 进行连接,以按顺序或批量刷新分区以加载和处理数据。 有关详细信息,请参阅表格编辑器文档中的 增量刷新

屏幕截图显示已选择“应用刷新策略”的表格编辑器。

空分区的 Power Query 筛选器

在将模型发布到服务之前,在 Power Query 编辑器中,需要在 ProductKey 列上添加另一个筛选器,以排除非 0 的任何值,或从 FactInternetSales 表中筛除所有数据

屏幕截图显示了 Power Query 编辑器,其中包含筛选出产品密钥的代码。

在 Power Query 编辑器中选择 “关闭并应用 ”后,定义增量刷新策略并保存模型后,模型将发布到服务。 在服务中,初始刷新操作在模型上运行。 FactInternetSales 表的分区是根据策略创建的,但由于所有数据被筛选掉,因此不会加载和处理任何数据。

初始刷新操作完成后,返回到 Power Query 编辑器。删除ProductKey列上的其他筛选器。 在 Power Query 编辑器中选择 “关闭并应用 ”并保存模型后, 模型不会再次发布。 如果模型再次发布,它将覆盖增量刷新策略设置,并在通过服务执行后续刷新操作时强制对模型进行完全刷新。 而是使用应用程序生命周期管理工具包(ALM)执行仅元数据部署,这将从模型中删除ProductKey筛选器。 然后,SSMS 可用于有选择地处理分区。 在所有分区已完全处理并且必须包括从 SSMS 对所有分区进行进程重新计算后,后续从服务进行刷新操作时,仅刷新增量刷新分区。

小窍门

请务必查看 Power BI 专家社区提供的视频、博客和其他内容。

若要了解有关处理 SSMS 中的表和分区的详细信息,请参阅 Process 数据库、表或分区(Analysis Services)。 若要详细了解如何使用 TMSL 处理模型、表和分区,请参阅 Refresh 命令(TMSL)。

用于检测数据更改的自定义查询

TMSL 和 TOM 可用于替代检测到的数据更改行为。 此方法不仅可以用于避免在内存中缓存中保留上次更新列,还可以启用配置或指令表通过提取、转换和加载(ETL)进程准备的方案,以便仅标记需要刷新的分区。 无论数据更新发生多久,此方法都可以创建更高效的增量刷新过程,其中仅刷新所需时间段。

pollingExpression 旨在成为轻量级 M 表达式或另一个 M 查询的名称。 它必须返回标量值,并将为每个分区执行。 如果返回的值与上次发生增量刷新时的值不同,则会将分区标记为完全处理。

以下示例展示了涵盖历史时期内所有 120 个月的追溯变更。 选择指定 120 个月而不是 10 年意味着数据压缩的效率可能不如预期,但可以避免需刷新整个历史年份,在每月即可满足的后退更改需求时这将更加昂贵。

"refreshPolicy": {
    "policyType": "basic",
    "rollingWindowGranularity": "month",
    "rollingWindowPeriods": 120,
    "incrementalGranularity": "month",
    "incrementalPeriods": 120,
    "pollingExpression": "<M expression or name of custom polling query>",
    "sourceExpression": [
    "let ..."
    ]
}

小窍门

请务必查看 Power BI 专家社区提供的视频、博客和其他内容。

仅元数据部署

将新版本的 .pbix 文件从 Power BI Desktop 发布到工作区时,如果已存在同名的模型,系统会提示替换现有模型。

屏幕截图显示了“替换模型”对话框。

在某些情况下,你可能不希望替换模型,尤其是增量刷新。 Power BI Desktop 中的模型可能比 Power BI 服务中的模型要小得多。 如果 Power BI 服务中的模型应用了增量刷新策略,则它可能有几年的历史数据,如果替换模型,该数据将丢失。 刷新所有历史数据可能需要几个小时,并导致用户的系统停机。

相反,最好只执行元数据部署,这样就可以部署新对象,而不会丢失历史数据。 例如,如果添加了一些度量值,则只需部署新度量值,而无需刷新数据,从而节省时间。

对于分配给为 XMLA 终结点读取/写入配置的高级容量的工作区,兼容工具仅限于元数据部署。 例如,ALM 工具包是 Power BI 模型的架构差异工具,可用于仅执行元数据部署。

Analysis Services Git 存储库下载并安装最新版本的 ALM 工具包。 Microsoft文档中不包括有关使用 ALM 工具包的分步指南。 “帮助”功能区上提供了 ALM 工具包文档链接和有关可支持性的信息。 若要仅执行元数据部署,请执行比较并选择正在运行的 Power BI Desktop 实例作为源,并将 Power BI 服务中的现有模型作为目标。 请考虑显示的差异,跳过更新带有增量刷新分区的表,或使用“选项”对话框来保留表更新时的分区。 验证所选内容以确保目标模型的完整性,然后更新。

屏幕截图显示了“ALM 工具包”窗口。

以编程方式添加增量刷新策略和实时数据

还可以使用 TMSL 和 TOM 通过 XMLA 终结点将增量刷新策略添加到现有模型。

注释

若要避免兼容性问题,请确保使用最新版本的 Analysis Services 客户端库。 例如,若要使用混合策略,版本必须为 19.27.1.8 或更高版本。

此过程包括以下步骤:

  1. 确保目标模型具有所需的最低兼容性级别。 在 SSMS 中,右键单击 [模型名称]>属性>兼容性级别。 若要提高兼容性级别,请使用 createOrReplace TMSL 脚本或检查以下 TOM 示例代码以获取示例。

    a. Import policy - 1550
    b. Hybrid policy - 1565
    
  2. RangeStartRangeEnd 参数添加到模型表达式。 如有必要,还添加一个函数,将日期/时间值转换为日期键。

  3. 定义一个RefreshPolicy对象,其中包含所需的存档(滚动窗口)和增量刷新周期,还包括一个根据RangeStartRangeEnd参数筛选目标表的源表达式。 根据实时数据要求,将刷新策略模式设置为 “导入 ”或 “混合 ”。 混合会导致 Power BI 向表添加 DirectQuery 分区,以便从上次刷新时间之后发生的数据源中提取最新更改。

  4. 将刷新策略添加到表并执行完全刷新,以便 Power BI 根据要求对表进行分区。

下面的代码示例演示如何使用 TOM 执行前面的步骤。 如果要按原样使用此示例,则必须具有 AdventureWorksDW 数据库的副本,并将 FactInternetSales 表导入模型。 该代码示例假定在模型中不存在 RangeStartRangeEnd 参数以及 DateKey 函数。 只需导入 FactInternetSales 表并将模型发布到 Power BI Premium 上的工作区。 然后更新 workspaceUrl,以便代码示例可以连接到你的模型。 根据需要更新更多代码行。

using System;
using TOM = Microsoft.AnalysisServices.Tabular;
namespace Hybrid_Tables
{
    class Program
    {
        static string workspaceUrl = "<Enter your Workspace URL here>";
        static string databaseName = "AdventureWorks";
        static string tableName = "FactInternetSales";
        static void Main(string[] args)
        {
            using (var server = new TOM.Server())
            {
                // Connect to the dataset.
                server.Connect(workspaceUrl);
                TOM.Database database = server.Databases.FindByName(databaseName);
                if (database == null)
                {
                    throw new ApplicationException("Database cannot be found!");
                }
                if(database.CompatibilityLevel < 1565)
                {
                    database.CompatibilityLevel = 1565;
                    database.Update();
                }
                TOM.Model model = database.Model;
                // Add RangeStart, RangeEnd, and DateKey function.
                model.Expressions.Add(new TOM.NamedExpression {
                    Name = "RangeStart",
                    Kind = TOM.ExpressionKind.M,
                    Expression = "#datetime(2021, 12, 30, 0, 0, 0) meta [IsParameterQuery=true, Type=\"DateTime\", IsParameterQueryRequired=true]"
                });
                model.Expressions.Add(new TOM.NamedExpression
                {
                    Name = "RangeEnd",
                    Kind = TOM.ExpressionKind.M,
                    Expression = "#datetime(2021, 12, 31, 0, 0, 0) meta [IsParameterQuery=true, Type=\"DateTime\", IsParameterQueryRequired=true]"
                });
                model.Expressions.Add(new TOM.NamedExpression
                {
                    Name = "DateKey",
                    Kind = TOM.ExpressionKind.M,
                    Expression =
                        "let\n" +
                        "    Source = (x as datetime) => Date.Year(x)*10000 + Date.Month(x)*100 + Date.Day(x)\n" +
                        "in\n" +
                        "    Source"
                });
                // Apply a RefreshPolicy with Real-Time to the target table.
                TOM.Table salesTable = model.Tables[tableName];
                TOM.RefreshPolicy hybridPolicy = new TOM.BasicRefreshPolicy
                {
                    Mode = TOM.RefreshPolicyMode.Hybrid,
                    IncrementalPeriodsOffset = -1,
                    RollingWindowPeriods = 1,
                    RollingWindowGranularity = TOM.RefreshGranularityType.Year,
                    IncrementalPeriods = 1,
                    IncrementalGranularity = TOM.RefreshGranularityType.Day,
                    SourceExpression =
                        "let\n" +
                        "    Source = Sql.Database(\"demopm.database.windows.net\", \"AdventureWorksDW\"),\n" +
                        "    dbo_FactInternetSales = Source{[Schema=\"dbo\",Item=\"FactInternetSales\"]}[Data],\n" +
                        "    #\"Filtered Rows\" = Table.SelectRows(dbo_FactInternetSales, each [OrderDateKey] >= DateKey(RangeStart) and [OrderDateKey] < DateKey(RangeEnd))\n" +
                        "in\n" +
                        "    #\"Filtered Rows\""
                };
                salesTable.RefreshPolicy = hybridPolicy;
                model.RequestRefresh(TOM.RefreshType.Full);
                model.SaveChanges();
            }
            Console.WriteLine("{0}{1}", Environment.NewLine, "Press [Enter] to exit...");
            Console.ReadLine();
        }
    }
}