私はPostgres 9.1を使用しています。2つのテーブルに参加しています。
wikidb=> \d page
Table "public.page"
Column | Type | Modifiers
-----------------------+---------------+------------------------------
page_id | bigint | not null
page_namespace | integer | not null default 0
page_title | text | not null default ''::text
[...]
Indexes:
[...]
"page_page_namespace_page_title_idx" UNIQUE, btree (page_namespace, page_title)
wikidb=> \d pagelinks
Table "public.pagelinks"
Column | Type | Modifiers
-------------------+---------+----------------------------
pl_from | bigint | not null default 0::bigint
pl_namespace | integer | not null default 0
pl_title | text | not null default ''::text
[...]
Indexes:
[...]
"pagelinks_pl_namespace_pl_title_pl_from_idx" btree (pl_namespace, pl_title, pl_from)
(名前空間、タイトル)列に両方のインデックスがあることに注意してください。 pagelinksテーブルの(pl_namespace、pl_title)ペアが(table_namespace、page_title)としてページテーブルに表示されないペアの数を調べることに興味があります。
結合を使用すると、次の計画が得られます。
wikidb=> explain SELECT COUNT(*)
FROM pagelinks
LEFT OUTER JOIN page
ON page.page_namespace = pagelinks.pl_namespace AND
page.page_title = pagelinks.pl_title
WHERE page.page_namespace IS NULL AND page.page_title IS NULL;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Aggregate (cost=1310748.56..1310748.57 rows=1 width=0)
-> Merge Anti Join (cost=1189384.02..1310748.56 rows=1 width=0)
Merge Cond: ((pagelinks.pl_title = page.page_title) AND (pagelinks.pl_namespace = page.page_namespace))
-> Sort (cost=1144343.89..1164498.31 rows=8061768 width=19)
Sort Key: pagelinks.pl_title, pagelinks.pl_namespace
-> Seq Scan on pagelinks (cost=0.00..219551.68 rows=8061768 width=19)
-> Sort (cost=45038.32..45975.52 rows=374880 width=20)
Sort Key: page.page_title, page.page_namespace
-> Seq Scan on page (cost=0.00..10331.80 rows=374880 width=20)
(9 rows)
ご覧のように、どちらのテーブルも並べ替えてマージします。インデックスにソートされた順序ですでに両方の列が含まれている場合、なぜこれが行われるのか理解できません。
説明はありますか?
インデックスにソートされた順序ですでに両方の列が含まれている場合、なぜこれが行われるのか理解できません。
Postgres 9.1を使用しています。
最も重要なのは、index-only scansがPostgres9.2に追加された主要なパフォーマンス機能です。 Postgres Wikiの詳細
現在のバージョンへのアップグレードを検討してください とにかくPostgres 9.1は古くなっています 。
古いバージョンでは、Postgresはどちらの方法でもテーブルにアクセスする必要があるため、追加されたオーバーヘッドに打ち勝つために、インデックスはより多くのパフォーマンス改善を提供する必要があります。また、UNIQUE
インデックスは、(page_namespace, page_title)
ごとに最大で1つの行が存在することを示しているため、テーブル全体を数えている間、インデックスはあまり役に立ちません。
1つの小さな改善:
SELECT COUNT(*) AS ct
FROM pagelinks l
LEFT JOIN page p ON p.page_namespace = l.pl_namespace
AND p.page_title = l.pl_title
WHERE p.page_namespace IS NULL;
AND p.page_title IS NULL;
たった1つの列をテストするだけで、証明すべきことがすべて証明されます。
それ以外の場合、クエリはほとんど最適です。たぶんNOT EXISTS
は競争できます:
SELECT count(*) AS ct
FROM pagelinks l
WHERE NOT EXISTS (
SELECT 1
FROM page
WHERE page_namespace = l.pl_namespace
AND page_title = l.pl_title
);
行式NOT IN
での結合または(pl_namespace,pl_title)
は、通常低速です。
基本的なテクニック:
PostgreSQL
は、ソートマージがより高速であると考えています。そして、9.1の私の手でそれは実際には両方のインデックスを歩くよりも高速です。 set enable_sort to off;
で自分で試して、どのような計画があり、どれくらいの時間がかかるかを確認できます。
並べ替えはかなり効率的です。 9.1では、行を実際に表示するためにテーブルにアクセスする必要があるため、インデックスのウォーキングは非効率的です。インデックスは可視性情報を格納しません。 9.2はその問題の一部を回避するインデックスのみのスキャンを導入しました。
また、インデックスリーフページは必ずしも論理的な順序であるとは限らないため、インデックスを論理的な順序で歩くと、ランダムなIOが大量に発生する可能性があります。 (もちろん、インデックススキャンの可視性についてテーブルをチェックすることもできます。)
9.1以降、多くの改善が行われました。
さて、あなたはここにいくつかの質問をリストアップしたので、私はそれらに答えるように最善を尽くします。
まず、あなたは言った:
「ページリンクテーブルの(pl_namespace、pl_title)ペアが(page_namespace、page_title)としてページテーブルに表示される(pl_namespace、pl_title)ペアの数を調べることに興味があります。」
あなたが実行しているクエリはそれに対して正しいですか、それとも説明を誤解していませんか?走れば
_EXPLAIN SELECT COUNT(*)
FROM pagelinks
LEFT OUTER JOIN page
ON page.page_namespace = pagelinks.pl_namespace AND
page.page_title = pagelinks.pl_title
WHERE page.page_namespace IS NULL AND page.page_title IS NULL;
_
次に、JOIN
に移動して、pagelinks
のすべての行エントリが保持され、NULL
が存在しなかったときにpage
行エントリに存在するようにします。 page
にあります。次に、フィルタリングしてNULL
sを見つけ、カウントします。
そのため、不一致があったpagelinks
テーブルのエントリ数を数えていますpage
テーブル内。
実際にこのクエリを実行しようとしていますか?
_EXPLAIN SELECT COUNT(*)
FROM pagelinks
JOIN page
ON page.page_namespace = pagelinks.pl_namespace AND
page.page_title = pagelinks.pl_title;
_
したがって、プランナは sort merge join がデータを結合するための最良の計画であると決定したため、そのアクションを完了するには何らかの形式のソートが必要です。しかし、私はあなたの要点を理解しました。つまり、namespace
およびtitle
フィールドは、インデックス付けのためにすでにソートされています。
ただし、この場合、プランナはpage
テーブルとpagelinks
テーブルの両方で順次スキャンを使用しており、すべてのエントリにアクセスします。このため、これらのエントリは順番に読み取られるため、ソートされていないものとして扱われます。テーブルヒープの。
これで問題が解決するかどうかは100%わかりませんが、ON
のJOIN
部分を変更して、実行しようとしていることのヒントをクエリプランナーに提供してみてください。
_EXPLAIN SELECT COUNT(*)
FROM pagelinks
JOIN page
ON (page.page_namespace,page.page_title) = (pagelinks.pl_namespace,pagelinks.pl_title);
_
またはおそらく:
_EXPLAIN SELECT COUNT(*)
FROM pagelinks
WHERE (pl_namespace,pl_title) IN
(SELECT page_namespace, page_title FROM page);
_
正直なところ、このクエリを書き換える方法はおそらくたくさんあります。プランナーがあなたの望むものを生み出すまで、私はそれに取り組んでいきます。
最後の質問では、COUNT(*)
がテーブルに触れる必要がある理由を尋ねます。さて、データベースはこのカウントをどこかから取得する必要があるため、インデックス全体をスキャンしてエントリ数をカウントするか、テーブル全体をスキャンしてエントリ数をカウントする必要があります。たまたま、テーブルスキャンがこの操作を完了するより速い方法です。
あなたのフィードバックに基づいて、あなたはまた試みるかもしれません:
_SELECT COUNT(*) FROM pagelinks WHERE ctid NOT IN(
SELECT ctid FROM pagelinks
WHERE (pl_namespace,pl_title) IN
(SELECT page_namespace, page_title FROM page));
_
または
_WITH page_data AS(
SELECT page_namespace, page_title FROM page
)
SELECT COUNT(*) FROM pagelinks WHERE ctid NOT IN(
SELECT ctid FROM pagelinks
WHERE (pl_namespace,pl_title) IN
(SELECT page_namespace, page_title FROM page_data));
_
クエリプランナーをすぐに使い始めて、より良いクエリプランを使用できるようにするためのアイデアをいくつかブレインストーミングしようとしています。私にはあなたのデータがないので、それらがうまくいくかどうか確信が持てません。