首页 > 解决方案 > CakePHP 3 ORM 产生允许的内存大小已用完

问题描述

我在 CakePHP 3.5.13 中有一个应用程序。我已经烘焙了一个未根据 Cake 的命名约定编写的遗留数据库。

应用程序的一部分有一个包含 255,693 行的表,称为substances. 相关的 CAS 编号已放入名为 的表中cas,并且这两个表之间的映射称为cas_substances

我正在尝试使用 CakePHP 的 ORM 编写一个搜索给定 CAS 的查询。

我似乎无法在 ORM 中编写我想要执行的查询,即使它的 MySQL 等价物非常简单。假设我正在搜索所有具有包含“1234”的 CAS 的物质 ID,查询在 MySQL 中将如下所示:

SELECT DISTINCT( s.id ) FROM substances s 
JOIN cas AS cas 
ON ( (cas.value LIKE '%1234%') ) 
JOIN cas_substances AS cassub 
ON (s.id = cassub.substance_id AND cassub.cas_id = cas.id)

直接在数据库上运行(通过 Navicat)在 0.39 秒内给我 63 行 - 预期。

因此,在尝试在 Cake 中编写此代码时,我将我的Table类配置如下:

// src/Model/Table/CasTable.php
public function initialize(array $config)
{
    parent::initialize($config);

    $this->setTable('cas');
    $this->setDisplayField('value');
    $this->setPrimaryKey('id');

    $this->belongsToMany('Substances', [
        'foreignKey' => 'cas_id',
        'targetForeignKey' => 'substance_id',
        'joinTable' => 'cas_substances'
    ]);
}


// src/Model/Table/CasSubstancesTable.php
public function initialize(array $config)
{
    parent::initialize($config);

    $this->setTable('cas_substances');
    $this->setDisplayField('id');
    $this->setPrimaryKey('id');

    $this->belongsTo('Cas', [
        'foreignKey' => 'cas_id',
        'joinType' => 'INNER'
    ]);
    $this->belongsTo('Substances', [
        'foreignKey' => 'substance_id',
        'joinType' => 'INNER'
    ]);
}


// src/Model/Table/SubstancesTable.php
public function initialize(array $config)
{
    parent::initialize($config);

    $this->setTable('substances');
    $this->setDisplayField('name');
    $this->setPrimaryKey('id');

    $this->belongsToMany('Cas', [
        'foreignKey' => 'substance_id',
        'targetForeignKey' => 'cas_id',
        'joinTable' => 'cas_substances'
    ]);
    // ...
 }

然后在控制器中,我试图获得不同的(MySQL 等价物DISTINCT()substances.id

// Begin the query
$query = $Substances->find()->select(['id' => 'id'])->distinct();

然后修改查询以过滤我的 CAS:

$query = $query->contain('Cas', function ($q) {
    return $q->where(['Cas.value' => '%'.$this->request->getData('cas_number').'%']);
});

当我尝试使用debug($query->all())它输出结果时,给了我一个 PHP 致命错误:

允许的内存大小 134217728 字节用尽(尝试分配 20480 字节)

仔细检查后,我似乎没有应用基于 CAS 过滤查询的条件。如果我这样做debug($query->all()->count()),它会给我 255,693 - 整个物质表,没有任何过滤。

我有几个问题:

  1. 如何编写此查询来过滤关联数据?我在这里的工作是基于通过条件包含部分文档。

  2. 我担心返回了多少数据。如果我运行与该查询等效的 MySQL,它会返回substances.id我想要的结果。Cake 正在生产大型对象——我知道这是因为 ORM 的工作原理——但这里肯定有内存影响吗?我需要将查询结果写入另一个表。使用 ORM 比仅仅编写普通 SQL 更好(或更容易)然后做CREATE TABLE tmp_table AS . $sql_select_string(前面给出$sql_select_stringSELECT语句在哪里)?

标签: cakephp-3.0

解决方案


为什么你的代码内存不足

当您使用 contains 时,它会告诉 cake 检索所有记录及其相关记录

换句话说,您的代码将获得 255,693 行物质,并且对于每一行它的 Cas 编号,但只有与 LIKE 匹配的那些

相反,您想检索所有且仅具有匹配 Cas 编号的记录

一种可能的解决方案

看来你需要matching方法here

$cas_number = $this->request->getData('cas_number');
$query = $Substances->find()
    ->select(['id' => 'Substances.id'])
    ->distinct()
    ->matching('Cas', function ($q) use ($cas_number) {
        return $q->where([
             'Cas.value LIKE' => '%'.$cas_number.'%'
        ]);
     });

这样 cake 加入三个表并执行搜索

通常这个查询给出重复的记录,你必须分组来过滤它们。在这种情况下,您正在使用 DISTINCT 来完成这项工作

这会给你一个听起来像的查询

SELECT DISTINCT Substances.id AS `id` 
FROM substances Substances
INNER JOIN cas_substances CasSubstances 
ON Substances.id = CasSubstances.substance_id 
INNER JOIN cas Cas 
ON (
    Cas.value like %1234% 
    AND Cas.id = CasSubstances.cas_id
)

在此处查看手册

更简单的解决方案

因为你只需要你可以简单地做的 id

$Substances->Cas->find()
    ->where([
         'Cas.value LIKE' => '%'.$cas_number.'%'
    ])
    ->contain(['CasSubstances'])
    ->select(['id' => 'CasSubstances.substance_id'])
    ->distinct();

这将为您节省一个加入


推荐阅读