質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PostgreSQL

PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

Q&A

解決済

3回答

6023閲覧

postgresのデータ取得時間を短縮したい

zekterra

総合スコア30

PostgreSQL

PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

0グッド

0クリップ

投稿2016/10/21 08:27

タイトルの通りpostgresからデータを取得する時間を短縮したいです。
以前にも質問させていただいたのですが、前回とは少し内容が変わっております。

テーブルには2700万件ほどデータが格納されております。

日付の範囲を指定して取得していたデータの中から100件以下のデータを取得して画面に表示させるということをやっております。

以前は指定された範囲内のデータを全て取得してプログラム内で間引いていたのですが、SQL内で間引くようにしました。
プログラム内で間引いていた時よりだいぶ速度は改善されたのですが、検索範囲を1年で検索した際は7秒近くかかっている状況になります。
それを3秒以内に抑えたいです。

vacuum、reindexは行っております。
テーブルの分割については色々事情があってなるべくしたくない方法です。
DBの設定、OS、ハードなどについては、すでに動いているシステムなので変更することが出来ない状況です。

なるべくSQLの見直し、テーブルの設計だけで速度を改善させたいです。

postgresのバージョンは9.5になります。
以下にテーブルの構成、SQL文、実行計画を載せます。

申し訳ございませんがどなたかアドバイスを頂ければと思います。

注)テーブルにrow_numの項目を追加してサブクエリを実行しないSQLでも行ったのですが、あまり変化はありませんでした。

※テーブル構成 テーブル名=「details_table」
response_time timestamp
AA_id varchar
port varchar
octets numeric

※SQL
SELECT
response_time
, octets_diff
FROM
(
SELECT
total.response_time
, total.octets
, row_number() over (ORDER BY response_time DESC) AS no
FROM
details_table total
WHERE
AA_id = '0000-0000-0000-0840'
AND port = '2110'
AND response_time BETWEEN '2006/10/20 11:27:05' AND '2008/10/20 11:27:05'
) AS totaltable
WHERE
mod(no, 960) = 1 ←検索範囲によって960という値を変化させてデータを100件以下取得する
ORDER BY
totaltable.response_time DESC
, no DESC

※実行計画
Sort (cost=193984.85..193984.90 rows=22 width=24) Sort Key: totaltable.response_time, totaltable.no
+-Subquery Scan on totaltable (cost=193844.45..193984.36 rows=22 width=24) Filter: (mod(totaltable.no, 960::bigint) = 1)
+-WindowAgg (cost=193844.45..193919.78 rows=4305 width=16)
+-Sort (cost=193844.45..193855.21 rows=4305 width=16) Sort Key: total.response_time
+-Seq Scan on details_table total (cost=0.00..193584.60 rows=4305 width=16) Filter: ((response_time >= '2006-10-20 11:27:05+09'::timestamp with time zone) AND (response_time <= '2008-10-20 11:27:05+09'::timestamp with time zone) AND ((AA_id)::text = '0000-0000-0000-0840'::text) AND ((port)::text = '2110'::text))

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

分割は否と言われていますが、可能で有ればパーティショニングをご検討されてはいかがでしょうか?

Postgresのパーティショニングは親テーブルと子テーブルの構成で親テーブルはデータを持たず、
親データ項目を継承した子テーブルがデータを保持します。

INSERT、UPDATE、DELETE、SELECTは親テーブルに対して行えます。当然、分割を単位で直接子テーブル
に同様なアクセスができます。
親テーブルへのアクセスは分割情報元に検索等の実行を子テーブルへと振り分けます。
パーティショニングも数は、100以下であれば親テーブルからの検索でも速度向上が望めます。
私事ですが現行DBはこの100を優に越えています、なので親検索は行わす子テーブルに直接アクセスしております。

例えばですが、テーブル項目AA_id、port、response_time の中で仮にportの分割が100以下になるとした場合、
親テーブル(details_table)と子テーブル(details_table_2110...)として作成する様にできます。(仮にですよ)
今回は、特に検索速度の向上なので1検索がパーティションを跨って行われる場合や分割キー以外での検索と
なら無い様なパーティショニングを見つけ出す事が必要です。言い換えるとそうでなければ検討はそこまでです。
運用者大変でしょうが、がんばってください。

ご参考になればと思い作成してみました。

sql

