读取处理 excel文件内容,循环处理的有什么优化方式吗?
//读取 excel 文件内容
//业务要求实时返回异常信息,老师好实时修订学生数据,不能走异步执行
$items->chunk(2000)->map(function ($sheets) use (&$errorData) {
foreach ($sheets as $key => $sheet) {
//目前每一列的验证会涉及查询数据库 (如学生账号/ 学校/ 学段 /年级/班级)都是独立的表 ,原来导入五六千学生挺快的几秒,现在主表学生 200 多万,每年学生增加100 万左右,
目前1000多条都需要十几秒
//每一列excel文件给的都是中文 ,xx学校 / xx班级
//大致会对每一列学校层级对应关系是否一致/存在 (学校->学段->年级->班级)查询 / 检验/这个是查询的数据库/索引走了
}
//最终包装完数据会涉及批量添加 / 更新 / 以及和http请求另外一个系统请求同步
//批量添加日志信息每一个执行的详细日志,表里也几百万
});
可以考虑下
swoole 协程
你现在慢是因为 (目前每一列的验证会涉及查询数据库) io开销大, 数据量大了 ,查询变慢了 所以导致你导出时间边长了,
1.合理设置数据库索引
2.检查查询是否命中索引
3.需要通过合理的方案减少数据库查询次数,如果每条都要查一次数据库,肯定会很慢的
举个简单的例子,导入的时候通过游标读取,累计到100条记录时候处理一次,查询数据库时把这100条数据所需要检验的数据一次性查出来,再通过遍历数据去检验,最后批次处理好数据后再批量添加数据
chunk函数的意义就是批量处理,所以你就按照批量处理的思维去处理数据库业务,目的就是减少数据库IO次数,例如每次读取100条,100条组装SQL去查询对应的表(按学校班级分组好数据,设置好索引),如果存在小表且业务字段简单的,可以使用链接查询,组装好数据批量写入,这样会提高不少。
chunk的话建议使用chunkById
还有就是 excel问题,用这个excel,省内存
根据这个量, 第一步应该还是异步,在发送异步队列的同时把本次异步记录(类似文件分片上传),然后在前端通过轮询,socket来请求异步是否完成,异常,然后做相应的操作; 第二是学校层级检验相关数据是否可以在循环前组装到本地(增大本地内存开销)
《原来导入五六千学生挺快的几秒,现在主表学生 200 多万,每年学生增加100 万左右,目前1000多条都需要十几秒》,从这里看,原因是就是数据量大了,数据库变慢了。 $items->chunk(2000) 这句话没有意义。下面都是每条循环了,为什么还要外面加一层循环? 1.首先看可以不可以,把每条验证,改为批量验证。几千次查询数据库,放在一个sql里面。 2.索引加的对不对 ,从你描述来看,索引加的还是有问题,200万的数据,不应该性能退化怎么快,建议每条sql研究一下 3.另外的方法再异步,fpm收到请求放入异步队列,前端使用轮询的方式查看处理进度,报错信息。这样用户和同步的感受是一样的。
即便是老师想实时修数据,是否可以考虑异步执行,通过 websocket 响应检测到的异常内容?
关于批量添加和excel导入这两个功能,和我几周前提问的问题差不多,既然要求实时,就不应该使用excel的方式去实现,而是使用批量添加。目前我在项目里采用的是方案一,如果感觉分批调接口会对服务器照成影响,可以考虑走websocket
我的建议是放到队列里面,多个消费者异步处理,excel也可以考虑流式读取, 至于实时修改?我只会说两个字"离谱"!
其实当你遇到这个问题时候,你也意识到这种设计在之前可能符合,随着业务的变化,程序之前所设计的方案其实都能重构。比如现在数据很大,里面有处理的逻辑什么的,可以将excel 转成普通的csv或者普通文件,采取逐行获取,写入中间表,再从中间表去实现你的最终完善逻辑,有些东西并不是一成不变,学会变通,技术架构都是一点点踩坑走出来的
你这个场景需要优化的应该是单条信息的查询,导入一千条肯定要循环一千次,每次查询判断耗时1ms那就是需要至少1s才能完成判断,但是能优化到0.1ms就相当于性能提高10倍。excel提供的应该不是数据主键,循环查询的逻辑里应该在第一时间把数据主键查出来。查其他表的时候不要分开查,尽量使用主键连表拼在一起把你要的字段单独选出来,不要用其他字段去查表。如果架构允许动可以把学生信息提前缓存到redis,key=>value(学生唯一标识=>学生信息json)的形式把热数据(比如在校生)缓存起来,excel导入的时候就可以先在redis中查询,redis没有这个key再去数据库查。