数据库查询构建器有时会返回数组而不是作为排队作业运行的对象

Posted

技术标签:

【中文标题】数据库查询构建器有时会返回数组而不是作为排队作业运行的对象【英文标题】:Database query builder sometimes returns array instead of object running as a queued job 【发布时间】:2018-05-18 04:25:40 【问题描述】:

TL;DR

我排队的作业有时似乎会失败,因为常规数据库查询(使用 Laravel 的标准查询构建器)不能可靠地返回我的config/database.php 中的 fetch 模式定义的 PHP 对象。它似乎有时会返回一个对象或一个数组,因此获取模式会以某种方式发生变化(甚至变回)。

详细问题

我正在使用 Laravel 的查询构建器查询外部数据库。 fetch模式设置为返回config/database.php中的对象:

'fetch' => PDO::FETCH_OBJ,

...基本上在我的应用程序的许多地方都可以使用。

有时,在将其作为排队作业执行时,查询的结果可能是数组而不是对象。作业运行正确的代码,我无法重现为什么有时会发生这种情况。

有人知道什么会导致这种结果吗?

例如,使用常规的get() 方法或chunk 都会发生这种情况。重新启动 queue runner 有助于消除错误,但它最终会回来!

我的代码如下所示:

$assetData = DB::connection('connection_name')->table('system')
  ->whereIn('system_id', $this->system_ids)->orderBy('system_id');

