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和之间的相等性addr被PRIMARY 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)
在旧版本中建立索引的建议: