web-dev-qa-db-ja.com

ORが関係する場合、Postgresはインデックスcondの代わりにフィルタを選択します

毎日約2,000万のレコードが追加されるテーブルがあり、そこからページ分割して人々がその中のすべてのデータにアクセスできるようにしていますが、クエリ時間は「まとも」である必要があります(私の場合) 30秒未満/クエリとして定義)。

これを行うには、過去にキーセットのページネーションを使用していましたが、この特定のクエリとテーブルでは、クエリ時間が非常に遅くなりました。これは、クエリプランナーが1日分のデータを除外して、インデックス条件スキャンの代わりにフィルターを実行します。

テーブルは次のようになります。

create table mmsi_positions_archive
(
    id bigserial not null
        constraint mmsi_positions_archive_pkey
            primary key,
    position_id uuid,
    previous_id uuid,
    mmsi bigint not null,
    collection_type varchar not null,
    accuracy numeric,
    maneuver numeric,
    rate_of_turn numeric,
    status integer,
    speed numeric,
    course numeric,
    heading numeric,
    position geometry(Point,4326),
    timestamp timestamp with time zone not null,
    updated_at timestamp with time zone default now(),
    created_at timestamp with time zone default now()
);

create index ix_mmsi_positions_archive_mmsi
    on mmsi_positions_archive (mmsi);

create index ix_mmsi_positions_archive_position_id
    on mmsi_positions_archive (position_id);

create index ix_mmsi_positions_archive_timestamp_mmsi_id_asc
    on mmsi_positions_archive (timestamp, id);

私がページ付けをしようとしている列はtimestampidです。これは、timestampのテーブル統計ターゲットも更新し、それを最大に設定したためです。値は10 000で、テーブルを分析しました。

テーブルも四半期ごとにパーティション化されていますが、現時点では、1つのパーティションのデータのみを操作しています。

高速クエリ

SELECT id
FROM mmsi_positions_archive
WHERE timestamp > '2019-03-10 00:00:00.000000+00:00'
  AND timestamp <= '2019-03-11 00:00:00+00:00'
ORDER BY timestamp, id
LIMIT 100

これは、次のクエリプランを提供します(mmsi_positions_archiveテーブル自体は空です。すべてのデータは*_p2019_q1 テーブル):

Limit  (cost=0.60..5.39 rows=100 width=16) (actual time=0.053..0.089 rows=100 loops=1)
  ->  Merge Append  (cost=0.60..773572.19 rows=16149157 width=16) (actual time=0.053..0.082 rows=100 loops=1)
"        Sort Key: mmsi_positions_archive.""timestamp"", mmsi_positions_archive.id"
        ->  Sort  (cost=0.01..0.02 rows=1 width=16) (actual time=0.009..0.009 rows=0 loops=1)
"              Sort Key: mmsi_positions_archive.""timestamp"", mmsi_positions_archive.id"
              Sort Method: quicksort  Memory: 25kB
              ->  Seq Scan on mmsi_positions_archive  (cost=0.00..0.00 rows=1 width=16) (actual time=0.001..0.001 rows=0 loops=1)
                    Filter: (("timestamp" > '2019-03-10 00:00:00+00'::timestamp with time zone) AND ("timestamp" <= '2019-03-11 00:00:00+00'::timestamp with time zone))
        ->  Index Only Scan using mmsi_positions_archive_p2019q1_timestamp_id_index on mmsi_positions_archive_p2019q1  (cost=0.58..571707.70 rows=16149156 width=16) (actual time=0.043..0.067 rows=100 loops=1)
              Index Cond: (("timestamp" > '2019-03-10 00:00:00+00'::timestamp with time zone) AND ("timestamp" <= '2019-03-11 00:00:00+00'::timestamp with time zone))
              Heap Fetches: 0
Planning time: 67.023 ms
Execution time: 0.128 ms

キーセットページネーションクエリ(遅い)

SELECT id
FROM mmsi_positions_archive
WHERE (timestamp > '2019-03-10 00:00:00.000000+00:00'
           OR (timestamp = '2019-03-10 00:00:00.000000+00:00' AND id >  1032749689))
  AND timestamp <= '2019-03-11 00:00:00+00:00'
