Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1k6kU2-0007bI-OY for pgsql-performance@arkaria.postgresql.org; Sat, 15 Aug 2020 00:55:42 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1k6kU1-0004nK-CZ for pgsql-performance@arkaria.postgresql.org; Sat, 15 Aug 2020 00:55:41 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1k6kU1-0004nC-0p for pgsql-performance@lists.postgresql.org; Sat, 15 Aug 2020 00:55:41 +0000 Received: from mail-il1-x142.google.com ([2607:f8b0:4864:20::142]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1k6kTx-0000CC-Ew for pgsql-performance@postgresql.org; Sat, 15 Aug 2020 00:55:39 +0000 Received: by mail-il1-x142.google.com with SMTP id p13so9959029ilh.4 for ; Fri, 14 Aug 2020 17:55:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=telsasoft-com.20150623.gappssmtp.com; s=20150623; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to:user-agent; bh=fiNDRUfpnV+J80R5KudB1lTH3NB1uAOQ4TiVcluFEJo=; b=lenPT2c9QDu5Pj6xMXSeR1t/dn5aBcxLFFCoYA6bIgLTksGVM5b2HF+bhni0vTjweB hc47zjZIzMrmtNOLysNkw4wRFUX2de7ohB+tg+K1H04kcPzORhUeLc3OjTcPm0svlgw8 VsDW+glAVWIqQzXe3sRcp5EGd72Aj5AgCmu55uQs64SGY+x1RCAHWtg9So8HMVekC7Vx txYLxWEEk7UI6QubBEE15Fpfi2k/TZejEd5RxlLawrtEXd+hWrUUNYRubKiX2uE45cVO f8LFLkKkkkrYHiw3saARO4Ua2xVCVe6zKt0JApUctWp/QzpkHAetUQrVRgxGPwEcawNF kR0Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to:user-agent; bh=fiNDRUfpnV+J80R5KudB1lTH3NB1uAOQ4TiVcluFEJo=; b=CaQS0FsOFZ5iWOir3JYcKVOkQ5DjoxXE2LXOBLwScXeX4V/QX7SQFesz2PKbE/OjRy YixSOGZhU2BJ9WJAvlo6qZ5pjcPG36fvutvg/9y/sMxD85DZbW8To90CbHLvzxY/Spy0 tM41C6dA+6vzv+rOH2nFnlKJetVe2Iqt1p+k8uJLZ60yfwVSkdZTkb1uOCtKSfk0d+W8 x8b8SjEHTRVrKCXS1xsKbLSvivUDwLTa0RNLTXRujJznDVHdzDDbQtNtf1Pz3a+WbRwX tguwwe4/Pe9lige/jvw8/ZEc2OSJtzBdD5WFSdGW/4p49Q5VnmX/i7eK6wK7WHbRHpAu yg2g== X-Gm-Message-State: AOAM532h+RfrDR1JCNeG6AeSki6BuKA+btLkG4BGn7tUWojShXcgWa1I i2QMx3M4Qn8AP7R904phyIX/lQ== X-Google-Smtp-Source: ABdhPJy0qb7BEyMsjrny9U8emMVU5XbJp48/V36i178pEMV1cbYpkUyAinATzOgRdrcFQ+5FzFBHAw== X-Received: by 2002:a92:9fdb:: with SMTP id z88mr4301356ilk.154.1597452935301; Fri, 14 Aug 2020 17:55:35 -0700 (PDT) Received: from pryzbyj.telsasoft (charmander.telsasoft.com. [50.244.222.1]) by smtp.gmail.com with ESMTPSA id z26sm3895614ilf.60.2020.08.14.17.55.33 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 14 Aug 2020 17:55:34 -0700 (PDT) Received: by pryzbyj.telsasoft (Postfix, from userid 1000) id 26028800864; Fri, 14 Aug 2020 19:55:33 -0500 (CDT) Date: Fri, 14 Aug 2020 19:55:33 -0500 From: Justin Pryzby To: Ken Tanzer Cc: pgsql-performance Subject: Re: Query takes way longer with LIMIT, and EXPLAIN takes way longer than actual query Message-ID: <20200815005532.GL4561@telsasoft.com> References: <20200814220424.GK4561@telsasoft.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: User-Agent: Mutt/1.9.4 (2018-02-28) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk On Fri, Aug 14, 2020 at 03:40:40PM -0700, Ken Tanzer wrote: > On Fri, Aug 14, 2020 at 3:04 PM Justin Pryzby wrote: > > Due to the over-estimated rowcount, the planner believes that (more) rows > > will be output (sooner) than they actually are: > > > > -> Nested Loop Semi Join (cost=47.11..31367302.81 ROWS=611586 > > width=336) (actual time=47.098..97236.123 ROWS=25 loops=1) > > > > So it thinks there's something to be saved/gained by using a plan that has a > > low startup cost. But instead, it ends up running for a substantial fraction > > of the total (estimated) cost. > > Got it. Is there any way to address this other than re-writing the > query? (Statistics? Or something else?) A usual trick is to change to write something like: |ORDER BY added_at + '0 seconds'::interval" which means an index scan on added_at doesn't match the ORDER BY exactly (the planner isn't smart enough to know better). You could try to address the misestimate, which is probably a good idea anyway. Or make it believe that it's going to work harder to return those 25 rows. Maybe you could change the "25" to a bind parameter, like LIMIT $N, or however the ORM wants it. You could change the index to a BRIN index, which doesn't help with ORDER BY (but will also affect other queries which want to ORDER BY). Maybe you could add an index on this expression and ANALYZE the table. I think it might help the estimate. Or it might totally change the shape of the plan, like allowing indexonly scan (which would probably require VACUUM).. Group Key: CASE WHEN (tbl_reference.to_table = 'client'::name) THEN tbl_reference.from_id WHEN (tbl_reference.from_table = 'client'::name) THEN tbl_reference.to_id ELSE NULL::integer END I know you didn't want to rewrite the query, but it looks to me like adjusting the schema or query might be desirable. agency=> EXPLAIN (ANALYZE,VERBOSE,BUFFERS,TIMING) SELECT * FROM Log WHERE log_id IN (SELECT CASE WHEN to_table='client' THEN from_id WHEN from_table='client' THEN to_id END FROM reference WHERE ((from_id_field = E'client_id' AND from_id = E'34918' AND from_table = E'client' AND to_table = E'log' ) OR (to_id_field = E'client_id' AND to_id = E'34918' AND to_table = E'client' AND from_table = E'log' ))) ORDER BY added_at DESC; To me that smells like a UNION (maybe not UNION ALL): SELECT FROM log WHERE EXISTS (SELECT 1 FROM reference ref WHERE log.log_id=ref.from_id AND to_table='client' AND from_id_field='client_id' AND from_id=$1 AND from_table='client' AND to_table='log') UNION SELECT FROM log WHERE EXISTS (SELECT 1 FROM reference ref WHERE log.log_id=ref.to_id AND from_table='client' AND to_id_field='client_id' AND to_id=$1 AND to_table='client' AND from_table='log') I guess you might know that various indexes are redundant: "index_tbl_log_log_type_code" btree (log_type_code) "tbl_log_log_type_code" btree (log_type_code) "tbl_log_test2" btree (log_type_code, added_at) "tbl_log_added_at" btree (added_at) "tbl_log_test" btree (added_at, log_type_code) "index_tbl_reference_to_table" btree (to_table) "index_tbl_reference_to_table_id" btree (to_table, to_id) "index_tbl_reference_is_deleted" btree (is_deleted) => Maybe that would be better as a WHERE NOT is_deleted clause on various indexes (?) -- Justin