CakePHP 3 ORM 生成允许的内存大小耗尽
CakePHP 3 ORM producing Allowed memory size exhausted
我在 CakePHP 3.5.13 中有一个应用程序。我烘焙了一个未根据 Cake 的命名约定编写的遗留数据库。
应用程序的一部分有 255,693 行的 table,称为 substances
。相关的 CAS 编号已被放入一个名为 cas
的 table 中,并且这两个 table 之间的映射称为 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 致命错误:
Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
仔细检查后,我的基于 CAS 过滤查询的条件似乎没有得到应用。如果我这样做 debug($query->all()->count())
它会给我 255,693 - 所有物质 table 没有任何过滤。
我有几个问题:
如何编写此查询来过滤关联数据?我在这里的工作基于文档的 Passing Conditions to Contain 部分。
我担心返回了多少数据。如果我 运行 相当于该查询的 MySQL,它只会返回 substances.id
,这正是我想要的。 Cake 正在生成大对象——我知道这是因为 ORM 的工作原理——但这里肯定有内存问题?我需要将查询结果写入另一个 table。使用 ORM 比仅仅编写 vanilla SQL 然后执行 CREATE TABLE tmp_table AS . $sql_select_string
(其中 $sql_select_string
是之前给出的 SELECT
语句)有何好处(或更容易)?
为什么你的代码内存不足
当您使用 contain 时,这会告诉 cake 检索所有记录及其相关记录
换句话说,您的代码将获得 255,693 行物质,以及每一行的 Cas 编号,但只有与 LIKE
匹配的那些
相反,您想要检索所有且仅检索具有匹配 Cas 编号的记录
一个可能的解决方案
看来你需要matching
这里的方法
$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
)
查看手册here
更简单的解决方案
因为您只需要 id 就可以了
$Substances->Cas->find()
->where([
'Cas.value LIKE' => '%'.$cas_number.'%'
])
->contain(['CasSubstances'])
->select(['id' => 'CasSubstances.substance_id'])
->distinct();
这将为您节省一次加入
我在 CakePHP 3.5.13 中有一个应用程序。我烘焙了一个未根据 Cake 的命名约定编写的遗留数据库。
应用程序的一部分有 255,693 行的 table,称为 substances
。相关的 CAS 编号已被放入一个名为 cas
的 table 中,并且这两个 table 之间的映射称为 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 致命错误:
Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
仔细检查后,我的基于 CAS 过滤查询的条件似乎没有得到应用。如果我这样做 debug($query->all()->count())
它会给我 255,693 - 所有物质 table 没有任何过滤。
我有几个问题:
如何编写此查询来过滤关联数据?我在这里的工作基于文档的 Passing Conditions to Contain 部分。
我担心返回了多少数据。如果我 运行 相当于该查询的 MySQL,它只会返回
substances.id
,这正是我想要的。 Cake 正在生成大对象——我知道这是因为 ORM 的工作原理——但这里肯定有内存问题?我需要将查询结果写入另一个 table。使用 ORM 比仅仅编写 vanilla SQL 然后执行CREATE TABLE tmp_table AS . $sql_select_string
(其中$sql_select_string
是之前给出的SELECT
语句)有何好处(或更容易)?
为什么你的代码内存不足
当您使用 contain 时,这会告诉 cake 检索所有记录及其相关记录
换句话说,您的代码将获得 255,693 行物质,以及每一行的 Cas 编号,但只有与 LIKE
匹配的那些相反,您想要检索所有且仅检索具有匹配 Cas 编号的记录
一个可能的解决方案
看来你需要matching
这里的方法
$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
)
查看手册here
更简单的解决方案
因为您只需要 id 就可以了
$Substances->Cas->find()
->where([
'Cas.value LIKE' => '%'.$cas_number.'%'
])
->contain(['CasSubstances'])
->select(['id' => 'CasSubstances.substance_id'])
->distinct();
这将为您节省一次加入