$emlAssetData->chunk(5000, function ($assetDataChunk) 
  foreach ($assetDataChunk AS $assetData) 
      if (!is_object($assetData)) 
          \Log::warning(__CLASS__.': '.json_encode($assetData));
      
  

  $assetData->field; // Sometimes fails because result is an array, instead of an object

我正在使用:

PHP 7.0 mysql v5.7.16 v5.1.49 Laravel 5.5

我的解决方法是将其添加到我查询这样的外部数据库的任何地方。

if (is_array($assetData)) 
    \Log::warning(__CLASS__." Converting array to object: ".json_encode($assetData));
    $assetData = (object)$assetData;

在这种情况下很难进行调试,因为它只发生在队列中运行:(

更新:2017-12-11:有关正在使用的 SQL/代码的更多详细信息

总结一下我在这里做的更特别的事情,可能与我的问题有关:

我查询的不是本地主机上运行的“默认”连接,而是外部数据库(在内部网络中) 我用的不是 Eloquent,而是 Laravel 的常规查询生成器 为了逐步查看结果,我使用了一个自己编写的自定义函数,它为每一行调用一个回调函数

背景:这会将旧数据库 MySQL v5.1.49 的各个部分导入我们项目的数据库。为了使这更容易,您可以像这样指定某种列映射(从旧字段/表名称到新字段/表名称)

$columnMapping = collect([
    'system.system_id'          => 'system_id',
    'staud_colours.colour_name' => 'system_name',
]);

接下来,您执行自定义查询并使用辅助函数将旧字段映射到新字段:

$items = \DB::connection('slave')->table('system')
    ->join('staud_colours', 'staud_colours.colour_id', '=', 'system.system_fremd_id')
    ->where('system.system_klasse', 'colours')->where('system.system_status', 1);

$this->prepareQueryToBeInsertedToDB($items, $columnMapping, function ($insertData) 
    static::create($insertData);
);

还有帮助函数,您可以在其中看到我添加的所有 ifs,因为我有时会收到一个数组而不是对象:

protected function prepareEmlQueryToBeInsertedToDB(
    Builder $items,
    Collection $columnMapping,
    Closure $callback,
    $orderBy = 'system.system_id'
) 
    // Step through each element of the mapping
    $items->orderBy($orderBy)->select($columnMapping->keys()->toArray())
        ->chunk(5000, function ($items) use ($columnMapping, $callback, $items) 
            foreach ($items AS $item) 

                $values = $columnMapping->mapWithKeys(function ($item, $key) use ($item) 
                    $key = Str::lower($key);

                    if (Str::contains($key, ' as ')) 
                        $column = array_reverse(explode(' as ', $key))[0];
                     else 
                        $column = substr(strrchr($key, "."), 1);
                    

                    if (!$item) 
                        \Log::error("Received damaged item from slave db: ".json_encode($item));
                    

                    if (is_array($item)) 
                        $item = (object)$item;
                    

                    if (!property_exists((object)$item, $column)) 
                        \Log::error("$column does not exist on item from slave db: ".json_encode($item));
                    

                    $value = $item->$column;

                    return [$item => $value];
                );

                if (!$values || $values->isEmpty()) 
                    info('No values: '.json_encode($values));
                

                // Now call the callback method for each item, passing an well prepared array in format:
                // column_name => value
                // so that it can be easily be used with something like static::create()
                $callback($values->toArray());
            
        );

【问题讨论】:

你能告诉我们所涉及的 SQL 查询吗? 是的,当然。我在原始问题中添加了更新 能否提供实际的 SQL 代码,而不仅仅是最终产生 SQL 代码的 Laravel 代码。 请参阅下面的答案,了解我的问题的根本原因。 【参考方案1】:

从 5.4 版开始,Laravel no longer supports configuration of the PDO fetch mode 通过 config/database.php。默认情况下,框架将获取模式设置为PDO::FETCH_OBJ,尽管我们可以通过监听StatementPrepared 事件来覆盖此设置:

Event::listen(StatementPrepared::class, function ($event) 
    $event->statement->setFetchMode(PDO::FETCH_ASSOC);
);

似乎某个排队的作业订阅了此事件并更改了获取模式。如果我们使用queue:work Artisan 控制台命令启动队列工作者,侦听器会等待任何后续作业,因为此命令一次所有启动应用程序em> 工作人员处理的作业。这可以解释为什么重新启动工作人员会暂时解决问题。

因此,更改获取模式的作业必须在完成或失败后将其设置回来。每当我们从作业更改任何全局应用程序状态时,我们都需要小心谨慎。

【讨论】:

@redless81 祝你好运!我想我们可以通过首先将该作业排队,然后运行问题中描述的作业来重现它。 我完全按照您描述的方式验证了它,这就是原因!伙计,非常感谢这个重要的提示。没有意识到这种影响。请记住,这种获取模式更改是基于语句的,但是使用数据库队列确实有点“应用程序范围”!在常规 MVC 上下文中使用它,这至少是一个请求范围内的更改,风险足够大!我同意开发人员可能随时都不会意识到这一点。【参考方案2】:

首先你必须让它工作。添加一个 if/else_if 检查结果是对象还是数组并相应地获取数据。如果您可以使用所有查询都将使用的“BaseDB”类对其进行抽象,那就更好了。

对于第二阶段,将一些日志记录到代码中以查找哪个队列作业正在返回一个数组并导致问题。正如@cyrossignol 提到的,可能有一些由脚本触发的事件监听器。了解更多。还要记住,问题可能出在 MySQL 上。也许您的查询在数据库中触发了某些条件,并且一些异常代码运行并返回一个数组而不是一个对象。

主要是现在修复代码并一点一点地查明实际问题。您现在可能找不到它,但随着时间的推移,您将有足够的信息来找到问题的根源。

【讨论】:

以上是关于数据库查询构建器有时会返回数组而不是作为排队作业运行的对象的主要内容,如果未能解决你的问题,请参考以下文章

结构数组的 Presto 查询返回单个结构元素作为列而不是结构行

我雄辩的查询构建器实例返回空,而 sql 子句返回结果。会欣赏第二只眼睛

直接从模型实例化查询构建器

如果从数据库中删除,Laravel 中的排队作业是不是会停止?

用于标量查询的 Doctrine 返回数组数组

Laravel 查询构建器 - 如何按别名分组,或进行原始 groupBy