1--仮にportで分割するとしました、他の項目であれば置換えしてください。(もしかしてAA_id) 2CREATE TABLE details_table ( 3response_time timestamp, 4AA_id varchar, 5port varchar, 6octets numeric); 7 8-- 2110, 2210, 2310, 2410 の4分割の場合 9-- INHERITSで親を継承してインデックスを作成しています。必要に応じて追加してください。response_time等 10-- CHECKを付けて分割の範囲を指定します。親テーブルからの検索使います。 11CREATE TABLE IF NOT EXISTS details_table_2110 ( 12 LIKE details_table INCLUDING INDEXES 13 INCLUDING DEFAULTS 14 INCLUDING CONSTRAINTS, 15 CHECK ( port = '2110' ) 16 ) INHERITS (details_table); 17CREATE INDEX details_table_2110_idx ON details_table_2110 (port); 18 19CREATE TABLE IF NOT EXISTS details_table_2210 ( 20 LIKE details_table INCLUDING INDEXES 21 INCLUDING DEFAULTS 22 INCLUDING CONSTRAINTS, 23 CHECK ( port = '2210' ) 24 ) INHERITS (details_table); 25CREATE INDEX details_table_2210_idx ON details_table_2210 (port); 26 27CREATE TABLE IF NOT EXISTS details_table_2310 ( 28 LIKE details_table INCLUDING INDEXES 29 INCLUDING DEFAULTS 30 INCLUDING CONSTRAINTS, 31 CHECK ( port = '2310' ) 32 ) INHERITS (details_table); 33CREATE INDEX details_table_2310_idx ON details_table_2310 (port); 34 35CREATE TABLE IF NOT EXISTS details_table_2410 ( 36 LIKE details_table INCLUDING INDEXES 37 INCLUDING DEFAULTS 38 INCLUDING CONSTRAINTS, 39 CHECK ( port = '2410' ) 40 ) INHERITS (details_table); 41CREATE INDEX details_table_2410_idx ON details_table_2410 (port); 42 43-- 親テーブルのトリガーを作成します。 44-- 親へのインサート命令を子テーブルへのインサートに置換えます。 45-- RETURN NULLにより親テーブルへのインサートを中止します。子テーブルへインサート 46CREATE OR REPLACE FUNCTION details_table_trigger() RETURNS TRIGGER AS 47$$ 48 BEGIN 49 -- key is port 50 EXECUTE 'INSERT INTO ' || 'details_table_' || new.port || ' VALUES(($1).*)' USING new; 51 RETURN NULL; 52 END; 53$$ LANGUAGE plpgsql; 54 55-- 上記のトリガーを親テーブルへ付けます。 56-- BEFORE INSERTとして親テーブルへのインサート前に呼び出し、NULLにてスキップします。 57DROP TRIGGER IF EXISTS details_table_trigger ON details_table; 58CREATE TRIGGER details_table_trg 59 BEFORE INSERT 60 ON details_table 61 FOR EACH ROW 62 EXECUTE PROCEDURE details_table_trigger(); 63 64/* 65 データの作成は、子テーブルへ直接行う方が早いです。 66 その際は子テーブル毎にインサートしてください。 67 今回大量なので分割子テーブルへ直接挿入する方がよろしいかと思います。 68*/

投稿2016/10/21 10:44

編集2016/10/24 02:09
A.Ichi

総合スコア4070

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

zekterra

2016/10/24 00:30

また回答して頂きありがとうございます。 子テーブルに分けるというのは考えきれませんでした。 52テーブルに分けられそうなのでそれで試してみます。
A.Ichi

2016/10/24 02:05 編集

親テーブルへのINSERT 結果行数が 0 になってしまいます(正常に作成されても)にご注意ください。子テーブルは大丈夫
zekterra

2016/10/24 02:59

わざわざSQLまで貼って頂きありがとうございます! 今はメモリDBの使用を検討しているのですが、メモリDBも容量の都合上で不安があり無理そうだなと思っている状態です。 メモリDBでも出来ない場合はその方法も検討してみます!
A.Ichi

2016/10/24 03:07

自分の言っている事が正しいかの検証も兼ねていますので、誤りが有りますればご指摘ください。
zekterra

2016/10/26 02:06

お世話になります。 実は、前々日にSQLを見直すことによって改善されることが判明しました。 方法としては、 1.カラムにシーケンス番号を追加する 2.検索範囲内の始めのシーケンス番号と終わりのシーケンス番号を取得する 3.2つのシーケンス番号の差からだいたい等間隔に100件取得できるようなシーケンス番号のリストを作成する(java内の処理) 4.シーケンス番号のリストをwhere文の中に記述する    例:where seq = 10 or seq = 20 or seq = 30 以上の方法でどうにか速度の改善をすることが出来ました。 分割するSQLも書いていただいたのですが検証する前に実装できてしまいました。 前回の質問に引き続き回答して下さり、ありがとうございました。
guest

0

ORDER BY totaltable.response_time DESC, no DESC

無駄なソートキーがあります。
ORDER BY no DESC
でいいかと

100件しか取らないなら
ORDER BY no DESC FETCH FIRST 100 ROWS ONLY
とすれば、効果があるかも

さらには、
Row_Numberとmodを組み合わせるのでなく、
NTileというWindow関数を使えば、効果があるかもしれませんね。

投稿2016/10/21 09:06

編集2016/10/21 09:13
tamako

総合スコア120

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

zekterra

2016/10/21 09:33

回答ありがとうございます。 NTileとFETCH FIRSTについては初めて見る関数です。 無駄なソートキーのご指摘ありがとうございます。 指摘された点を改善してみます。
tamako

2016/10/21 09:51

NTileの仕様を勘違いしてました。 mod(Row_Number() over(order by ソートキー),割る数) と NTile(割る数) over(order by ソートキー) は、結果というか値の分布がだいぶ違いますね。 NTileは多分使えないです。
guest

0

自己解決

1.カラムにシーケンス番号を追加する
2.検索範囲内の始めのシーケンス番号と終わりのシーケンス番号を取得する
3.2つのシーケンス番号の差からだいたい等間隔に100件取得できるようなシーケンス番号のリストを作成する(java内の処理)
4.シーケンス番号のリストをwhere文の中に記述する
例:where seq = 10 or seq = 20 or seq = 30
以上の方法でどうにか速度の改善をすることが出来ました。

投稿2016/10/26 02:07

zekterra

総合スコア30

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問