さまざまなタイムスタンプ(2列)でのクエリの最適化


96

Ubuntu 12.04でPostgreSQL 9.1を使用しています。

時間範囲内のレコードを選択する必要があります。テーブルにtime_limitsは2つのtimestampフィールドと1つのintegerプロパティがあります。実際のテーブルには、このクエリに関係しない追加の列があります。

create table (
   start_date_time timestamp,
   end_date_time timestamp, 
   id_phi integer, 
   primary key(start_date_time, end_date_time,id_phi);

このテーブルには、およそ2Mのレコードが含まれています。

次のようなクエリには膨大な時間がかかりました。

select * from time_limits as t 
where t.id_phi=0 
and t.start_date_time <= timestamp'2010-08-08 00:00:00'
and t.end_date_time   >= timestamp'2010-08-08 00:05:00';

そこで、別のインデックスを追加してみました-PKの逆です:

create index idx_inversed on time_limits(id_phi, start_date_time, end_date_time);

パフォーマンスが向上したという印象を受けました。テーブルの中央にあるレコードにアクセスする時間は、より合理的であるようです。40〜90秒の間です。

ただし、時間範囲の中央の値の場合はまだ数十秒です。そして、テーブルの終わりをターゲットにすると(時系列的に)、さらに2倍になります。

explain analyzeは初めてこのクエリプランを取得しようとしました。

 Bitmap Heap Scan on time_limits  (cost=4730.38..22465.32 rows=62682 width=36) (actual time=44.446..44.446 rows=0 loops=1)
   Recheck Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
   ->  Bitmap Index Scan on idx_time_limits_phi_start_end  (cost=0.00..4714.71 rows=62682 width=0) (actual time=44.437..44.437 rows=0 loops=1)
         Index Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
 Total runtime: 44.507 ms

depesz.comの結果を参照してください。

検索を最適化するにはどうすればよいですか?あなたはすべての時間が列を一度2つのタイムスタンプスキャン費やされて見ることができますid_phiに設定されているが0。そして、タイムスタンプの大きなスキャン(60K行!)がわかりません。彼らは主キーでインデックス付けされていませんidx_inversedか?

タイムスタンプタイプから別のものに変更する必要がありますか?

GISTおよびGINインデックスについて少し読みました。カスタムタイプの特定の条件でより効率的になる可能性があることを収集します。私のユースケースにとって実行可能なオプションですか?


1
まあ45秒です。45msと表示される理由がわかりません。45msの速さであれば、文句を言うことすらありません。または、分析を実行する時間かもしれません。ダンノ しかし、私が測定するのは40/50秒です。
ステファンローランド

2
explain analyze出力で報告される時間は、クエリがサーバーで必要な時間です。クエリに45秒かかる場合、データベースからクエリを実行するプログラムへのデータの転送に追加時間がかかります。結局62622行であり、各行が大きい(例:長いvarcharまたはtext列がある)場合、転送時間に影響を与える可能性があります劇的に。
-a_horse_with_no_name

@a_horse_with_no_name:rows=62682 rowsはプランナーの推定値です。クエリは0行を返します。(actual time=44.446..44.446 rows=0 loops=1)
アーウィンブランドステッター

@ErwinBrandstetter:ああ、そうだね。見落としていました。しかし、実行時間についてのEXPLAIN ANALYZYの出力はまだ見たことがありません。
-a_horse_with_no_name

回答:


162

Postgres 9.1以降の場合:

CREATE INDEX idx_time_limits_ts_inverse
ON time_limits (id_phi, start_date_time, end_date_time DESC);

ほとんどの場合、インデックスのソート順はほとんど関係ありません。Postgresは、実質的に高速で逆方向にスキャンできます。ただし、複数の列に対する範囲クエリの場合、大きな違いが生じる可能性があります。密接に関連する:

クエリを検討してください。

SELECT *
FROM   time_limits
WHERE  id_phi = 0
AND    start_date_time <= '2010-08-08 00:00'
AND    end_date_time   >= '2010-08-08 00:05';

id_phiインデックスの最初の列の並べ替え順序は関係ありません。同等性=)がチェックされているため、最初に来る必要があります。あなたはその権利を得ました。この関連する回答の詳細:

