Php 使用Propel在PostgreSQL中遍历一个大表

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以及其他在这里没有任何重要意义的列。此表包含有关设备上安装的应用程序及其上次活动日期

我最近有一个任务,就是使用Prope在PostgreSQL中迭代一个大表(~40KK记录),遇到了性能问题,包括内存限制和执行速度。我的脚本已经运行了22个小时

任务是根据某些条件(过去6个月内未处于活动状态)检索记录,并将其存档(移动到另一个表)以及其他表中的所有相关实体

我的脚本正在处理的主表有几个列:
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, …]);