web-dev-qa-db-ja.com

一時ファイルが原因でクエリのパフォーマンスが低下しますか?

これがクエリです:

SELECT "products".* 
FROM "products"
WHERE (status > 100) 
AND "products"."above_revenue_average" = 't' 
AND ("products"."category_id" NOT IN (5))
ORDER BY "products"."start_date" DESC 

statusstart_dateにインデックスがあります。

アプリケーションからクエリを実行するたびに、ログに次の情報が記録されます。

[WHITE] temporary file:
path "pg_tblspc/16386/PG_9.3_201306121/pgsql_tmp/pgsql_tmp2544.0", size 37093376 
Query: SELECT "products".* FROM "products"  WHERE (status > 100) 
AND "products"."above_revenue_average" = 't' 
AND ("products"."category_id" NOT IN (5))  ORDER BY "products"."start_date" DESC 

この一時ファイルの作成がパフォーマンスの低下の原因だと思います。

EXPLAIN ANALYZEを実行すると、次の結果が得られます。

QUERY PLAN
 Sort  (cost=63395.28..63403.51 rows=16460 width=524)
       (actual time=524.134..562.635 rows=65294 loops=1)
   Sort Key: start_date
   Sort Method: external merge  Disk: 36224kB
   ->  Bitmap Heap Scan on products  
        (cost=4803.40..60389.73 rows=16460 width=524)
        (actual time=27.390..397.879 rows=65294 loops=1)
         Recheck Cond: (status > 100)
         Filter: (above_revenue_average AND (category_id <> 5))
         Rows Removed by Filter: 25115
         ->  Bitmap Index Scan on index_products_on_status
            (cost=0.00..4802.58 rows=89662 width=0) 
            (actual time=18.006..18.006 rows=90411 loops=1)
               Index Cond: (status > 100)
 Total runtime: 577.870 ms
(10 rows)

次に http://explain.depesz.com/ を使用して、少し読みやすくしました。

ノードタイプごとの統計

+-------------------+-------+--------------+------------+
|     node type     | count | sum of times | % of query |
+-------------------+-------+--------------+------------+
| Bitmap Heap Scan  |     1 | 379.873 ms   | 67.5 %     |
| Bitmap Index Scan |     1 | 18.006 ms    | 3.2 %      |
| Sort              |     1 | 164.756 ms   | 29.3 %     |
+-------------------+-------+--------------+------------+

テーブルごとの統計

+------------------+------------+--------------+------------+
|    Table name    | Scan count |  Total time  | % of query |
+------------------+------------+--------------+------------+
| scan type        | count      | sum of times | % of table |
| products         | 1          | 379.873 ms   | 67.5 %     |
| Bitmap Heap Scan | 1          | 379.873 ms   | 100.0 %    |
+------------------+------------+--------------+------------+

さらにインデックスを追加して、データベースのパフォーマンスを向上させることはできますか?多分いくつかのものですか?何か案は?

7
bnussey

work_mem

明らかに、ソート操作はディスクに流出します。

Sort Method: external merge  Disk: 36224kB

より多くのwork_memは、 @ Kassandryがすでに提案しているように クエリを支援できます。 Memory出力にDiskではなくEXPLAINが表示されるまで設定を増やします。ただし、1つのクエリに基づいて一般的な設定を増やすことはおそらく悪い考えです。適切な設定は、利用可能なRAMおよび完全な状況によって異なります。 Postgres Wiki を読むことから始めます。

クエリを修正するには、同じトランザクションwork_memのみを使用して、トランザクションに十分なSET LOCALを設定します。

BEGIN;

SET LOCAL work_mem = '45MB';

SELECT ...

COMMIT;   -- or ROLLBACK

おそらく40 MBを少し超える容量が必要です。インメモリ表現は、ディスク上の表現よりも少し大きいです。関連:

クエリ

クエリ(ノイズを削除した後):

SELECT * 
FROM   products
WHERE  status > 100
AND    above_revenue_average  -- boolean can be used directly
AND    category_id <> 5
ORDER  BY start_date DESC;

行の幅は0.5キロバイトです(width=524)。すべての列を返す必要がありますか? (通常、必要ありません。)work_memの問題が既に発生しているため、全体的なパフォーマンスを向上させるために、クエリから必要なSELECTリストの列のみをリストします。

関連する列のいずれかをNULLにすることはできますか? category_idおよびstart_dateでは特に重要です。あなたはその場合に適応したいかもしれません...

インデックス

複数列のインデックスは確かにパフォーマンスに役立ちます。 ( @ Paulと同じように )コストと利益を比較検討する必要があります。このクエリのパフォーマンスが重要であるか、非常に一般的である場合は、それを実行してください。クエリごとに特別なインデックスを作成しないでください。できるだけ少なく、必要なだけ。インデックスを共有するとより強力になるため、より多くのインデックスがキャッシュに残る可能性が高くなります。

above_revenue_averageのようなboolean列は、インデックス列ではなく、部分インデックスの条件の典型的な候補です。

不完全な情報に基づく私の大まかな推測:

CREATE INDEX prod_special_idx ON products (start_date DESC)
WHERE  above_revenue_average
AND    status > 100
AND    category_id <> 5;

インデックスでDESC NULLS LASTを使用し、start_dateをNULLにできるかどうかを照会します。

4

この種類のクエリのパフォーマンスを向上させる最も簡単で最も効果的な方法の1つは、SET work_mem=40MBを実行して(並べ替え用の一時ファイルが32MB以下あり、追加の作業が頻繁に役立つため)、クエリを実行して確認することですEXPLAIN ANALYZEプランがディスクからメモリ内ソートに変更された場合。その後、RESET work_memを実行して、値をpostgresql.confのデフォルト値に戻します。

基本的なアイデアは、次のリンクのwork_memで説明されています: PostgreSQLサーバーのチューニング 。個々のクエリのこの設定を変更すると、利点が得られ、言及された欠点が回避されます。

お役に立てば幸いです。 =)

2
Kassandry

結合された述語の選択性に応じて、この特定のクエリに適したインデックスは次のようになると思います。

CREATE INDEX index_name
ON products
    (above_revenue_average ASC, start_date DESC)
WHERE
    status > 100
    AND category_id <> 5;

SELECT *は、上記のインデックスにすべての列が含まれていないため、潜在的に問題があります。これにより、プランナーでインデックスが選択されない場合は、最初に主キー列のみをフェッチしてから、テーブルに結合して、選択したキーのみの追加の列を収集できます。

2
Paul White 9