项目背景

数据来源:所有数据均为外部导入,最大数据量在10w+

输出数据:导出经过业务处理之后的数据

使用框架:fastadmin

涉及的问题:

1、数据读取

2、数据保存

使用数据:10w+

解决方案:

方案一:直接利用框架提供的功能导入Excel数据

结果:一分钟之后超时,最终执行完成时间在3分钟左右

分析:其中数据读取和数据保存(使用模型批量保存拆分为100,1000,10000)都十分耗时,而且在超时之后,系统其它功能无法响应。

可行性:不可行

方案二:将Excel数据换成csv格式

结果:一分钟之后还是超时,最终执行时间大约90s左右。

分析:读取csv格式的时候需要转码(这里很耗时,取消之后很快但是数据乱码了)。数据保存(使用模型批量保存拆分为100,1000,10000)部分依然很耗时。

可行性:不可行

方案三:取消转码,使用MySQL原生方式保存数据

结果:偶尔会成功(这里是单条数据方式插入),大约执行时间在60s左右。

分析:MySQL在执行sql语句的时候多次解析语句导致消耗很多时间,可以将语句合并为一条语句执行。

可行性:勉强可以

方案四:由于方案三可以知道多条语句执行比较耗时,修改为合并执行

结果:成功,大约耗时10-20s

分析:MySQL的语句是有大小限制,避免后续数据量大的时候出现错误。这里将数据拆分多次执行(10000,20000,50000 耗时差距在1s左右,基本可以看做没区别),最后将合并之后的sql语句在进行转码,最终耗时也在10-20s之间。
可行性:可以。

数据处理基础代码如下(网上代码修改,如有侵权,联系必删)

<?php

namespace app\common\library;
use think\Db; class CsvReader {
private $csv_file;
private $spl_object = null;
private $error; public function __construct($csv_file = '') {
if($csv_file && file_exists($csv_file)) {
$this->csv_file = $csv_file;
}
} public function set_csv_file($csv_file) {
if(!$csv_file || !file_exists($csv_file)) {
$this->error = 'File invalid';
return false;
}
$this->csv_file = $csv_file;
$this->spl_object = null;
} public function get_csv_file() {
return $this->csv_file;
} private function _file_valid($file = '') {
$file = $file ? $file : $this->csv_file;
if(!$file || !file_exists($file)) {
return false;
}
if(!is_readable($file)) {
return false;
}
return true;
} private function _open_file() {
if(!$this->_file_valid()) {
$this->error = 'File invalid';
return false;
}
if($this->spl_object == null) {
$this->spl_object = new \SplFileObject($this->csv_file, 'rb');
}
return true;
} /**
* 读取时候直接转码
* @* @param integrity $start 数据长度
* @* @param integrity $length 数据长度
* @* @param Callback $call 需要处理业务回调方法
* @* @param Array $para 表头名称与数据库表字段对应关系
*/
public function get_data($start = 0, $length = 0, $call=null, $para=null) {
if(!$this->_open_file()) {
return false;
}
$length = $length ? $length : $this->get_lines();
$start = $start - 1;
$start = ($start < 0) ? 0 : $start; $data = [];
$this->spl_object->seek($start);
$rowindex=[];
$tpara = [];
if (!empty($para)) {
$thead = (array)$this->spl_object->fgetcsv();
$index = 0;
foreach ($thead as $key => $val) {
$encoding = mb_detect_encoding($val, ['utf-8', 'gbk', 'latin1', 'big5']);
if ($encoding != 'utf-8') {
$val = mb_convert_encoding($val, 'utf-8', $encoding);
}
if (isset($para[$val])) {
$rowindex[] = $index;
$tpara[$index] = $para[$val];
} $index++;
}
$this->spl_object->next();
} while ($length-- && !$this->spl_object->eof()) {
$tdata =(array)$this->spl_object->fgetcsv();
$db = []; foreach ($rowindex as $index) {
if(!isset($tdata[$index]))continue; $value = $tdata[$index];
$value = is_null($value) ? '' : trim($value);
$encoding = mb_detect_encoding($value, ['utf-8', 'gbk', 'latin1', 'big5']);
if ($encoding != 'utf-8') {
$db[$tpara[$index]] = mb_convert_encoding($value, 'utf-8', $encoding);
}
else{
$db[$tpara[$index]] = $value;
}
} if ($call) {
$t=call_user_func_array( $call ,[$db]); if ($t) {
$data[] = $t;
}
}else {
$data[]=$db;
}
$this->spl_object->next();
}
return $data;
} /**
* 读取时候不转码,并且保存到数据库
* @* @param Int $start 数据长度
* @* @param Int $length 数据长度
* @* @param Callback $call 需要处理业务回调方法
* @* @param Array $para 表头名称与数据库表字段对应关系
* @* @param String $table 需要出入的表名
*/
public function get_data1($length = 0, $start = 0,$call=null,$para=null,$table=null) {
if(!$this->_open_file()) {
return false;
}
$length = $length ? $length : $this->get_lines();
$start = $start - 1;
$start = ($start < 0) ? 0 : $start; $data = [];
$this->spl_object->seek($start);
$rowindex=[]; $tpara = []; //表头索引与数据库字段对应关系
if (!empty($para)) {
$thead = (array)$this->spl_object->fgetcsv();
// var_dump($thead);
$index = 0;
foreach ($thead as $key => $val) {
$encoding = mb_detect_encoding($val, ['utf-8', 'gbk', 'latin1', 'big5']);
if ($encoding != 'utf-8') {
$val = mb_convert_encoding($val, 'utf-8', $encoding);
}
if (isset($para[$val])) {
$rowindex[] = $index;
$tpara[$index] = $para[$val];
} $index++;
}
$this->spl_object->next();
} while ($length-- && !$this->spl_object->eof()) {
$tdata =(array)$this->spl_object->fgetcsv();
$db = []; foreach ($rowindex as $idx) {
if(!isset($tdata[$idx]))continue; $value = $tdata[$idx];
$value = is_null($value) ? '' : trim($value);
$db[$tpara[$idx]] = $value;
} // 如需要处理业务,数字可以,其他文字不行(未转码)
if ($call) {
$t=call_user_func_array( $call ,[$db]); if ($t) {
$data[] = $t;
}
}else {
$data[]=$db;
}
$this->spl_object->next(); if (count($data)==10000) {
$this->insertsdb($table,$para,$data);
$data=[];
}
} if (count($data)>0) {
$this->insertsdb($table,$para,$data);
}
}
/**
* @* @param String $table 表名
* @* @param Array $para 要插入表的字段
* @* @param Array $data 要插入表的数据
*/
public function insertsdb($table,$para,$data)
{
$sql = 'insert into '.$table.' (';
$p = implode(',',$para);
$sql .= $p.') values'; $tmpval='';
foreach ($data as $key => $value) {
$v = implode('","',array_values($value)); $tmpval .='("'.$v.'"),';
} $sql .= rtrim($tmpval,','); // 利用事务也可以减少执行时间
Db::startTrans();
try{
$encoding = mb_detect_encoding($sql, ['utf-8', 'gbk', 'latin1', 'big5']);
$sql = mb_convert_encoding($sql, 'utf-8', $encoding);
$sql = str_replace("\\","",$sql);
Db::execute($sql);
// 提交事务
Db::commit();
} catch (\Exception $e) {
// 回滚事务
Db::rollback(); } }
public function get_lines() {
if(!$this->_open_file()) {
return false;
}
$this->spl_object->seek(filesize($this->csv_file));
return $this->spl_object->key();
} public function get_error() {
return $this->error;
}
}