Postgresはid_phi = 0すぐにジャンプして、一致するインデックスの次の2列を考慮することができます。これらは、逆ソート順<=>=)の範囲条件でクエリされます。私のインデックスでは、適格な行が最初に来ます。Bツリーインデックス1で可能な限り高速な方法である必要があります。

  • 必要なのstart_date_time <= somethingは、インデックスのタイムスタンプが最も早いことです。
    • 適格である場合は、列3も確認します。
      最初の行が適格となるまで再帰します(超高速)。
  • 必要なのend_date_time >= somethingは、インデックスの最新のタイムスタンプが最初にあることです。
    • 条件を満たしている場合、最初の行が行を取得しなくなるまで行をフェッチし続けます(超高速)。
      列2の次の値に進みます。

Postgresは、前方または後方にスキャンできます。インデックスの作成方法では、最初の2つの列で一致するすべての行を読み取ってから、3番目の列でフィルター処理する必要があります。インデックスとORDER BYマニュアルの章を必ずお読みください。それはあなたの質問にかなり合います。

最初の2列で一致する行数は?テーブルの時間範囲の開始に近いものは
ほとんどありませんstart_date_time。しかし、ほとんどすべてのid_phi = 0は表の時系列の終わりにあります!そのため、開始時間が遅くなるとパフォーマンスが低下します。

プランナーの見積もり

プランナrows=62682は、クエリ例の見積もりをします。それらのうち、資格があるものはありません(rows=0)。テーブルの統計ターゲットを増やすと、より良い推定値が得られる場合があります。2.000.000行の場合...

ALTER TABLE time_limits ALTER start_date_time SET STATISTICS 1000;
ALTER TABLE time_limits ALTER end_date_time   SET STATISTICS 1000;

...支払うかもしれません。またはさらに高い。この関連する回答の詳細:

id_phi(いくつかの異なる値のみ、均等に分布している)ではなく、タイムスタンプ(異なる値のロット、不均一に分布している)には必要ないでしょう。
また、改善されたインデックスではそれほど重要ではないと思います。

CLUSTER / pg_repack

さらに高速にしたい場合は、テーブル内の行の物理的な順序を合理化できます。短時間(たとえば、営業時間外)だけテーブルをロックして、テーブルを書き換え、インデックスに従って行を並べ替える余裕がある場合:

ALTER TABLE time_limits CLUSTER ON idx_time_limits_inversed;

同時アクセスの場合、pg_repackを検討してください。これは排他ロックなしで同じことを実行できます。

いずれにせよ、テーブルから読み取る必要があるブロックが少なくなり、すべてが事前にソートされます。これは、テーブルへの書き込みが物理的なソート順を断片化することにより、時間の経過とともに悪化する1回限りの効果です。

Postgres 9.2+のGiSTインデックス

1 pg 9.2+では、別の、おそらくより高速なオプションがあります:範囲列のGiSTインデックス。

  • そこを内蔵しているため、範囲種類timestamptimestamp with time zonetsrangetstzrange。通常、btreeインデックスは、などの追加integer列の方が高速ですid_phi。小さくて安価でも維持できます。ただし、結合インデックスを使用すると、クエリはおそらく全体的に高速になります。

  • テーブル定義を変更するか、式インデックスを使用します

  • 手元にある複数列のGiSTインデックスbtree_gistには、を含める演算子クラスを提供する追加のモジュール(データベースごとに1回)もインストールする必要がありますinteger

三連!複数列機能のGiSTインデックス

CREATE EXTENSION IF NOT EXISTS btree_gist;  -- if not installed, yet

