PostgreSQLパフォーマンスチューニングのために ~重いSQLの見つけ方~
タグ [
Tech
PostgreSQL
]
- 2008/05/13
牧野克俊
こんにちは、牧野です。
先日、あるウェブサイトで使用しているPostgreSQLを見直す機会がありました。

PostgreSQLのパフォーマンスを手軽に上げるには、
・データベースの設定ファイルのパラメータを変更する
・アプリケーションの方で重いSQLを発行している場所を見つけて、その部分を直していく
・vaccumを実行する
など、いくつか方法が考えられます。
今回は、PostgreSQLで実行時間が長いSQLを見つける方法について紹介します。
本題に入る前に、まずはテスト用データベースを作っておきます。
そして、次のようなプログラムを作成、実行して、ダミーデータも入れておきます。
musicテーブルとartistテーブルの2テーブルだけで適当なデータですが、とりあえずこれでデータベースは準備できました。
次に、重いSQLが実行されたらログに出力するようにpostgresql.confを変更します。
postgresql.confは通常、PostgreSQLのデータディレクトリの中(例えば /var/lib/pgsql/data内)に置かれています。
自分のテストサーバはCentOS、PostgreSQL7.4系で、この場合だと以下の部分を変更します。
8系だとsyslog = 1ではなく、log_destination = 'syslog' というように変わっています。
log_min_duration_statementについてですが、設定された数字を上回る実行時間のSQLがログに出力されるようになります。
単位はミリセカンドです。※本番で行う場合は、最初は大きい値を設定しましょう。
設定についてはドキュメントに詳細が載っています。
http://www.postgresql.jp/document/pg831doc/html/runtime-config-logging.html
設定ファイルを再読み込みすると、/var/log/messagesに実行されたSQLが全て出力されると思います。
PostgreSQL用のログファイルを作成する場合は、/etc/syslog.confに
ログファイルを見ながらSQLを実行してみると、
こんな感じでログが出力されます。この例だと4秒以上かかっています。
重いSQLが見つかったらexplainを実行します。
cost=の部分が実行時間に関わってきます。マニュアルを見ると、
http://www.postgresql.jp/document/pg831doc/html/using-explain.html
cost=(数字)..(数字)
の最初の数字が、最初の行が返されるまでの時間、次の数字が全ての行が返されるまでの合計時間とありますが、バージョンによって違う場合があるそうです。が、とにかく、artist_idでソートするところとlimitのところで膨大な時間がかかっていることがわかります。
そこで、artist_idにインデックスを定義して、
それから実行してみると、
実行時間が80分の1以下になりました。
再度explain文を実行すると、
…これは極端な例ですが、このような地道な作業の繰り返しで相当パフォーマンスを上げられる場合があります。ユーザが頻繁にアクセスするようなところに一箇所でも重い部分があると、全体のパフォーマンスに大きく影響します。
PostgreSQLが重くて困っている方は試してみてはいかがでしょうか。
先日、あるウェブサイトで使用しているPostgreSQLを見直す機会がありました。
PostgreSQLのパフォーマンスを手軽に上げるには、
・データベースの設定ファイルのパラメータを変更する
・アプリケーションの方で重いSQLを発行している場所を見つけて、その部分を直していく
・vaccumを実行する
など、いくつか方法が考えられます。
今回は、PostgreSQLで実行時間が長いSQLを見つける方法について紹介します。
本題に入る前に、まずはテスト用データベースを作っておきます。
- createdb
test
そして、次のようなプログラムを作成、実行して、ダミーデータも入れておきます。
- $db
= pg_connect('dbname=test'); - $sql
= " CREATE TABLE artist ( artist_id serial primary key, name text not null ) - ";
- pg_query($sql);
- $sql
= " CREATE TABLE music ( music_id serial primary key, artist_id integer not null, title text not null ) - ";
- pg_query($sql);
- for
($i = 0; $i < 10000; $i++) { $a_name = serialize(time() + $i); $sql = "INSERT INTO artist(name) VALUES('{$a_name}')"; $res = pg_query($sql); $sql = "SELECT artist_id FROM artist WHERE name = '{$a_name}'"; $res = pg_query($sql); $tmp = pg_fetch_array($res); $artist_id = $tmp['artist_id']; for ($j = 0; $j < 50; $j++) { $title = serialize(time() + $j + $i); $sql = "INSERT INTO music(artist_id, title) VALUES({$artist_id}, '{$title}')"; pg_query($sql); } - }
musicテーブルとartistテーブルの2テーブルだけで適当なデータですが、とりあえずこれでデータベースは準備できました。
次に、重いSQLが実行されたらログに出力するようにpostgresql.confを変更します。
postgresql.confは通常、PostgreSQLのデータディレクトリの中(例えば /var/lib/pgsql/data内)に置かれています。
自分のテストサーバはCentOS、PostgreSQL7.4系で、この場合だと以下の部分を変更します。
- syslog
= 1 - log_min_duration_statement
= 0
8系だとsyslog = 1ではなく、log_destination = 'syslog' というように変わっています。
log_min_duration_statementについてですが、設定された数字を上回る実行時間のSQLがログに出力されるようになります。
単位はミリセカンドです。※本番で行う場合は、最初は大きい値を設定しましょう。
設定についてはドキュメントに詳細が載っています。
http://www.postgresql.jp/document/pg831doc/html/runtime-config-logging.html
設定ファイルを再読み込みすると、/var/log/messagesに実行されたSQLが全て出力されると思います。
- /etc/init.d/postgresql
reload
PostgreSQL用のログファイルを作成する場合は、/etc/syslog.confに
- local0.*
/var/log/postgres.log
- /etc/init.d/syslog
restart
ログファイルを見ながらSQLを実行してみると、
- tail
-f /var/log/postgres.log - May
13 19:22:43 makmak postgres[14003]: [10-1] LOG: duration: 4145.884 ms statement: select * from music order by artist_id desc limit 10 offset 100;
こんな感じでログが出力されます。この例だと4秒以上かかっています。
重いSQLが見つかったらexplainを実行します。
- test=>explain
select * from music order by artist_id desc limit 10 offset 100; QUERY PLAN - ---------------------------------------------------------------------------
Limit (cost=76910.28..76910.31 rows=10 width=40) -> Sort (cost=76910.03..78160.04 rows=500001 width=40) Sort Key: artist_id -> Seq Scan on music (cost=0.00..12046.01 rows=500001 width=40) - (4
rows)
cost=の部分が実行時間に関わってきます。マニュアルを見ると、
http://www.postgresql.jp/document/pg831doc/html/using-explain.html
cost=(数字)..(数字)
の最初の数字が、最初の行が返されるまでの時間、次の数字が全ての行が返されるまでの合計時間とありますが、バージョンによって違う場合があるそうです。が、とにかく、artist_idでソートするところとlimitのところで膨大な時間がかかっていることがわかります。
そこで、artist_idにインデックスを定義して、
- test=>
create index artist_id_index on music(artist_id);
それから実行してみると、
- May
13 20:42:58 makmak postgres[15680]: [1-1] LOG: duration: 49.107 ms statement: select * from music order by artist_id desc limit 10 offset 100;
実行時間が80分の1以下になりました。
再度explain文を実行すると、
- test=>
explain select * from music order by artist_id desc limit 10 offset 100; QUERY PLAN - --------------------------------------------------------------------------------------------------------
Limit (cost=344.61..379.07 rows=10 width=40) -> Index Scan Backward using artist_id_index on music (cost=0.00..1723054.54 rows=500000 width=40) - (2
rows)
…これは極端な例ですが、このような地道な作業の繰り返しで相当パフォーマンスを上げられる場合があります。ユーザが頻繁にアクセスするようなところに一箇所でも重い部分があると、全体のパフォーマンスに大きく影響します。
PostgreSQLが重くて困っている方は試してみてはいかがでしょうか。
コメントフォーム
トラックバック
最近の記事
- もうすぐ健康診断があるんだ・・・ [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日 : 岡本雄樹]



最近のコメント