web-dev-qa-db-ja.com

列でソートされたインデックスがあるにもかかわらず、クエリプランが依然としてテーブルをソートするのはなぜですか?

私は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)

ご覧のように、どちらのテーブルも並べ替えてマージします。インデックスにソートされた順序ですでに両方の列が含まれている場合、なぜこれが行われるのか理解できません。

説明はありますか?

2
orm

インデックスにソートされた順序ですでに両方の列が含まれている場合、なぜこれが行われるのか理解できません。

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)は、通常低速です。

基本的なテクニック:

3

PostgreSQLは、ソートマージがより高速であると考えています。そして、9.1の私の手でそれは実際には両方のインデックスを歩くよりも高速です。 set enable_sort to off;で自分で試して、どのような計画があり、どれくらいの時間がかかるかを確認できます。

並べ替えはかなり効率的です。 9.1では、行を実際に表示するためにテーブルにアクセスする必要があるため、インデックスのウォーキングは非効率的です。インデックスは可視性情報を格納しません。 9.2はその問題の一部を回避するインデックスのみのスキャンを導入しました。

また、インデックスリーフページは必ずしも論理的な順序であるとは限らないため、インデックスを論理的な順序で歩くと、ランダムなIOが大量に発生する可能性があります。 (もちろん、インデックススキャンの可視性についてテーブルをチェックすることもできます。)

9.1以降、多くの改善が行われました。

4
jjanes

さて、あなたはここにいくつかの質問をリストアップしたので、私はそれらに答えるように最善を尽くします。

クエリは正しいですか?

まず、あなたは言った:

「ページリンクテーブルの(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にあります。次に、フィルタリングしてNULLsを見つけ、カウントします。

そのため、不一致があった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%わかりませんが、ONJOIN部分を変更して、実行しようとしていることのヒントをクエリプランナーに提供してみてください。

_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));
_

クエリプランナーをすぐに使い始めて、より良いクエリプランを使用できるようにするためのアイデアをいくつかブレインストーミングしようとしています。私にはあなたのデータがないので、それらがうまくいくかどうか確信が持てません。

2
Chris