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 1oBSWg-0006iO-FP for pgsql-admin@arkaria.postgresql.org; Wed, 13 Jul 2022 02:54:58 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1oBSWe-0001ip-KL for pgsql-admin@arkaria.postgresql.org; Wed, 13 Jul 2022 02:54:56 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1oBSWd-0001iY-Vn for pgsql-admin@lists.postgresql.org; Wed, 13 Jul 2022 02:54:56 +0000 Received: from mail-qt1-x834.google.com ([2607:f8b0:4864:20::834]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1oBSWb-0008Hp-1c for pgsql-admin@lists.postgresql.org; Wed, 13 Jul 2022 02:54:54 +0000 Received: by mail-qt1-x834.google.com with SMTP id r17so8339178qtx.6 for ; Tue, 12 Jul 2022 19:54:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:date:mime-version:user-agent:subject:content-language:to :cc:references:from:in-reply-to; bh=y/a7yv8eE93ZsCih7nIGqJBnCJFrgoxd/2fJH6GKTmI=; b=Q3SoVL6JhJ2QU/24MBy5QcmuZwL1ya0nLG4jihnOa6zj5hOKoTIczJia9m0SjiOJ4F wR2Fq6sVE2Cs80mFqcgMuNBqn1nvlgVm59b82G2caIZHlFPRrGYhiOentlf3uteM5jI0 cfic5O66Zr3wuhjxTElJONHAaT5WOpHQqShX2Utg2B0O9bNR6QVpXCV2Vgb140l+pyFK FklUv5kzmTgsUHAUFc3BP4jzqzzPYOMuL4VzZwjM7YJvAZw0VWctfuWi5tpC26l0VidG i5l3XW6GvIKgyVN1wBLQEsFZJabPpZbOvL0y8t7zkHyB2pjyfl2HahJxdrhAiRnGIqok 6Dqg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:date:mime-version:user-agent:subject :content-language:to:cc:references:from:in-reply-to; bh=y/a7yv8eE93ZsCih7nIGqJBnCJFrgoxd/2fJH6GKTmI=; b=L7RBMlFcvCMRo/YuVtJCFH7nzgkoKMfVmkGz+Dl3Ac0H5fcjvvlWiH6Eo0EZ3vEEK4 oY7mkszcfbHG1a6Cx1g3pO/sdsqb/6E3VYJRyn7oSAkZVukcMhbSScyPb6Y0OM+nA/tl lVV64Aaw8itcn1boKuXcah+sRTBZACdS8fw48cxfNzTkALVZGJBTqpLbS5mVHiMJyW22 HjJ1VeYb64PLEIpDWdSyQAfvxuE73FGmLeFtBe3PE+twgYlj3vXGzLolE4/TP/bUHonD oEEtOqfEUyGaWDAXnt5fpJPAGfJKn1g2dHFTch7JsXDU+jCbEJovIrGxsBTi+WlXg3sE tBVA== X-Gm-Message-State: AJIora8oozVM57+tHSas+DHuxzRwyRnQ72PZcX14cyTz8etmQMde7bnn htwoKzocwdXawBr4wsvUTBiJnyVVuuvk/Q== X-Google-Smtp-Source: AGRyM1uarjAH7fMbufs6PvqJCOuMmBBEsWSf6Uh/c3Vaqhc6Fab3ipDRW63ZHqaSQ9syCP/FOetZ9A== X-Received: by 2002:a05:622a:14d0:b0:317:3cb0:80a1 with SMTP id u16-20020a05622a14d000b003173cb080a1mr1025534qtx.145.1657680891894; Tue, 12 Jul 2022 19:54:51 -0700 (PDT) Received: from [192.168.2.100] (pool-72-79-19-93.nwrknj.east.verizon.net. [72.79.19.93]) by smtp.gmail.com with ESMTPSA id o22-20020a05620a0d5600b006af08c26774sm9822176qkl.47.2022.07.12.19.54.51 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 12 Jul 2022 19:54:51 -0700 (PDT) Content-Type: multipart/alternative; boundary="------------d3Q0iF8fqTe4Y9amctrn0hyV" Message-ID: Date: Tue, 12 Jul 2022 22:54:50 -0400 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.11.0 Subject: Re: [EXT] Re: Improve "select count(*)" query - takes more than 30 mins for some large tables Content-Language: en-US To: MichaelDBA Vitale , "Pierson Patricia L (Contractor)" Cc: "pgsql-admin@lists.postgresql.org" References: <1B1927BA-F7DE-40E8-8F67-BCC001CD4C6B@gmail.com> <1eb233ff-90f0-529b-f0cc-fa982f668a8f@gmail.com> <1221536526.20277.1657636277921@email.ionos.com> <1037158693.49441.1657650345834@email.ionos.com> From: Mladen Gogala In-Reply-To: <1037158693.49441.1657650345834@email.ionos.com> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk This is a multi-part message in MIME format. --------------d3Q0iF8fqTe4Y9amctrn0hyV Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit On 7/12/22 14:25, MichaelDBA Vitale wrote: > That is not true: doing the select on the primary key will still > result in a table scan, not an index scan.  The heap always gets > accessed for select counts. > > Regards, > Michael Vitale Well, not always: mgogala=# explain select count(*) from emp;                         QUERY PLAN ----------------------------------------------------------  Aggregate  (cost=1.18..1.19 rows=1 width=8)    ->  Seq Scan on emp  (cost=0.00..1.14 rows=14 width=0) (2 rows) That is the classic "select count(*)".  Now, let's see PK: mgogala=# \d emp                            Table "mgogala.emp"   Column  |            Type             | Collation | Nullable | Default ----------+-----------------------------+-----------+----------+---------  empno    | smallint                    |           | not null |  ename    | character varying(10)       |           |          |  job      | character varying(9)        |           |          |  mgr      | smallint                    |           |          |  hiredate | timestamp without time zone |           |          |  sal      | double precision            |           |          |  comm     | double precision            |           |          |  deptno   | smallint                    |           |          | Indexes:     "emp_pkey" PRIMARY KEY, btree (empno) Foreign-key constraints:     "fk_deptno" FOREIGN KEY (deptno) REFERENCES dept(deptno) Now, let's do scan on "count(empno)": mgogala=# explain select count(empno) from emp;                         QUERY PLAN ----------------------------------------------------------  Aggregate  (cost=1.18..1.19 rows=1 width=8)    ->  Seq Scan on emp  (cost=0.00..1.14 rows=14 width=2) (2 rows) So far, so good. Sequential scan, despite the existence of the NOT NULL primary key. Now comes the twist: mgogala=# set enable_seqscan=off; SET mgogala=# explain select count(empno) from emp;                                    QUERY PLAN -------------------------------------------------------------------------------- -  Aggregate  (cost=12.38..12.39 rows=1 width=8)    ->  Index Only Scan using emp_pkey on emp (cost=0.14..12.35 rows=14 width=2) (2 rows) Voila, we've got index only "count" scan. So, in some cases Postgres does do index only scan. What is surprising is that pg_hint_plan doesn't produce the desired results: mgogala=# explain /*+ IndexScan(emp emp_pkey) */ select count(empno) from emp;                                   QUERY PLAN -------------------------------------------------------------------------------  Aggregate  (cost=10000000001.17..10000000001.18 rows=1 width=8)    ->  Seq Scan on emp  (cost=10000000000.00..10000000001.14 rows=14 width=2)  JIT:    Functions: 3    Options: Inlining true, Optimization true, Expressions true, Deforming true (5 rows) This is strange in more ways than one: 1. The hint didn't work, Postgres still performed sequential scan 2. JIT got involved for the reasons that are completely unclear. 3. The cost is enormous, despite the fact the the "emp" table has only 14 rows and is fully contained in a single block. However, if we do GUC hint, the result behaves as expected: mgogala=# explain /*+ set(enable_seqscan off) */ select count(empno) from emp e;                                     QUERY PLAN -------------------------------------------------------------------------------- ---  Aggregate  (cost=12.38..12.39 rows=1 width=8)    ->  Index Only Scan using emp_pkey on emp e (cost=0.14..12.35 rows=14 width=2) (2 rows) So, it is possible to force index only scan. However, from purely theoretic point of view, the speed difference is questionable. Indexes are stored differently than tables, they have branch blocks and data bocks with an ample free space in the data blocks to prevent block splits and index level increase, with mandatory re-balancing. Indexes are actually quite large structures, sometimes even larger than the underlying tables. In general, going after the index-only scan doesn't look like a good strategy. After all this work, I also have a question: does PostgreSQL read index blocks in batches or it does single block scans? Some other databases have 2 events: "db file scattered read" for sequential scan (called "full table scan" in that specific vernacular) and "db file sequential read" for index scans. Those database only do single block reads from indexes while the tables can be read using batches of blocks. Does PostgreSQL do the same thing? -- Mladen Gogala Database Consultant Tel: (347) 321-1217 https://dbwhisperer.wordpress.com --------------d3Q0iF8fqTe4Y9amctrn0hyV Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit
On 7/12/22 14:25, MichaelDBA Vitale wrote:
That is not true: doing the select on the primary key will still result in a table scan, not an index scan.  The heap always gets accessed for select counts.

Regards,
Michael Vitale

Well, not always:

mgogala=# explain select count(*) from emp;
                        QUERY PLAN                        
----------------------------------------------------------
 Aggregate  (cost=1.18..1.19 rows=1 width=8)
   ->  Seq Scan on emp  (cost=0.00..1.14 rows=14 width=0)
(2 rows)

That is the classic "select count(*)".  Now, let's see PK:

mgogala=# \d emp
                           Table "mgogala.emp"
  Column  |            Type             | Collation | Nullable | Default
----------+-----------------------------+-----------+----------+---------
 empno    | smallint                    |           | not null |
 ename    | character varying(10)       |           |          |
 job      | character varying(9)        |           |          |
 mgr      | smallint                    |           |          |
 hiredate | timestamp without time zone |           |          |
 sal      | double precision            |           |          |
 comm     | double precision            |           |          |
 deptno   | smallint                    |           |          |
Indexes:
    "emp_pkey" PRIMARY KEY, btree (empno)
Foreign-key constraints:
    "fk_deptno" FOREIGN KEY (deptno) REFERENCES dept(deptno)


Now, let's do scan on "count(empno)":

mgogala=# explain select count(empno) from emp;
                        QUERY PLAN                        
----------------------------------------------------------
 Aggregate  (cost=1.18..1.19 rows=1 width=8)
   ->  Seq Scan on emp  (cost=0.00..1.14 rows=14 width=2)
(2 rows)

So far, so good. Sequential scan, despite the existence of the NOT NULL primary key. Now comes the twist:

mgogala=# set enable_seqscan=off;
SET
mgogala=# explain select count(empno) from emp;
                                   QUERY PLAN                                   
 
--------------------------------------------------------------------------------
-
 Aggregate  (cost=12.38..12.39 rows=1 width=8)
   ->  Index Only Scan using emp_pkey on emp  (cost=0.14..12.35 rows=14 width=2)
(2 rows)

Voila, we've got index only "count" scan. So, in some cases Postgres does do index only scan. What is surprising is that pg_hint_plan doesn't produce the desired results:

mgogala=# explain /*+ IndexScan(emp emp_pkey) */ select count(empno) from emp;
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Aggregate  (cost=10000000001.17..10000000001.18 rows=1 width=8)
   ->  Seq Scan on emp  (cost=10000000000.00..10000000001.14 rows=14 width=2)
 JIT:
   Functions: 3
   Options: Inlining true, Optimization true, Expressions true, Deforming true
(5 rows)

This is strange in more ways than one:

  1. The hint didn't work, Postgres still performed sequential scan
  2. JIT got involved for the reasons that are completely unclear.
  3. The cost is enormous, despite the fact the the "emp" table has only 14 rows and is fully contained in a single block.

However, if we do GUC hint, the result behaves as expected:

mgogala=# explain /*+ set(enable_seqscan off) */ select count(empno) from emp e;
                                    QUERY PLAN                                  
   
--------------------------------------------------------------------------------
---
 Aggregate  (cost=12.38..12.39 rows=1 width=8)
   ->  Index Only Scan using emp_pkey on emp e  (cost=0.14..12.35 rows=14 width=2)
(2 rows)

So, it is possible to force index only scan. However, from purely theoretic point of view, the speed difference is questionable. Indexes are stored differently than tables, they have branch blocks and data bocks with an ample free space in the data blocks to prevent block splits and index level increase, with mandatory re-balancing. Indexes are actually quite large structures, sometimes even larger than the underlying tables. In general, going after the index-only scan doesn't look like a good strategy.

After all this work, I also have a question: does PostgreSQL read index blocks in batches or it does single block scans? Some other databases have 2 events: "db file scattered read" for sequential scan (called "full table scan" in that specific vernacular) and "db file sequential read" for index scans. Those database only do single block reads from indexes while the tables can be read using batches of blocks. Does PostgreSQL do the same thing?

-- 
Mladen Gogala
Database Consultant
Tel: (347) 321-1217
https://dbwhisperer.wordpress.com
--------------d3Q0iF8fqTe4Y9amctrn0hyV--