Unpredictable query performance in Postgresql - sql

I have tables like that in a Postgres 9.3 database:
A <1---n B n---1> C
Table A contains ~10^7 rows, table B is rather big with ~10^9 rows and C contains ~100 rows.
I use the following query to find all As (distinct) that match some criteria in B and C (the real query is more complex, joins more tables and checks more attributes within the subquery):
Query 1:
explain analyze
select A.SNr from A
where exists (select 1 from B, C
where B.AId = A.Id and
B.CId = C.Id and
B.Timestamp >= '2013-01-01' and
B.Timestamp <= '2013-01-12' and
C.Name = '00000015')
limit 200;
That query takes about 500ms (Note that C.Name = '00000015' exists in the table):
Limit (cost=119656.37..120234.06 rows=200 width=9) (actual time=427.799..465.485 rows=200 loops=1)
-> Hash Semi Join (cost=119656.37..483518.78 rows=125971 width=9) (actual time=427.797..465.460 rows=200 loops=1)
Hash Cond: (a.id = b.aid)
-> Seq Scan on a (cost=0.00..196761.34 rows=12020034 width=13) (actual time=0.010..15.058 rows=133470 loops=1)
-> Hash (cost=117588.73..117588.73 rows=125971 width=4) (actual time=427.233..427.233 rows=190920 loops=1)
Buckets: 4096 Batches: 8 Memory Usage: 838kB
-> Nested Loop (cost=0.57..117588.73 rows=125971 width=4) (actual time=0.176..400.326 rows=190920 loops=1)
-> Seq Scan on c (cost=0.00..2.88 rows=1 width=4) (actual time=0.015..0.030 rows=1 loops=1)
Filter: (name = '00000015'::text)
Rows Removed by Filter: 149
-> Index Only Scan using cid_aid on b (cost=0.57..116291.64 rows=129422 width=8) (actual time=0.157..382.896 rows=190920 loops=1)
Index Cond: ((cid = c.id) AND ("timestamp" >= '2013-01-01 00:00:00'::timestamp without time zone) AND ("timestamp" <= '2013-01-12 00:00:00'::timestamp without time zone))
Heap Fetches: 0
Total runtime: 476.173 ms
Query 2: Changing C.Name to something that doesn't exist (C.Name = 'foo') takes 0.1ms:
explain analyze
select A.SNr from A
where exists (select 1 from B, C
where B.AId = A.Id and
B.CId = C.Id and
B.Timestamp >= '2013-01-01' and
B.Timestamp <= '2013-01-12' and
C.Name = 'foo')
limit 200;
Limit (cost=119656.37..120234.06 rows=200 width=9) (actual time=0.063..0.063 rows=0 loops=1)
-> Hash Semi Join (cost=119656.37..483518.78 rows=125971 width=9) (actual time=0.062..0.062 rows=0 loops=1)
Hash Cond: (a.id = b.aid)
-> Seq Scan on a (cost=0.00..196761.34 rows=12020034 width=13) (actual time=0.010..0.010 rows=1 loops=1)
-> Hash (cost=117588.73..117588.73 rows=125971 width=4) (actual time=0.038..0.038 rows=0 loops=1)
Buckets: 4096 Batches: 8 Memory Usage: 0kB
-> Nested Loop (cost=0.57..117588.73 rows=125971 width=4) (actual time=0.038..0.038 rows=0 loops=1)
-> Seq Scan on c (cost=0.00..2.88 rows=1 width=4) (actual time=0.037..0.037 rows=0 loops=1)
Filter: (name = 'foo'::text)
Rows Removed by Filter: 150
-> Index Only Scan using cid_aid on b (cost=0.57..116291.64 rows=129422 width=8) (never executed)
Index Cond: ((cid = c.id) AND ("timestamp" >= '2013-01-01 00:00:00'::timestamp without time zone) AND ("timestamp" <= '2013-01-12 00:00:00'::timestamp without time zone))
Heap Fetches: 0
Total runtime: 0.120 ms
Query 3: Resetting the C.Name to something that exists (like in the first query) and increasing the timestamp by 3 days uses another query plan than before, but is still fast (200ms):
explain analyze
select A.SNr from A
where exists (select 1 from B, C
where B.AId = A.Id and
B.CId = C.Id and
B.Timestamp >= '2013-01-01' and
B.Timestamp <= '2013-01-15' and
C.Name = '00000015')
limit 200;
Limit (cost=0.57..112656.93 rows=200 width=9) (actual time=4.404..227.569 rows=200 loops=1)
-> Nested Loop Semi Join (cost=0.57..90347016.34 rows=160394 width=9) (actual time=4.403..227.544 rows=200 loops=1)
-> Seq Scan on a (cost=0.00..196761.34 rows=12020034 width=13) (actual time=0.008..1.046 rows=12250 loops=1)
-> Nested Loop (cost=0.57..7.49 rows=1 width=4) (actual time=0.017..0.017 rows=0 loops=12250)
-> Seq Scan on c (cost=0.00..2.88 rows=1 width=4) (actual time=0.005..0.015 rows=1 loops=12250)
Filter: (name = '00000015'::text)
Rows Removed by Filter: 147
-> Index Only Scan using cid_aid on b (cost=0.57..4.60 rows=1 width=8) (actual time=0.002..0.002 rows=0 loops=12250)
Index Cond: ((cid = c.id) AND (aid = a.id) AND ("timestamp" >= '2013-01-01 00:00:00'::timestamp without time zone) AND ("timestamp" <= '2013-01-15 00:00:00'::timestamp without time zone))
Heap Fetches: 0
Total runtime: 227.632 ms
Query 4: But that new query plan utterly fails when searching for a C.Name that doesn't exist::
explain analyze
select A.SNr from A
where exists (select 1 from B, C
where B.AId = A.Id and
B.CId = C.Id and
B.Timestamp >= '2013-01-01' and
B.Timestamp <= '2013-01-15' and
C.Name = 'foo')
limit 200;
Now it takes 170 seconds (vs. 0.1ms before!) to return the same 0 rows:
Limit (cost=0.57..112656.93 rows=200 width=9) (actual time=170184.979..170184.979 rows=0 loops=1)
-> Nested Loop Semi Join (cost=0.57..90347016.34 rows=160394 width=9) (actual time=170184.977..170184.977 rows=0 loops=1)
-> Seq Scan on a (cost=0.00..196761.34 rows=12020034 width=13) (actual time=0.008..794.626 rows=12020034 loops=1)
-> Nested Loop (cost=0.57..7.49 rows=1 width=4) (actual time=0.013..0.013 rows=0 loops=12020034)
-> Seq Scan on c (cost=0.00..2.88 rows=1 width=4) (actual time=0.013..0.013 rows=0 loops=12020034)
Filter: (name = 'foo'::text)
Rows Removed by Filter: 150
-> Index Only Scan using cid_aid on b (cost=0.57..4.60 rows=1 width=8) (never executed)
Index Cond: ((cid = c.id) AND (aid = a.id) AND ("timestamp" >= '2013-01-01 00:00:00'::timestamp without time zone) AND ("timestamp" <= '2013-01-15 00:00:00'::timestamp without time zone))
Heap Fetches: 0
Total runtime: 170185.033 ms
All queries were run after "alter table set statistics" with a value of 10000 on all columns and after running analyze on the whole db.
Right now it looks like the slightest change of a parameter (not even of the SQL) can make Postgres choose a bad plan (0.1ms vs. 170s in this case!). I always try to check query plans when changing things, but it's hard to ever be sure that something will work when such small changes on parameters can make such huge differences. I have similar problems with other queries too.
What can I do to get more predictable results?
(I have tried modifying certain query planning parameters (set enable_... = on/off) and some different SQL statements - joining+distinct/group by instead of "exists" - but nothing seems to make postgres choose "stable" query plans while still providing acceptable performance).
Edit #1: Table + index definitions
test=# \d a
Tabelle äpublic.aô
Spalte | Typ | Attribute
--------+---------+----------------------------------------------------
id | integer | not null Vorgabewert nextval('a_id_seq'::regclass)
anr | integer |
snr | text |
Indexe:
"a_pkey" PRIMARY KEY, btree (id)
"anr_snr_index" UNIQUE, btree (anr, snr)
"anr_index" btree (anr)
Fremdschlnssel-Constraints:
"anr_fkey" FOREIGN KEY (anr) REFERENCES pt(id)
Fremdschlnsselverweise von:
TABLE "b" CONSTRAINT "aid_fkey" FOREIGN KEY (aid) REFERENCES a(id)
test=# \d b
Tabelle äpublic.bô
Spalte | Typ | Attribute
-----------+-----------------------------+-----------
id | uuid | not null
timestamp | timestamp without time zone |
cid | integer |
aid | integer |
prop1 | text |
propn | integer |
Indexe:
"b_pkey" PRIMARY KEY, btree (id)
"aid_cid" btree (aid, cid)
"cid_aid" btree (cid, aid, "timestamp")
"timestamp_index" btree ("timestamp")
Fremdschlnssel-Constraints:
"aid_fkey" FOREIGN KEY (aid) REFERENCES a(id)
"cid_fkey" FOREIGN KEY (cid) REFERENCES c(id)
test=# \d c
Tabelle äpublic.cô
Spalte | Typ | Attribute
--------+---------+----------------------------------------------------
id | integer | not null Vorgabewert nextval('c_id_seq'::regclass)
name | text |
Indexe:
"c_pkey" PRIMARY KEY, btree (id)
"c_name_index" UNIQUE, btree (name)
Fremdschlnsselverweise von:
TABLE "b" CONSTRAINT "cid_fkey" FOREIGN KEY (cid) REFERENCES c(id)

