ビューといくつかの結合を介してクエリを実行しているPostgreSQLデータベースに3つのテーブルがあります。
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
;
現在、すべてのテーブルの行数は約15万行で、実行に非常に長い時間(約3時間)が必要ですSELECT * from ipaddr_summary;
PostgreSQL 9.3を実行する2Gのメモリを搭載したIntel Pentium 4 2.8GHzデュアルコア。
この特定のスキーマまたはビューを再構成または最適化してクエリを高速化する方法はありますか?それともハードウェアの問題ですか?クラウドで強力なVM=を起動してテストしますが、ハードウェアを増やすだけで最適化する方法があるかどうかを確認したいと考えていました。
ハードウェアの問題もある可能性があります-どのように知る必要がありますか?しかし、クエリには確かに問題があります。
最初にまず、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;
次に、クエリは非常にうまくいかないように疑わしく見えます。 2つの無相関結合は行を乗算できます。
各テーブルに15万行あるため、huge(意図しない)デカルト積の可能性があります。私の教育的な推測、あなたは本当にこれが欲しい:
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
今はGROUP BY
も外側のSELECT
には不要だと思います。カウントを修正するだけでなく、これは数桁も速くなり、プロキシのクロス結合が回避されます。
最初にipaddrs
に結合し(特に、述語から行をフィルタリングしている場合)、次に集計するか、表示されているようにサブクエリで最初に集計します。このバリアントの有用性は、データの分散に大きく依存します。 fewipaddr
の数が多い場合に最適です。詳細:
最後に、index supportが必要です。 ipaddr
とaddr
の等価性は、PRIMARY KEY
のデフォルトのbtreeインデックスでカバーされます。とにかく、テーブル全体に対するクエリは、おそらく順次スキャンを使用しています。
「に含まれる」演算子<<
演算子の場合、GINまたはGistインデックスが必要です。最良のオプションは、Postgresの新しい (inet_ops
Gist index operator class です9.4(データ型inet
およびcidr
をサポート):
CREATE INDEX ON network_info USING Gist (network inet_ops);
インデックスを単純な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;
古いバージョンでのインデックス作成に関するアドバイス:
あなたの<<はインデックスを利用していないと思います。あなたの主キーがbtreeインデックスを作成し、その特定の操作をインデックス可能にするために必要なのはGistインデックスだからです。 (説明を使用して、分析を確認します)。そのインデックスが使用されていない場合は、
のようなことをやってみてください
CREATE INDEX idx_network_info_network_Gist
ON network_info USING Gist(network inet_ops);
CREATE INDEX idx_ipaddr_info_ipaddr_Gist
ON ipaddr_info USING Gist(ipaddr inet_ops);
そして、あなたのaddrとipaddrカラムで同様に行います。