Php 使用Propel在PostgreSQL中遍历一个大表
我最近有一个任务,就是使用Prope在PostgreSQL中迭代一个大表(~40KK记录),遇到了性能问题,包括内存限制和执行速度。我的脚本已经运行了22个小时 任务是根据某些条件(过去6个月内未处于活动状态)检索记录,并将其存档(移动到另一个表)以及其他表中的所有相关实体 我的脚本正在处理的主表有几个列:Php 使用Propel在PostgreSQL中遍历一个大表,php,postgresql,pdo,propel,Php,Postgresql,Pdo,Propel,我最近有一个任务,就是使用Prope在PostgreSQL中迭代一个大表(~40KK记录),遇到了性能问题,包括内存限制和执行速度。我的脚本已经运行了22个小时 任务是根据某些条件(过去6个月内未处于活动状态)检索记录,并将其存档(移动到另一个表)以及其他表中的所有相关实体 我的脚本正在处理的主表有几个列:id、device\u id、application\u id、last\u activity\u date以及其他在这里没有任何重要意义的列。此表包含有关设备上安装的应用程序及其上次活动日期
id
、device\u id
、application\u id
、last\u activity\u date
以及其他在这里没有任何重要意义的列。此表包含有关设备上安装的应用程序及其上次活动日期的信息。可能有多条记录具有相同的设备id
和不同的应用程序id
。以下是表格中的一个示例:
id | device_id | application_id | last_init_date
----------+-----------+----------------+---------------------
1 | 1 | 1 | 2013-09-24 17:09:01
2 | 1 | 2 | 2013-09-19 20:36:23
3 | 1 | 3 | 2014-02-11 00:00:00
4 | 2 | 4 | 2013-09-29 20:12:54
5 | 3 | 5 | 2013-08-31 19:41:05
因此,如果此表中特定设备id
的最大上次活动日期
超过6个月,则该设备被视为足够旧,可以存档。以下是查询:
SELECT device_id
FROM device_applications
GROUP BY device_id
HAVING MAX(last_init_date) < '2014-06-16 08:00:00'
选择设备\u id
来自设备应用程序
按设备\u id分组
最大值(上次初始日期)<'2014-06-16 08:00:00'
在Propel中,它看起来像:
\DeviceApplicationsQuery::create()
->select('DeviceId')
->groupByDeviceId()
->having('MAX(device_applications.LAST_INIT_DATE) < ?', $date->format('Y-m-d H:i:s'))
->find();
\DeviceApplicationQuery::create()
->选择('DeviceId')
->groupByDeviceId()
->具有('MAX(device\u applications.LAST\u INIT\u DATE)<?',$DATE->format('Y-m-dh:i:s'))
->查找();
正如您所理解的,结果集太大,无法放入内存中,因此我必须以某种方式将其拆分为块
问题是:在这种情况下,为了减少内存消耗和加快脚本速度,最好选择什么策略?
在我的回答中,我将向您展示我迄今为止的发现。我知道遍历一个大表的三种策略 1.良好的旧限额/抵销 这种方法的问题是,数据库实际上检查记录,您希望使用
OFFSET
跳过这些记录。以下是一段引用自:
被OFFSET子句跳过的行仍然必须在服务器内部计算;因此,较大的>偏移量可能是低效的
下面是一个简单的示例(不是我最初的查询):
执行计划:
Limit (cost=37.93..50.57 rows=100 width=264) (actual time=0.630..0.835 rows=100 loops=1)
-> Index Scan using device_applications_device_id_application_id_unique on device_applications (cost=0.00..5315569.97 rows=42043256 width=264) (actual time=0.036..0.806 rows=400 loops=1)
Total runtime: 0.873 ms
请特别注意索引扫描部分的实际结果。它显示,PostgreSQL使用的是400记录,即偏移量(300)加上限制(100)。因此,这种方法效率很低,特别是考虑到初始查询的复杂性
2.按某列排列
我们可以通过使查询与表的范围一起工作来避免limit/offset方法的限制,这些范围是通过按列对表进行切片来实现的
为了澄清,让我们想象一下,你有一个包含100条记录的表,你可以将这个表分成5个范围,每个范围20条记录:0-20,20-40,40-60,60-80,80-100,然后使用较小的子集。在我的例子中,我们可以根据device\u id
来确定范围的列。查询如下所示:
SELECT device_id
FROM device_applications
WHERE device_id >= 1 AND device_id < 1000
GROUP BY device_id
HAVING MAX(last_init_date) < '2014-06-16 08:00:00';
在Propel中,您可以通过PropelOnDemandFormatter
类使用此PDO的功能。因此,最终代码:
$devApps = \DeviceApplicationsQuery::create()
->setFormatter('\PropelOnDemandFormatter')
->select('DeviceId')
->groupByDeviceId()
->having('MAX(device_applications.LAST_INIT_DATE) < ?', $date->format('Y-m-d H:i:s'))
->find();
/** @var \DeviceApplications $devApp */
foreach ($devApps as $devApp) {
// Do something
}
$devApps=\DeviceApplicationsQuery::create()
->setFormatter(“\PropertionDemandFormatter”)
->选择('DeviceId')
->groupByDeviceId()
->具有('MAX(device\u applications.LAST\u INIT\u DATE)<?',$DATE->format('Y-m-dh:i:s'))
->查找();
/**@var\DeviceApplications$devApp*/
foreach($devApps作为$devApp){
//做点什么
}
在这里,调用
find()
不会获取数据,而是通过按需创建对象来创建集合。如果您使用PHP,并且不需要将结果水合为PHP实体(对象),则可以使用PommProject/Foundation包。脚本将简单地类似于
<?php
$loader = require __DIR__.'/vendor/autoload.php';
$pomm = new PommProject\Foundation\Pomm(
[
'project_name' => ['dsn' => 'pgsql://user:pass@host:port/db_name']
]
);
$sql = <<<SQL
with
removed as (delete from a_table where val1 = $* and … returning *)
insert into another_table select * from removed
SQL;
$pomm['your_project']
->getQueryManager()
->query($sql, [$value1, …]);
为什么不能只通过一个查询将所有记录移动到存档中?40k记录并没有那么多。40KK是4000万条,4000万条记录要多得多,但如果你的硬件能够处理这么多的数据,速度可能会更快。是否在单个查询中检查了包含删除和插入的公共表表达式?ORM(任何类型的ORM)和性能始终是一个挑战,ORM不是为它而设计的。是的,我知道ORM通常不适合这种情况,但问题是主要实体(设备)有很多相关实体(实际上还有5个表,带有外键约束)也必须存档。归档相关记录的逻辑是在ORM类中实现的。这就是我所做的,我在回答中指出了这一点。感谢您的提示,我不知道Pomm项目。感谢您的回答,最终使用->setFormatter(ModelCriteria::FORMAT_ON_DEMAND)()
$devApps = \DeviceApplicationsQuery::create()
->setFormatter('\PropelOnDemandFormatter')
->select('DeviceId')
->groupByDeviceId()
->having('MAX(device_applications.LAST_INIT_DATE) < ?', $date->format('Y-m-d H:i:s'))
->find();
/** @var \DeviceApplications $devApp */
foreach ($devApps as $devApp) {
// Do something
}
<?php
$loader = require __DIR__.'/vendor/autoload.php';
$pomm = new PommProject\Foundation\Pomm(
[
'project_name' => ['dsn' => 'pgsql://user:pass@host:port/db_name']
]
);
$sql = <<<SQL
with
removed as (delete from a_table where val1 = $* and … returning *)
insert into another_table select * from removed
SQL;
$pomm['your_project']
->getQueryManager()
->query($sql, [$value1, …]);