项目背景

数据来源:所有数据均为外部导入,最大数据量在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. python多线程——如何停止一个死循环的线程

    进程想要执行任务就需要依赖线程.换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程. 那什么是多线程?提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多 ...

  2. 真的可惜,四面阿里,结果我被JVM垃圾回收机制与 OOM异常卡住了

    前言 为什么需要垃圾回收 首先我们来聊聊为什么会需要垃圾回收,假设我们不进行垃圾回收会造成什么后果,我们举一个简单的例子 我们住在一个房子里面,我们每天都在里面生活,然后垃圾都丢在房子里面,又不打扫, ...

  3. swupdate实例

    平台:imx8mm 系统:linux 4.4   如果需要系统了解swupdate,请参考文章:嵌入式系统更新swupdate分类   一.制作升级包 emmcsetup.lua用来描述update执 ...

  4. Gradle全局代理配置

    配置文件路径:C:\Users\myName\.gradle\gradle.properties 代理配置内容: systemProp.http.proxyHost=127.0.0.1 systemP ...

  5. js控制某个div在页面加载完成5秒后隐藏

    <div id="k">测试</div><script>setTimeout("document.getElementById('k' ...

  6. linux(centos7.x)安装jdk

    一.下载与安装 下载地址:链接:https://pan.baidu.com/s/1g7MF1xqlOxWnLGf2shl3NA   提取码:epae  下载完成后将安装包上传到linxu环境中,并将其 ...

  7. Java基础教程——包装类

    Java出道之时,自诩为"纯面向对象的语言",意思是之前的所谓"面向对象语言"不纯. 但是,有人指责Java也不纯--8种基本类型并非类类型.为此,Java为他 ...

  8. Java基础教程——二维数组

    二维数组 Java里的二维数组其实是数组的数组,即每个数组元素都是一个数组. 每个数组的长度不要求一致,但最好一致. // 同样有两种风格的定义方法 int[][] _arr21_推荐 = { { 1 ...

  9. schema设计陷阱

    1.太多的列: mysql的存储引擎api工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列.从行缓冲中将编码过的列转换成行数据结构的操作代价是非常高的. ...

  10. [BUGCASE]FixedDataTable表格数据渲染错误

    一.问题描述 广告配置中绑定第三方规格ID表格数据,有一部分展示错乱,具体如下: 表格组件使用 Facebook 的 (fixed-data-table) 组件 二.原因分析 1.检查props 先查 ...