Hi,
I am using Postgres version 9.4.4 on a Mac machine. I have 2 queries that
differ only in the order by clause. One of it has 'nulls last' and the
other one does not have it. The performance difference between the two is
considerable.
The slower of the two queries is
SELECT wos.notificationstatus,
wos.unrepliedcount,
wos.shownotestotech,
wos.ownerid,
wos.isfcr,
aau.user_id,
wo.workorderid AS "WOID",
wo.is_catalog_template AS "TemplateType",
wo.title AS "Title",
wo.is_catalog_template,
aau.first_name AS "Requester",
cd.categoryname AS "Category",
ti.first_name AS "Owner",
wo.duebytime AS "DueBy",
wo.fr_duetime,
wo.completedtime AS "CompletedTime",
wo.respondedtime AS "RespondedTime",
wo.resolvedtime AS "ResolvedTime",
qd.queuename AS "Group",
std.statusname AS "Status",
wo.createdtime AS "CreatedDate",
wos.isread,
wos.hasattachment,
wos.appr_statusid,
wos.priorityid,
wo.templateid AS "TemplateId",
pd.priorityid,
pd.priorityname AS "Priority",
pd.prioritycolor AS "PriorityColor",
wos.isoverdue,
wos.is_fr_overdue,
wos.linkedworkorderid,
wos.editing_status,
wos.editorid,
wos.linkedworkorderid,
wo.isparent,
sduser.isvipuser,
sduser_onbehalfof.isvipuser AS "ONBEHALFOFVIP",
wo.isparent,
wos.statusid,
sdorganization.name AS "Site",
wo.workorderid AS "RequestID"
FROM workorder wo
left join workorder_fields wof
ON wo.workorderid=wof.workorderid
left join servicecatalog_fields scf
ON wo.workorderid=scf.workorderid
left join wotoprojects wtp
ON wo.workorderid=wtp.workorderid
left join sitedefinition
ON wo.siteid=sitedefinition.siteid
left join sdorganization
ON sitedefinition.siteid=sdorganization.org_id
inner join workorderstates wos
ON wo.workorderid=wos.workorderid
left join categorydefinition cd
ON wos.categoryid=cd.categoryid
left join aaauser ti
ON wos.ownerid=ti.user_id
left join aaauser aau
ON wo.requesterid=aau.user_id
left join prioritydefinition pd
ON wos.priorityid=pd.priorityid
left join statusdefinition std
ON wos.statusid=std.statusid
left join workorder_queue wo_queue
ON wo.workorderid=wo_queue.workorderid
left join queuedefinition qd
ON wo_queue.queueid=qd.queueid
left join departmentdefinition dpt
ON wo.deptid=dpt.deptid
left join leveldefinition lvd
ON wos.levelid=lvd.levelid
left join modedefinition mdd
ON wo.modeid=mdd.modeid
left join urgencydefinition urgdef
ON wos.urgencyid=urgdef.urgencyid
left join impactdefinition impdef
ON wos.impactid=impdef.impactid
left join requesttypedefinition rtdef
ON wos.requesttypeid=rtdef.requesttypeid
left join subcategorydefinition scd
ON wos.subcategoryid=scd.subcategoryid
left join itemdefinition icd
ON wos.itemid=icd.itemid
left join servicedefinition serdef
ON wo.serviceid=serdef.serviceid
left join aaauser cbau
ON wo.createdbyid=cbau.user_id
left join aaauser oboaau
ON wo.oboid=oboaau.user_id
left join sduser
ON wo.requesterid=sduser.userid
left join sduser sduser_onbehalfof
ON wo.oboid=sduser_onbehalfof.userid
left join workorder_fields
ON wo.workorderid=workorder_fields.workorderid
WHERE ((
wos.statusid = 1)
AND (
wo.isparent = TRUE))
ORDER BY 7 DESC nulls last limit 25
On removing 'nulls last' from the order by clause the query becomes very
fast. I have attached the query plan for both the queries.
>From the plan it looks like the second query is able to efficiently use the
workorder_pk index ( The node 'Index Scan Backward using workorder_pk on
workorder' returns 25 rows) whereas the first query is not able to use the
index efficiently (more than 300k rows are returned from the same node).
The column workorderid is a PK column. The query optimizer should ideally
know that there is no nulls in this column and in effect there is no
difference between the two queries.
I tried the same in Postgres 10 and the slower query performs much better
due to parallel sequential scans but still it is less efficient than the
query without 'nulls last'.
I thought it would be best to raise this with the Postgres team.
Regards,
Nanda
pg_9_4_Fast
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=7.00..79.41 rows=25 width=268) (actual time=0.568..1.348 rows=25
loops=1)
-> Nested Loop Left Join (cost=7.00..250829.09 rows=86592 width=268)
(actual time=0.567..1.343 rows=25 loops=1)
-> Nested Loop Left Join (cost=6.71..223240.48 rows=86592 width=275)
(actual time=0.565..1.320 rows=25 loops=1)
-> Nested Loop Left Join (cost=6.42..195651.87 rows=86592
width=282) (actual time=0.556..1.198 rows=25 loops=1)
Join Filter: (wos.statusid = std.statusid)
-> Nested Loop Left Join (cost=6.42..194351.80
rows=86592 width=273) (actual time=0.411..1.022 rows=25 loops=1)
-> Nested Loop Left Join (cost=6.29..180268.60
rows=86592 width=253) (actual time=0.409..0.999 rows=25 loops=1)
-> Nested Loop Left Join
(cost=6.00..152702.85 rows=86592 width=234) (actual time=0.402..0.891 rows=25
loops=1)
-> Nested Loop Left Join
(cost=5.71..124414.37 rows=86592 width=223) (actual time=0.390..0.601 rows=25
loops=1)
Join Filter: (wo.workorderid =
wo_queue.workorderid)
Rows Removed by Join Filter: 25
-> Nested Loop Left Join
(cost=4.69..123112.82 rows=86592 width=208) (actual time=0.042..0.230 rows=25
loops=1)
-> Nested Loop Left Join
(cost=4.56..109029.62 rows=86592 width=205) (actual time=0.039..0.208 rows=25
loops=1)
-> Nested Loop Left
Join (cost=4.41..83014.38 rows=86592 width=205) (actual time=0.037..0.187
rows=25 loops=1)
-> Nested Loop
Left Join (cost=4.27..68290.31 rows=86592 width=205) (actual time=0.035..0.163
rows=25 loops=1)
-> Merge
Join (cost=4.12..36022.21 rows=86592 width=205) (actual time=0.030..0.123
rows=25 loops=1)
Merge Cond: (wo.workorderid = wos.workorderid)
->
Index Scan Backward using workorder_pk on workorder wo (cost=0.42..18645.63
rows=302945 width=157) (actual time=0.013..0.048 rows=25 loops=1)
Filter: isparent
->
Index Scan Backward using workorderstates_fk1_idx on workorderstates wos
(cost=0.42..15538.74 rows=86592 width=136) (actual time=0.012..0.034 rows=25
loops=1)
Filter: (statusid = 1)
-> Index
Only Scan using wotoprojects_fk1_idx on wotoprojects wtp (cost=0.15..0.29
rows=8 width=8) (actual time=0.001..0.001 rows=0 loops=25)
Index Cond: (workorderid = wo.workorderid)
Heap
Fetches: 0
-> Index Only
Scan using sitedefinition_fk2_idx on sitedefinition (cost=0.14..0.16 rows=1
width=8) (actual time=0.000..0.000 rows=0 loops=25)
Index
Cond: (siteid = wo.siteid)
Heap
Fetches: 0
-> Index Scan using
sdorganization_pk on sdorganization (cost=0.14..0.29 rows=1 width=16) (actual
time=0.000..0.000 rows=0 loops=25)
Index Cond:
(sitedefinition.siteid = org_id)
-> Index Scan using
categorydefinition_pk on categorydefinition cd (cost=0.14..0.15 rows=1
width=19) (actual time=0.000..0.000 rows=0 loops=25)
Index Cond:
(wos.categoryid = categoryid)
-> Materialize (cost=1.02..2.67
rows=1 width=23) (actual time=0.013..0.014 rows=1 loops=25)
-> Hash Right Join
(cost=1.02..2.67 rows=1 width=23) (actual time=0.324..0.341 rows=1 loops=1)
Hash Cond: (qd.queueid
= wo_queue.queueid)
-> Seq Scan on
queuedefinition qd (cost=0.00..1.46 rows=46 width=23) (actual
time=0.154..0.161 rows=46 loops=1)
-> Hash
(cost=1.01..1.01 rows=1 width=16) (actual time=0.158..0.158 rows=1 loops=1)
Buckets: 1024
Batches: 1 Memory Usage: 1kB
-> Seq Scan on
workorder_queue wo_queue (cost=0.00..1.01 rows=1 width=16) (actual
time=0.154..0.155 rows=1 loops=1)
-> Index Scan using aaauser_pk on
aaauser ti (cost=0.29..0.32 rows=1 width=19) (actual time=0.010..0.011 rows=1
loops=25)
Index Cond: (wos.ownerid = user_id)
-> Index Scan using aaauser_pk on aaauser aau
(cost=0.29..0.31 rows=1 width=19) (actual time=0.003..0.003 rows=1 loops=25)
Index Cond: (wo.requesterid = user_id)
-> Index Scan using prioritydefinition_pk on
prioritydefinition pd (cost=0.14..0.15 rows=1 width=20) (actual
time=0.000..0.000 rows=0 loops=25)
Index Cond: (wos.priorityid = priorityid)
-> Materialize (cost=0.00..1.19 rows=1 width=17) (actual
time=0.006..0.006 rows=1 loops=25)
-> Seq Scan on statusdefinition std
(cost=0.00..1.19 rows=1 width=17) (actual time=0.142..0.145 rows=1 loops=1)
Filter: (statusid = 1)
Rows Removed by Filter: 14
-> Index Scan using sduser_fk1_idx on sduser (cost=0.29..0.31
rows=1 width=9) (actual time=0.003..0.004 rows=1 loops=25)
Index Cond: (wo.requesterid = userid)
-> Index Scan using sduser_fk1_idx on sduser sduser_onbehalfof
(cost=0.29..0.31 rows=1 width=9) (actual time=0.000..0.000 rows=0 loops=25)
Index Cond: (wo.oboid = userid)
Planning time: 36.746 ms
Execution time: 1.710 ms
(54 rows)
pg_9_4_Slow
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=51055.44..51055.50 rows=25 width=268) (actual
time=2746.814..2746.819 rows=25 loops=1)
-> Sort (cost=51055.44..51271.92 rows=86592 width=268) (actual
time=2746.812..2746.813 rows=25 loops=1)
Sort Key: wo.workorderid
Sort Method: top-N heapsort Memory: 31kB
-> Hash Left Join (cost=2800.05..48611.88 rows=86592 width=268)
(actual time=49.285..2634.867 rows=86130 loops=1)
Hash Cond: (wo.oboid = sduser_onbehalfof.userid)
-> Hash Left Join (cost=2120.45..47174.59 rows=86592
width=275) (actual time=36.652..2574.275 rows=86130 loops=1)
Hash Cond: (wo.requesterid = sduser.userid)
-> Nested Loop Left Join (cost=1440.86..44871.40
rows=86592 width=282) (actual time=23.899..2449.283 rows=86130 loops=1)
Join Filter: (wos.statusid = std.statusid)
-> Hash Left Join (cost=1440.86..43571.33
rows=86592 width=273) (actual time=23.889..2372.958 rows=86130 loops=1)
Hash Cond: (wos.priorityid = pd.priorityid)
-> Hash Left Join (cost=1439.52..42528.00
rows=86592 width=253) (actual time=23.848..2323.138 rows=86130 loops=1)
Hash Cond: (wo.requesterid = aau.user_id)
-> Hash Left Join
(cost=779.93..40244.81 rows=86592 width=234) (actual time=11.128..2198.954
rows=86130 loops=1)
Hash Cond: (wos.ownerid =
ti.user_id)
-> Hash Left Join
(cost=120.34..38058.72 rows=86592 width=223) (actual time=0.280..2106.865
rows=86130 loops=1)
Hash Cond: (wo.workorderid =
wo_queue.workorderid)
-> Hash Left Join
(cost=117.66..37731.31 rows=86592 width=208) (actual time=0.239..2060.880
rows=86130 loops=1)
Hash Cond:
(wos.categoryid = cd.categoryid)
-> Hash Left Join
(cost=116.37..36687.54 rows=86592 width=205) (actual time=0.218..2013.580
rows=86130 loops=1)
Hash Cond:
(wo.siteid = sitedefinition.siteid)
-> Merge Left
Join (cost=108.95..36355.39 rows=86592 width=205) (actual time=0.041..1974.608
rows=86130 loops=1)
Merge
Cond: (wo.workorderid = wtp.workorderid)
-> Merge
Join (cost=4.12..36022.21 rows=86592 width=205) (actual time=0.032..1940.839
rows=86130 loops=1)
Merge Cond: (wo.workorderid = wos.workorderid)
->
Index Scan Backward using workorder_pk on workorder wo (cost=0.42..18645.63
rows=302945 width=157) (actual time=0.015..1020.405 rows=302945 loops=1)
Filter: isparent
->
Index Scan Backward using workorderstates_fk1_idx on workorderstates wos
(cost=0.42..15538.74 rows=86592 width=136) (actual time=0.012..765.145
rows=86130 loops=1)
Filter: (statusid = 1)
Rows Removed by Filter: 216815
-> Sort
(cost=104.83..108.61 rows=1510 width=8) (actual time=0.008..0.008 rows=0
loops=1)
Sort
Key: wtp.workorderid
Sort
Method: quicksort Memory: 25kB
->
Seq Scan on wotoprojects wtp (cost=0.00..25.10 rows=1510 width=8) (actual
time=0.000..0.000 rows=0 loops=1)
-> Hash
(cost=6.27..6.27 rows=92 width=16) (actual time=0.165..0.165 rows=92 loops=1)
Buckets:
1024 Batches: 1 Memory Usage: 5kB
-> Hash
Right Join (cost=3.07..6.27 rows=92 width=16) (actual time=0.072..0.128
rows=92 loops=1)
Hash
Cond: (sdorganization.org_id = sitedefinition.siteid)
->
Seq Scan on sdorganization (cost=0.00..1.93 rows=93 width=16) (actual
time=0.005..0.012 rows=93 loops=1)
->
Hash (cost=1.92..1.92 rows=92 width=8) (actual time=0.055..0.055 rows=92
loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 4kB
-> Seq Scan on sitedefinition (cost=0.00..1.92 rows=92 width=8) (actual
time=0.004..0.019 rows=92 loops=1)
-> Hash
(cost=1.13..1.13 rows=13 width=19) (actual time=0.013..0.013 rows=13 loops=1)
Buckets: 1024
Batches: 1 Memory Usage: 1kB
-> Seq Scan on
categorydefinition cd (cost=0.00..1.13 rows=13 width=19) (actual
time=0.003..0.006 rows=13 loops=1)
-> Hash (cost=2.67..2.67
rows=1 width=23) (actual time=0.033..0.033 rows=1 loops=1)
Buckets: 1024
Batches: 1 Memory Usage: 1kB
-> Hash Right Join
(cost=1.02..2.67 rows=1 width=23) (actual time=0.016..0.032 rows=1 loops=1)
Hash Cond:
(qd.queueid = wo_queue.queueid)
-> Seq Scan on
queuedefinition qd (cost=0.00..1.46 rows=46 width=23) (actual
time=0.004..0.007 rows=46 loops=1)
-> Hash
(cost=1.01..1.01 rows=1 width=16) (actual time=0.003..0.003 rows=1 loops=1)
Buckets:
1024 Batches: 1 Memory Usage: 1kB
-> Seq
Scan on workorder_queue wo_queue (cost=0.00..1.01 rows=1 width=16) (actual
time=0.002..0.002 rows=1 loops=1)
-> Hash (cost=400.93..400.93
rows=20693 width=19) (actual time=10.824..10.824 rows=20693 loops=1)
Buckets: 4096 Batches: 1
Memory Usage: 1132kB
-> Seq Scan on aaauser ti
(cost=0.00..400.93 rows=20693 width=19) (actual time=0.006..4.313 rows=20693
loops=1)
-> Hash (cost=400.93..400.93
rows=20693 width=19) (actual time=12.689..12.689 rows=20693 loops=1)
Buckets: 4096 Batches: 1 Memory
Usage: 1040kB
-> Seq Scan on aaauser aau
(cost=0.00..400.93 rows=20693 width=19) (actual time=0.005..5.184 rows=20693
loops=1)
-> Hash (cost=1.15..1.15 rows=15 width=20)
(actual time=0.027..0.027 rows=15 loops=1)
Buckets: 1024 Batches: 1 Memory Usage:
1kB
-> Seq Scan on prioritydefinition pd
(cost=0.00..1.15 rows=15 width=20) (actual time=0.017..0.021 rows=15 loops=1)
-> Materialize (cost=0.00..1.19 rows=1 width=17)
(actual time=0.000..0.000 rows=1 loops=86130)
-> Seq Scan on statusdefinition std
(cost=0.00..1.19 rows=1 width=17) (actual time=0.004..0.008 rows=1 loops=1)
Filter: (statusid = 1)
Rows Removed by Filter: 14
-> Hash (cost=420.93..420.93 rows=20693 width=9) (actual
time=12.726..12.726 rows=20693 loops=1)
Buckets: 4096 Batches: 1 Memory Usage: 970kB
-> Seq Scan on sduser (cost=0.00..420.93
rows=20693 width=9) (actual time=0.006..5.942 rows=20693 loops=1)
-> Hash (cost=420.93..420.93 rows=20693 width=9) (actual
time=12.584..12.584 rows=20693 loops=1)
Buckets: 4096 Batches: 1 Memory Usage: 970kB
-> Seq Scan on sduser sduser_onbehalfof
(cost=0.00..420.93 rows=20693 width=9) (actual time=0.005..5.884 rows=20693
loops=1)
Planning time: 69.901 ms
Execution time: 2749.108 ms
(75 rows)