一直以为我的VBA代码已经很成熟了
在处理一个比较复杂的数据流程时,我基本上实现了"一键操作"——8个步骤依次执行,生成报表,全程自动化。
听起来很不错对吧?
但随着数据量的增加,处理速度越来越慢。昨天实际跑了一次:251秒(4.2分钟)。
数据流程到底有多复杂?
先说说我每天要面对的数据处理任务:
数据源:4份表
- 计划表
- 热区表
- 冷区表
- 机组表
需要生成:3份核心表
- 总表
- 未分表
- 已分表
处理步骤:8个独立模块
步骤
功能
操作的表
N01
尺寸提取
计划
N02
批号+炉号合并
热区→总表
N03
前面数据填充
计划→总表
N04
后面数据填充+计算
计划/热区/冷区→总表
N05
切头切尾标记
计划→总表
N06
分班基础数据
总表→未分班/已分班
N07
班次日期写入+重量拆分
机组→已分班
N08
废明细写入
废明细→已分班
数据量:大约1200-1500行,30多列,8个步骤加起来要处理将近30万次单元格操作。
听起来很复杂?确实复杂。但更让人头疼的是速度。
为什么VBA这么慢?
我一开始以为是数据量大的问题。但后来发现,慢的根本原因是读写方式不对。
For i = 2 To lastRowdValue = ws.Cells(i, 4).Value ' 读1个单元格iValue = ws.Cells(i, 9).Value ' 再读1个result = ExtractNumber(dValue) ' 处理ws.Cells(i, 26).Value = result ' 写1个Next i看起来没问题?实际上,这里藏着一个巨大的性能陷阱。
VBA读写Excel单元格,本质上是跨进程通信。
每次Cells(i,j).Value都要走一遍这个流程:
VBA代码 → COM接口 → Excel引擎 → 内存
一次还好,但一万次呢?
我们的报表里,30万次单元格操作 = 30万次跨进程通信。
每次通信约0.001-0.002秒,再算上Excel的计算、屏幕刷新、格式检查...
251秒,一点不少。。。。
打个比方:
- 不用数组:每次从北京搬1块砖到上海,搬了30万趟
- 用数组:一次把30万块砖装进集装箱,一次性运过去(一位大佬给我的回复。)
在以前文章评论区,有位大佬留下一句话:"为什么不用数组"。
我开始上网查资料,发现数组才是处理大批量数据的王。
数组优化的核心就一句话:
一次读入,内存处理,一次写回。
' 改成数组版本Dim dataArr As Variant' 一次性读取所有数据dataArr = ws.Range("A3:Z" & lastRow).ValueFor i = 1 To UBound(dataArr, 1)' 所有操作都在内存数组中完成dValue = dataArr(i, 4)iValue = dataArr(i, 9)result = ExtractNumber(dValue)dataArr(i, 26) = resultNext i' 一次性写回ws.Range("A3:Z" & lastRow).Value = dataArrCOM调用次数从30,000次骤降到3次(读1次 + 写1次,再加上一次数组赋值)。
速度差距,就是这么来的。
分批改造的过程第一步:先挑简单的改
N01(数据提取写入)是最简单的,只有一个循环,没有复杂的逻辑。先用它做实验。
' 原版:15秒Sub N01原版()For i = 1 To lastRowws.Cells(i, 26) = ExtractNumber(ws.Cells(i, 4))Next iEnd Sub' 数组版:0.8秒Sub N01数组版()dataArr = ws.Range("A3:Z" & lastRow).ValueFor i = 1 To UBound(dataArr, 1)dataArr(i, 26) = ExtractNumber(dataArr(i, 4))Next iws.Range("A3:Z" & lastRow).Value = dataArrEnd Sub从15秒到0.8秒,18倍提升。
第二步:改N04(最复杂的核心步骤)
N04要同时查3个表(计划、热区、冷区),原来需要40多秒。这是最大的瓶颈。
改完后实测:从40秒降到2秒。
第三步:翻车时刻——数组不是万能药
N07(已分班日期写入)是8个步骤里最复杂的,涉及到:
- 从机组表匹配班次
- 插入新行(Rows.Insert)
- 复制行数据(Rows.Copy)
- 按支数比例拆分重量
- 桔黄色标记分班数据
- 补全空值、写入公式
我试了三次改写成数组版,每次都以数据错乱告终。
第一次:数据全部丢失
第二次:数据重复,翻了一倍
第三次:格式全乱,颜色标记消失
结论:当涉及到插入行、复制行、动态行号变化时,数组版的风险极高。
我选择了认怂——N07保持原始代码。与其为了快30秒冒着数据全乱的风险,不如接受这3-5秒的代价。(这里请大佬请点一二 有没有什么好办法处理!)
最终结果对比
版本
耗时
数据准确性
结论
原始VBA
251秒
✅ 准确
太慢
全数组版
5秒
❌ 数据错乱
不能用
混合方案
10秒
✅ 准确
生产可用
各步骤详细对比
步骤
原版耗时
数组版耗时
提升
N01
15秒
0.8秒
18倍
N02
10秒
0.7秒
14倍
N03
20秒
1秒
20倍
N04
40秒
2秒
20倍
N05
10秒
0.5秒
20倍
N06
15秒
0.8秒
18倍
N07
50秒
保持原版
N08
30秒
保持原版
总计
190秒
~10秒
19倍
从251秒到10秒,提升了25倍!
给同样在学习VBA的朋友的建议
1、不要想着一次性完成所有的工作,那是大佬们的操作,我们可以一步一步的分步骤来来拆分。
可以分为N个步骤来完成。
2、一步一步来调试,完成一步保存一步。最终也可以成功。。最后可以加一个"主控"来实现一键操作。
3、学以致用,我用得到的我去学习,用不到,可以先了解。就和这次的“数组”,不知道的时候,就只会认为VBA就是这么个速度。知道了,学习了,才知道“数组”,能让VBA的速度这么快。
4、复杂逻辑别硬上数组
当涉及到Rows.Insert、Rows.Copy时,数组处理极其复杂,风险极高。
什么时候该用数组?什么时候不该?
场景
建议
大量单元格读写
✅ 用数组
纯数据循环处理
✅ 用数组
需要插入/删除行
⚠️ 谨慎使用
需要复制行数据
⚠️ 谨慎使用
涉及行号动态变化
⚠️ 谨慎使用
总结
对比维度
不用数组
用数组
差距
单次读写
跨进程通信
内存操作
1000倍+
1000行×30列
30,000次通信
2次通信
15,000倍
实际耗时
190-251秒
10秒
19-25倍
代码复杂度
简单
稍复杂
可接受
风险
中(需测试)
从251秒到10秒,不是魔法,只是把"每次搬一块砖"变成了"一次搬一车砖"。
如果你的VBA代码还在逐行读写单元格,花一个下午改造一下,省下4分钟,也给自己省下一份焦虑。
热门跟贴