Postgres 9.4.1に次の形式で約325万行のテーブルがあります
CREATE TABLE stats
(
id serial NOT NULL,
type character varying(255) NOT NULL,
"references" jsonb NOT NULL,
path jsonb,
data jsonb,
"createdAt" timestamp with time zone NOT NULL,
CONSTRAINT stats_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
type
は、50文字以下の単純な文字列です。
references
列は、キー値のリストを持つオブジェクトです。基本的に、単純なキー値の任意のリストであり、1レベルだけの深さで、値は常に文字列です。かもしれない
{
"fruit": "Plum"
"car": "toyota"
}
それとも
{
"project": "2532"
}
createdAt
タイムスタンプは、常にデータベースから生成されるわけではありません(ただし、値が指定されていない場合、デフォルトで生成されます)
現在、テストデータのみを含むテーブルを使用しています。このデータでは、すべての行にproject
キーが参照として含まれています。したがって、プロジェクトキーを持つ行は325万行あります。 project
参照には、正確に400,000の異なる値があります。 type
フィールドには5つの異なる値しかありませんが、これは本番環境では数百以下になるでしょう。
したがって、次のクエリをすばやく実行できるようにテーブルにインデックスを付けようとしています。
SELECT
EXTRACT(Epoch FROM (MAX("createdAt") - MIN("createdAt")))
FROM
stats
WHERE
stats."references"::jsonb ? 'project' AND
(
stats."type" = 'event1' OR
(
stats."type" = 'event2' AND
stats."createdAt" > '2015-11-02T00:00:00+08:00' AND
stats."createdAt" < '2015-12-03T23:59:59+08:00'
)
)
GROUP BY stats."references"::jsonb->> 'project'
クエリは、同じ参照を持つ2つの統計行に基づいて、2つのイベント間の時間距離を返します。この場合はproject
です。 type
と選択されたreference
の値ごとに1つの行しかありませんが、返される結果が0である行がない場合もあります(これは、後で別の部分で平均化されます)より大きなクエリ)。
createdAt
type
列とreferences
列にインデックスを作成しましたが、クエリ実行プランが代わりにフルスキャンを実行しているようです。
インデックス
CREATE INDEX "stats_createdAt_references_type_idx"
ON stats
USING btree
("createdAt", "references", type COLLATE pg_catalog."default");
実行計画:
HashAggregate (cost=111188.31..111188.33 rows=1 width=38)
(actual time=714.499..714.499 rows=0 loops=1)
Group Key: ("references" ->> 'project'::text)
-> Seq Scan on stats (cost=0.00..111188.30 rows=1 width=38)
(actual time=714.498..714.498 rows=0 loops=1)
Filter: (
(("references" ? 'project'::text)
AND ((type)::text = 'event1'::text)) OR
(((type)::text = 'event2'::text)
AND ("createdAt" > '2015-11-02 05:00:00+13'::timestamp with time zone)
AND ("createdAt" < '2015-12-04 04:59:59+13'::timestamp with time zone)))
Rows Removed by Filter: 3258680
Planning time: 0.163 ms
Execution time: 714.534 ms
インデックス作成とクエリの実行計画にまったくこだわっていないので、誰かが私を正しい方向に向けることができればすばらしいでしょう。
編集
Erwinによって指摘されたように、正しいインデックスがあったとしても、クエリから返されたテーブルの部分が非常に大きいため、テーブルスキャンが引き続き発生するように見えます。これは、このデータセットの場合、これが私が取得できる最速のクエリ時間であることを意味しますか?プロジェクト参照なしで60Mの無関係な行を追加した場合、それはインデックスを使用する可能性があります(正しいインデックスがある場合)が、データを追加してクエリを高速化できる方法がわかりません。多分私は何かが欠けています。
あなたの現在の説明によるとインデックスはあなたの現在のクエリで(もしあったとしても)多く助けにはなりません。
したがって、プロジェクトキーを持つ行は325万行あります。
これはtotal行数でもあるため、この述語はtrue
for(ほぼ)every row ...であり、まったく選択的ではありません。ただし、jsonb
列_"references"
_に役立つインデックスはありません。 _("createdAt", "references", type)
_のbtreeインデックスに含めても意味がありません。
次のように、_"reference"
_に一般的に役立つGINインデックスがあったとしても、
_CREATE INDEX stats_references_gix ON stats USING gin ("references");
_
... Postgresにはまだ jsonb
列内の個々のキーに関する有用な統計はありません。
type
には5つの異なる値しかありません
クエリは、1つのタイプのすべてと別のタイプの不明な部分を選択します。これはすべての行の推定20-40%です。シーケンシャルスキャンが最も確実な方法です。 全行の約5%以下の場合、インデックスは意味を持ち始めます。
テストするには、セッションでのデバッグ用を設定して、可能なインデックスを強制できます。
_SET enable_seqscan = off;
_
リセット:
_RESET enable_seqscan;
_
遅いクエリが表示されます...
プロジェクト値でグループ化します。
_
GROUP BY "references"->> 'project'
_
そして:
プロジェクト参照には正確に400,000の異なる値があります。
これは、プロジェクトあたり平均で8行です。 LATERALサブクエリでプロジェクトごとに最小値と最大値のみを選択した場合、値の頻度に応じて、すべての行の推定3〜20%を取得する必要があります...
このインデックスを試してください。今あるものよりも意味があります。
_CREATE INDEX stats_special_idx ON stats (type, ("references" ->> 'project'), "createdAt")
WHERE "references" ? 'project';
_
Postgresは引き続きシーケンシャルスキャンにフォールバックする可能性があります...
正規化されたスキーマ/より選択的な基準/最小と最大のみを選択するよりスマートなクエリ_"createdAt"
_ ...
私はあなたのクエリを次のように書きます:
_SELECT EXTRACT(Epoch FROM (MAX("createdAt") - MIN("createdAt")))
FROM stats
WHERE "references" ? 'project'
AND (type = 'event1' OR
type = 'event2'
AND "createdAt" >= '2015-11-02 00:00:00+08:00' -- I guess you want this
AND "createdAt" < '2015-12-04 00:00:00+08:00'
)
GROUP BY "references"->> 'project'; -- don't cast
_
ここにキャストしないでください:
_stats."references"
_::jsonb ? 'project'
列はすでにjsonb
であり、何も得られません。述語was selectiveの場合、インデックスの使用はキャストによって禁止されている可能性があります。
_"createdAt"
_の述語は、おそらく下限と上限でincorrectです。丸一日を含めるには、提案された代替案を検討してください。
references
は 予約語 であるため、常に二重引用符で囲む必要があります。識別子として使用しないでください。 _"createdAt"
_のような二重引用符で囲まれたCaMeLケース名の場合も同様です。許可されていますが、エラーが発生しやすく、不要な複雑化が生じます。
type
type character varying(255) NOT NULL,
タイプは、50文字以下の単純なストリングです。
Typeフィールドには5つの異なる値しかありません。これは、本番環境では数百以下になるでしょう。
これはどれも意味がないようです。
varchar(255)
自体は、ほとんど意味がありません 。 255文字は、Postgresでは重要ではない任意の制限です。integer
列_type_id
_(小さなtype
テーブルを参照)があり、行ごとに4バイトしか占有せず、インデックスが小さくて高速になります。理想的には、project
テーブルにすべてのプロジェクトをリストし、stats
に別の短整数FK列_project_id
_を記述します。このようなクエリを高速化します。そして、選択的な基準については、提案された正規化がなくてもmuchより高速なクエリが可能です。これらの線に沿って: