[pgsql-jp: 29514] Re: インデックスの利用について

Tatsuo Ishii t-ishii @ sra.co.jp
2003年 3月 31日 (月) 10:30:18 JST


石井です.

> PostgreSQL 7.3.2を使用していて少し疑問に思いましたので
> お教え下さい。
> 
> 以下のようなテーブルがあります。
> 
> table1
> create table table1(
>     t1_key_p1  varchar(10),
>     t1_key_p2  integer,
>     t1_key_p3  integer,
>     t1_key_s1  varchar(20),
>     t1_key_s2  varchar(50),
>     t1_data1   integer,
>     t1_data2   integer,
>     primary key (t1_key_p1, t1_key_p2, t1_key_p3)
> );
> create index table1_key1 on table1 
>     (t1_key_p1, t1_key_s1, t1_key_s2, t1_key_p2);
> 
> データ件数は約100万件です。vaccum full, analyzeはしてあります。
> 
> このテーブルに対し
> 
> explain select t1_key_s2 from table1
>  where t1_key_p1 = 'aaa'
>  and t1_key_s1 = 'bbb'
>  and t1_key_s2 >= '999'
>  group by t1_key_p1, t1_key_s1, t1_key_s2;
> 
> を実行すると
> 
> Group  (cost=56581.99..56745.32 rows=1633 width=31)
>  ->  Sort  (cost=56581.99..56622.82 rows=16333 width=31)
>   Sort Key: t1_key_p1, t1_key_s1, t1_key_s2
>   ->  Seq Scan on table1 (cost=0.00..55439.06 rows=16333 width=31)
>         Filter: ((t1_key_p1 = 'aaa'::character varying)
>             AND (t1_key_s1 = 'bbb'::character varying)
>             AND (t1_key_s2 >= '999'::character varying))
> 
> Seq Scan(?)になり非常に遅くなります。そこで
> 
> set enable_seqscan = false;
> 
> として同じクエリーを実行すると
> 
> Group  (cost=0.00..63985.26 rows=1633 width=31)
>  ->  Index Scan using table1_key1 on table1
>   (cost=0.00..63862.77 rows=16333 width=31)
>    Index Cond: ((t1_key_p1 = 'aaa'::character varying)
>      AND (t1_key_s1 = 'bbb'::character varying)
>      AND (t1_key_s2 >= '999'::character varying))
> 
> となり早いです。
> (ちなみに結果は1件です。)
> 
> postgresql.confを書き換えるのは他に影響がありそうですし、
> 毎回"set enable_seqscan = false"をするしかないでしょうか?

あるいは set enable_sort = false にするか.どちらにしてもあまりおすす
めできない方法なので,もうちょっとなんとかならないか考えてみました(ち
なみに7.4ではsortを使わずにgroup byができるようになったので,デフォル
トのままでもかなり良くなっています).

(1) 統計情報の改善

この記事のスレッドを見ると,

>  where t1_key_p1 = 'aaa'
>  and t1_key_s1 = 'bbb'
>  and t1_key_s2 >= '999'


のWHERE句の抽出結果が実際には1であるとのことです.しかしオプティマイザ
の見積もりは(cost=0.00..63862.77 rows=16333 width=31)にあるように,
16333であり,このあたり非常に解離があるように思います.つまり,

>     t1_key_p1  varchar(10),
>     t1_key_s1  varchar(20),
>     t1_key_s2  varchar(50),

のどれかあるいは全部が非常に癖のあるデータ分布になっていて,オプティマ
イザが対応できていないように思われます.そこで,まずはanalyzeするとき
のデータのサンプリングをもっときめ細かくし,少しでもオプティマイザの見
積もりが現実に近づくようにするのが先決問題です.具体的には,

ALTER TABLE table1 ALTER COLUMN t1_key_p1 SET STATISTICS 20;
ALTER TABLE table1 ALTER COLUMN t1_key_s1 SET STATISTICS 20;
ALTER TABLE table1 ALTER COLUMN t1_key_s2 SET STATISTICS 20;

を実行してから再度ANAKYZEをします.

20というのはデフォルトの約2倍のきめ細かさで統計情報を取ってくることを
意味します.これで効果がなければ,もっと数値を大きくしてみてください.
ただし,あまり大きくするとANALYZEに時間がかかるようになります.

(2) effective_cache_sizeの調整

# effective_cache_size = 1000

という行がpostgresql.confにあります.これは,カーネルのバッファキャッ
シュの大きさの見積もり値で,通常 effective_cache_size * 8192 がバイト
に換算した値になります.つまりデフォルト値は8MBです.この値の範囲にター
ゲットテーブルが収まっていると,オプティマイザはより積極的にインデック
スを使うようになります.実際にインデックスを使った方が速いということは,
effective_cache_size が実情よりも低すぎる設定になっていると考えられま
す.問題のテーブルの大きさが実際どの位なのかわかりませんが,
とりあえずeffective_cache_size を10000位にしてみてはどうでしょう.

(3) limitの追加

問い合わせの出力行が高々n行である場合,LIMIT nを追加するとインデックス
が使われるようになる場合があります.つまり,

select t1_key_s2 from table1
where t1_key_p1 = 'aaa'
  and t1_key_s1 = 'bbb'
  and t1_key_s2 >= '999'
  group by t1_key_p1, t1_key_s1, t1_key_s2
LIMIT 10;

などとするわけですね.

以上,お試し下さい((1)-(3)は併用も可能です).
--
Tatsuo Ishii



pgsql-jp メーリングリストの案内