大きなPostgresSQLテーブルでCOUNT / GROUP-BYのパフォーマンスを改善しますか?


24

PostgresSQL 9.2を実行していますが、約6,700,000行の12列の関係があります。これには3D空間にノードが含まれ、各ノードはユーザー(作成者)を参照します。どのユーザーがいくつのノードを作成したかを照会するには、次のことを行います(詳細を追加explain analyze)。

EXPLAIN ANALYZE SELECT user_id, count(user_id) FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                    QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253668.70..253669.07 rows=37 width=8) (actual time=1747.620..1747.623 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220278.79 rows=6677983 width=8) (actual time=0.019..886.803 rows=6677983 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1747.653 ms

ご覧のとおり、これには約1.7秒かかります。これは、データの量を考えるとそれほど悪くはありませんが、これを改善できるかどうかは疑問です。ユーザー列にBTreeインデックスを追加しようとしましたが、これは何の助けにもなりませんでした。

代替案はありますか?


完全を期すために、これはすべてのインデックスを備えた完全なテーブル定義です(外部キーの制約、参照、トリガーはありません)。

    Column     |           Type           |                      Modifiers                    
---------------+--------------------------+------------------------------------------------------
 id            | bigint                   | not null default nextval('concept_id_seq'::regclass)
 user_id       | bigint                   | not null
 creation_time | timestamp with time zone | not null default now()
 edition_time  | timestamp with time zone | not null default now()
 project_id    | bigint                   | not null
 location      | double3d                 | not null
 reviewer_id   | integer                  | not null default (-1)
 review_time   | timestamp with time zone |
 editor_id     | integer                  |
 parent_id     | bigint                   |
 radius        | double precision         | not null default 0
 confidence    | integer                  | not null default 5
 skeleton_id   | bigint                   |
Indexes:
    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)
    "skeleton_id_treenode_index" btree (skeleton_id)
    "treenode_editor_index" btree (editor_id)
    "treenode_location_x_index" btree (((location).x))
    "treenode_location_y_index" btree (((location).y))
    "treenode_location_z_index" btree (((location).z))
    "treenode_parent_id" btree (parent_id)
    "treenode_user_index" btree (user_id)

編集:これは、@ ypercubeによって提案されたクエリ(およびインデックス)を使用した場合の結果です(クエリはなしで約5.3秒かかりますEXPLAIN ANALYZE)。

EXPLAIN ANALYZE SELECT u.id, ( SELECT COUNT(*) FROM treenode AS t WHERE t.project_id=1 AND t.user_id = u.id ) AS number_of_nodes FROM auth_user As u;
                                                                        QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on auth_user u  (cost=0.00..6987937.85 rows=46 width=4) (actual time=29.934..5556.147 rows=46 loops=1)
   SubPlan 1
     ->  Aggregate  (cost=151911.65..151911.66 rows=1 width=0) (actual time=120.780..120.780 rows=1 loops=46)
           ->  Bitmap Heap Scan on treenode t  (cost=4634.41..151460.44 rows=180486 width=0) (actual time=13.785..114.021 rows=145174 loops=46)
                 Recheck Cond: ((project_id = 1) AND (user_id = u.id))
                 Rows Removed by Index Recheck: 461076
                 ->  Bitmap Index Scan on treenode_user_index  (cost=0.00..4589.29 rows=180486 width=0) (actual time=13.082..13.082 rows=145174 loops=46)
                       Index Cond: ((project_id = 1) AND (user_id = u.id))
 Total runtime: 5556.190 ms
(9 rows)

Time: 5556.804 ms

編集2:これは、@ erwin-brandstetterが示唆したように(スキーマ最適化はまだ)indexon を使用した場合の結果ですproject_id, user_id(クエリは元のクエリと同じ速度で1.5秒実行されます)。

EXPLAIN ANALYZE SELECT user_id, count(user_id) as ct FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                        QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253670.88..253671.24 rows=37 width=8) (actual time=1807.334..1807.339 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220280.62 rows=6678050 width=8) (actual time=0.183..893.491 rows=6678050 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1807.368 ms
(4 rows)

あなたはまた、テーブルを持っていUsersuser_id、主キーとして?
ypercubeᵀᴹ

Postgres用のサードパーティの列ストアアドオンがあることを確認しました。また、新しい
iOS

2
良いのおかげで、明確な、完全な疑問-バージョン、テーブル定義など
クレイグリンガー

@ypercubeはい、Usersテーブルがあります。
トムカ14

どのように多くの異なったproject_iduser_id?テーブルは継続的に更新されていますか、それともマテリアライズドビューで作業できますか(しばらくの間)。
アーウィンブランドステッター14

回答:


25

主な問題は、インデックスの欠落です。しかし、もっとあります。

SELECT user_id, count(*) AS ct
FROM   treenode
WHERE  project_id = 1
GROUP  BY user_id;
  • 多くのbigint列があります。おそらくやり過ぎ。通常、やなどのinteger列には十分以上です。これは次の項目にも役立ちます。テーブル定義を最適化しながら、データのアライメントパディングに重点を置いて、この関連する回答を検討してくださいただし、残りのほとんども適用されます。project_iduser_id

  • 部屋:にはインデックスproject_idがありません。一つ作る。これは、この回答の残りの部分よりも重要です。
    その間、複数列のインデックスを作成します。

    CREATE INDEX treenode_project_id_user_id_index ON treenode (project_id, user_id);

    あなたが私のアドバイスに従えば、integerここで完璧になります:

  • user_idは定義されているNOT NULLため、count(user_id)と同等count(*)ですが、後者は少し短く、高速です。(この特定のクエリでは、これはuser_id定義されていなくても適用されNOT NULLます。)

  • idはすでに主キーであるため、追加のUNIQUE制約は役に立たないバラストです。もうやめろ:

    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)

    余談:id列名として使用しません。のような記述的なものを使用しますtreenode_id

