使用 XMLA 终结点的高级增量刷新和实时数据

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

分区

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

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

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

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

分区的创建和命名是以周期为单位的:年、季度、月和日。 最近的分区(刷新分区)包含在策略中指定的刷新周期内的行。 历史分区包含完整周期直至刷新周期的行。 如果启用实时,DirectQuery 分区将拾取在刷新周期结束日期之后发生的任何数据更改。 刷新分区和历史分区的粒度取决于在定义策略时所选的刷新周期和历史(存储)周期。

例如,如果今天的日期是 2021 年 2 月 2 日,并且数据源上的 FactInternetSales 表包含到今天为止的行,则我们的策略指定包括实时更改、刷新最近一天刷新周期中的行,并存储过去三年历史期间的行。 然后,通过第一次刷新操作,为将来的更改创建 DirectQuery 分区,为今天的行创建一个新的导入分区,为昨天(一整天,2021 年 2 月 1 日)创建一个历史分区。 为上一个整个月份(2021 年 1 月)创建历史分区,为前一个整个年度(2020 年)创建历史分区,并为 2019 年和 2018 年整个年度创建历史分区。 不会创建整个季度分区,因为 2021 年第一个完整季度尚未结束。

Diagram shows the partition naming granularity described in the text.

对于每个刷新操作,只刷新刷新周期分区,并更新 DirectQuery 分区的日期筛选器以仅包含在当前刷新周期之后发生的更改。 一个新的刷新分区是在更新的刷新周期内为具有新日期/时间的新行创建的,而具有日期/时间的现有行已经在刷新周期的现有分区内,并已刷新了更新。 不会再刷新日期/时间早于刷新周期的行。

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

模型始终保留整个历史存储周期的分区,以及直至当前刷新周期的整个周期分区。 在示例中,可以在 2018 年、2019 年、2020 年的分区,以及 2021Q101 月份周期、2021Q10201 日周期的分区和当天刷新周期分区中保留三年的完整历史数据。 由于示例保留三年的历史数据,因此将保留 2018 年分区直至 2022 年 1 月 1 日首次刷新。

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

使用 SQL Server Management Studio 管理刷新

可以使用 SQL Server Management Studio (SSMS) 查看和管理通过应用增量刷新策略创建的分区。 例如,使用 SSMS 可以刷新增量刷新周期之外的特定历史分区,从而执行回溯更新,而无需刷新所有历史数据。 在启动时也可以使用 SSMS,通过以批处理方式增量添加/刷新历史分区,为大型模型加载历史数据。

Screenshot shows the Partitions window in SSMS.

重写增量刷新行为

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

Screenshot shows the Script button in Process Table dialog.

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

  • applyRefreshPolicy。 如果表定义了增量刷新策略,applyRefreshPolicy 将确定是否应用策略。 如果未应用策略,完全处理操作会保持分区定义不变,并且会完全刷新表中的所有分区。 默认值为 true。

  • effectiveDate。 如果正在应用增量刷新策略,它需要知道当前日期,才能确定增量刷新和历史周期的滚动窗口范围。 使用 effectiveDate 参数,可以重写当前日期。 此参数对于将数据增量刷新到过去或未来某个日期的测试、演示和业务方案(如未来预算)十分有用。 默认值是当前日期。

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

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

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

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

确保最佳性能

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

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

防止初始完全刷新超时

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

通过启动初始刷新操作,服务可以为增量刷新表创建分区对象,但不会将历史数据加载到任何分区中并进行处理。 然后使用 SSMS 选择性地处理分区。 根据要为每个分区加载的数据量,可以按顺序处理每个分区或小批量处理分区,以降低其中一个或多个分区导致超时的可能性。 以下方法适用于任何数据源。

应用刷新策略

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

Screenshot show the Tabular Editor with Apply Refresh Policy selected.

针对空分区的 Power Query 筛选器

在将模型发布到服务之前,请在 Power Query 编辑器中,向 ProductKey 列添加另一个筛选器以有效地筛选掉 0 以外的任何值,或筛选掉 FactInternetSales 表中的所有数据。

Screenshot shows the Power Query Editor with code that filters out the product key.

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

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

提示

请务必查看 Power BI 社区中 BI 专家分享的视频、博客等内容。

若要详细了解如何使用 SSMS 处理表和分区,请参阅处理数据库、表或分区 (Analysis Services)。 若要详细了解如何使用 TMSL 处理模型、表和分区,请参阅刷新命令 (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 社区中 BI 专家分享的视频、博客等内容。

仅元数据部署

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

Screenshot shows the Replace model dialog.

在某些情况下,你可能不希望替换模型,特别是在使用增量刷新时。 Power BI Desktop 中的模型可能比 Power BI 服务中的模型小得多。 如果 Power BI 服务中的模型已应用增量刷新策略,它可能包含多个年份的历史数据;如果替换模型,这些历史数据就会丢失。 刷新所有历史数据可能需要数小时,并导致用户的系统停机时间发生。

相反,建议执行仅元数据部署,这样可以在不丢失历史数据的情况下部署新对象。 例如,如果已添加一些度量值,可以只部署新度量值,而无需刷新数据,从而节省时间。

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

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

Screenshot shows the ALM Toolkit window.

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

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

注意

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

此过程包括以下步骤:

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

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

  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();
        }
    }
}