
刷新慢,通常不是数据库慢,而是数据根本没在数据库里算。
小李负责的销售日报,每天早上 8:30 自动刷新。三个月前只要 40 秒,现在要 6 分 12 秒。
他先找 IT。IT 看了一眼服务器:
「SQL Server 资源使用率不到 15%,不是我们这边的问题。」
他再找 DBA。DBA 跑了一遍 SQL Profiler,截图发过来:
「你的 Power BI 在跑一条 SELECT * FROM SalesOrder,扫了全表 1200 万行。数据库不慢,是你每次刷新都在把整张表搬进 Power BI。」
小李打开 Power Query 编辑器,点开「销售订单」查询,数了一下:22 个应用步骤。筛选、去重、分组、合并、加自定义列、改数据类型,每一步他都觉得必须做。
问题不在数据库,也不在「22 步」这个数字本身,而在这些步骤最后变成了在哪里算。
Power Query 连接 SQL Server 这类关系型数据源时,有一个机制叫查询折叠。
简单说:你用 M 语言写的转换步骤——筛选行、选择列、分组聚合、排序——会被翻译成一条 SQL 语句发给数据库执行。数据库算完,只把结果返回给你。
| 情况 | 数据库实际执行的 | 传回 Power BI 的数据 |
|---|---|---|
| 折叠正常 | 带 WHERE、GROUP BY 的完整 SQL | 筛选和汇总后的结果,例如 30 万行 |
| 折叠断开 | SELECT * 或只带前几步条件的 SQL | 全表 1200 万行,到本地再由 M 引擎逐条处理 |
折叠时,1200 万行先筛到 30 万行再传;折叠断了,1200 万行先进网卡、进内存,再被本地引擎一道道处理。
这 6 分钟不是算出来的,是搬出来的。
折叠的规则很硬:一旦某一步不可折叠,它后面的所有步骤——哪怕本身可以折叠——也一起失效。整条链从那个断点开始,全部在本地执行。
小李打开「查询折叠指示器」(Power Query 编辑器 → 视图 → 勾选「查询折叠指示器」),看到的顺序是:
| 步骤 | 折叠状态 |
|---|---|
| 导航(Navigation) | 已折叠 |
| 删除其他列 | 已折叠 |
| 筛选:日期 ≥ 2026-01-01 | 已折叠 |
| 添加自定义列:Month = Date.Month([OrderDate]) | 未折叠 · 断点 |
| 筛选:金额 > 1000 | 未折叠 |
| 分组汇总 | 未折叠 |
| ……后续所有步骤 | 全部未折叠 |
断点在第 4 步。Date.Month 在 SQL Server 端没有等价写法,折叠从这里断掉;后面的筛选、分组、去重,全部由 Power BI 在本地算完。
SQL Server 收到的实际查询是什么?DBA 截到的就是这一条:
SELECT * FROM [SalesOrder]
WHERE [OrderDate] >= '2026-01-01'
前 3 步折叠成了 WHERE 条件。第 4 步一断,第 5 步的「金额 > 1000」根本没进 SQL——它是数据全部被拉到本地之后才筛的。
小李做了三件事,顺序不能颠倒:先让能折叠的步骤回到数据库,再处理那些确实折叠不了的。
01重排步骤:把能折叠的操作用到断点前面。
他原本的顺序是:
导航 → 删列 → 筛选日期 → 添加自定义列 → 筛选金额 → 分组
调整之后:
导航 → 删列 → 筛选日期 → 筛选金额 → 分组 → 添加自定义列
金额筛选和分组排到了自定义列前面,SQL Server 收到的查询变成:
SELECT [OrderDate], [Amount], [ProductID], [Region]
FROM [SalesOrder]
WHERE [OrderDate] >= '2026-01-01' AND [Amount] > 1000
GROUP BY [OrderDate], [ProductID], [Region]
数据库先筛再分组,返回的结果集只有 2 万行。自定义列在 2 万行上做,毫秒级。
02替换 M 函数:用可折叠的写法替掉不可折叠的。
他原来写 Date.Month([OrderDate]),是为了在报表里按月筛选。这个诉求完全可以在数据源端解决:
-- 在 SQL Server 建一个视图,把月份变成独立列
CREATE VIEW vw_SalesOrder AS
SELECT *, MONTH(OrderDate) AS OrderMonth
FROM SalesOrder
Power Query 直接连视图,OrderMonth 就是一个普通整数列,折叠不受影响。
03推到源:把复杂逻辑交给数据库。
另一步是 Table.AddColumn 里用一串嵌套 if 做客户分级。这在 Power Query 里不可折叠,但写成 SQL 的 CASE WHEN 完全没问题。他把这个字段也加进视图,Power Query 只负责拉现成数据。
修完再看折叠指示器:
| 步骤 | 折叠状态 |
|---|---|
| 导航(Navigation) | 已折叠 |
| 删除其他列 | 已折叠 |
| 筛选:日期 ≥ 2026-01-01 | 已折叠 |
| 筛选:金额 > 1000 | 已折叠 |
| 分组汇总 | 已折叠 |
| 添加自定义列(字段来自视图) | 已折叠 |
全绿。第二天早上 8:30,刷新用了 51 秒。
小李的问题不是技术不行,是他把 Power Query 当成了 ETL 工具。
Power Query 的定位是数据接入层:把数据从源系统接进来,做必要的列筛选和简单转换。它的设计目标是「把数据搬到模型里」,不是让你在 1200 万行上做字符串拼接、日期运算和嵌套条件判断。
一个粗略的判断标准:步骤超过 15 步,同时有 3 个以上自定义列,折叠大概率已经断了。
把复杂计算推回数据源。SQL 视图、存储过程,甚至直接在数据库里加计算列——任何能在数据源端完成的事,都比在 Power Query 里做强。
也不是所有步骤都能推到源,比如跨源合并、模糊匹配、调用外部接口。这类操作要放在折叠的最末端,并且先想办法把数据量压下来。
不用等下次刷新变慢,现在打开 Power BI Desktop,做三件事:
01打开折叠指示器。文件 → 选项和设置 → 选项 → Power Query 编辑器 → 勾选「查询折叠指示器」。然后打开 Power Query,看每个查询最后一步的图标颜色。
02找到第一个红色步骤。它就是断点。看它是什么操作——自定义列、日期函数、文本处理?把能折叠的步骤移到它前面。
03右键最后一步,看「查看原生查询」。灰色说明整条链都没折叠;可用就点开看 SQL 长什么样。如果只是一句不带 WHERE 的 SELECT *,说明你的筛选根本没进数据库。
三步走完还看不明白,就把这条 SQL 发给 DBA 对一下:数据库实际收到的,和你在 Power Query 里以为的,经常不是一回事。
查询折叠不是优化技巧,而是 Power Query 连接关系型数据库的基本工作方式。折叠断了,等于你主动选择了「把所有数据拉到本地再算」。
这件事的代价不会一次爆发出来:数据量从 300 万涨到 1200 万,刷新就从 40 秒变成 6 分钟。等到有人开始抱怨日报出不来,问题往往已经存在很久了。
这句话值 6 分钟——报表每天早上多等的那 6 分钟。
想查清报表刷新慢在哪一步?
申请试用智能小V,做一次语义模型与刷新体检 →


在线咨询







