有没有办法可以加快针对此特定视图的查询?

Bry*_*yan 3 postgresql performance join database-design index-tuning postgresql-performance

我在 PostgreSQL 数据库中有三个表,我通过视图和一些连接进行查询。

CREATE TABLE network_info (
  network         CIDR          NOT NULL,
  some_info       TEXT          NULL,
  PRIMARY KEY (network)
);

CREATE TABLE ipaddr_info (
  ipaddr          INET          NOT NULL,
  some_info       INT           NULL,
  PRIMARY KEY (ipaddr, some_info)
);

CREATE TABLE ipaddrs (
  addr            INET          NOT NULL,
  PRIMARY KEY (addr)
);

CREATE VIEW ipaddr_summary AS
SELECT DISTINCT
  i.addr                  AS ip_address,
  a.some_info             AS network_info,
  COUNT(b.ipaddr)         AS ip_info_count
FROM ipaddrs AS i
LEFT JOIN network_info AS a
  ON (i.addr << a.network)
LEFT JOIN ipaddr_info AS b
  ON (i.addr = b.ipaddr)
GROUP BY i.addr, a.some_info
;
Run Code Online (Sandbox Code Playgroud)

所有表现在都有大约 150K 行,运行需要很长时间(大约 3 小时) SELECT * from ipaddr_summary;在运行 PostgreSQL 9.3 的具有 2G 内存的 Intel Pentium 4 2.8GHz 双核上。

有没有一种方法可以重组或优化这个特定的模式或视图以加快查询速度,还是硬件问题?我将在云中启动一个强大的 VM 并进行测试,但想看看是否有一种方法可以在不使用更多硬件的情况下对其进行优化。

Erw*_*ter 10

可能存在硬件问题 - 我们应该如何知道?但是查询肯定存在问题。

首先DISTINCT从你的VIEW定义中删除。它什么都不做(但使事情复杂化并减慢了速度)。关于 SO 的相关答案及解释:

到达这个(清理过的)查询:

SELECT i.addr      AS ip_address
     , a.some_info AS network_info
     , COUNT(b.ipaddr) AS ip_info_count
FROM   ipaddrs           i
LEFT   JOIN ipaddr_info  b  ON i.addr = b.ipaddr
LEFT   JOIN network_info a  ON i.addr << a.network
GROUP  BY 1,2;
Run Code Online (Sandbox Code Playgroud)

接下来,查询看起来很可疑,好像它出错了。两个不相关的连接可以将行相乘:

每个表中有 15 万行,就有可能产生巨大的(非预期的)笛卡尔积。我有根据的猜测,你真的想要这个:

SELECT addr        AS ip_address
     , a.some_info AS network_info
     , b.ip_info_count
FROM   ipaddrs i
LEFT   JOIN (
   SELECT  ipaddr AS addr, count(*) AS ip_info_count
   FROM    ipaddr_info
   GROUP   BY 1
  ) b USING (addr)
LEFT   JOIN network_info a ON i.addr << a.network
Run Code Online (Sandbox Code Playgroud)

我怀疑GROUP BY在外部也不需要SELECT现在。除了固定计数外,这也可能快几个数量级,避免代理交叉连接。

您可以先加入ipaddrs(特别是如果您有谓词从中过滤行)然后聚合,或者首先在子查询中聚合,如显示。此变体的有用性在很大程度上取决于数据分布。这对少数 ipaddr有大数目的人来说很棒。细节:

最后,您需要索引支持ipaddr和之间的相等性addrPRIMARY KEY. 无论如何,对整个表的查询可能正在使用顺序扫描。

对于“包含于”操作<<符,您需要一个 GIN 或 GiST 索引。最好的选择是Postgres 9.4 中新的inet_opsGiST 索引运算符类(支持数据类型和):inetcidr

CREATE INDEX ON network_info USING gist (network inet_ops);
Run Code Online (Sandbox Code Playgroud)

不确定索引是否可以用于普通INNER(或OUTER)连接。暂时无法测试。也许您需要相关子查询或LATERAL连接来利用索引:

SELECT addr AS ip_address
     , a.network_info
     , b.ip_info_count
FROM   ipaddrs i
LEFT   JOIN (
   SELECT  ipaddr AS addr, count(*) AS ip_info_count
   FROM    ipaddr_info
   GROUP   BY 1
  ) b USING (addr)
LEFT   JOIN LATERAL (
   SELECT some_info AS network_info
   FROM   network_info
   WHERE  network >> i.addr
   ) a ON TRUE;
Run Code Online (Sandbox Code Playgroud)

在旧版本中建立索引的建议: