如何使用多线程在mysql数据库中上传csv文件


How can i upload a csv file in mysql database using multithreads?

  1. 我有一个csv文件,包含数百万个电子邮件地址,我想用PHP快速上传到mysql数据库中
  2. 现在我使用的是一个单线程程序,它需要太多的时间来上传

//get the csv file
$file = $_FILES['csv']['tmp_name'];
$handle = fopen($file,"r");
//loop through the csv file and insert into database
do {
    if ($data[0]) {
        $expression = "/^[_a-z0-9-]+('.[_a-z0-9-]+)*@[a-z0-9-]+('.[a-z0-9-]+)*('.[a-z]{2,3})$/";
        if (preg_match($expression, $data[0])) {
            $query=mysql_query("SELECT * FROM `postfix`.`recipient_access` where recipient='".$data[0]."'");
            mysql_query("SET NAMES utf8");
            $fetch=mysql_fetch_array($query);
            if($fetch['recipient']!=$data[0]){
                $query=mysql_query("INSERT INTO `postfix`.`recipient_access`(`recipient`, `note`) VALUES('".addslashes($data[0])."','".$_POST['note']."')");
            }
        }
    }
} while ($data = fgetcsv($handle,1000,",","'"));

首先,我再怎么强调也不为过;修复你的压痕-这会让每个人的生活更轻松。

其次,答案在很大程度上取决于你遇到的实际瓶颈:

  • 正则表达式非常慢,尤其是当它们处于循环中时
  • 数据库往往可以很好地用于WRITES或READS,但不能同时用于两者:请尝试提前减少查询量
  • 毫无疑问,循环中的PHP代码越少,它的工作速度就越快。考虑递减条件(例如)
  • 记录在案,您的代码对mysql注入是不安全的:先过滤$_POST[*]
  • [*]说到这一点,访问变量比访问数组的索引更快,比如$_POST
  • 您可以通过让主程序将巨大的CSV文件拆分为较小的CSV文件,并将每个文件运行到不同的进程中来模拟多线程

common.php

class FileLineFinder {
    protected $handle, $length, $curpos;
    public function __construct($file){
        $handle = fopen($file, 'r');
        $length = strlen(PHP_EOL);
    }
    public function next_line(){
        while(!feof($this->handle)){
            $b = fread($this->handle, $this->length);
            $this->curpos += $this->length;
            if ($b == PHP_EOL) return $this->curpos;
        }
        return false;
    }
    public function skip_lines($count){
        for($i = 0; $i < $count; $i++)
            $this->next_line();
    }
    public function __destruct(){
        fclose($this->handle);
    }
}
function exec_async($cmd, $outfile, $pidfile){
    exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outfile, $pidfile));
}

main.php

require('common.php');
$maxlines = 200;      // maximum lines subtask will be processing at a time
$note = $_POST['note'];
$file = $_FILES['csv']['tmp_name'];
$outdir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'out' . DIRECTORY_SEPARATOR;
//make sure our output directory exists
if(!is_dir($outdir))
    if(!mkdir($outdir, 0755, true))
        die('Cannot create output directory: '.$outdir);
// run a task for each chunk of lines in the csv file
$i = 0; $pos = 0;
$l = new FileLineFinder($file);
do {
    $i++;
    exec_async(
        'php -f sub.php -- '.$pos.' '.$maxlines.' '.escapeshellarg($file).' '.escapeshellarg($note),
        $outdir.'proc'.$i.'.log',
        $outdir.'proc'.$i.'.pid'
    );
    $l->skip_lines($maxlines);
} while($pos = $l->next_line());
// wait for each task to finish
do {
    $tasks = count(glob($outdir.'proc*.pid'));
    echo 'Remaining Tasks: '.$tasks.PHP_EOL;
} while ($tasks > 0);
echo 'Finished!'.PHP_EOL;

sub.php

require('common.php');
$start = (int)$argv[1];
$count = (int)$argv[2];
$file = $argv[3];
$note = mysql_real_escape_string($argv[4]);
$lines = 0;
$handle = fopen($file, 'r');
fseek($handle, $start, SEEK_SET);
$expression = "/^[_a-z0-9-]+('.[_a-z0-9-]+)*@[a-z0-9-]+('.[a-z0-9-]+)*('.[a-z]{2,3})$/";
mysql_query('SET NAMES utf8');
//loop through the csv file and insert into database
do {
    $lines++;
    if ($data[0]) {
        if (preg_match($expression, $data[0])) {
            $query = mysql_query('SELECT * FROM `postfix`.`recipient_access` where recipient="'.$data[0].'"');
            $fetch = mysql_fetch_array($query);
            if($fetch['recipient'] != $data[0]){
                $query = mysql_query('INSERT INTO `postfix`.`recipient_access`(`recipient`, `note`) VALUES("'.$data[0].'","'.$note.'")');
            }
        }
    }
} while (($data = fgetcsv($handle, 1000, ',', '''')) && ($lines < $count));

积分

  • https://stackoverflow.com/a/2162528/314056
  • https://stackoverflow.com/a/45966/314056

最迫切需要做的事情是确保数据库被正确索引,以便为每一行执行的查找查询尽可能快。

除此之外,你根本没什么可做的。对于多线程解决方案,你必须脱离PHP。

您也可以在mySQL中导入CSV文件,然后使用PHP脚本清除多余的数据——这可能是最快的方法。

只是一个一般的建议:加速任何程序的关键是知道哪个部分占用了大部分时间。

然后想办法减少它。有时你会对实际结果感到非常惊讶。

顺便说一句,我不认为多线程能解决你的问题。

将整个循环放入SQL事务中。这将使事情加速一个数量级。