Performance Postgres 8.3中的位图扫描比索引扫描Postgres 9.4快2倍?

Performance Postgres 8.3中的位图扫描比索引扫描Postgres 9.4快2倍?,performance,postgresql,query-optimization,postgresql-performance,postgresql-9.4,Performance,Postgresql,Query Optimization,Postgresql Performance,Postgresql 9.4,在新硬件上将Postgres从8.3.8升级到9.4.1。一组具有代表性的查询显示,新系统的性能提高了1倍到3倍。然而,我们的一个高负荷区域总是比较慢 解释输出 8.3.8: Nested Loop (cost=25.78..709859.61 rows=1 width=4) (actual time=14.972..190.591 rows=32 loops=1) -> Bitmap Heap Scan on prime p (cost=25.78..1626.92 rows=

在新硬件上将Postgres从8.3.8升级到9.4.1。一组具有代表性的查询显示,新系统的性能提高了1倍到3倍。然而,我们的一个高负荷区域总是比较慢

解释
输出 8.3.8:

Nested Loop  (cost=25.78..709859.61 rows=1 width=4) (actual time=14.972..190.591 rows=32 loops=1)
  ->  Bitmap Heap Scan on prime p  (cost=25.78..1626.92 rows=1066 width=4) (actual time=1.567..9.597 rows=10742 loops=1)
        Recheck Cond: ((pid = ANY ('{28226,53915,83421,82118397,95513866}'::integer[])) AND (tid = ANY ('{1,2,3}'::integer[])))
        Filter: (NOT deleted)
        ->  Bitmap Index Scan on FOO_IDX1  (cost=0.00..25.73 rows=1066 width=0) (actual time=1.144..1.144 rows=10742 loops=1)
              Index Cond: ((pid = ANY ('{28226,53915,83421,82118397,95513866}'::integer[])) AND (deleted = false) AND (tid = ANY ('{1,2,3}'::integer[])))
  ->  Index Scan using FOO_IDX2 on data d  (cost=0.00..663.88 rows=1 width=4) (actual time=0.017..0.017 rows=0 loops=10742)
        Index Cond: (d.pid = p.pid)
        Filter: (lower("substring"(d.value, 1, 1000)) ~~ '%something%'::text)
Total runtime: 190.639 ms
9.4.1:

Nested Loop  (cost=1.15..335959.94 rows=1 width=4) (actual time=24.712..365.057 rows=32 loops=1)
  ->  Index Scan using FOO_IDX1 on prime p  (cost=0.57..953.17 rows=1033 width=4) (actual time=0.048..13.884 rows=10741 loops=1)
        Index Cond: ((pid = ANY ('{28226,53915,83421,82118397,95513866}'::integer[])) AND (deleted = false) AND (tid = ANY ('{1,2,3}'::integer[])))
        Filter: (NOT deleted)
  ->  Index Scan using FOO_IDX2 on data d  (cost=0.57..324.29 rows=1 width=4) (actual time=0.032..0.032 rows=0 loops=10741)
        Index Cond: (pid = p.pid)
        Filter: (lower("substring"(value, 1, 1000)) ~~ '%something%'::text)
        Rows Removed by Filter: 11
Planning time: 0.940 ms
Execution time: 365.156 ms
索引 设置 改变以下范围并不能改善这种情况

checkpoint_completion_target = 0.5
checkpoint_segments = 32
checkpoint_timeout = 30min
cpu_index_tuple_cost = 0.005
cpu_operator_cost = 0.0025
cpu_tuple_cost = 0.01
default_statistics_target = 500 (evaluated 100 to 10000 analyse after each)
effective_cache_size = 288GB
enable_seqscan = off
from_collapse_limit = 8
geqo = off
join_collapse_limit = 8
random_page_cost = 1.0
seq_page_cost = 1.0
shared_buffers = 96GB
work_mem = 64MB
对于
something%
我们也看到了类似的结果

在我们把它放在这里几年之前,我想知道我是否还有什么可以做的来优化这些重要案例

陈述 表定义 简化和消毒

\d prime

    Column     |            Type             |                    Modifiers
---------------+-----------------------------+-------------------------------------------------
 pid           | integer                     | not null default nextval('prime_seq'::regclass)
 deleted       | boolean                     |
 ppid          | integer                     |
 tid           | integer                     |

\d data

     Column     |  Type   |                      Modifiers
----------------+---------+------------------------------------------------------
 pdid           | integer | not null default nextval('data_seq'::regclass)
 pid            | integer |
 value          | text    |
新测试结果 我尝试了一系列默认的统计数据

default_statistics_target = 100  @ 381 ms
default_statistics_target = 500  @ 387 ms
default_statistics_target = 1000 @ 384 ms
default_statistics_target = 5000 @ 369 ms
(测试循环之间的分析和预热)

该值可以在我们的应用程序的其他领域产生重大差异。500似乎很理想,5000+导致其他区域减速3到10倍


我们的工具包的设计应确保整个数据库始终位于内存中

random_page_cost =  1.0 @ 372 ms
random_page_cost =  1.1 @ 372 ms 
random_page_cost =  4.0 @ 370 ms 
random_page_cost = 10.0 @ 369 ms

在362毫秒时启用\u bitmapscan=off(结果与预期相同)

早些时候,我还尝试在491毫秒时启用_indexscan=off(当然触发了不同的计划)

是的,第8.3页的计划使用索引和位图索引扫描——我认为这是这个问题的“核心”

感谢您提供相关文章的链接


关于列顺序的建议非常有趣

  • 在我们的规模和增长中,以下模式的最佳字段顺序是什么

  • 重新组织已加载表上的列顺序以实现好处的最有效方法是什么

  • 素数具有:

    integer
    text
    boolean
    boolean
    integer
    integer
    smallint
    integer
    timestamp without time zone
    timestamp without time zone
    timestamp without time zone
    text
    
    数据具有:

    integer
    integer
    integer
    text
    

    • 在该方法中未观察到可测量的差异,相同的计划(+/-5毫秒)
    • 我们通常首先尝试使用prime检查acl、status等,以缩小在数据中搜索的记录范围(prime是大小的1/10)


    为了处理未编排的情况,我们使用操作符类“text\u pattern\u ops”创建了第二个索引

    我们以前评估过多列GIN索引,但没有实现预期的效益。复杂,因为A)要满足acl、状态和类似的多个标准,B)需要点击“精确短语”,这需要重新检查结果短语。从长远来看,我对使用全文方法持乐观态度,到目前为止,我们尝试过的配方并不比 老派BTREE方法;然而

    杜松子酒试验1

    CREATE EXTENSION btree_gin
    CREATE INDEX FOO_IDX3 ON data USING GIN (to_tsvector('simple', lower(left(value, 1000))), pid)
    ANALYSE data
    
    SELECT p.pid
    FROM prime p
      INNER JOIN data d ON p.pid = d.pid
    WHERE to_tsvector('simple', lower(left(d.value, 1000))) @@ to_tsquery('simple', 'something')
      AND p.tid IN (1,2,3)
      AND p.deleted = FALSE
      AND p.ppid IN (28226, 53915, 83421, 82118397, 95513866)
    
    Execution time: 1034.866 ms (without phrase recheck)
    
    杜松子酒试验2

    CREATE EXTENSION pg_trgm 
    CREATE INDEX FOO_IDX4 ON data USING gin (left(value,1000) gin_trgm_ops, pid);
    ANALYSE data
    
    SELECT p.pid
    FROM prime p
      INNER JOIN data d ON p.pid = d.pid
    WHERE left(d.value,1000) LIKE '%Something%'
      AND p.tid IN (1,2,3)
      AND p.deleted = FALSE
      AND p.ppid IN (28226, 53915, 83421, 82118397, 95513866)
    
    
    Hash Join  (cost=2870.42..29050.89 rows=1 width=4) (actual time=668.333..2262.101 rows=32 loops=1)
      Hash Cond: (d.pid = p.pid)
      ->  Bitmap Heap Scan on data d  (cost=230.30..26250.04 rows=25716 width=4) (actual time=653.130..2234.736 rows=38659 loops=1)
            Recheck Cond: ("left"(value, 1000) ~~ '%Something%'::text)
            Rows Removed by Index Recheck: 146677
            Heap Blocks: exact=161810
            ->  Bitmap Index Scan on FOO_IDX4  (cost=0.00..223.87 rows=25716 width=0) (actual time=575.442..575.442 rows=185336 loops=1)
                  Index Cond: ("left"(value, 1000) ~~ '%Something%'::text)
      ->  Hash  (cost=2604.33..2604.33 rows=2863 width=4) (actual time=15.158..15.158 rows=10741 loops=1)
            Buckets: 1024  Batches: 1  Memory Usage: 378kB
            ->  Index Scan using FOO_IDX4 on prime p  (cost=0.57..2604.33 rows=2863 width=4) (actual time=0.064..11.737 rows=10741 loops=1)
                  Index Cond: ((ppid = ANY ('{28226,53915,83421,82118397,95513866}'::integer[])) AND (deleted = false) AND (tid = ANY ('{1,2,3}'::integer[])))
                  Filter: (NOT deleted)
    Planning time: 1.861 ms
    Execution time: 2262.210 ms
    


    我们已经在prime w/“ppid,deleted,tid”上有一个索引,很抱歉,这一点最初不清楚。

    查询计划不好的最常见原因是统计数据或成本设置不能很好地反映现实:

    只有当随机访问实际上与顺序访问一样快时,
    random\u page\u cost=1.0
    的设置才有意义,只有当您的数据库完全驻留在RAM中时才有意义。一个包含80M和750M行的表的数据库可能太大了。如果我的假设是正确的,稍微提高成本设置可能会解决问题。至少尝试
    1.1
    ,可能更多。运行测试以找到设置中的最佳位置

    通常我会先跑:

    SET enable_bitmapscan = off;
    
    在第9.4页的当前会话中,然后再次测试。棘手的部分是,您的查询可能同时需要索引和位图索引扫描。我需要查看查询

    随机页面成本的极低设置有利于索引扫描而不是位图索引扫描。如果该成本设置具有误导性,则会得到较差的查询计划

    在dba.SE上对该相关问题的回答有更多解释:

    表格设计 表的设计很简单,但通常最好不要在整数列之间放置boolen列*,因为这样做会浪费磁盘空间。更好:

    pid           | integer  | not null default nextval('prime_seq'::regclass)
    tid           | integer  | 
    deleted       | boolean  |
    
    这只是一个小小的改进,但它没有缺点

    查询 可以通过多种方式进行改进:

    SELECT pid
    FROM   data  d
    JOIN   prime p USING (pid)
    WHERE  left(d.value,1000) LIKE '%something%'
    AND    p.pid IN (28226, 53915, 83421, 82118397, 95513866) 
    AND    p.tid IN (1, 2, 3)
    AND    p.deleted = FALSE;
    
    • 左侧(d.value,1000)
      子字符串(d.value,11000)
      短且快(需要第9.1页+)

    • text\u pattern\u ops
      索引仅用于左锚定模式与
      LIKE
      匹配。你的表情没有固定。(我看到您也在使用锚定模式。)为此,请使用trigram GIN索引,该索引由附加模块提供,对于大表,尤其是在第9.4页(改进的GIN索引)中,它的速度大大提高

    指数 要在下面的GIN索引中包含
    integer
    pid
    ,首先安装附加模块,该模块提供必要的GIN操作员类。每个数据库运行一次:

    做一些假设,这将非常适合您的查询。
    数据上的多列三元GIN索引

    CREATE INDEX data_value_gin_trgm_idx ON data
    USING gin (left(value,1000) gin_trgm_ops, pid);
    
    以及
    prime
    上的部分多列索引:

    CREATE INDEX prime_pid_tip_idx ON prime (pid, tip)
    WHERE  deleted = FALSE;
    

    这里讲的是数量级。

    一种稍有不同的方法,首先通过(CTE)从“数据表”生成最大可能结果集,然后返回到prime以通过acl、状态等进行细化,将时间从365毫秒减少到142毫秒(节省223毫秒)。这项技术似乎比8.3基线更快

    WITH d as (SELECT pid
    FROM data
    WHERE LOWER(left(value,1000)) LIKE '%something%'
    AND fid IN (nnn,nnn,...))
    SELECT p.pid FROM d INNER JOIN prime p on p.pid = d.pid
    WHERE p.tid IN (1,2,3)
    AND p.deleted = FALSE
    AND p.ppid IN (28226,53915,83421,82118397,95513866)
    
    计划时间:1.417毫秒
    执行时间:141.508毫秒


    我将进一步评估CTE的意外影响。

    您能给我们看一下解释分析的结果吗?补充,谢谢Frank。我看不到您在问题中的实际疑问。表定义的(相关部分)也会有所帮助。检查
    [postgresql performance]
    的标记信息以了解说明。已添加。我不确定更多的索引/约束信息在这种情况下是否有用,所以暂时搁置。@Nothrock:根据您的更新添加了更多。
    SELECT pid
    FROM   data  d
    JOIN   prime p USING (pid)
    WHERE  left(d.value,1000) LIKE '%something%'
    AND    p.pid IN (28226, 53915, 83421, 82118397, 95513866) 
    AND    p.tid IN (1, 2, 3)
    AND    p.deleted = FALSE;
    
    CREATE EXTENSION btree_gin;
    
    CREATE INDEX data_value_gin_trgm_idx ON data
    USING gin (left(value,1000) gin_trgm_ops, pid);
    
    CREATE INDEX prime_pid_tip_idx ON prime (pid, tip)
    WHERE  deleted = FALSE;
    
    WITH d as (SELECT pid
    FROM data
    WHERE LOWER(left(value,1000)) LIKE '%something%'
    AND fid IN (nnn,nnn,...))
    SELECT p.pid FROM d INNER JOIN prime p on p.pid = d.pid
    WHERE p.tid IN (1,2,3)
    AND p.deleted = FALSE
    AND p.ppid IN (28226,53915,83421,82118397,95513866)