Power BI | 模型跑了两年突然报内存不足......
01、模型跑了两年早上突然挂了周一早上8:47小李的钉钉弹了三条消息。第一条来自业务副总「今天的销售日报怎么没推送」第二条来自IT运维群「Power BI Service刷新失败报错Resource Governing — not enough memory。」第三条是系统自动发的「语义模型刷新已连续失败3次当前状态已禁用。」小李打开Power BI Desktop点刷新——转了两分钟弹窗内存不足无法完成此操作。他看了一眼模型信息1.8 GB。两年前上线时是420 MB。数据量涨了不到一倍模型体积涨了四倍多。问题究竟出在哪里别着急着花钱在打开Azure Portal买更高SKU之前先看看VertiPaq到底在你的模型里存了什么。02、错误1把数据源的每一列都拖进去了小李用DAX Studio连上模型跑了VertiPaq Analyzer。结果一出来他自己都愣住了。前五大内存消耗列五列加起来占了模型体积的54%。而这些列小李的报表里一列都没用到。他当年从SQL Server导数据的时候用的是 SELECT *。这是一个几乎每个Power BI开发者都犯过的错。源表有40列你觉得万一以后用得上全拉进来了。但Power BI不是SQL Server——它不存原始数据它要把每一列压进VertiPaq的列式存储引擎。而这个引擎的效率取决于一个东西基数。03、为什么高基数 内存灾难VertiPaq对一列数据的压缩分四步字典编码把所有唯一值放进字典数据只存索引。如果一列有100万行但只有3个唯一值如「华东」「华南」「华北」字典只有3条压缩后几乎不占空间。值编码如果是整数列且基数可控直接用最少比特位存值连字典都省了。RLE游程编码连续相同值存成「值×次数」。排过序的列——比如日期——效果极好。哈希编码如果前三步都搞不定高基数、非整数VertiPaq只能对每个值算哈希、建一个大字典。这是最差的编码方式没有之一。回到小李那五列OrderGUID每行一个不同的GUID字符串。字典有多大字典等于行数。字典编码彻底失效RLE完全无效只能哈希编码硬扛。370 MB就这么来的。CustomerNote客服备注自由文本。每行内容都不一样但又不是数值同样哈希编码伺候。VertiPaq精确到秒的时间戳。问题不在它是DateTime类型而在它的精度。每行一个不同的秒级值 → 基数接近行数 → 字典爆炸。同等基数下DateTime列比整数列多占近7倍内存——因为整数可以用值编码DateTime不行。VertiPaq不是被数据量打败的是被「唯一值太多」打败的。10亿行只有3个产品的销售表压缩比能做到20:1。100万行全是GUID的表可能比10亿行还大。04、错误2把时间精度留给了报表层小李的第二大问题是CreatedDateTime。业务上销售报表只看到「天」——日趋势、周对比、月汇总。但源系统里存的是精确到秒的时间戳他原封不动拉进来了。处理方式极其简单在Power Query里把CreatedDateTime拆成两列——OrderDateDate类型和OrderHour整数0-23。效果有多夸张实测数据显示一个DateTime列从秒级精度降到天级精度该列占模型的比例从62%骤降到2.5%整模型从约400 MB降到271 MB。如果确实需要小时级分析降到小时级就够了。如果确实需要小时级分析降到小时级就够了。05、错误3Auto Date/Time 悄悄吃掉20%内存小李在 VertiPaq Analyzer 里还发现了一大堆他不认识的隐藏表——名字全是LocalDateTable_xxx。这是Power BI的默认功能自动日期/时间。每当你模型里有一个Date或DateTime列Power BI就在后台悄悄给你建一张隐藏日期表。小李的模型有7个日期列7张隐藏表合计占了约180 MB。关掉它文件→选项和设置→选项→数据加载→取消勾选「自动日期/时间」。然后自己建一张显式日期维度表一张就够了所有事实表都关联它。06、错误4关系建在了字符串列上小李的模型里订单表和客户表通过 CustomerCode 关联。CustomerCode 是个VARCHAR(20) 的字符串列形如 CUST-2023-SH-001587。VertiPaq 分析显示这一列的关系层次结构大小高达42 MB——因为关系索引里存的是字符串键不是整数键。修起来也简单在数据源端给客户表加一列整数代理键CustomerKey订单表同步加上关系建在整数列上。字符串CustomerCode留在维度表里做展示用但不再参与关系。改完后关系索引从42 MB降到3 MB。整数键的字典编码效率远高于字符串键。07、修完后的结果1.8GB → 312MB小李花了一个下午做了五件事从Power Query里删掉OrderGUID、CustomerNote、ProductImageURL、LastModifiedBy四列把CreatedDateTime拆成OrderDateDate和OrderHour整数关闭自动日期/时间建了一张显式日期维度表在数据源端加了整数代理键关系改到整数列上把CreatedDateTime的另一个副本也一并处理掉重新跑VertiPaq Analyzer模型从1800 MB降到312 MB压缩了83%。刷新时间从6分半降到1分10秒。那三条钉钉消息再也没出现过。他没有多花一分钱升级容量。08、一套你可以照搬的排查流程下次你的Power BI模型报内存不足别急着加配置。按这四步走第一步跑VertiPaq AnalyzerDAX Studio连上模型→Advanced→View Metrics。导出结果按Total Size降序排列找出前20大列。第二步标记三类「必杀」列基数超过50万的列、字典大小超过10 MB的列、你没在任何一个报表视觉对象里用到的列。第三步逐列处理没用的列直接在Power Query里删掉高精度DateTime拆成DateHourGUID/长字符串键在数据源端换成整数代理键自由文本备注如果不是分析对象就删掉。第四步验证重新跑VertiPaq Analyzer对比优化前后。压缩比低于5:1说明还有优化空间做到10:1以上算优秀。大多数人以为Power BI模型变大是因为数据太多了其实很可能并不是。模型变大是因为唯一值太多了。VertiPaq不怕行数多怕的是每一行都不一样。下次从数据源拉数据时先问自己两个问题「报表里真用得上吗」、「这列的基数大概多少」这两个问题比任何优化技巧都管用。