在做一个数据维护工作的 php 脚本时,从插入数据库的记录中发现,数据记录被插入了两次。

通过加入 Log 日志标记发现,问题源头在 foreach 循环执行上数据库操作上。查看循环对象,并没有重复记录,所以是 foreach 方法体本身执行了两次,这是我不能理解的。猜测与循环执行的数据库异步操作有关。

以下为循环异步操作的代码示例:

        $orders = Orders::where('money', '>', 0)->get();
        foreach ($orders as $order) {
            if ($order->id == 1111) {
                Log::info('['.date('H:i:s').'] [order_id] 1111 executing...');
            }
            $this->customer_orders->handleProfit($order, $order->money);
        }

$customer_orders 是自动注入的 repository 类,handleProfit 则主要处理数据维护的操作。

日志内容:

[2021-09-29 10:41:37] production.INFO: [10:41:37] [order_id] 1111 executing...
[2021-09-29 10:41:37] production.INFO: [10:41:37] [order_id] 1111 executing...

可以看到两次执行时间几乎为同一时间,js 在处理循环内异步操作时也会发生相同的情况。

我的猜想:
在循环中某一异步操作(需要执行很长时间时),循环体会自动执行下一次操作。等到异步操作返回时,循环重新执行的地方是方法体首行。这就导致了循环体被重复执行了一次,包括异步操作本身。

解决方法

handleProfit 添加 return 返回语句,使得循环体需要等待异步操作结束再去执行下一次循环。异步变同步,这样就可以了。