调用代码如下

    public function import()
{
$file = $this->request->request('file');
if (!$file) {
$this->error(__('Parameter %s can not be empty', 'file'));
}
$filePath = ROOT_PATH . DS . 'public' . DS . $file;
if (!is_file($filePath)) {
$this->error(__('No results were found'));
}
$csv = new CsvReader($filePath);
// 此处公司业务不便公布(大约有20个参数),根据自己业务自行调整
$para =[
'单位详细名称'=>'company_name',
'省份'=>'province',
'地市'=>'city',
'区县'=>'country',
'是否关闭'=>'is_shutdown',
'公司Id'=>'company_id'
];
$data = $csv->get_data1(0,0,[$this,'handdbCallBack'],$para,'fa_bs_pollutant_discharge_info');
if($data)
{
$this->error('失败');
}
$this->success();
} /**
* 业务处理回调
*/
public function handdbCallBack($row)
{ if ($row) { // 当前编码是CP936
// 这里可以使用 $encoding = mb_detect_encoding($str, ['utf-8', 'gbk', 'latin1', 'big5'])
// 获取当前数据编码
$encoding='CP936';
$test = mb_convert_encoding('是', $encoding, 'utf-8');
if (isset($row['is_shutdown'])) {
$row['is_shutdown']=$test==$row['is_shutdown']?1:0;
}
// 此处为雪花ID生成,自行百度
$row['company_id'] = $this->snow->generateID(); return $row;
}
return null;
}

关于导出部分,可以直接导出csv格式,速度会很快。这里就不上代码了,自行百度。

以上是所有内容,以此记录一下,便于以后查阅。如有问题或者更好的解决方案欢迎各位指出。谢谢!!!

