Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gwJds-0006Oz-I4 for pgsql-performance@arkaria.postgresql.org; Wed, 20 Feb 2019 04:37:56 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1gwJdr-0005Ia-4r for pgsql-performance@arkaria.postgresql.org; Wed, 20 Feb 2019 04:37:55 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gwJdq-0005IT-Mq for pgsql-performance@lists.postgresql.org; Wed, 20 Feb 2019 04:37:54 +0000 Received: from mail-it1-x143.google.com ([2607:f8b0:4864:20::143]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gwJdn-0002qn-6M for pgsql-performance@lists.postgresql.org; Wed, 20 Feb 2019 04:37:53 +0000 Received: by mail-it1-x143.google.com with SMTP id i2so12450771ite.5 for ; Tue, 19 Feb 2019 20:37:50 -0800 (PST) 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=YrKEc/f7PFkz2xHHoR5YXTA24JtzLQSdet5EHX38Hkk=; b=QSXeL1tdOifkYy1joUB77Hn6t5hpKPINDlCjDRDOKbGOBS0/8VE8MMQJWzSUf5Be0S RRpvelwT8WIcfQvwJ9HCDjjzRL28htLZ+C1g1y1iaO+KwptygC8U7jUChixt2IvESrmp TxwJ9q5A41wP2SeViFbt1XGA9zSYsZ4/QeNYytLikyiKxDIpRee+uI4QF0TMRhfoUlsk tHvhol7hm4XE7owe5YyYPVDVr6dAfcqoSL4Irytmb8ixGsGyFDdG0O/UMUy8ryKBc1WI /hHP5qEqrXagAgbX7koVxu467i0p23p5deiysve6a4t46pUdggFFXbTdtyL21FLF9I2Z AHKA== 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=YrKEc/f7PFkz2xHHoR5YXTA24JtzLQSdet5EHX38Hkk=; b=jFR4GhxsJ9l9c/p0MDaJPazhBdmIgr3Sfh5WSTXzhUwzNJLunlGdlkwLWenyC0PvYp ItG7+eKiVrs4B/q5+VRjjl+WbId7RIJtZPWrx04YIthf7rRmub4GiNMCb+eafaOg6JLA 5WF5ze7t/450dvPrfrcNAb49v0vENELOOkDenqwuhytNOSwHmhXAx2dtbhNPFHfbP7Cp Nr3CKHXHUx3BHwIJ+TodcBy/vp7WpyRYWFoU8hNwPFA0S+3UGJrIzJDsKt1JEAgUjhXx /DTPEB98X/MJ2+v/2KcQMw+0n+q1BSh+0d4uYda0Oj0LbDk+O3WbaunipL8BfmmOVwdW /TvA== X-Gm-Message-State: AHQUAuYzX2TG83xoNBtojk/u1pRuHxeGujdqUaPHmwurQbFRmp3qnmcS b1yx0JfsPfRUzlPsPFYwNQ5roQ== X-Google-Smtp-Source: AHgI3IYoJpC/Ql0l8C6u6gkBoXhVE4fMrdIR69QSs1vcr372zjSLuDx9Z212yxJTasMGj7xBAhNq4Q== X-Received: by 2002:a24:45e3:: with SMTP id c96mr3976352itd.89.1550637470221; Tue, 19 Feb 2019 20:37:50 -0800 (PST) Received: from pryzbyj (charmander.telsasoft.com. [50.244.222.1]) by smtp.gmail.com with ESMTPSA id 127sm2201570itl.25.2019.02.19.20.37.49 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 19 Feb 2019 20:37:49 -0800 (PST) Received: by pryzbyj (Postfix, from userid 1000) id 376BB800A28; Tue, 19 Feb 2019 22:37:48 -0600 (CST) Date: Tue, 19 Feb 2019 22:37:48 -0600 From: Justin Pryzby To: Abi Noda Cc: pgsql-performance@lists.postgresql.org Subject: Re: Why isn't an index scan being used? Message-ID: <20190220043748.GN28750@telsasoft.com> References: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: User-Agent: Mutt/1.5.23 (2014-03-12) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk On Tue, Feb 19, 2019 at 05:10:43PM -0700, Abi Noda wrote: > I have a table as defined below. The table contains 1,027,616 rows, 50,349 > of which have state='open' and closed IS NULL. Since closed IS NULL for all > rows where state='open', I want to remove the unnecessary state column. > > CREATE TABLE tickets ( > id bigserial primary key, > state character varying, > closed timestamp, ... > ); > > CREATE INDEX "state_index" ON "tickets" ("state") WHERE ((state)::text = > 'open'::text)); > > As part of the process of removing the state column, I am trying to index > the closed column so I can achieve equal query performance (index scan) as > when I query on the state column as shown below: > > EXPLAIN ANALYZE select title, created, closed, updated from tickets where state = 'open'; > Index Scan using state_index on tickets (cost=0.29..23430.20 rows=50349 width=64) (actual time=17.221..52.110 rows=51533 loops=1) > > However, when I index the closed column, a bitmap scan is used instead of > an index scan, with slightly slower performance. Why isn't an index scan > being used, given that the exact same number of rows are at play as in my > query on the state column? How do I index closed in a way where an index > scan is used? > > CREATE INDEX closed_index ON tickets (id) WHERE closed IS NULL; > EXPLAIN ANALYZE select title, created, closed, updated from tickets where closed IS NULL; > Bitmap Heap Scan on tickets (cost=824.62..33955.85 rows=50349 width=64) (actual time=10.420..56.095 rows=51537 loops=1) > -> Bitmap Index Scan on closed_index (cost=0.00..812.03 rows=50349 width=0) (actual time=6.005..6.005 rows=51537 loops=1) Are you really concerned about 4ms ? If this is a toy-sized test system, please try on something resembling production, perhaps by loading production or fake data, or perhaps on a production system within a transactions (begin; CREATE INDEX CONCURRENTLY; explain ...; rollback). You can see that most of the estimated cost is from the table (the index scan accounts for only 812 of total 33955 cost units). So I'm guessing the planner thinks that an index scan will either 1) access the table randomly; and/or, 2) access a large fraction of the table. If it was just built, the first (partial/conditional/predicate/where) index will scan table in its "physical" order (if not sequentially). The 2nd index is going to scan table in order of ID, which I'm guessing is not "correlated" with its physical order, so an index scan cost is computed as accessing a larger fraction of the table (but by using an "bitmap" scan it's at least in physical order). In fact: 50349/17478 = ~3 tuples/page is low, so you're accessing a large fraction of the table to return a small fraction of its tuples. You can check what it thinks here: https://wiki.postgresql.org/wiki/Slow_Query_Questions#Statistics:_n_distinct.2C_MCV.2C_histogram You could try CLUSTERing the table on ID (which requires a non-partial index) and ANALYZEing (which might cause this and other queries to be planned and/or perform differently). That causes the table to be locked exclusively. Then, the planner knows that scanning index and returning results ordered by IDs (which doesn't matter) will also access table in physical order (which matters), and maybe fewer pages need to be read, too. Justin