3

こんにちは、Ika プラン (7,5 GB RAM) で postgresql 9.1.6 を実行している Heroku でホストされています。私は車と呼ばれるテーブルを持っています。次のことを行う必要があります。

SELECT COUNT(*) FROM "cars" WHERE "cars"."reference_id" = 'toyota_hilux'

これには非常に多くの時間がかかります(64秒!!!)

Aggregate  (cost=2849.52..2849.52 rows=1 width=0) (actual time=63388.390..63388.391 rows=1 loops=1)
  ->  Bitmap Heap Scan on cars  (cost=24.76..2848.78 rows=1464 width=0) (actual time=1169.581..63387.361 rows=739 loops=1)
        Recheck Cond: ((reference_id)::text = 'toyota_hilux'::text)
        ->  Bitmap Index Scan on index_cars_on_reference_id  (cost=0.00..24.69 rows=1464 width=0) (actual time=547.530..547.530 rows=832 loops=1)
              Index Cond: ((reference_id)::text = 'toyota_hilux'::text)
Total runtime: 64112.412 ms

少し背景:

テーブルには約 3.2m の行があり、私が当てにしようとしている列には次の設定があります。

reference_id character varying(50);

およびインデックス:

CREATE INDEX index_cars_on_reference_id
  ON cars
  USING btree
  (reference_id COLLATE pg_catalog."default" );

私は何を間違っていますか?このパフォーマンスは期待すべきものではないと思いますが、そうすべきでしょうか?

4

1 に答える 1

5

@Satyaがコメントで主張していることは、まったく真実ではありません。一致するインデックスが存在する場合、プランナーは、テーブル統計がテーブルの約 5% (依存) よりも多くを返すことを示唆している場合にのみ、フル テーブル スキャンを選択します。これは、テーブル全体をスキャンする方が高速であるためです。

あなた自身の質問からわかるように、これはあなたのクエリには当てはまりません。Bitmap Index Scanに続いてBitmap Heap Scanを使用します。単純なインデックススキャンを期待していましたが。(?)

Explain の出力でさらに 2 つのことに気付きました
。最初のスキャンでは 832 行が検出されましたが、2 番目のスキャンではカウントが 739 に減少しました。これは、インデックスに多くのデッド タプルがあることを示しています。

各ステップの実行時間を確認しEXPLAIN ANALYZE、結果を質問に追加してください。

まず、EXPLAIN ANALYZE を使用してクエリを 2 ~ 3 回再実行し、キャッシュにデータを取り込みます。最初の実行と比較した最後の実行の結果は?

次:

VACUUM ANALYZE cars;

再実行します。

テーブルに多くの書き込み操作がある場合は、フィル ファクターを 100 未満に設定します。

ALTER TABLE cars SET (fillfactor=90);

行サイズが大きい場合や書き込み操作が多い場合は、値を下げてください。それで:

VACUUM FULL ANALYZE cars;

これにはしばらく時間がかかります。再実行します。

または、これを行う余裕がある場合 (および他の重要なクエリに矛盾する要件がない場合):

CLUSTER cars USING index_cars_on_reference_id;

これにより、インデックスの物理的な順序でテーブルが書き換えられるため、この種のクエリが大幅に高速化されます。


スキーマを正規化する

これを非常に高速にする必要がある場合は、主キーを持つテーブルcar_typeserial作成し、テーブルから参照しますcars。これにより、必要なインデックスが現在の数分の一に縮小されます。

言うまでもなく、これを試す前にバックアップを作成してください。

CREATE temp TABLE car_type (
   car_type_id serial PRIMARY KEY
 , car_type text
 );

INSERT INTO car_type (car_type)
SELECT DISTINCT car_type_id FROM cars ORDER BY car_type_id;

ANALYZE car_type;

CREATE UNIQUE INDEX car_type_uni_idx ON car_type (car_type); -- unique types

ALTER TABLE cars RENAME COLUMN car_type_id TO car_type; -- rename old col
ALTER TABLE cars ADD COLUMN car_type_id int; -- add new int col

UPDATE cars c
SET car_type_id = ct.car_type_id
FROM car_type ct
WHERE ct.car_type = c.car_type;

ALTER TABLE cars DROP COLUMN car_type; -- drop old varchar col

CREATE INDEX cars_car_type_id_idx ON cars (car_type_id);    

ALTER TABLE cars 
ADD CONSTRAINT cars_car_type_id_fkey FOREIGN KEY (car_type_id )
REFERENCES car_type (car_type_id) ON UPDATE CASCADE; -- add fk

VACUUM FULL ANALYZE cars;

または、全力を出したい場合は、次のようにします。

CLUSTER cars USING cars_car_type_id_idx;

クエリは次のようになります。

SELECT count(*)
FROM   cars
WHERE  car_type_id = (SELECT car_type_id FROM car_type
                      WHERE car_type = 'toyota_hilux')

そして、さらに速くなるはずです。主にインデックスとテーブルが小さくなったためですが、integer処理が処理よりも高速であるためvarcharです。varcharただし、列のクラスター化されたテーブルに対して劇的な効果は得られません。

歓迎すべき副作用: 型の名前を変更する必要がある場合UPDATE、大きなテーブルをまったくいじることなく、小さな行から 1 つの行になります。

于 2012-10-22T16:10:24.473 に答える