Query optimiser is not using 'not null' constraint when 'order by nulls last' clause is used

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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)

[Postgresql General]     [Postgresql PHP]     [PHP Users]     [PHP Home]     [PHP on Windows]     [Kernel Newbies]     [PHP Classes]     [PHP Books]     [PHP Databases]     [Yosemite]

  Powered by Linux