记一次PHP在多进程下批量插入mysql的异常

2 篇文章 0 订阅

因公司业务发展,建立了数百个微信群,每个群有若干群成员,群成员信息保存在第三方托管平台。现在因业务需求,需要将所有群成员通过第三方提供的接口保存到自己系统(每次请求只能获取一个群的群成员),如果以单进程处理,需要耗费大量时间,所以使用php的pnctl_fork 创建若干个子进程,然后循环批量获取群成员。具体方式为先在自己系统获取到所有的群列表,然后每50个群分为一组,然后遍历每组fork一个进程,每个进程遍历各自的每个群调用接口获取成员。代码如下(laravel框架):

		$group = DB::table('wx_group')->get()->toArray();//所有群信息
        $data = array_chunk($group,50);//每50个群分为一组
        $k = 0;
        while ($k < count($data)) {
            $pid = pcntl_fork();
            if($pid == -1){
                die('error');
            }
            if($pid > 0){
                cli_set_process_title("wgj_parent");
            }elseif($pid == 0){
                cli_set_process_title("wgj_children");
                //调用第三方接口获取到群成员(代码省略)
                $arr = '群成员二维数组';
                foreach($arr as $i => $j) {
                    $m = [
                        'cluster_id'=>$j['cluster_id'],
                        'wx_first_id'=>$j['wx_first_id'],
                        'nickname'=>$j['nickname'],
                    ];
                    DB::beginTransaction();
                    try{
                        DB::table('wgj_test')->insert($m);
                        DB::commit();
                    }catch (\Exception $e){
                        DB::rollBack();
                        Log::error('群成员处理:'.$e->getMessage());
                    }
                };
                exit(0);//子进程执行完毕,立马退出
            }
            $k++;
        }
        $n = 0;
        //等待所有子进程退出,防止僵尸进程
        while ($n < count($data)) {
            $nPID = pcntl_wait($status);
            if ($nPID > 0) {
                echo "{$nPID} exit\n";
                ++$n;
            }
        }
        var_dump('success');

操作完成,查看群成员数量,发现与第三方平台提供的数量不一致,检查错误日志发现mysql提示:PDOStatement::execute(): MySQL server has gone away,百度都说是因为mysql执行时间或执行缓存容量超过了默认值,修改max_allowed_packet=50M 后,问题依然没有解决。

折磨了两个小时候,最后联想到子进程会复制父进程的所有内存变量信息,在fork之前,父进程中已存在mysql的连接信息,那子进程mysql的链接对象也是从父进程复制的,就是说每个子进程用的是同一个mysql链接对象,这儿可能会出现问题(至于为什么,目前仍在研究中)。
于是就做了一个测试:如果在创建子进程之前,父进程不去连接mysql,那么子进程中就不会有mysql的连接对象,子进程想要操作mysql,需要自己去建立链接对象,这样每个子进程就有自己独立的mysql链接对象,因为子进程有自己独立的内存空间。经过多次测试,发现数据正常。

于是得出解决方法:父进程在自己系统查询到所有群列表后,立即关闭mysql连接,fork子进程后,子进程中就不会有mysql连接对象,经多次测试,数据正常,问题得以解决。

关闭mysql链接对象:在while循环前加上如下代码

DB::disconnect('mysql'); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值