CREATE INDEX idx_time_limits_funky ON time_limits USING gist
(id_phi, tsrange(start_date_time, end_date_time, '[]'));

使用する演算子「の範囲が含まれている」@>今、あなたのクエリに:

SELECT *
FROM   time_limits
WHERE  id_phi = 0
AND    tsrange(start_date_time, end_date_time, '[]')
    @> tsrange('2010-08-08 00:00', '2010-08-08 00:05', '[]')

Postgres 9.3+のSP-GiSTインデックス

SP-のGiSTインデックスはさらに高速クエリのこの種のかもしれない- を除いこと、マニュアルを引用します

現在、B-tree、GiST、GIN、およびBRINインデックスタイプのみがマルチカラムインデックスをサポートしています。

Postgres 12でも同様です。上のインデックスと上の2番目のインデックス
を組み合わせる必要がありspgistます。追加のオーバーヘッドがあるため、これが競合できるかどうかはわかりません。コラム のみのベンチマークに関連する回答:(tsrange(...))btree(id_phi)
tsrange


78
SOとDBAのそれぞれの答えは、非常に高い付加価値/専門知識であり、ほとんどの場合、最も完成度が高いことを少なくとも1回だけ伝える必要があります。一度言ってください:尊敬!。
ステファンローランド

1
メルシービエン!:)それで、あなたはより速い結果を得ましたか?
アーウィンブランドステッター

私の集中的に厄介なクエリから生成された大きな一括コピーを終了させなければならないので、プロセスを本当に遅くし、質問をする前に何時間も回っていました。しかし、私は計算し、明日の朝まで回転させて、それが終了し、新しいテーブルが明日に満たされる準備ができたことに決めました。私は仕事中にインデックスを同時に作成しようとしましたが、アクセスが多すぎるために、インデックスの作成をロックする必要があります。ソリューションで明日も同じテスト時間を繰り返します。また、debian / ubuntuの9.2へのアップグレード方法も調べました;-)。
ステファンローランド

2
@StephaneRolland:クエリの実行に40秒以上かかっているのに、Explain分析の出力に45ミリ秒が表示されるのは興味深いことです。
-a_horse_with_no_name

1
@John:Postgresはインデックスを前後に走査できますが、同じスキャンで方向を変更することはできません。理想的には、ノードごとにすべての適格な行が最初(または最後)にありますが、最良の結果を得るには、すべての列で同じ配置(クエリ述語の一致)でなければなりません。
アーウィンブランドステッター

5

ただし、アーウィンの答えはすでに包括的なものです。

タイムスタンプの範囲タイプは、Jeff DavisのTemporal拡張機能を備えたPostgreSQL 9.1で使用できます:https : //github.com/jeff-davis/PostgreSQL-Temporal

注:機能が制限されています(Timestamptzを使用し、 '[)'スタイルのみを重複させることができます)。また、PostgreSQL 9.2にアップグレードする理由は他にもたくさんあります。


3

別の順序で複数列のインデックスを作成してみることができます。

primary key(id_phi, start_date_time,end_date_time);

マルチカラムインデックスのインデックスの順序にも関連する同様の質問を一度投稿しました。重要なのは、最初に最も制限の厳しい条件を使用して検索スペースを削減することです。

編集:私の間違い。これで、このインデックスが既に定義されていることがわかります。


私はすでに両方のインデックスを持っています。主キーは、他のですが、あなたが提案するインデックスがすでに存在している、とあなたが見た場合に使用されているものである場合を除き説明:Bitmap Index Scan on idx_time_limits_phi_start_end
ステファン・ロラン

1

急速に増加しました(1秒から70ミリ秒)

多くの測定値と多くのレベル(l列)(30s、1m、1hなど)の集計を含むテーブルがあり$s、開始と$e終了の2つの範囲バインド列があります。

2つのマルチカラムインデックスを作成しました。1つは開始用、もう1つは終了用です。

