tstzrange
変数の正確な等価性が必要な更新を実行しています。〜1M行が変更され、クエリには〜13分かかります。の結果はここでEXPLAIN ANALYZE
見ることができ、実際の結果はクエリプランナーが推定した結果とは大きく異なります。問題は、インデックススキャンで単一の行が返されることを期待していることです。t_range
これは、範囲タイプの統計が他のタイプの統計とは異なる方法で保存されるという事実に関連しているようです。pg_stats
列のビューを見ると、n_distinct
is -1であり、他のフィールド(most_common_vals
などmost_common_freqs
)は空です。
ただし、t_range
どこかに統計が保存されている必要があります。完全に同等ではなくt_rangeで「within」を使用する非常に類似した更新の実行には約4分かかり、実質的に異なるクエリプランを使用します(こちらを参照)。一時テーブルのすべての行と履歴テーブルのかなりの部分が使用されるため、2番目のクエリプランは理にかなっています。さらに重要なことは、クエリプランナーがのフィルタに対してほぼ正しい行数を予測することt_range
です。
の分布t_range
は少し珍しいです。このテーブルを使用して別のテーブルの履歴状態を保存していますが、他のテーブルへの変更は大きなダンプで一度に発生するため、の値はあまり多くありませんt_range
。の一意の値のそれぞれに対応するカウントはt_range
次のとおりです。
t_range | count
-------------------------------------------------------------------+---------
["2014-06-12 20:58:21.447478+00","2014-06-27 07:00:00+00") | 994676
["2014-06-12 20:58:21.447478+00","2014-08-01 01:22:14.621887+00") | 36791
["2014-06-27 07:00:00+00","2014-08-01 07:00:01+00") | 1000403
["2014-06-27 07:00:00+00",infinity) | 36791
["2014-08-01 07:00:01+00",infinity) | 999753
t_range
上記のdistinctのカウントは完了しているため、カーディナリティは〜3Mです(このうち〜1Mは、いずれかの更新クエリの影響を受けます)。
クエリ1のパフォーマンスがクエリ2よりもはるかに低いのはなぜですか?私の場合、クエリ2が適切な代替品ですが、正確な範囲の均等性が本当に必要な場合、Postgresでよりスマートなクエリプランを使用するにはどうすればよいですか?
インデックス付きのテーブル定義(無関係な列の削除):
Column | Type | Modifiers
---------------------+-----------+------------------------------------------------------------------------------
history_id | integer | not null default nextval('gtfs_stop_times_history_history_id_seq'::regclass)
t_range | tstzrange | not null
trip_id | text | not null
stop_sequence | integer | not null
shape_dist_traveled | real |
Indexes:
"gtfs_stop_times_history_pkey" PRIMARY KEY, btree (history_id)
"gtfs_stop_times_history_t_range" gist (t_range)
"gtfs_stop_times_history_trip_id" btree (trip_id)
クエリ1:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND sth.t_range = '["2014-08-01 07:00:01+00",infinity)'::tstzrange;
クエリ2:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND '2014-08-01 07:00:01+00'::timestamptz <@ sth.t_range;
Q1は999753行を更新し、Q2は999753 + 36791 = 1036544を更新します(つまり、一時テーブルでは、時間範囲条件に一致するすべての行が更新されます)。
@ypercubeのコメントに応答してこのクエリを試しました。
クエリ3:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND sth.t_range <@ '["2014-08-01 07:00:01+00",infinity)'::tstzrange
AND '["2014-08-01 07:00:01+00",infinity)'::tstzrange <@ sth.t_range;
クエリプランと結果(ここを参照)は、前の2つのケースの中間でした(約6分)。
2016/02/05編集
1.5年後にデータにアクセスできなくなったため、同じ構造(インデックスなし)でカーディナリティが類似したテストテーブルを作成しました。jjanesの答えは、原因が更新に使用される一時テーブルの順序である可能性があることを提案しました。track_io_timing
(Amazon RDSを使用して)アクセスできないため、仮説を直接テストできませんでした。
(lower(t_range),upper(t_range))
、平等をチェックするため、通常のbtreeインデックスを追加することです。
(a = b)
を2つの「含む」条件に変換した場合はどうなります(a @> b AND b @> a)
か?計画は変わりますか?