ORDER BY timestamp, id
LIMIT 100

これは、この説明を提供し、最終的には実行が非常に遅くなります:

Limit  (cost=0.60..25.08 rows=100 width=16) (actual time=332918.152..332918.192 rows=100 loops=1)
  ->  Merge Append  (cost=0.60..41278140.09 rows=168591751 width=16) (actual time=332918.152..332918.189 rows=100 loops=1)
"        Sort Key: mmsi_positions_archive.""timestamp"", mmsi_positions_archive.id"
        ->  Sort  (cost=0.01..0.02 rows=1 width=16) (actual time=0.004..0.004 rows=0 loops=1)
"              Sort Key: mmsi_positions_archive.""timestamp"", mmsi_positions_archive.id"
              Sort Method: quicksort  Memory: 25kB
              ->  Seq Scan on mmsi_positions_archive  (cost=0.00..0.00 rows=1 width=16) (actual time=0.001..0.001 rows=0 loops=1)
                    Filter: (("timestamp" <= '2019-03-11 00:00:00+00'::timestamp with time zone) AND (("timestamp" > '2019-03-10 00:00:00+00'::timestamp with time zone) OR (("timestamp" = '2019-03-10 00:00:00+00'::timestamp with time zone) AND (id > 1032749689))))
        ->  Index Only Scan using mmsi_positions_archive_p2019q1_timestamp_id_index on mmsi_positions_archive_p2019q1  (cost=0.58..39170743.18 rows=168591750 width=16) (actual time=332918.147..332918.181 rows=100 loops=1)
              Index Cond: ("timestamp" <= '2019-03-11 00:00:00+00'::timestamp with time zone)
              Filter: (("timestamp" > '2019-03-10 00:00:00+00'::timestamp with time zone) OR (("timestamp" = '2019-03-10 00:00:00+00'::timestamp with time zone) AND (id > 1032749689)))
              Rows Removed by Filter: 953622052
              Heap Fetches: 0
Planning time: 0.778 ms
Execution time: 332918.226 ms

