【问题标题】:Improving performance for simple left join in postgreSQL提高 postgreSQL 中简单左连接的性能
【发布时间】:2016-12-06 20:13:00
【问题描述】:

我正在尝试在 postgreSQL 数据库中的两个表之间进行左连接,发现它需要大约 14 分钟才能运行。从现有的 SO 帖子来看,这种类型的连接似乎应该是几秒钟的量级,所以我想知道如何提高这种连接的性能。我正在使用8 GB RAMWindows 8 机器上运行64-bit postgreSQL version 9.4.4,使用pgAdmin III。表结构如下:

表 A:“parcels_qtr”:

包裹(文字)|年(整数)| qtr(文本)| lpid(pk,文本)|

有 1550 万行,每列都有索引,“lpid”是主键。我还通过标准的真空过程运行了这张表。

表 B:“postalvac_qtr”:

包裹(文字)|年(整数)| qtr(文本)| lpid (pk, 文本) | vacCountY (int) |

有 618,000 条记录,除“vacCountY”外的所有字段都被索引,“lpid”是主键。这也经历了一个标准的真空过程。

使用数据输出运行时,大约需要 14 分钟。当使用explain (analyze, buffers) 运行时,需要一分钟多一点的时间。第一个问题 - 这种性能差异完全归因于打印数据还是其他原因?

第二个问题,我可以把这个运行时间缩短到几秒钟吗?

这是我的 SQL 代码:

EXPLAIN (ANALYZE, BUFFERS)
select a.parcel,
   a.lpid,
   a.yr,
   a.qtr,
   b."vacCountY"
from parcels_qtr as a
left join postalvac_qtr as b
on a.lpid = b.lpid;

这是我的解释语句的结果:https://explain.depesz.com/s/uKkK

我对 postgreSQL 还很陌生,因此非常感谢耐心和解释!

【问题讨论】:

  • yr (int) | qtr (text) 这看起来像年和季度,为什么不使用日期字段代替(丢失文本字段)并对其执行 date_trunc() ? all fields except "vacCountY" are indexed and "lpid" is the primary key. 请学习一些有关数据建模的知识,数据库不应该是带有索引的电子表格。
  • 嘿@joop,感谢有关日期字段的提示。我会试试的。我在这些其他字段上添加了索引,因为它们可能会参与未来查询的连接。关于数据建模,您建议我查看哪些资源?你有什么特别的书或教程吗?
  • 从您的问题中不清楚您的表格是什么意思。 parcels_qtr 看起来像计数字段“vacCountY”的预聚合。什么是 lpid(用作连接字段)。 parcel (text) | yr (int) | qtr (text) | lpid (pk, text) | vacCountY (int) | 是什么意思。与您的克林贡符号 IMHP 相比,这里的大多数人更喜欢普通的 sql DDL 表定义。
  • 这种性能差异是否完全归因于打印数据:是的,查询需要 53 秒,其余时间用于获取和显示结果,假设有 1500 万行。

标签: sql database performance postgresql join


【解决方案1】:

您要求数据库做很多工作。光看解释计划,就是:

  1. 读入整个表格 (postalvac_qtr)
  2. 基于lpid构建哈希
  3. 读入另一个更大的表格 (parcels_qtr)
  4. 散列每个 15MM lpids,并将它们与现有散列表匹配

这些桌子有多大?您可以通过发出以下命令进行检查:

SELECT pg_size_pretty(pg_relation_size('parcels_qtr'));

我几乎可以肯定这个散列连接正在溢出到磁盘,并且它的结构方式(“给我所有来自这两个表的数据”),这是不可能的不会。

索引没有帮助,也不能。只要您要查询整个表,使用索引只会让事情变慢——postgres 无论如何都必须遍历整个表,所以它不妨发出顺序扫描。

至于为什么查询的性能与explain analyze 不同,我怀疑你是对的。 1- 向您的客户端发送 1500 万行,2- 尝试显示它的组合,将导致超出实际查询的显着减速。

那么,你能做些什么呢?

首先,这个查询想要做什么?您希望多久获取这两个表中的所有数据,完全未经过滤?如果这很常见,您可能需要考虑回到需求阶段并找出另一种方法来满足该需求(例如,获取给定年份和季度的所有数据是否合理?)。如果它不常见(例如,每天导出),那么 1-14 分钟可能就可以了。

其次,您应该确保您的表格没有臃肿。如果您的桌子上有大量的updatedelete 流量,那么它们会随着时间的推移而增长。 autovacuum 守护进程可以帮助解决这个问题,但偶尔发出vacuum full 也会有所帮助。

第三,您可以尝试调整您的数据库配置。在postgresql.conf 中,有一些参数,例如服务器可用于磁盘缓存的预期 RAM 量,以及服务器可用于排序或连接的 RAM 量(在溢出到磁盘之前)。通过修改这些类型的参数,您也许可以提高速度。

第四,您可能想要重新访问您的架构。您是否希望将年份和季度作为两个单独的列,还是使用 date 类型的单个列会更好?您想要text 键,还是使用bigint(串行或派生自text 列)会更好,这可能会更快地加入? parcelyrqtr 字段是否确实需要在两个表中,还是在一个表中重复数据?

无论如何,我希望这会有所帮助。

【讨论】:

  • 哇,这非常有帮助,非常感谢您花时间解释。您提到的关于进行表扫描的内容很有意义。我认为这将是一个相当罕见的任务,所以也许你是对的,那 14 分钟并不是世界末日。很高兴知道对于我正在尝试做的事情,这不是一个令人发指的运行时间。正如您和@joop 所建议的那样,我将检查数据库配置中的参数,并使用数据类型。很多东西可以在这里尝试,谢谢!
  • Parker,既然你是新来的,我可以指出,在这里说“谢谢”的首选方式是对好的问题和有用的答案进行投票(一旦你有足够的声誉这样做) ,并接受对您提出的任何问题最有帮助的答案(这也会稍微提升您的声誉)。
  • 谢谢大家!回答采纳!避免显示数据可能是最大的帮助,使用更合适的数据类型也是如此。
  • 如果两个表中的所有字段都被索引,我们应该期望 Postgres 选择合并连接,它会执行得更好,对吧?
  • 我希望 postgres 提供一个“哈希集连接”计划,它只会在内存中构建一个 lpid 的哈希集,并从磁盘上的源表中读取行数据。我敢打赌,在这种情况下它会表现得更好,因为对于 97% 的行,它会通过内存快速确定没有匹配的行,并且它只需要在加载剩余的 3% 时敲击磁盘。不断检查溢出到磁盘上的哈希听起来更糟
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-16
  • 2013-05-18
  • 1970-01-01
相关资源
最近更新 更多