php - 使用 Propel 遍历 PostgreSQL 中的大表

标签 php postgresql pdo propel

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

任务是根据某些条件检索记录(过去 6 个月不活动)并将其存档(移至另一个表)以及其他表中的所有相关实体。

我的脚本正在处理的主表有几列:iddevice_idapplication_idlast_activity_date 和其他,在这里没有任何重要意义。此表包含有关设备上安装的应用程序及其上次事件日期的信息。可能有多个记录具有相同的 device_id 和不同的 application_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

因此,如果此表中特定 device_id 的最大 last_activity_date 早于 6 个月,则该设备被视为足够旧,可以存档。这是查询:

SELECT device_id
FROM device_applications
GROUP BY device_id
HAVING MAX(last_init_date) < '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();

正如您所知,结果集太大,无法放入内存,因此我必须以某种方式将其分成 block 。

问题是:在这种情况下选择什么策略来减少内存消耗并加快脚本速度? 在我的回答中,我将向您展示迄今为止我发现的内容。

最佳答案

我知道遍历大表的三种策略。

1。好的旧限制/偏移量

这种方法的问题在于,数据库实际上会检查您想要使用 OFFSET 跳过的记录。这是 doc 的引用:

The rows skipped by an OFFSET clause still have to be computed inside the server; therefore a large > OFFSET might be inefficient.

这是一个简单的示例(不是我最初的查询):

explain (analyze)
SELECT *
FROM device_applications
ORDER BY device_id
LIMIT 100
OFFSET 300;

执行计划:

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。按某列排列

我们可以通过使查询与表的范围一起工作来避免限制/偏移方法的限制,这些范围是通过按列对表进行切片而实现的。

为了澄清,让我们想象一下您有一个包含 100 条记录的表,您可以将该表分为五个范围,每个范围有 20 条记录:0 - 20、20 - 40、40 - 60、60 - 80、80 - 100,然后处理较小的子集。就我而言,我们可以选择的列是 device_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';

它按device_id对记录进行分组,提取范围并在last_init_date上应用条件。当然,可能(并且在大多数情况下)不会有与条件匹配的记录。因此,这种方法的问题是您必须扫描整个表,即使您要查找的记录只是所有记录的 5%。

3。使用光标

我们需要的是 cursor 。游标允许迭代结果集,而无需立即获取整个数据。在 PHP 中,当您迭代 PDOStatement 时,您会使用游标。 。一个简单的例子:

$stmt = $dbh->prepare("SELECT * FROM table");
$stmt->execute();

// Iterate over statement using a cursor
foreach ($stmt as $row) {
    // Do something
}

在 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
}

此处对 find() 的调用不会获取数据,而是会根据需要创建对象来创建一个集合。

关于php - 使用 Propel 遍历 PostgreSQL 中的大表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27501914/

相关文章:

php - go-pear.bat 文件根本不会安装 PEAR

php - 如何在 OOP PDO 中正确使用 MySql SUM 函数?

php - 如何在 PHP 中通过 CheckBox 填充数据库单元格是或否?

php - 我如何使用 php 创建基于模式的 xml 文档?

postgresql - 在 postgresql 中提取周数的匹配年份

node.js - nodeJS 将数据插入 PostgreSQL 错误

sql - 向现有 PostgreSQL 索引添加唯一约束

php - PDO 事务是否涵盖 PDO::query()?

php - PDO、准备好的语句和 SQL 注入(inject)

php - 在 phpmailer 主题中使用 Acute 时出现问题