私の理解から、これはインデックス条件Index Cond: ("timestamp" <= '2019-03-11 00:00:00+00'::timestamp with time zoneは、インデックスデータの約2,000万* 70行を超えるseqスキャンを実行し、それらを除外します。

回避策

私はいくつかのテストを行い、問題がステートメントのORであることを理解しました。どちらも、私がORしない場合、迅速な計画を提供します。だから私はそれを切り替えて、必要なデータを取得するためにUNIONクエリを作成しました:

SELECT id
FROM (
         SELECT *
         FROM (
                  SELECT id        AS id,
                         timestamp AS timestamp
                  FROM mmsi_positions_archive
                  WHERE timestamp = '2019-03-10 00:00:00.000000+00:00'
                    AND id > 1032749689
                  ORDER BY timestamp, id
                  LIMIT 100
              ) keyset
         UNION
         SELECT *
         FROM (
                  SELECT id        AS id,
                         timestamp AS timestamp
                  FROM mmsi_positions_archive
                  WHERE timestamp > '2019-03-10 00:00:00.000000+00:00'
                    AND timestamp <= '2019-03-11 00:00:00+00:00'
                  ORDER BY timestamp, id
                  LIMIT 100
              ) all_after
     ) archive_ids
ORDER BY timestamp, id
LIMIT 100

高速クエリと次のクエリプランを生成する:

Limit  (cost=34.27..34.52 rows=100 width=16) (actual time=0.232..0.242 rows=100 loops=1)
  ->  Sort  (cost=34.27..34.77 rows=200 width=16) (actual time=0.231..0.238 rows=100 loops=1)
"        Sort Key: mmsi_positions_archive.""timestamp"", mmsi_positions_archive.id"
        Sort Method: quicksort  Memory: 34kB
        ->  HashAggregate  (cost=22.63..24.63 rows=200 width=16) (actual time=0.151..0.167 rows=200 loops=1)
"              Group Key: mmsi_positions_archive.id, mmsi_positions_archive.""timestamp"""
              ->  Append  (cost=0.71..21.63 rows=200 width=16) (actual time=0.028..0.111 rows=200 loops=1)
                    ->  Limit  (cost=0.71..12.24 rows=100 width=16) (actual time=0.028..0.049 rows=100 loops=1)
                          ->  Merge Append  (cost=0.71..17.43 rows=145 width=16) (actual time=0.027..0.046 rows=100 loops=1)
                                Sort Key: mmsi_positions_archive.id
                                ->  Index Scan using mmsi_positions_archive_pkey on mmsi_positions_archive  (cost=0.12..8.14 rows=1 width=16) (actual time=0.010..0.010 rows=0 loops=1)
                                      Index Cond: (id > 1032749689)
                                      Filter: ("timestamp" = '2019-03-10 00:00:00+00'::timestamp with time zone)
                                ->  Index Only Scan using mmsi_positions_archive_p2019q1_timestamp_id_index on mmsi_positions_archive_p2019q1  (cost=0.58..7.46 rows=144 width=16) (actual time=0.017..0.028 rows=100 loops=1)
                                      Index Cond: (("timestamp" = '2019-03-10 00:00:00+00'::timestamp with time zone) AND (id > 1032749689))
                                      Heap Fetches: 0
                    ->  Limit  (cost=0.60..5.39 rows=100 width=16) (actual time=0.012..0.049 rows=100 loops=1)
                          ->  Merge Append  (cost=0.60..773572.19 rows=16149157 width=16) (actual time=0.011..0.044 rows=100 loops=1)
"                                Sort Key: mmsi_positions_archive_1.""timestamp"", mmsi_positions_archive_1.id"
                                ->  Sort  (cost=0.01..0.02 rows=1 width=16) (actual time=0.005..0.005 rows=0 loops=1)
"                                      Sort Key: mmsi_positions_archive_1.""timestamp"", mmsi_positions_archive_1.id"
                                      Sort Method: quicksort  Memory: 25kB
                                      ->  Seq Scan on mmsi_positions_archive mmsi_positions_archive_1  (cost=0.00..0.00 rows=1 width=16) (actual time=0.001..0.001 rows=0 loops=1)
                                            Filter: (("timestamp" > '2019-03-10 00:00:00+00'::timestamp with time zone) AND ("timestamp" <= '2019-03-11 00:00:00+00'::timestamp with time zone))
                                ->  Index Only Scan using mmsi_positions_archive_p2019q1_timestamp_id_index on mmsi_positions_archive_p2019q1 mmsi_positions_archive_p2019q1_1  (cost=0.58..571707.70 rows=16149156 width=16) (actual time=0.006..0.031 rows=100 loops=1)
                                      Index Cond: (("timestamp" > '2019-03-10 00:00:00+00'::timestamp with time zone) AND ("timestamp" <= '2019-03-11 00:00:00+00'::timestamp with time zone))
                                      Heap Fetches: 0
Planning time: 1.059 ms
Execution time: 0.312 ms

UNIONアプローチを使用するようにクエリを書き換えても大丈夫ですが、Postgresが一目で簡単に理解できるクエリで同じ高速の結果を得るのに役立つ方法があるかどうか疑問に思いますOR

これはAWS Aurora Postgres 9.6でも実行しています。私たちはいくつかのメジャーリリースが遅れていることを認識しており、できるだけ早くアップグレードすることを計画していますが、現時点では、これを機能させる必要があります。 :)

2
gaqzi

幸い、これはPostgreSQLで非常に簡単です。これは、インデックスを利用できる「行の値」(または複合値)間の比較をサポートするためです。

だからあなたは書くことができます:

WHERE (timestamp, id) > ('2019-03-10 00:00:00+00:00', 1032749689)
  AND timestamp <= '2019-03-11 00:00:00+00:00'
ORDER BY timestamp, id
LIMIT 100

そのような行の値の比較はlexicographicalであり、まさに望みどおりです。

これがその機能の ドキュメントリンク です。

3
Laurenz Albe