選択クエリを調整しました。開始範囲が指定された範囲にある範囲を選択します。さらに、終了範囲が指定された範囲にある範囲を選択します。

説明は、インデックスを効率的に使用する2つの行のストリームを示します。

インデックス:

drop index if exists agg_search_a;
CREATE INDEX agg_search_a
ON agg (measurement_id, l, "$s");

drop index if exists agg_search_b;
CREATE INDEX agg_search_b
ON agg (measurement_id, l, "$e");

クエリを選択:

select "$s", "$e", a, t, b, c from agg
where 
    measurement_id=0 
    and l =  '30s'
    and (
        (
            "$s" > '2013-05-01 02:05:05'
            and "$s" < '2013-05-01 02:18:15'
        )
        or 
        (
             "$e" > '2013-05-01 02:00:05'
            and "$e" < '2013-05-01 02:18:05'
        )
    )

;

説明する:

[
  {
    "Execution Time": 0.058,
    "Planning Time": 0.112,
    "Plan": {
      "Startup Cost": 10.18,
      "Rows Removed by Index Recheck": 0,
      "Actual Rows": 37,
      "Plans": [
    {
      "Startup Cost": 10.18,
      "Actual Rows": 0,
      "Plans": [
        {
          "Startup Cost": 0,
          "Plan Width": 0,
          "Actual Rows": 26,
          "Node Type": "Bitmap Index Scan",
          "Index Cond": "((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$s\" > '2013-05-01 02:05:05'::timestamp without time zone) AND (\"$s\" < '2013-05-01 02:18:15'::timestamp without time zone))",
          "Plan Rows": 29,
          "Parallel Aware": false,
          "Actual Total Time": 0.016,
          "Parent Relationship": "Member",
          "Actual Startup Time": 0.016,
          "Total Cost": 5,
          "Actual Loops": 1,
          "Index Name": "agg_search_a"
        },
        {
          "Startup Cost": 0,
          "Plan Width": 0,
          "Actual Rows": 36,
          "Node Type": "Bitmap Index Scan",
          "Index Cond": "((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$e\" > '2013-05-01 02:00:05'::timestamp without time zone) AND (\"$e\" < '2013-05-01 02:18:05'::timestamp without time zone))",
          "Plan Rows": 39,
          "Parallel Aware": false,
          "Actual Total Time": 0.011,
          "Parent Relationship": "Member",
          "Actual Startup Time": 0.011,
          "Total Cost": 5.15,
          "Actual Loops": 1,
          "Index Name": "agg_search_b"
        }
      ],
      "Node Type": "BitmapOr",
      "Plan Rows": 68,
      "Parallel Aware": false,
      "Actual Total Time": 0.027,
      "Parent Relationship": "Outer",
      "Actual Startup Time": 0.027,
      "Plan Width": 0,
      "Actual Loops": 1,
      "Total Cost": 10.18
    }
      ],
      "Exact Heap Blocks": 1,
      "Node Type": "Bitmap Heap Scan",
      "Plan Rows": 68,
      "Relation Name": "agg",
      "Alias": "agg",
      "Parallel Aware": false,
      "Actual Total Time": 0.037,
      "Recheck Cond": "(((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$s\" > '2013-05-01 02:05:05'::timestamp without time zone) AND (\"$s\" < '2013-05-01 02:18:15'::timestamp without time zone)) OR ((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$e\" > '2013-05-01 02:00:05'::timestamp without time zone) AND (\"$e\" < '2013-05-01 02:18:05'::timestamp without time zone)))",
      "Lossy Heap Blocks": 0,
      "Actual Startup Time": 0.033,
      "Plan Width": 44,
      "Actual Loops": 1,
      "Total Cost": 280.95
    },
    "Triggers": []
  }
]

秘nodesは、計画ノードに必要な行のみが含まれることです。以前は、プランノードでを選択したために数千行を取得しall points from some point in time to the very end、次のノードで不要な行を削除しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.