PHP MySQL 快速导入10万条数据的更多相关文章

  1. mysql快速导入5000万条数据过程记录(LOAD DATA INFILE方式)

    mysql快速导入5000万条数据过程记录(LOAD DATA INFILE方式) 首先将要导入的数据文件top5000W.txt放入到数据库数据目录/var/local/mysql/data/${d ...

  2. 复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

    从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负责的模块connector就派上了用场. ...

  3. Mvc+Dapper+存储过程分页10万条数据

    10万条数据采用存储过程分页实现(Mvc+Dapper+存储过程) 有时候大数据量进行查询操作的时候,查询速度很大强度上可以影响用户体验,因此自己简单写了一个demo,简单总结记录一下: 技术:Mvc ...

  4. 【转】Oracle中如何用一条SQL快速生成10万条测试数据

    转自http://blog.csdn.net/welken/article/details/4971887   做数据库开发或管理的人经常要创建大量的测试数据,动不动就需要上万条,如果一条一条的录入, ...

  5. 性能优化:虚拟列表,如何渲染10万条数据的dom,页面同时不卡顿

    列表大概有2万条数据,又不让做成分页,如果页面直接渲染2万条数据,在一些低配电脑上可能会照成页面卡死,基于这个需求,我们来手写一个虚拟列表 思路 列表中固定只显示少量的数据,比如60条 在列表滚动的时 ...

  6. MariaDB(MySql)使用储存过程和随机函数插入10万条数据

    ))default charset =utf8; #定义一个随机切割字符串的函数 delimiter // create function randStr() ) begin ) default 'A ...

  7. 【原创】10万条数据采用存储过程分页实现(Mvc+Dapper+存储过程)

    有时候大数据量进行查询操作的时候,查询速度很大强度上可以影响用户体验,因此自己简单写了一个demo,简单总结记录一下: 技术:Mvc4+Dapper+Dapper扩展+Sqlserver 目前主要实现 ...

  8. DataTable 快速导入数据库——百万条数据只需几秒

    public void InsertTable(DataTable dt, string TabelName, DataColumnCollection dtColum) { string str = ...

  9. 最短时间(几秒内)利用C#往SQLserver数据库一次性插入10万条数据

    用途说明: 公司要求做一个数据导入程序,要求将Excel数据,大批量的导入到数据库中,尽量少的访问数据库,高性能的对数据库进行存储.于是在网上进行查找,发现了一个比较好的解决方案,就是采用SqlBul ...

随机推荐

  1. 写的太细了!Spring MVC拦截器的应用,建议收藏再看!

    Spring MVC拦截器 拦截器是Spring MVC中强大的控件,它可以在进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作. 拦截器概述 对于任何优秀的MVC框架, ...

  2. 面试必看!靠着这份字节和腾讯的面经,我成功拿下了offer!

    准备 敲定了方向和目标后就开始系统准备,主要分为以下几个方面来准备. 算法题 事先已经看过别人的社招面经知道头条每轮技术面都有算法题,而这一块平时练习的比较少,校招时刷的题也忘记了很多.因此系统复习的 ...

  3. 云原生应用Go语言:你还在考虑的时候,别人已经应用实践

    摘要:在近日于上海召开的第六届Gopher China大会上,华为云微服务首席架构师田晓亮分享了<华为云的Go语言云原生实战经验>,讲述如何构建韧性.高可靠.安全的云原生应用系统,并孵化云 ...

  4. 如何制作C语言基本数据类型的思维导图

    在使用C语言编写程序时,数据类型是一个非常重要的内容,任何一个不被重视的数据错误都会使编译器无法翻译,导致程序报错. 使用思维导图来梳理各个数据类型是一个很有效的记忆方法,接下来就为大家展示一下我用i ...

  5. 关于GoldWave为Vegas制作音频交叉淡化特效的教程分享

    在Vegas里对音频交叉淡化的处理,是通过将两段音频交叠.调整交叠部分的音量.选取交叉淡化类型这三步来实现的,许多步骤是在音频轨道拖动音量线来实现的,操作上不够灵敏精细.其实,单就音频的交叉淡化处理, ...

  6. leetcode 33和 leetcode81

    //感想: 1.对于这两题,我真的是做到吐,这篇博客本来是昨晚准备写的,但是对于这个第二题,我真的做到头痛,实在是太尼玛的吐血了,主要是我也是头铁,非要找到那个分界点. 2.其实之前在牛客网上做过非常 ...

  7. JVM垃圾回收器、内存分配与回收策略

    新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...

  8. 2017-2018 ACM-ICPC Latin American Regional Programming Contest J - Jumping frog 题解(gcd)

    题目链接 题目大意 一只青蛙在长度为N的字符串上跳跃,"R"可以跳上去,"P"不可以跳上去. 字符串是环形的,N-1和0相连. 青蛙的跳跃距离K的取值范围是[1 ...

  9. Java基础教程——String类

    String类 Java程序中的所有字符串字面值(如 "abc" )都是String的实例 字符串是常量(因为 String 对象是不可变的,所以可以共享) 字符串的本质是字符数组 ...

  10. TIOBE 11月指数:C语言居首,稳居宝座,Python直逼第二!

    官方网址:https://www.tiobe.com/tiobe-index/   ​ 这是自近20年前TIOBE指数开始以来,Java和C第一次不再占据前两位.C仍然是第一位的,但是现在第二个位置是 ...