PostgreSQLの手軽なSQLチューニング
タグ [
Tech
PostgreSQL
]
- 2008/07/24
牧野克俊
こんばんは、牧野です。
今日は前々回の話題に戻って、PostgreSQLのチューニングの話です。

この前は重いSQLをどうやって見つけるか紹介しました。今回は処理を速くするためのSQLの具体例を紹介します。
1.インデックスを使う
以前も書いたので省略しますが、データ数が多くなってくると(数万件以上とか)インデックスが正しく使えているかどうかで負荷のかかり方が大きく変わってきます。
複数カラムの条件検索の場合は、必要に応じて複合インデックスを作成します。その時は、プログラムの方でWHERE句の順番に気をつけましょう。
2.VACUUMとREINDEX
特にバッチプログラムで頻繁にデータ更新を行うようなテーブルがある時は要注意です。VACUUMをしないままで運用していくと大変なことになる場合があります。バッチ処理の後等定期的にVACUUMするようにしましょう。
自動バキュームを使うのも有効です。8.1、8.2の場合はpostgresql.confのautovacuumをonにします。
8.3だとデフォルトで自動バキュームがオンになっています。あと、8.3にするとvacuumそのものの必要性がかなり低下します。
データ更新が頻繁にあるテーブルでは、定期的にREINDEXもしましょう。
REINDEX TABLE テーブル名;
大量のデータを更新してしまっている場合、VACUUMだけではあまり改善しないことがあります。
そんな時はVACUUM FULLを実行します。
10万件余りのデータを10回INSERT、DELETEした場合の比較
また、テーブルのデータを全部消す時は、TRUNCATEを使った方がいい場合がほとんどです。DELETE時に実行するトリガはTRUNCATE時には実行されませんが、TRUNCATEだとVACUUM、VACUUM FULLの必要がなくDELETEよりも高速です。
3.COUNT()、MAX()、MIN()、DISTINCTに注意
COUNT()、MAX()、MIN()関数やDISTINCTを使うと、対象カラムにインデックスが張ってあってもデータ数が多くなってくると重くなることがあります。
MAX()、MIN()はORDER BYで、DISTINCTはGROUP BYで代用可能です。
COUNT()は、あまりいい方法を知りません。重くてどうしようもなかったら、データ数を入れるテーブルを作る、とか。。。何か他にうまい方法を知っている方がいましたらぜひ教えて下さい。
以上、気付いたことから順に挙げました。
MySQLではサブクエリを使うと極端に遅くなることがあった(100倍以上時間がかかった)のでPostgreSQLでも試してみましたが、PostgreSQLではそんなことはないようです。
今日は前々回の話題に戻って、PostgreSQLのチューニングの話です。
この前は重いSQLをどうやって見つけるか紹介しました。今回は処理を速くするためのSQLの具体例を紹介します。
1.インデックスを使う
以前も書いたので省略しますが、データ数が多くなってくると(数万件以上とか)インデックスが正しく使えているかどうかで負荷のかかり方が大きく変わってきます。
複数カラムの条件検索の場合は、必要に応じて複合インデックスを作成します。その時は、プログラムの方でWHERE句の順番に気をつけましょう。
2.VACUUMとREINDEX
特にバッチプログラムで頻繁にデータ更新を行うようなテーブルがある時は要注意です。VACUUMをしないままで運用していくと大変なことになる場合があります。バッチ処理の後等定期的にVACUUMするようにしましょう。
自動バキュームを使うのも有効です。8.1、8.2の場合はpostgresql.confのautovacuumをonにします。
8.3だとデフォルトで自動バキュームがオンになっています。あと、8.3にするとvacuumそのものの必要性がかなり低下します。
データ更新が頻繁にあるテーブルでは、定期的にREINDEXもしましょう。
REINDEX TABLE テーブル名;
大量のデータを更新してしまっている場合、VACUUMだけではあまり改善しないことがあります。
そんな時はVACUUM FULLを実行します。
10万件余りのデータを10回INSERT、DELETEした場合の比較
- test=>
explain select count(*) from music; QUERY PLAN - --------------------------------------------------------------------
Aggregate (cost=10747.35..10747.35 rows=1 width=0) -> Seq Scan on music (cost=0.00..10476.28 rows=108428 width=0) - (2
rows) - test=>
vacuum full music; - VACUUM
- test=>
explain select count(*) from music; QUERY PLAN - -------------------------------------------------------------------
Aggregate (cost=2209.35..2209.35 rows=1 width=0) -> Seq Scan on music (cost=0.00..1938.28 rows=108428 width=0) - (2
rows)
また、テーブルのデータを全部消す時は、TRUNCATEを使った方がいい場合がほとんどです。DELETE時に実行するトリガはTRUNCATE時には実行されませんが、TRUNCATEだとVACUUM、VACUUM FULLの必要がなくDELETEよりも高速です。
3.COUNT()、MAX()、MIN()、DISTINCTに注意
COUNT()、MAX()、MIN()関数やDISTINCTを使うと、対象カラムにインデックスが張ってあってもデータ数が多くなってくると重くなることがあります。
MAX()、MIN()はORDER BYで、DISTINCTはGROUP BYで代用可能です。
- test=>
explain select max(second) from music; QUERY PLAN - -------------------------------------------------------------------
Aggregate (cost=2209.35..2209.35 rows=1 width=4) -> Seq Scan on music (cost=0.00..1938.28 rows=108428 width=4) - (2
rows) - test=>
explain select second from music order by second desc limit 1; QUERY PLAN - ---------------------------------------------------------------------------
Limit (cost=0.00..0.05 rows=1 width=4) -> Index Scan Backward using music_second_index on music (cost=0.00..5263.88 rows=108428 width=4) - (2
rows)
- test=>
explain select distinct artist_id from music; QUERY PLAN - -------------------------------------------------------------------------
Unique (cost=11006.32..11548.46 rows=2143 width=4) -> Sort (cost=11006.32..11277.39 rows=108428 width=4) Sort Key: artist_id -> Seq Scan on music (cost=0.00..1938.28 rows=108428 width=4) - (4
rows) - test=>
explain select artist_id from music group by artist_id; QUERY PLAN - -------------------------------------------------------------------
HashAggregate (cost=2209.35..2209.35 rows=2143 width=4) -> Seq Scan on music (cost=0.00..1938.28 rows=108428 width=4) - (2
rows)
COUNT()は、あまりいい方法を知りません。重くてどうしようもなかったら、データ数を入れるテーブルを作る、とか。。。何か他にうまい方法を知っている方がいましたらぜひ教えて下さい。
以上、気付いたことから順に挙げました。
MySQLではサブクエリを使うと極端に遅くなることがあった(100倍以上時間がかかった)のでPostgreSQLでも試してみましたが、PostgreSQLではそんなことはないようです。
コメント
コメントフォーム
トラックバック
-
- [sql]count(*)をトリガを使って from iakioの日記
- COUNT()は、あまりいい方法を知りません。重くてどうしようもなかった...
2008年07月28日 17:28
最近の記事
- もうすぐ健康診断があるんだ・・・ [2010年09月02日 : 阿部恵]
- Photoshopで壁紙を作りながら、基本的な使い方を覚える [2010年09月01日 : 鴨田健次]
- はじめての共同作業 Canvas編 (node.js + websocket) [2010年09月01日 : 中川善樹]
- 「PHP×Flex(後編)」PHPテクニカルセミナー(無料)第4弾の募集を開始しました!! [2010年08月26日 : 和田記光]
- 【HTML5】Canvasでお絵かきしてみた(前編) [2010年08月25日 : 橋本章史]
- MacにgroongaのMySQL用ストレージエンジン [2010年08月23日 : 笹亀弘]
- Appleのサイトで見たiPhone4をFireworksで描いてみました-1/2 [2010年08月19日 : 和田記光]
- iPad版の会社紹介を作ってみました [2010年08月19日 : 小林有佳]
- iPhoneアプリ開発開始時に気をつけるべきファイルの取り扱い (2) [2010年08月19日 : 亀本大地]
- symfonyセミナー動画無料公開! [2010年08月13日 : 岡本雄樹]



count(*)ではなく
count(カラム名)にしたほうが
はやいそうです。
10万件くらいデータの入っているmusicテーブルで調べてみたのですが、自分の環境では違いが出ませんでした。。
テーブルのカラム数が少なくて、データ量も小さいので差が出なかったのかもしれません。