Your problem is that the query needs to evaluate the correlated sub query for the entire table a. When Postgres quickly finds 200 random rows that fit (which seems to occasionally be the case when c.name exists), it yields them accordingly, and reasonably fast if there are plenty to choose from. But when no such rows exists, it evaluates the entire hogwash in the exists() statement as many times as table a has rows, hence the performance issue you're seeing.
Adding an uncorrelated where clause will most certainly fix a number of edge cases:
and exists(select 1 from c where name = ?)
It might also work when you join the latter with b and write it as a cte:
with bc as (
select aid
from b join c on b.cid = c.bid
and b.timestamp between ? and ?
and c.name = ?
)
select a.id
from a
where exists (select 1 from bc)
and exists (select 1 from bc where a.id = bc.aid)
limit 200
If not, just toss in the bc query verbatim instead of using the cte. The point here is to force Postgres to consider the bc lookup as independent, and bail early if the resulting set yields no rows at all.
I assume your query is more complex in the end, but note that the above could be rewritten as:
with bc as (...)
select aid
from bc
limit 200
Or:
with bc as (...)
select a.id
from a
where a.id in (select aid from bc)
limit 200
Both should yield better plans in edge cases.
(Side note: it's usually unadvisable to limit without ordering.)

Maybe try to rewrite query with CTE?
with BC as (
select distinct B.AId from B where
B.Timestamp >= '2013-01-01' and
B.Timestamp <= '2013-01-12' and
B.CId in (select C.Id from C where C.Name = '00000015')
limit 200
)
select A.SNr from A where A.Id in (select AId from BC)
If I understand correctly - limit could be easily put inside BC query to avoid scan on table A.

Related

PostgreSQL 9.5.15 - Left join with huge table taking time

I need your suggestion on the below query performance issue.
CREATE TEMPORARY TABLE tableA( id bigint NOT NULL, second_id bigint );
CREATE INDEX idx_tableA_id ON tableA USING btree (id);
CREATE INDEX idx_tableA_second_id ON tableA USING btree (second_id);
here the table A having 100K records.
CREATE TABLE tableB( id bigint NOT NULL);
CREATE INDEX idx_tableB_id ON tableB USING btree (id);
But the table B having the 145GB volume of data.
If i execute the query with one left join like below,
select a.id from table A left join table B on B.id = A.id
or
select a.id from table A left join table B on B.d_id = A.Second_id
getting the data quicker. But when i combine both the LEFT JOIN, then the query taking 30 minutes to query the records.
SELECT a.id
FROM tableA A LEFT JOIN tableB B on B.id = A.id
LEFT JOIN tableB B1 on B1.id = A.second_id;
Got the indexes on the respective columns. Any other performance suggestions to reduce the execution time.
VERSION: "PostgreSQL 9.5.15 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.2 20140120 (Red Hat 4.8.2-16), 64-bit"
Execution plan
Hash Right Join (cost=18744968.20..108460708.81 rows=298384424 width=8) (actual time=469896.453..1290666.446 rows=26520 loops=1)
Hash Cond: (tableB.id = tableA.id)
-> Seq Scan on tableB ubp1 (cost=0.00..63944581.96 rows=264200740 width=8) (actual time=127.504..1182339.167 rows=268297289 loops=1)
Filter: (company_type_id = 2)
Rows Removed by Filter: 1409407086
-> Hash (cost=18722912.16..18722912.16 rows=1764483 width=8) (actual time=16564.303..16564.303 rows=26520 loops=1)
Buckets: 2097152 Batches: 1 Memory Usage: 17420kB
-> Merge Join (cost=6035.58..18722912.16 rows=1764483 width=8) (actual time=37.964..16503.057 rows=26520 loops=1)
-> Nested Loop Left Join (cost=0.86..18686031.22 rows=1752390 width=8) (actual time=0.019..16412.634 rows=26520 loops=1)
-> Index Scan using idx_tableA_id on tableA A (cost=0.29..94059.62 rows=26520 width=16) (actual time=0.013..69.016 rows=26520 loops=1)
-> Index Scan using idx_tableB_id on tableB B (cost=0.58..699.36 rows=169 width=8) (actual time=0.458..0.615 rows=0 loops=26520)
Index Cond: (tableA.id = tableB.second_id)
Filter: (company_type_id = 2)
Rows Removed by Filter: 2
-> Sort (cost=6034.21..6100.97 rows=26703 width=8) (actual time=37.941..54.444 rows=26520 loops=1)
Rows Removed by Filter: 105741
Planning time: 0.878 ms
Execution time: 1290674.154 ms
Thanks and Regards,
Thiru.M
I suspect
B1.second_id either does not have an index
B1.second_id is not unique (or a primary key)
B1.second_id is part of a multi-column index where it is not the first column in the index
If you have an index on that column, you could also try moving the indexes to a different volume so that it's not in contention with the main data retrieval.
Run EXPLAIN on your query to verify that indexes are being used instead of falling back to a sequential scan on a 145GB volume.
You also didn't mention how much RAM your database has available. If your settings combined with your query is making the system swap, you could see precipitous drops in performance as well.

COUNT * is too slow using postgresql

I am observing that COUNT(*) from table is not an optimised query when it comes to deep SQLs.
Here's the sql I am working with
SELECT COUNT(*) FROM "items"
INNER JOIN (
SELECT c.* FROM companies c LEFT OUTER JOIN company_groups ON c.id = company_groups.company_id
WHERE company_groups.has_restriction IS NULL OR company_groups.has_restriction = 'f' OR company_groups.company_id = 1999 OR company_groups.group_id IN ('3','2')
GROUP BY c.id
) AS companies ON companies.id = stock_items.vendor_id
LEFT OUTER JOIN favs ON items.id = favs.item_id AND favs.user_id = 999 AND favs.is_visible = TRUE
WHERE "items"."type" IN ('Fashion') AND "items"."visibility" = 't' AND "items"."is_hidden" = 'f' AND (items.depth IS NULL OR (items.depth >= '0' AND items.depth <= '100')) AND (items.table IS NULL OR (items.table >= '0' AND items.table <= '100')) AND (items.company_id NOT IN (199,200,201))
This query is taking 4084.8ms to count from 0.35 Million records from database.
I am using Rails as framework, so the SQL I am composing fires a COUNT query of the original query whenever I call results.count
Since, I am using LIMIT and OFFSET so basic results are loading in less than 32.0ms (which is way too fast)
Here's the output of the EXPLAIN ANALYSE
Merge Join (cost=70743.22..184962.02 rows=7540499 width=4) (actual time=4018.351..4296.963 rows=360323 loops=1)
Merge Cond: (c.id = items.company_id)
-> Group (cost=0.56..216.21 rows=4515 width=4) (actual time=0.357..5.165 rows=4501 loops=1)
Group Key: c.id
-> Merge Left Join (cost=0.56..204.92 rows=4515 width=4) (actual time=0.303..2.590 rows=4504 loops=1)
Merge Cond: (c.id = company_groups.company_id)
Filter: ((company_groups.has_restriction IS NULL) OR (NOT company_groups.has_restriction) OR (company_groups.company_id = 1999) OR (company_groups.group_id = ANY ('{3,2}'::integer[])))
Rows Removed by Filter: 10
-> Index Only Scan using companies_pkey on companies c (cost=0.28..128.10 rows=4521 width=4) (actual time=0.155..0.941 rows=4508 loops=1)
Heap Fetches: 3
-> Index Scan using index_company_groups_on_company_id on company_groups (cost=0.28..50.14 rows=879 width=9) (actual time=0.141..0.480 rows=878 loops=1)
-> Materialize (cost=70742.66..72421.11 rows=335690 width=8) (actual time=4017.964..4216.381 rows=362180 loops=1)
-> Sort (cost=70742.66..71581.89 rows=335690 width=8) (actual time=4017.955..4140.168 rows=362180 loops=1)
Sort Key: items.company_id
Sort Method: external merge Disk: 6352kB
-> Hash Left Join (cost=1.05..35339.74 rows=335690 width=8) (actual time=0.617..3588.634 rows=362180 loops=1)
Hash Cond: (items.id = favs.item_id)
-> Seq Scan on items (cost=0.00..34079.84 rows=335690 width=8) (actual time=0.504..3447.355 rows=362180 loops=1)
Filter: (visibility AND (NOT is_hidden) AND ((type)::text = 'Fashion'::text) AND (company_id <> ALL ('{199,200,201}'::integer[])) AND ((depth IS NULL) OR ((depth >= '0'::numeric) AND (depth <= '100'::nume (...)
Rows Removed by Filter: 5814
-> Hash (cost=1.04..1.04 rows=1 width=4) (actual time=0.009..0.009 rows=0 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 8kB
-> Seq Scan on favs (cost=0.00..1.04 rows=1 width=4) (actual time=0.008..0.008 rows=0 loops=1)
Filter: (is_visible AND (user_id = 999))
Rows Removed by Filter: 3
Planning time: 3.526 ms
Execution time: 4397.849 ms
Please advise on how should I make it work faster!
P.S.: All the columns are indexed like type, visibility, is_hidden, table, depth etc.
Thanks in advance!
Well, you have two parts that select everything (SELECT *) in your query, maybe you could limit that and see if it helps, example:
SELECT COUNT(OneSpecificColumn)
FROM "items"
INNER JOIN
( SELECT c.(AnotherSpecificColumn)
FROM companies c
LEFT OUTER JOIN company_groups ON c.id = company_groups.company_id
WHERE company_groups.has_restriction IS NULL
OR company_groups.has_restriction = 'f'
OR company_groups.company_id = 1999
OR company_groups.group_id IN ('3',
'2')
GROUP BY c.id) AS companies ON companies.id = stock_items.vendor_id
LEFT OUTER JOIN favs ON items.id = favs.item_id
AND favs.user_id = 999
AND favs.is_visible = TRUE
WHERE "items"."type" IN ('Fashion')
AND "items"."visibility" = 't'
AND "items"."is_hidden" = 'f'
AND (items.depth IS NULL
OR (items.depth >= '0'
AND items.depth <= '100'))
AND (items.table IS NULL
OR (items.table >= '0'
AND items.table <= '100'))
AND (items.company_id NOT IN (199,
200,
201))
You could also check if those left joins are all necessary, inner joins are less costly and may speed up your search.
The lion's share of the time is spent in the sequential scan of items, and that cannot be improved, because you need almost all of the rows in the table.
So the only ways to improve the query are
see that items is cached in memory
get faster storage

DISTINCT with ORDER BY very slow

So I am using postgres for the first time and finding it rather slow to run distinct and group by queries, currently i am trying to find the latest record and whether or not it is working or not.
This is the first query I came up with:
SELECT DISTINCT ON (device_id) c.device_id, c.timestamp, c.working
FROM call_logs c
ORDER BY c.device_id, c.timestamp desc
And it works but it is taking along time to run.
Unique (cost=94840.24..97370.54 rows=11 width=17) (actual time=424.424..556.253 rows=13 loops=1)
-> Sort (cost=94840.24..96105.39 rows=506061 width=17) (actual time=424.423..531.905 rows=506061 loops=1)
Sort Key: device_id, "timestamp" DESC
Sort Method: external merge Disk: 13272kB
-> Seq Scan on call_logs c (cost=0.00..36512.61 rows=506061 width=17) (actual time=0.059..162.932 rows=506061 loops=1)
Planning time: 0.152 ms
Execution time: 557.957 ms
(7 rows)
I have updated the query to use the following which is faster but very ugly:
SELECT c.device_id, c.timestamp, c.working FROM call_logs c
INNER JOIN (SELECT c.device_id, MAX(c.timestamp) AS timestamp
FROM call_logs c
GROUP BY c.device_id)
newest on newest.timestamp = c.timestamp
and the analysis:
Nested Loop (cost=39043.34..39136.08 rows=12 width=17) (actual time=216.406..216.580 rows=15 loops=1)
-> HashAggregate (cost=39042.91..39043.02 rows=11 width=16) (actual time=216.347..216.351 rows=13 loops=1)
Group Key: c_1.device_id
-> Seq Scan on call_logs c_1 (cost=0.00..36512.61 rows=506061 width=16) (actual time=0.026..125.482 rows=506061 loops=1)
-> Index Scan using call_logs_timestamp on call_logs c (cost=0.42..8.44 rows=1 width=17) (actual time=0.016..0.016 rows=1 loops=13)
Index Cond: ("timestamp" = (max(c_1."timestamp")))
Planning time: 0.318 ms
Execution time: 216.631 ms
(8 rows)
Even 200ms does seem a little slow to me as all I want is the top record per device (which is in an indexed table)
AND this is my index it is using:
CREATE INDEX call_logs_timestamp
ON public.call_logs USING btree
(timestamp)
TABLESPACE pg_default;
I have tried the below index but does not help at all:
CREATE INDEX dev_ts_1
ON public.call_logs USING btree
(device_id, timestamp DESC, working)
TABLESPACE pg_default;
Any ideas am I missing something obvious?
200 ms really isn't that bad for going through 500k rows. But for this query:
SELECT DISTINCT ON (device_id) c.device_id, c.timestamp, c.working
FROM call_logs c
ORDER BY c.device_id, c.timestamp desc
Then your index on call_logs(device_id, timestamp desc, working) should be an optimal index.
Two other ways to write the query for the same index are:
select c.*
from (select c.device_id, c.timestamp, c.working, c.*,
row_number() over (partition by device_id order by timestamp desc) as seqnum
from call_logs c
) c
where seqnum = 1;
and:
select c.device_id, c.timestamp, c.working
from call_logs c
where not exists (select 1
from call_logs c2
where c2.device_id = c.device_id and
c2.timestamp > c.timestamp
);

Why Postgresql using sec scan in my query

I have a problem with one sql query - it became run too long.
That problem had place one month ago, before everything was OK.
EXPLAIN tell me that Postgresql using Sec scan, well, problem is clear... But why it using sec scan? DB has indexes for all needed keys.
I have spend a lot of time to fix it, but all attemps have failed :(
Please, help!
There is SQL:
EXPLAIN ANALYZE SELECT
t.id,t.category_id,t.category_root_id,t.title,t.deadtime,
t.active,t.initial_cost,t.remote,t.created,t.work_type,
a.city_id,city.city_name_ru AS city_label, curr.short_name AS currency,
l1.val AS root_category_title, l2.val AS category_title, t.service_tasktotop,
t.service_taskcolor,t.service_tasktime, m.name AS metro,
count(tb.id) AS offers_cnt, t.contact_phone_cnt
FROM tasks AS t
LEFT JOIN tasks_address AS a ON ( a.task_id=t.id )
LEFT JOIN geo.cities AS city ON ( a.city_id=city.id_city )
LEFT JOIN catalog_categories AS r ON ( r.id=t.category_root_id )
LEFT JOIN catalog_categories AS c ON ( c.id=t.category_id)
LEFT JOIN localizations AS l1 ON (l1.lang='ru' AND l1.component='catalog' AND l1.subcomponent='categories' AND l1.var=r.name)
LEFT JOIN localizations AS l2 ON (l2.lang='ru' AND l2.component='catalog' AND l2.subcomponent='categories' AND l2.var=c.name)
LEFT JOIN tasks_bets AS tb ON ( tb.task_id=t.id )
LEFT JOIN paym.currencies AS curr ON ( t.currency_id=curr.id )
LEFT JOIN geo.metro AS m ON ( a.metro_id=m.id )
WHERE t.trust_level > 0
AND (a.region_id IN (1, 0) OR a.region_id IS NULL)
AND (a.country_id IN (1, 0) OR a.country_id IS NULL)
AND t.task_type=1
GROUP BY t.id,t.category_id,t.category_root_id,t.title,t.deadtime,t.active,t.initial_cost,t.remote,t.created,t.work_type, a.city_id, city.city_name_ru, curr.short_name, l1.val, l2.val, t.contact_phone_cnt, t.service_tasktotop,t.service_taskcolor,t.service_tasktime, m.name
ORDER BY
CASE
WHEN t.active=1 THEN
CASE
WHEN t.service_tasktotop > 1392025702 THEN 100
ELSE 150
END
WHEN t.active=2 THEN
CASE
WHEN t.service_tasktotop > 1392025702 THEN 200
ELSE 250
END
WHEN t.active=3 THEN 300
WHEN t.active=4 THEN 400
WHEN t.active=5 THEN 500
WHEN t.active=-1 THEN 600
WHEN t.active=-2 THEN 700
WHEN t.active=-3 THEN 800
WHEN t.active=-4 THEN 900
ELSE 1000
END,
CASE
WHEN t.service_tasktotop>1392025702 THEN t.service_tasktotop
ELSE t.created
END
DESC
LIMIT 30 OFFSET 0
There is EXPLAIN dump:
Limit (cost=17101.17..17101.24 rows=30 width=701) (actual time=248.486..248.497 rows=30 loops=1)
-> Sort (cost=17101.17..17156.12 rows=21982 width=701) (actual time=248.484..248.487 rows=30 loops=1)
Sort Key: (CASE WHEN (t.active = 1) THEN CASE WHEN (t.service_tasktotop > 1392025702) THEN 100 ELSE 150 END WHEN (t.active = 2) THEN CASE WHEN (t.service_tasktotop > 1392025702) THEN 200 ELSE 250 END WHEN (t.active = 3) THEN 300 WHEN (t.active = 4) THEN 400 WHEN (t.active = 5) THEN 500 WHEN (t.active = (-1)) THEN 600 WHEN (t.active = (-2)) THEN 700 WHEN (t.active = (-3)) THEN 800 WHEN (t.active = (-4)) THEN 900 ELSE 1000 END), (CASE WHEN (t.service_tasktotop > 1392025702) THEN t.service_tasktotop ELSE t.created END)
Sort Method: top-N heapsort Memory: 35kB
-> GroupAggregate (cost=14363.65..16451.94 rows=21982 width=701) (actual time=212.801..233.808 rows=6398 loops=1)
-> Sort (cost=14363.65..14418.61 rows=21982 width=701) (actual time=212.777..216.111 rows=18347 loops=1)
Sort Key: t.id, t.category_id, t.category_root_id, t.title, t.deadtime, t.active, t.initial_cost, t.remote, t.created, t.work_type, a.city_id, city.city_name_ru, curr.short_name, l1.val, l2.val, t.contact_phone_cnt, t.service_tasktotop, t.service_taskcolor, t.service_tasktime, m.name
Sort Method: quicksort Memory: 6388kB
-> Hash Left Join (cost=2392.33..5939.31 rows=21982 width=701) (actual time=18.986..64.598 rows=18347 loops=1)
Hash Cond: (a.metro_id = m.id)
-> Hash Left Join (cost=2384.20..5628.92 rows=21982 width=681) (actual time=18.866..57.534 rows=18347 loops=1)
Hash Cond: (t.currency_id = curr.id)
-> Hash Left Join (cost=2383.09..5325.56 rows=21982 width=678) (actual time=18.846..50.126 rows=18347 loops=1)
Hash Cond: (t.id = tb.task_id)
-> Hash Left Join (cost=809.08..2760.18 rows=5935 width=674) (actual time=10.987..32.460 rows=6398 loops=1)
Hash Cond: (a.city_id = city.id_city)
-> Hash Left Join (cost=219.25..2029.39 rows=5935 width=158) (actual time=2.883..20.952 rows=6398 loops=1)
Hash Cond: (t.category_root_id = r.id)
-> Hash Left Join (cost=203.42..1969.65 rows=5935 width=125) (actual time=2.719..18.048 rows=6398 loops=1)
Hash Cond: (t.category_id = c.id)
-> Hash Left Join (cost=187.60..1909.91 rows=5935 width=92) (actual time=2.522..15.021 rows=6398 loops=1)
Hash Cond: (t.id = a.task_id)
Filter: (((a.region_id = ANY ('{1,0}'::integer[])) OR (a.region_id IS NULL)) AND ((a.country_id = ANY ('{1,0}'::integer[])) OR (a.country_id IS NULL)))
-> Seq Scan on tasks t (cost=0.00..1548.06 rows=7337 width=84) (actual time=0.008..6.337 rows=7337 loops=1)
Filter: ((trust_level > 0) AND (task_type = 1))
-> Hash (cost=124.49..124.49 rows=5049 width=18) (actual time=2.505..2.505 rows=5040 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 256kB
-> Seq Scan on tasks_address a (cost=0.00..124.49 rows=5049 width=18) (actual time=0.002..1.201 rows=5040 loops=1)
-> Hash (cost=14.91..14.91 rows=73 width=37) (actual time=0.193..0.193 rows=74 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 5kB
-> Hash Left Join (cost=6.46..14.91 rows=73 width=37) (actual time=0.113..0.168 rows=74 loops=1)
Hash Cond: ((c.name)::text = (l2.var)::text)
-> Seq Scan on catalog_categories c (cost=0.00..7.73 rows=73 width=17) (actual time=0.001..0.017 rows=74 loops=1)
-> Hash (cost=5.42..5.42 rows=84 width=46) (actual time=0.105..0.105 rows=104 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 8kB
-> Seq Scan on localizations l2 (cost=0.00..5.42 rows=84 width=46) (actual time=0.005..0.056 rows=104 loops=1)
Filter: (((lang)::text = 'ru'::text) AND ((component)::text = 'catalog'::text) AND ((subcomponent)::text = 'categories'::text))
-> Hash (cost=14.91..14.91 rows=73 width=37) (actual time=0.155..0.155 rows=74 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 5kB
-> Hash Left Join (cost=6.46..14.91 rows=73 width=37) (actual time=0.085..0.130 rows=74 loops=1)
Hash Cond: ((r.name)::text = (l1.var)::text)
-> Seq Scan on catalog_categories r (cost=0.00..7.73 rows=73 width=17) (actual time=0.002..0.016 rows=74 loops=1)
-> Hash (cost=5.42..5.42 rows=84 width=46) (actual time=0.080..0.080 rows=104 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 8kB
-> Seq Scan on localizations l1 (cost=0.00..5.42 rows=84 width=46) (actual time=0.004..0.046 rows=104 loops=1)
Filter: (((lang)::text = 'ru'::text) AND ((component)::text = 'catalog'::text) AND ((subcomponent)::text = 'categories'::text))
-> Hash (cost=363.26..363.26 rows=18126 width=520) (actual time=8.093..8.093 rows=18126 loops=1)
Buckets: 2048 Batches: 1 Memory Usage: 882kB
-> Seq Scan on cities city (cost=0.00..363.26 rows=18126 width=520) (actual time=0.002..3.748 rows=18126 loops=1)
-> Hash (cost=1364.56..1364.56 rows=16756 width=8) (actual time=7.844..7.844 rows=16757 loops=1)
Buckets: 2048 Batches: 1 Memory Usage: 655kB
-> Seq Scan on tasks_bets tb (cost=0.00..1364.56 rows=16756 width=8) (actual time=0.005..4.180 rows=16757 loops=1)
-> Hash (cost=1.05..1.05 rows=5 width=9) (actual time=0.008..0.008 rows=5 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 1kB
-> Seq Scan on currencies curr (cost=0.00..1.05 rows=5 width=9) (actual time=0.003..0.005 rows=5 loops=1)
-> Hash (cost=5.28..5.28 rows=228 width=28) (actual time=0.112..0.112 rows=228 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 14kB
-> Seq Scan on metro m (cost=0.00..5.28 rows=228 width=28) (actual time=0.004..0.050 rows=228 loops=1)
Total runtime: 248.990 ms
Table:
id serial NOT NULL,
author_id integer DEFAULT 0,
category_id integer DEFAULT 0,
category_root_id integer DEFAULT 0,
title character varying,
description text,
deadtime integer DEFAULT 0,
helper_date integer DEFAULT 0,
active integer DEFAULT 1,
initial_cost integer DEFAULT 0,
conditional integer DEFAULT 0,
remote integer DEFAULT 0,
tariff_id integer DEFAULT 0,
created integer DEFAULT 0,
views integer DEFAULT 0,
accepted_helper_id integer DEFAULT 0,
accept_date integer DEFAULT 0,
auction_bet_id integer DEFAULT 0,
token character varying,
repute2helper text,
repute2author text,
active_dated integer DEFAULT 0,
bot integer,
repute_level integer,
seo_body text,
seo_body_active smallint DEFAULT 0,
service_tasktotop integer DEFAULT 0,
service_taskcolor integer DEFAULT 0,
service_tasktime integer DEFAULT 0,
type_id smallint DEFAULT 1,
partner_id integer NOT NULL DEFAULT 0,
trust_level smallint NOT NULL DEFAULT 1,
trust_date integer NOT NULL DEFAULT 0,
active_cause character varying(1500),
admin_notes text,
currency_id smallint NOT NULL DEFAULT 0,
work_type smallint NOT NULL DEFAULT 0,
helpers_gender smallint NOT NULL DEFAULT 0,
helpers_langs integer[],
service_notifyhelpers integer NOT NULL DEFAULT 0,
contact_phone character varying(50),
contact_email character varying(50),
fastclose smallint NOT NULL DEFAULT 0,
access_code character varying(250),
contact_phone_cnt integer NOT NULL DEFAULT 0,
author_in_task integer NOT NULL DEFAULT 0,
task_type smallint NOT NULL DEFAULT 1
Indexes:
CREATE INDEX tasks_author_in_task_idx
ON tasks
USING btree
(author_in_task );
CREATE INDEX tasks_deadtime_bot_created_active_dated_currency_id_idx
ON tasks
USING btree
(deadtime , bot , created , active_dated , currency_id );
CREATE INDEX tasks_idxs
ON tasks
USING btree
(id , active , category_id , category_root_id , remote , type_id , partner_id , trust_level );
CREATE INDEX tasks_service_tasktotop_service_taskcolor_service_tasktime_idx
ON tasks
USING btree
(service_tasktotop , service_taskcolor , service_tasktime );
CREATE INDEX tasks_task_type_idx
ON tasks
USING btree
(task_type );
i recommend to use explain.depesz.com, with this service it's much easier to see where is the problem.
Here http://explain.depesz.com/s/vHT is your explain, as you can see on stats tab seq scans are not a problem. Only 3.1 % of total runtime duration. On other hand sorting operations take a lot time (67%). Do you really need to sort by so many columns?
Sort Key: t.id, t.category_id, t.category_root_id, t.title, t.deadtime, t.active, t.initial_cost, t.remote, t.created, t.work_type, a.city_id, city.city_name_ru, curr.short_name, l1.val, l2.val, t.contact_phone_cnt, t.service_tasktotop, t.service_taskcolor, t.service_tasktime, m.name
last thing, do you have index on every column which is used with JOINS? look here for my simple example, i'm doing simple left join on table with table itself. First with indexed column, than without index. Look at plans (merge joins vs hash joins) and times. Remember that both table columns used in join should be in some index.
P.S. and always do ANALYZE on table, to be sure if planner has actual statistics!
Table "public.people"
Column | Type | Modifiers
------------+---------+-----------------------------------------------------
id | integer | not null default nextval('people_id_seq'::regclass)
username | text |
department | text |
salary | integer |
deleted | boolean | not null default false
Indexes:
"people_pkey" PRIMARY KEY, btree (id)
"people_department_idx" btree (department)
"people_department_salary_idx" btree (department, salary)
"people_salary_idx" btree (salary)
sebpa=# explain analyze select * from people a left join people b on a.id = b.id where a.salary < 30000;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Merge Left Join (cost=0.57..2540.51 rows=19995 width=82) (actual time=0.022..19.710 rows=19995 loops=1)
Merge Cond: (a.id = b.id)
-> Index Scan using people_pkey on people a (cost=0.29..1145.29 rows=19995 width=41) (actual time=0.011..6.645 rows=19995 loops=1)
Filter: (salary < 30000)
Rows Removed by Filter: 10005
-> Index Scan using people_pkey on people b (cost=0.29..1070.29 rows=30000 width=41) (actual time=0.008..3.769 rows=19996 loops=1)
Total runtime: 20.969 ms
(7 rows)
sebpa=# alter table people drop constraint people_pkey;
ALTER TABLE
sebpa=# vacuum analyze people;
VACUUM
sebpa=# explain analyze select * from people a left join people b on a.id = b.id where a.salary < 30000;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Hash Right Join (cost=1081.94..2829.39 rows=19995 width=82) (actual time=10.767..47.147 rows=19995 loops=1)
Hash Cond: (b.id = a.id)
-> Seq Scan on people b (cost=0.00..581.00 rows=30000 width=41) (actual time=0.001..2.989 rows=30000 loops=1)
-> Hash (cost=656.00..656.00 rows=19995 width=41) (actual time=10.753..10.753 rows=19995 loops=1)
Buckets: 2048 Batches: 2 Memory Usage: 733kB
-> Seq Scan on people a (cost=0.00..656.00 rows=19995 width=41) (actual time=0.007..5.827 rows=19995 loops=1)
Filter: (salary < 30000)
Rows Removed by Filter: 10005
Total runtime: 48.884 ms
OK - so you've run an explain on a complicated query and seen a seq-scan then jumped to conclusions.
Explain output can be tricky to read on a small screen, but there's a nice chap who's built a tool for us. Let's post it to explain.depesz.com
http://explain.depesz.com/s/DTCz
This shows you nicely coloured output. Those sequential scans? Take only milliseconds.
The big time consumer seems to be that sort (151ms by itself). It's sorting 18,000 rows by a lot of fields and uses about 6.4MB of memory to do so.
There's nothing worth concentrating on apart from this sort. There are only three plausible options:
Make sure your work_mem is > 6.4MB for this query (set work_mem=...)
Add an index that matches the fields you want to sort by (might work/might not, but it will be a big index that's expensive to update).
Rewrite the query - use a subquery to filter + group your tasks then join to the other tables. Difficult to say if/how much it will help.
Start with #1 - that'll only take a few minutes to test and is a likely candidate if the query used to be quicker.

Optimizing postgres query

QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Unique (cost=32164.87..32164.89 rows=1 width=44) (actual time=221552.831..221552.831 rows=0 loops=1)
-> Sort (cost=32164.87..32164.87 rows=1 width=44) (actual time=221552.827..221552.827 rows=0 loops=1)
Sort Key: t.date_effective, t.acct_account_transaction_id, p.method, t.amount, c.business_name, t.amount
-> Nested Loop (cost=22871.67..32164.86 rows=1 width=44) (actual time=221552.808..221552.808 rows=0 loops=1)
-> Nested Loop (cost=22871.67..32160.37 rows=1 width=52) (actual time=221431.071..221546.619 rows=670 loops=1)
-> Nested Loop (cost=22871.67..32157.33 rows=1 width=43) (actual time=221421.218..221525.056 rows=2571 loops=1)
-> Hash Join (cost=22871.67..32152.80 rows=1 width=16) (actual time=221307.382..221491.019 rows=2593 loops=1)
Hash Cond: ("outer".acct_account_id = "inner".acct_account_fk)
-> Seq Scan on acct_account a (cost=0.00..7456.08 rows=365008 width=8) (actual time=0.032..118.369 rows=61295 loops=1)
-> Hash (cost=22871.67..22871.67 rows=1 width=16) (actual time=221286.733..221286.733 rows=2593 loops=1)
-> Nested Loop Left Join (cost=0.00..22871.67 rows=1 width=16) (actual time=1025.396..221266.357 rows=2593 loops=1)
Join Filter: ("inner".orig_acct_payment_fk = "outer".acct_account_transaction_id)
Filter: ("inner".link_type IS NULL)
-> Seq Scan on acct_account_transaction t (cost=0.00..18222.98 rows=1 width=16) (actual time=949.081..976.432 rows=2596 loops=1)
Filter: ((("type")::text = 'debit'::text) AND ((transaction_status)::text = 'active'::text) AND (date_effective >= '2012-03-01'::date) AND (date_effective < '2012-04-01 00:00:00'::timestamp without time zone))
-> Seq Scan on acct_payment_link l (cost=0.00..4648.68 rows=1 width=15) (actual time=1.073..84.610 rows=169 loops=2596)
Filter: ((link_type)::text ~~ 'return_%'::text)
-> Index Scan using contact_pk on contact c (cost=0.00..4.52 rows=1 width=27) (actual time=0.007..0.008 rows=1 loops=2593)
Index Cond: (c.contact_id = "outer".contact_fk)
-> Index Scan using acct_payment_transaction_fk on acct_payment p (cost=0.00..3.02 rows=1 width=13) (actual time=0.005..0.005 rows=0 loops=2571)
Index Cond: (p.acct_account_transaction_fk = "outer".acct_account_transaction_id)
Filter: ((method)::text <> 'trade'::text)
-> Index Scan using contact_role_pk on contact_role (cost=0.00..4.48 rows=1 width=4) (actual time=0.007..0.007 rows=0 loops=670)
Index Cond: ("outer".contact_id = contact_role.contact_fk)
Filter: (exchange_fk = 74)
Total runtime: 221553.019 ms
Your problem is here:
-> Nested Loop Left Join (cost=0.00..22871.67 rows=1 width=16) (actual time=1025.396..221266.357 rows=2593 loops=1)
Join Filter: ("inner".orig_acct_payment_fk = "outer".acct_account_transaction_id)
Filter: ("inner".link_type IS NULL)
-> Seq Scan on acct_account_transaction t (cost=0.00..18222.98 rows=1 width=16) (actual time=949.081..976.432 rows=2596 loops=1)
Filter: ((("type")::text = 'debit'::text) AND ((transaction_status)::text = 'active'::text) AND (date_effective >= '2012-03-01'::date) AND (date_effective
Seq Scan on acct_payment_link l (cost=0.00..4648.68 rows=1 width=15) (actual time=1.073..84.610 rows=169 loops=2596)
Filter: ((link_type)::text ~~ 'return_%'::text)
It expects to find 1 row in acct_account_transaction, while it finds 2596, and similarly for the other table.
You did not mention Your postgres version (could You?), but this should do the trick:
SELECT DISTINCT
t.date_effective,
t.acct_account_transaction_id,
p.method,
t.amount,
c.business_name,
t.amount
FROM
contact c inner join contact_role on (c.contact_id=contact_role.contact_fk and contact_role.exchange_fk=74),
acct_account a, acct_payment p,
acct_account_transaction t
WHERE
p.acct_account_transaction_fk=t.acct_account_transaction_id
and t.type = 'debit'
and transaction_status = 'active'
and p.method != 'trade'
and t.date_effective >= '2012-03-01'
and t.date_effective < (date '2012-03-01' + interval '1 month')
and c.contact_id=a.contact_fk and a.acct_account_id = t.acct_account_fk
and not exists(
select * from acct_payment_link l
where orig_acct_payment_fk == acct_account_transaction_id
and link_type like 'return_%'
)
ORDER BY
t.date_effective DESC
Also, try setting appropriate statistics target for relevant columns. Link to the friendly manual: http://www.postgresql.org/docs/current/static/sql-altertable.html
What are your indexes, and have you analysed lately? It's doing a table scan on acct_account_transaction even though there are several criteria on that table:
type
date_effective
If there are no indexes on those columns, then a compound one one (type, date_effective) could help (assuming there are lots of rows that don't meet the criteria on those columns).
I remove my first suggestion, as it changes the nature of the query.
I see that there's too much time spent in the LEFT JOIN.
First thing is to try to make only a single scan of the acct_payment_link table. Could you try rewriting your query to:
... LEFT JOIN (SELECT * FROM acct_payment_link
WHERE link_type LIKE 'return_%') AS l ...
You should check your statistics, as there's difference between planned and returned numbers of rows.
You haven't included tables' and indexes' definitions, it'd be good to take a look on those.
You might also want to use contrib/pg_tgrm extension to build index on the acct_payment_link.link_type, but I would make this a last option to try out.
BTW, what is the PostgreSQL version you're using?
Your statement rewritten and formatted:
SELECT DISTINCT
t.date_effective,
t.acct_account_transaction_id,
p.method,
t.amount,
c.business_name,
t.amount
FROM contact c
JOIN contact_role cr ON cr.contact_fk = c.contact_id
JOIN acct_account a ON a.contact_fk = c.contact_id
JOIN acct_account_transaction t ON t.acct_account_fk = a.acct_account_id
JOIN acct_payment p ON p.acct_account_transaction_fk
= t.acct_account_transaction_id
LEFT JOIN acct_payment_link l ON orig_acct_payment_fk
= acct_account_transaction_id
-- missing table-qualification!
AND link_type like 'return_%'
-- missing table-qualification!
WHERE transaction_status = 'active' -- missing table-qualification!
AND cr.exchange_fk = 74
AND t.type = 'debit'
AND t.date_effective >= '2012-03-01'
AND t.date_effective < (date '2012-03-01' + interval '1 month')
AND p.method != 'trade'
AND l.link_type IS NULL
ORDER BY t.date_effective DESC;
Explicit JOIN statements are preferable. I reordered your tables according to your JOIN logic.
Why (date '2012-03-01' + interval '1 month') instead of date '2012-04-01'?
Some table qualifications are missing. In a complex statement like this that's bad style. May be hiding a mistake.
The key to performance are indexes where appropriate, proper configuration of PostgreSQL and accurate statistics.
General advice on performance tuning in the PostgreSQL wiki.