☰
网格信息批量导入导出怎么做?一次三万条台账的工程复盘
2026/10/2 12:11:35 网站建设 项目流程

一、三万条台账,全量导入要四十分钟

去年冬天接手一个镇的网格化管理模块,甲方给了一张人员台账,三万一千多条记录,字段三十一个,从姓名、身份证号、联系方式,到网格编号、房号、是否低保,谁家归到哪个格都写在表里。需求听着简单,每季度更新一次,要能一键导进来,导错了得说清哪一行错在哪一步。

第一版做得很朴素,前端选文件,后端逐行读、逐行校验、逐行写库。第一轮压测跑完,三万条用了四十分钟,中途因为一条重复记录整批回滚。更麻烦的是浏览器等到超时先断开,用户看到的是失败,库里其实已经落了一万多条,第二次再导就变成重复数据,只能人工比对,这件事我们赔进去整整两天。

二、直觉做法错在把三件事揉成一件

把导入拆开看,它至少是三件事。先把文本变成结构化记录,再判定这条记录合不合格,然后才是落库。第一版把三步揉在一个循环里,任何一步慢整条链路就慢,任何一步错整批就废,错误处理也就无从下手。

慢的根子在两处。一处是把整个文件读成一个巨大的列表放在内存里,三万行乘三十一个字段,再叠上解析出来的对象,内存峰值冲到七百多兆,村一级服务器只有两核四G,跑到一半系统开始换页。另一处是逐行写库,每条记录一次网络往返,再加一次事务提交,三万次往返本身就是分钟级的开销。

错的根子在于校验发生在写库之后。数据已经进库了才发现某行的身份证校验位对不上,此时要么整批回滚,要么留下脏数据,两条路都难看。合理的顺序是先判定再写,而且判定结果要能和回执对上号,用户看到行号才知道回表格改哪一格。

三、三条路各自要付什么代价

第一条路是用 ORM 的批量保存,攒够一千条提交一次。代码量最少,代价是对象本身占内存,而且生成的语句仍是一行一条,只是把往返合并了,解析和转义的开销照样在,实测三万条仍要八到十分钟,内存峰值也没降下来。

第二条路是走数据库自带的文件装载能力,把文件整理成纯文本后一次性灌进去。这条路快,十万行十几秒就能完,代价很硬,校验几乎做不了,出错只能拿到一句笼统的失败提示,而且要求数据库进程能读到那个文件,跨机器部署时还得先把文件搬过去,路径和权限都是麻烦。

第三条是我们最后选的,看起来最笨,把校验挪到写库之前,用分批加参数化批量插入。每批固定五百条,批与批之间提交,一批失败只影响这一批,前面成功的保留,回执把行号和原因一起给出去。用户改完表格重跑一遍,靠业务键去重,已经进去的不会再进第二次。

四、把导入拆成四段

定下来的结构是四段。第一段读文件,只做一件事,把每一行原样读成字符串数组,不猜类型。第二段校验,按列定义逐格判定必填、长度、格式,身份证单独算校验位,任何一格不过就把这一行整行挑出来。第三段分批写库,开一个异步任务,边写边把进度记在任务记录里。第四段出回执,成功的条数、失败的条数、每个失败行号和原因,一起生成一份文件让用户下载。

四段结构在万村乐数字乡村的网格模块上前后改了三轮,第一轮只有批量写,第二轮补上校验,第三轮才把异步和回执拆开。真正让它稳定下来的不是某个技术,而是每一段只对上一段的输出负责,中间不共享可变状态,出了问题往前一段找就行。

导出的链路同样要重新设计,思路正好反过来。查库不能把结果集一次读进内存,我们改成游标分页,每页两千条,取一批写一批到本地文件,写完立刻释放引用。文件写满一个阈值就切一个新分片,最后再按需打包,整个过程内存占用是一条平的直线,跟数据量大小基本无关。

五、几个不能随手定的字段与参数

列定义必须显式,不能让表格解析器自己猜。手机号、身份证号、行政区划码、门牌号这些看着像数字的列,一律按文本读,否则十八位身份证会被转成科学计数法,末几位直接变零,门牌号的前导零也会掉。日期同理,按文本读进来再自己解析,避开被自动识别成本地格式。

校验规则我们写成声明式,绑在列上,而不是散在各个判断分支里。加一列只改配置,不改流程,这一条在项目后期救过我们,因为甲方半年内加了七八个字段,每次都要动代码是不可能的。

批大小也不是随手定的。小了提交次数多,写库开销成倍涨,大了单次事务太长,锁持有时间久,中断之后要重来的量也大。我们在五百到两千之间试了几轮,最后按记录宽度定,字段多的用五百,字段少的用两千,这一条后来写成了默认参数。

六、上线后撞出来的三个坑

第一个坑是身份证精度。表格里那列看着是数字,第三方读表组件默认也按数字解析,十八位整数超过了双精度的安全范围,末几位直接变成零,导进去以后校验位当然对不上,回执里一片红。改法是打开文件的瞬间就把所有列声明成文本,宁可多写一行配置,也不让解析器替我们做类型推断。

第二个坑是导出超时。镇里导出全量台账要四十多秒,网关的等待上限是三十秒,请求直接被掐断,用户点一次失败一次。改法是把导出改成异步任务,接口只返回任务号,前端轮询进度,文件生成好再给一个下载入口。顺手加了文件过期清理,超过一天的临时文件自动删掉,磁盘不至于被堆满。

第三个坑是重复导入的幂等。村与村之间会有人员调入调出,同一张表常常要连着导两次,如果没有去重,数据会翻倍。改法是给人员表加上身份证加网格编号的业务键,写入时按这个键判断是新增还是更新,重导同一份数据不会产生新行,导错之后重导一次就能覆盖回正确状态。

七、这层解决不了什么

这套链路解决的是格式与入库,管不了数据本身对不对。表格里写着一个不存在的网格编号,校验位算得再准也没用,这类跨表的语义检查我们放到导入之后单跑一遍,列出可疑行让人工确认,而不是硬拦着不让导。

它也不负责合并同一人在不同村重复登记的情况。现实里一个人可能在娘家村和婆家村各有一份台账,两边身份证一样、网格不一样,程序只能按业务键判重,判断不了哪个才是当前有效的那份,这属于业务规则,得由镇里给出口径。

八、小结

回头看,整个改动里没有特别新鲜的算法,值钱的是把一件被当成一步的事拆成了四步,并且让每一步都能单独失败、单独重跑。三万条的导入从四十分钟压到八十秒左右,失败回执能精确到行号,重导不会造重复,这三条加起来才让村干部真的敢自己动手导。

从身份证末位变零到网关三十秒断连,万村乐数字乡村网格模块上的每一条坑都对应一次线上返工。如果你们也在做批量导入导出,建议先把列定义和业务键这两件事定死,剩下的坑大半都能提前躲开。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询