アプリケーションにmessage
テーブルがあり、このテーブルで最も一般的に実行されるSQLクエリの1つは次のとおりです。
select * from message message0_
where message0_.sender_id=? or message0_.recipient_id=?
order by message0_.send_date asc;
基本的に、現在のユーザーが受信または送信したメッセージのクエリを実行しています。
sender_id
&recipient_id
のパラメーターの値は、現在のユーザーIDです。
次のインデックスを作成しました。
CREATE INDEX ON message(recipient_id, sender_id);
SQLクエリのOR句を念頭に置いて、私のユースケースでフィールドの順序が重要かどうかを知りたいのですが。
誰か助けてくれますか?
ユースケースでは、実際には2つの異なるインデックスが必要です。
_CREATE INDEX ON messages(recipient_id);
CREATE INDEX ON messages(sender_id);
_
インデックス付きの複数列は、クエリの_recipient_id = ??
_部分で使用されますが、他のデータベースでは使用されません(他のデータベースで使用できます)方法を知っている Oracleなどのスキャンをスキップ (ただし、非常に効率的ではない場合があります)。
_PRIMARY KEY
_がmessage(s)
テーブルにどのように対応するかによって、それらの1つが不要になる場合があります(PKに関連付けられた暗黙のインデックスが機能します)。
次に、クエリ_as is
_を使用してPostgreSQLにBitmapOrを実行させるか、クエリをUNION
に変換してOR
を回避します。タイミングはどちらの場合もほぼ同じでなければなりません。 _sender_id
_と_recipient_id
_の両方が同じメッセージがないことが確かな場合(つまり、自分にメッセージを送信した人がいない場合、または送信した場合は送信しません)それを2回表示することに注意してください)、次に_UNION ALL
_(少し速い)を代わりに使用して同じ結果を得ることができます。
OR
条件は、ほとんどのデータベースでうまく処理されない傾向があります。この特定のケースは、かなり一般的でかなり単純ですが、PostgreSQLによって適切に処理されます。
テーブル定義(簡略化)
_CREATE TABLE users
(
user_id integer PRIMARY KEY,
user_name text
) ;
CREATE TABLE messages
(
sender_id integer NOT NULL REFERENCES users(user_id)
ON UPDATE CASCADE ON DELETE RESTRICT,
recipient_id integer NOT NULL REFERENCES users(user_id)
ON UPDATE CASCADE ON DELETE RESTRICT,
send_date timestamp NOT NULL DEFAULT now(),
message_text text,
PRIMARY KEY(sender_id, recipient_id, send_date)
) ;
CREATE INDEX ON messages (recipient_id) ;
-- Following index not needed, given our primary key
-- CREATE INDEX ON messages (sender_id) ;
_
テーブルにシミュレーションデータを入力します。
_-- Create 1000 users
INSERT INTO users (user_id, user_name)
SELECT
user_id, 'user' || user_id AS user_name
FROM
generate_series(1, 1000) AS x(user_id) ;
-- Create (aprox.) 100000 messages
INSERT INTO messages (sender_id, recipient_id, send_date)
SELECT
(random()*999+1)::integer AS sender_id,
(random()*999+1)::integer AS recipient_id,
send_date
FROM
generate_series(timestamp '2017-01-01', timestamp '2017-01-31', interval '25 seconds') AS x(send_date);
ANALYZE;
_
そして、この時点で、取得した実行プランがどれであるかを確認します。
_-- Checking the query with OR
EXPLAIN ANALYZE
SELECT *
FROM messages
WHERE recipient_id = 123 OR sender_id = 123
ORDER BY send_date ;
_
与える...
_QUERY PLAN
1 Sort (cost=420.21..420.71 rows=200 width=48) (actual time=0.430..0.445 rows=192 loops=1)
2 Sort Key: send_date
3 Sort Method: quicksort Memory: 34kB
4 -> Bitmap Heap Scan on messages (cost=10.31..412.56 rows=200 width=48) (actual time=0.092..0.389 rows=192 loops=1)
5 Recheck Cond: ((recipient_id = 123) OR (sender_id = 123))
6 Heap Blocks: exact=157
7 -> BitmapOr (cost=10.31..10.31 rows=200 width=0) (actual time=0.061..0.061 rows=0 loops=1)
8 -> Bitmap Index Scan on messages_recipient_id_idx (cost=0.00..5.04 rows=100 width=0) (actual time=0.033..0.033 rows=97 loops=1)
9 Index Cond: (recipient_id = 123)
10 -> Bitmap Index Scan on messages_pkey (cost=0.00..5.17 rows=100 width=0) (actual time=0.025..0.025 rows=95 loops=1)
11 Index Cond: (sender_id = 123)
12 Planning time: 0.379 ms
13 Execution time: 0.527 ms
_
そしてUNION
を使用:
_-- Not using OR, but UNION-ing
EXPLAIN ANALYZE
SELECT *
FROM messages
WHERE recipient_id = 123
UNION
SELECT *
FROM messages
WHERE sender_id = 123
ORDER BY send_date ;
_
次のクエリプランを取得します。
_QUERY PLAN
1 Sort (cost=538.87..539.37 rows=200 width=48) (actual time=0.387..0.399 rows=192 loops=1)
2 Sort Key: messages.send_date
3 Sort Method: quicksort Memory: 34kB
4 -> HashAggregate (cost=529.22..531.22 rows=200 width=48) (actual time=0.268..0.317 rows=192 loops=1)
5 Group Key: messages.sender_id, messages.recipient_id, messages.send_date, messages.message_text
6 -> Append (cost=5.07..527.22 rows=200 width=48) (actual time=0.038..0.192 rows=192 loops=1)
7 -> Bitmap Heap Scan on messages (cost=5.07..262.55 rows=100 width=48) (actual time=0.038..0.094 rows=97 loops=1)
8 Recheck Cond: (recipient_id = 123)
9 Heap Blocks: exact=89
10 -> Bitmap Index Scan on messages_recipient_id_idx (cost=0.00..5.04 rows=100 width=0) (actual time=0.022..0.022 rows=97 loops=1)
11 Index Cond: (recipient_id = 123)
12 -> Bitmap Heap Scan on messages messages_1 (cost=5.19..262.67 rows=100 width=48) (actual time=0.033..0.085 rows=95 loops=1)
13 Recheck Cond: (sender_id = 123)
14 Heap Blocks: exact=86
15 -> Bitmap Index Scan on messages_pkey (cost=0.00..5.17 rows=100 width=0) (actual time=0.021..0.021 rows=95 loops=1)
16 Index Cond: (sender_id = 123)
17 Planning time: 0.178 ms
18 Execution time: 0.521 ms
_
完全を期すために、_UNION ALL
_:
_ QUERY PLAN
1 Sort (cost=537.11..537.62 rows=201 width=48) (actual time=0.213..0.229 rows=160 loops=1)
2 Sort Key: messages.send_date
3 Sort Method: quicksort Memory: 32kB
4 -> Append (cost=5.08..529.42 rows=201 width=48) (actual time=0.034..0.173 rows=160 loops=1)
5 -> Bitmap Heap Scan on messages (cost=5.08..264.74 rows=101 width=48) (actual time=0.034..0.086 rows=82 loops=1)
6 Recheck Cond: (recipient_id = 123)
7 Heap Blocks: exact=77
8 -> Bitmap Index Scan on messages_recipient_id_idx (cost=0.00..5.05 rows=101 width=0) (actual time=0.022..0.022 rows=82 loops=1)
9 Index Cond: (recipient_id = 123)
10 -> Bitmap Heap Scan on messages messages_1 (cost=5.19..262.67 rows=100 width=48) (actual time=0.030..0.075 rows=78 loops=1)
11 Recheck Cond: (sender_id = 123)
12 Heap Blocks: exact=72
13 -> Bitmap Index Scan on messages_pkey (cost=0.00..5.17 rows=100 width=0) (actual time=0.020..0.020 rows=78 loops=1)
14 Index Cond: (sender_id = 123)
15 Planning time: 0.222 ms
16 Execution time: 0.280 ms
_
どちらの場合も、最初の2つのアプローチの実行計画はほとんど同じで、UNION ALLの方が優れていることがわかります。
まったく同じことを行ってもCREATE INDEX ON messages (recipient_id) ;
ステートメントがない場合は、次のようになります。
_QUERY PLAN
1 Sort (cost=2123.86..2124.36 rows=200 width=48) (actual time=27.305..27.320 rows=227 loops=1)
2 Sort Key: send_date
3 Sort Method: quicksort Memory: 35kB
4 -> Seq Scan on messages (cost=0.00..2116.22 rows=200 width=48) (actual time=0.108..27.097 rows=227 loops=1)
5 Filter: ((recipient_id = 123) OR (sender_id = 123))
6 Rows Removed by Filter: 103454
7 Planning time: 0.474 ms
8 Execution time: 27.392 ms
QUERY PLAN
1 Sort (cost=2135.60..2136.10 rows=201 width=48) (actual time=15.716..15.803 rows=227 loops=1)
2 Sort Key: messages.send_date
3 Sort Method: quicksort Memory: 35kB
4 -> HashAggregate (cost=2125.90..2127.91 rows=201 width=48) (actual time=15.571..15.621 rows=227 loops=1)
5 Group Key: messages.sender_id, messages.recipient_id, messages.send_date, messages.message_text
6 -> Append (cost=0.00..2123.89 rows=201 width=48) (actual time=0.061..15.371 rows=227 loops=1)
7 -> Seq Scan on messages (cost=0.00..1857.01 rows=100 width=48) (actual time=0.060..15.092 rows=117 loops=1)
8 Filter: (recipient_id = 123)
9 Rows Removed by Filter: 103564
10 -> Bitmap Heap Scan on messages messages_1 (cost=5.20..264.87 rows=101 width=48) (actual time=0.086..0.248 rows=110 loops=1)
11 Recheck Cond: (sender_id = 123)
12 Heap Blocks: exact=101
13 -> Bitmap Index Scan on messages_pkey (cost=0.00..5.18 rows=101 width=0) (actual time=0.066..0.066 rows=110 loops=1)
14 Index Cond: (sender_id = 123)
15 Planning time: 0.333 ms
16 Execution time: 16.006 ms
QUERY PLAN
1 Sort (cost=2131.58..2132.08 rows=201 width=48) (actual time=14.847..14.865 rows=227 loops=1)
2 Sort Key: messages.send_date
3 Sort Method: quicksort Memory: 35kB
4 -> Append (cost=0.00..2123.89 rows=201 width=48) (actual time=0.076..14.731 rows=227 loops=1)
5 -> Seq Scan on messages (cost=0.00..1857.01 rows=100 width=48) (actual time=0.076..14.497 rows=117 loops=1)
6 Filter: (recipient_id = 123)
7 Rows Removed by Filter: 103564
8 -> Bitmap Heap Scan on messages messages_1 (cost=5.20..264.87 rows=101 width=48) (actual time=0.082..0.209 rows=110 loops=1)
9 Recheck Cond: (sender_id = 123)
10 Heap Blocks: exact=101
11 -> Bitmap Index Scan on messages_pkey (cost=0.00..5.18 rows=101 width=0) (actual time=0.060..0.060 rows=110 loops=1)
12 Index Cond: (sender_id = 123)
13 Planning time: 0.284 ms
14 Execution time: 14.931 ms
_
...いくつかの順次スキャンが必要なため、これは悪いクエリプランです。このアプローチは this Rextester でも確認できます。
可能な解決策の1つは次のとおりです。
CREATE INDEX ON message(recipient_id);
CREATE INDEX ON message(sender_id);
そして実行:
select * from message message0_
where message0_.sender_id=?
UNION ALL
select * from message message0_
where message0_.recipient_id=?
order by message0_.send_date asc;