PostgreSQL 重新排序或提示条件(具有昂贵自定义功能的条件)
Posted
技术标签:
【中文标题】PostgreSQL 重新排序或提示条件(具有昂贵自定义功能的条件)【英文标题】:PostgreSQL reordering or hinting conditions (condition with expesive custom function) 【发布时间】:2015-07-01 13:42:40 【问题描述】:短版:
有什么方法可以改变条件的顺序(使用自定义函数)并在 postgreSQL 中加入另一个条件的操作? Postgres 将为每条记录调用该函数,即使连接条件会删除 90% 的记录(我尝试过 8.4 和 9.3 - 两者都相同)
长版: 我有包含 cca 2M 记录的表 blob - 此表包含键和 base64 编码的 blob。这些 blob 大约 2-4KB 大,它们的内部结构几乎不重要 - 我有自定义 C 函数,可以从 blob 中提取一些信息,主要是它的唯一 ID(用作键)和某种客户 ID(用于稍后过滤)。
一个进程不断从外部源填充此表 - 它获取 blob 并将其插入到表 blob 中,并以其 ID 作为键 - 类似于:
INSERT into blobs (blobid,blob) VALUES (getIDfromblob('abcde...'),'abcde...');
现在我的客户端不经常连接到数据库并且需要下载他们需要处理的任何新 blob(根据 blob 中的 customerID) - 每个客户端将只获得整个表的一小部分(cca 1:1000 )。所以我有第二个名为 blobdownloads 的表,其中包含 blobid 和 customerID。此表中的记录意味着具有某些 customerID 的客户端已经看到具有某些 blobID 的 blob(确实下载了它或忽略了它)。我有自定义 C 函数 getCustomerIDfromblob 从 blob 返回 customerID,但它需要解析 blob 并且执行起来相当昂贵 - 它会产生某种过度输出,因此我需要使用 ilike 进行比较。
实际上我遇到了两个问题:首先我需要找到尚未下载的 blob(针对特定的 customerID) - 只是说我需要做一个补充。据我所知,有两种合理的方法 - 外连接或“如果不存在”,从理论的角度来看两者都不完美,但它们至少以合理的方式工作:
SELECT * from blobs LEFT OUTER JOIN blobdownloads USING (blobid)
WHERE (blobdownloads.customerID is null) or (blobdownloads.customerID != 'customer1')
此查询应返回客户 ID 为 customer1 的客户端根本没有看到的所有 blob。这个想法是客户端将使用这个查询,通过它自己的客户 ID 过滤它:
INSERT into blobdownloads ... where getCustomerIDfromblob(blob) ilike '%customer1%' RETURNING *;
并使用 RETURNING 语句将这些插入到 blobdownloads 表中并处理所有返回的 blob。然后它将运行带有否定条件且不返回的相同查询:
INSERT into blobdownloads ... where not (getCustomerIDfromblob(blob) ilike '%customer1%');
最后是我遇到的问题。该解决方案正在运行,并且正在将所有应该发送的 blob 发送到应有的位置,没有冲突,我可以强制客户端重新下载一些 blob(在 blobdownloads 中删除它们),没有并发问题,没有丢失的 blob 等。 . 唯一的问题是查询比应有的要慢得多。问题是查询计划器正在整个表 blob 上使用自定义函数执行条件,然后它通过连接条件过滤结果。由于通常的状态是大多数 blob 已经下载,因此需要的时间比它可能要长得多。
带有外连接的 SELECT 没有 getCustomerIDfromblob(blob) ilike '%customer1%' 条件大约需要 5-30 秒并返回大约 1K 条记录(自上次下载以来的所有新 blob)。当我添加条件时(即使没有 INSERT),生成 cca 10 记录大约需要 5-8 分钟,因为引擎首先对 blob 中的每个 blob 执行自定义函数,而不是仅仅过滤结果。我确实试图找到某种提示,但我能找到的只是像Order of ANDS in Where clause for greatest performance 这样的帖子,基本上是在说“优化器做得最好,不要碰它......”。我试图增加函数的成本,但执行计划没有改变(只有数字变大了:-))。执行计划如下所示:
Nested Loop Anti Join
Join Filter: (blobs.blobid = blobdownloads.blobid) Filter: ((blobdownloads.customerID IS NULL) OR (blobdownloads.customerID <> 'customer1'::text))(3493519887,55)
|--Seq Scan on blobs
| Filter: (getCustomerIDfromBlob(blob) ~~ '%customer1%'(3486446561,93)
|--Materialize (71534,92)
|--Seq Scan on blobdownloads (41476,61)
注 1: 这些函数被声明为 IMMUTABLE 并且它们上有一个功能索引和其他合理的索引(适用于所有条件)。
注2: 我可以通过将 ilike 更改为 = 来改进执行时间。 Postgres 比使用功能索引并且整个查询在 1 秒内运行 - 但我无法从函数中删除额外的输出,更可能的是整个“是否为 customer1”条件将被移动到 C 代码中它可能需要评估更多的 blob 内部数据(这是我无法控制的)。
【问题讨论】:
你为什么认为你需要这个函数?顺便说一句:tl;博士:请向我们展示真正的查询。 我需要这个函数的原因很简单:它使用外部库来进行 blob 解析,正如我所说,它将来可能会做更多,它的开发超出了我的范围......我无法显示真正的查询,但 TL 部分中的查询是真实的,除了表/列名...... 在 blob 中隐藏(键)字段违反了 1NF。只是说... 你是对的,但这是一个特殊情况,我只负责分发 blob... 一个 blob 中可以(共)存在多少个 customerid? 【参考方案1】:连接导致除了 customer1 之外的 所有 客户与 blobs 表相结合,这可能不是您想要的:
SELECT * from blobs LEFT OUTER JOIN blobdownloads USING (blobid)
WHERE blobdownloads.customerID is null
OR blobdownloads.customerID != 'customer1'
;
我认为您的意图是找到所有尚未报告给 customer1 的 blob:
SELECT * from blobs bl
WHERE NOT EXISTS (
SELECT *
FROM blobdownloads bd
WHERE bd.blobid = bl.blobid
AND bd.customerID = 'customer1'
);
【讨论】:
你是对的 - 这是我的错误。但现在我只对一位客户进行了测试,所以没关系。我将重做内部查询,看看它是否有助于解决问题 错误已修复,但问题仍然存在。您使用 NOT EXISTS 的内部查询大约需要 5 秒,但是当我通过调用函数添加外部查询时,大约需要 9 分钟以上是关于PostgreSQL 重新排序或提示条件(具有昂贵自定义功能的条件)的主要内容,如果未能解决你的问题,请参考以下文章