追加情報

Q:How many different project_id and user_id?
A: not more than five different project_id

つまり、Postgresはクエリを満たすためにテーブル全体の約20%を読み取る必要があります。インデックスのみのスキャンを使用できない場合、テーブルの順次スキャンは、インデックスを使用するよりも高速になります。ここで得られるパフォーマンスはありません-テーブルとサーバー設定を最適化することを除いて。

索引のみのスキャン:実行し、それがいかに効果的な参照してくださいVACUUM ANALYZE(表を排他ロック)あなたがそれを余裕がある場合。その後、クエリを再試行してください。これ、インデックスのみを使用した場合の速度がやや速くなります。この関連する回答を最初に読んでください:

また、Postgres 9.6で追加さたマニュアルページと、インデックスのみのスキャンに関するPostgres Wikiもあります


1
アーウィン、ご提案ありがとうございます。あなたは正しい、user_idそしてproject_id integer十分である必要があります。ここで約70ms節約するcount(*)代わりに使用するcount(user_id)と、知っておくと便利です。最初の投稿にEXPLAIN ANALYZEあなたの提案indexを追加した後、クエリを追加しました。ただし、パフォーマンスは向上しません(ただし、害はありません)。indexはまったく使用されていないようです。スキーマの最適化をすぐにテストします。
トムカ14

1
無効にするseqscanと、インデックスが使用されます(Index Only Scan using treenode_project_id_user_id_index on treenode)が、クエリには約2.5秒かかります(seqscanの場合よりも約1秒長くなります)。
トムカ14

1
更新いただきありがとうございます。これらの欠けている部分は私の質問の一部であったはずです、そうです。私は彼らの影響に気付いていませんでした。あなたが提案したようにスキーマを最適化します---それから得られるものを見てみましょう。あなたの説明をありがとう、それは私にとって理にかなっているので、私はあなたの答えを受け入れられたものとしてマークします。
トムカ14

7

最初にインデックスを追加し(project_id, user_id)てから、9.3バージョンで次のクエリを試してください。

SELECT u.user_id, c.number_of_nodes 
FROM users AS u
   , LATERAL
     ( SELECT COUNT(*) AS number_of_nodes 
       FROM treenode AS t
       WHERE t.project_id = 1 
         AND t.user_id = u.user_id
     ) c 
-- WHERE c.number_of_nodes > 0 ;   -- you probably want this as well
                                   -- to show only relevant users

9.2では、これを試してください:

SELECT u.user_id, 
       ( SELECT COUNT(*) 
         FROM treenode AS t
         WHERE t.project_id = 1 
           AND t.user_id = u.user_id
       ) AS number_of_nodes  
FROM users AS u ;

usersテーブルがあると思います。そうでない場合は、次のものに置き換えusersます。
(SELECT DISTINCT user_id FROM treenode)


ご回答どうもありがとうございました。あなたは正しい、私はユーザーテーブルを持っています。ただし、9.2でクエリを使用すると、インデックスが作成されているかどうかに関係なく、結果を取得するのに約5秒かかります。このようにインデックスを作成しましたCREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);が、USING句なしでも試しました。私は何かを見逃していますか?
トムカ14

usersテーブルには行がいくつあり、クエリは何行を返します(つまり、を持っているユーザーは何人いますproject_id=1か)。インデックスを追加した後、このクエリの説明を表示できますか?
ypercubeᵀᴹ

1
最初に、私は最初のコメントで間違っていました。推奨インデックスがない場合、結果を取得するには約40秒(!)かかります。indexインプレースでは約5秒かかります。混乱させて申し訳ありません。私のusersテーブルには、46のエントリがあります。クエリは9行のみを返します。驚くべきことに、SELECT DISTINCT user_id FROM treenode WHERE project_id=1;38行を返します。をexplain最初の投稿に追加しました。混乱を防ぐため、usersテーブルは実際に呼び出されauth_userます。
トムカ14

SELECT DISTINCT user_id FROM treenode WHERE project_id=1;クエリが9行しか返さないのに、38行を返す方法はありませんか。
ypercubeᵀᴹ

これを試すことができますか?:SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;
ypercubeᵀᴹ14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.