由于Postgres

时间:2017-05-11 14:14:09

标签: sql performance postgresql

我在这里有一个奇怪的查询执行性能案例。查询在WHERE子句中具有日期值,执行速度因日期值而异。

实际上:

  • 对于过去30天范围内的日期,执行需要 3分钟

  • 对于过去30天范围之前的日期,执行需要几秒

下面列出了查询,其中的日期是最近30天的范围:

select 
    sk2_.code as col_0_0_, 
    bra4_.code as col_1_0_, 
    st0_.quantity as col_2_0_, 
    bat1_.forecast as col_3_0_ 
from 
    TBL_st st0_, 
    TBL_bat bat1_, 
    TBL_sk sk2_, 
    TBL_bra bra4_ 
where 
    st0_.batc_id=bat1_.id 
    and bat1_.sku_id=sk2_.id 
    and bat1_.bran_id=bra4_.id 
    and  not (exists (select 
        1 
    from 
        TBL_st st6_, 
        TBL_bat bat7_, 
        TBL_sk sk10_ 
    where 
        st6_.batc_id=bat7_.id 
        and bat7_.sku_id=sk10_.id 
        and bat7_.bran_id=bat1_.bran_id 
        and sk10_.code=sk2_.code 
        and st6_.date>st0_.date 
        and sk10_.acco_id=1 
        and st6_.date>='2017-04-20' 
        and st6_.date<='2017-04-30')) 
    and sk2_.acco_id=1 
    and st0_.date>='2017-04-20' 
    and st0_.date<='2017-04-30' 

以下是查询的计划,其中包含过去30天范围内的日期:

Nested Loop  (cost=289.06..19764.03 rows=1 width=430) (actual time=3482.062..326049.246 rows=249 loops=1) 
  ->  Nested Loop Anti Join  (cost=288.91..19763.86 rows=1 width=433) (actual time=3482.023..326048.023 rows=249 loops=1) 
        Join Filter: ((st6_.date > st0_.date) AND ((sk10_.code)::text = (sk2_.code)::text)) 
        Rows Removed by Join Filter: 210558 
        ->  Nested Loop  (cost=286.43..13719.38 rows=1 width=441) (actual time=4.648..2212.042 rows=2474 loops=1) 
              ->  Nested Loop  (cost=286.00..6871.33 rows=13335 width=436) (actual time=4.262..657.823 rows=666738 loops=1) 
                    ->  Index Scan using uk_TBL_sk0_account_code on TBL_sk sk2_  (cost=0.14..12.53 rows=1 width=426) (actual time=1.036..1.084 rows=50 loops=1) 
                          Index Cond: (acco_id = 1) 
                    ->  Bitmap Heap Scan on TBL_bat bat1_  (cost=285.86..6707.27 rows=15153 width=26) (actual time=3.675..11.308 rows=13335 loops=50) 
                          Recheck Cond: (sku_id = sk2_.id) 
                          Heap Blocks: exact=241295 
                          ->  Bitmap Index Scan on ix_al_batc_sku_id  (cost=0.00..282.07 rows=15153 width=0) (actual time=3.026..3.026 rows=13335 loops=50) 
                                Index Cond: (sku_id = sk2_.id) 
              ->  Index Scan using ix_al_stle_batc_id on TBL_st st0_  (cost=0.42..0.50 rows=1 width=21) (actual time=0.002..0.002 rows=0 loops=666738) 
                    Index Cond: (batc_id = bat1_.id) 
                    Filter: ((date >= '2017-04-20 00:00:00'::timestamp without time zone) AND (date <= '2017-04-30 00:00:00'::timestamp without time zone)) 
                    Rows Removed by Filter: 1 
        ->  Nested Loop  (cost=2.49..3023.47 rows=1 width=434) (actual time=111.345..130.883 rows=86 loops=2474) 
              ->  Hash Join  (cost=2.06..2045.18 rows=1905 width=434) (actual time=0.010..28.028 rows=54853 loops=2474) 
                    Hash Cond: (bat7_.sku_id = sk10_.id) 
                    ->  Index Scan using ix_al_batc_bran_id on TBL_bat bat7_  (cost=0.42..1667.31 rows=95248 width=24) (actual time=0.009..11.045 rows=54853 loops=2474) 
                          Index Cond: (bran_id = bat1_.bran_id) 
                    ->  Hash  (cost=1.63..1.63 rows=1 width=426) (actual time=0.026..0.026 rows=50 loops=1) 
                          Buckets: 1024  Batches: 1  Memory Usage: 11kB 
                          ->  Seq Scan on TBL_sk sk10_  (cost=0.00..1.63 rows=1 width=426) (actual time=0.007..0.015 rows=50 loops=1) 
                                Filter: (acco_id = 1) 
              ->  Index Scan using ix_al_stle_batc_id on TBL_st st6_  (cost=0.42..0.50 rows=1 width=16) (actual time=0.002..0.002 rows=0 loops=135706217) 
                    Index Cond: (batc_id = bat7_.id) 
                    Filter: ((date >= '2017-04-20 00:00:00'::timestamp without time zone) AND (date <= '2017-04-30 00:00:00'::timestamp without time zone)) 
                    Rows Removed by Filter: 1 
  ->  Index Scan using TBL_bra_pk on TBL_bra bra4_  (cost=0.14..0.16 rows=1 width=13) (actual time=0.003..0.003 rows=1 loops=249) 
        Index Cond: (id = bat1_.bran_id) 
Planning time: 8.108 ms 
Execution time: 326049.583 ms

以下是与过去30天范围之前的日期相同的查询:

select 
    sk2_.code as col_0_0_, 
    bra4_.code as col_1_0_, 
    st0_.quantity as col_2_0_, 
    bat1_.forecast as col_3_0_ 
from 
    TBL_st st0_, 
    TBL_bat bat1_, 
    TBL_sk sk2_, 
    TBL_bra bra4_ 
where 
    st0_.batc_id=bat1_.id 
    and bat1_.sku_id=sk2_.id 
    and bat1_.bran_id=bra4_.id 
    and  not (exists (select 
        1 
    from 
        TBL_st st6_, 
        TBL_bat bat7_, 
        TBL_sk sk10_ 
    where 
        st6_.batc_id=bat7_.id 
        and bat7_.sku_id=sk10_.id 
        and bat7_.bran_id=bat1_.bran_id 
        and sk10_.code=sk2_.code 
        and st6_.date>st0_.date 
        and sk10_.acco_id=1 
        and st6_.date>='2017-01-20' 
        and st6_.date<='2017-01-30')) 
    and sk2_.acco_id=1 
    and st0_.date>='2017-01-20' 
    and st0_.date<='2017-01-30'

以下是查询的计划,其中包含最近30天范围之前的日期:

  Hash Join  (cost=576.33..27443.95 rows=48 width=430) (actual time=132.732..3894.554 rows=250 loops=1) 
  Hash Cond: (bat1_.bran_id = bra4_.id) 
  ->  Merge Anti Join  (cost=572.85..27439.82 rows=48 width=433) (actual time=132.679..3894.287 rows=250 loops=1) 
        Merge Cond: ((sk2_.code)::text = (sk10_.code)::text) 
        Join Filter: ((st6_.date > st0_.date) AND (bat7_.bran_id = bat1_.bran_id)) 
        Rows Removed by Join Filter: 84521 
        ->  Nested Loop  (cost=286.43..13719.38 rows=48 width=441) (actual time=26.105..1893.523 rows=2491 loops=1) 
              ->  Nested Loop  (cost=286.00..6871.33 rows=13335 width=436) (actual time=1.159..445.683 rows=666738 loops=1) 
                    ->  Index Scan using uk_TBL_sk0_account_code on TBL_sk sk2_  (cost=0.14..12.53 rows=1 width=426) (actual time=0.035..0.084 rows=50 loops=1) 
                          Index Cond: (acco_id = 1) 
                    ->  Bitmap Heap Scan on TBL_bat bat1_  (cost=285.86..6707.27 rows=15153 width=26) (actual time=1.741..7.148 rows=13335 loops=50) 
                          Recheck Cond: (sku_id = sk2_.id) 
                          Heap Blocks: exact=241295 
                          ->  Bitmap Index Scan on ix_al_batc_sku_id  (cost=0.00..282.07 rows=15153 width=0) (actual time=1.119..1.119 rows=13335 loops=50) 
                                Index Cond: (sku_id = sk2_.id) 
              ->  Index Scan using ix_al_stle_batc_id on TBL_st st0_  (cost=0.42..0.50 rows=1 width=21) (actual time=0.002..0.002 rows=0 loops=666738) 
                    Index Cond: (batc_id = bat1_.id) 
                    Filter: ((date >= '2017-01-20 00:00:00'::timestamp without time zone) AND (date <= '2017-01-30 00:00:00'::timestamp without time zone)) 
                    Rows Removed by Filter: 1 
        ->  Materialize  (cost=286.43..13719.50 rows=48 width=434) (actual time=15.584..1986.953 rows=84560 loops=1) 
              ->  Nested Loop  (cost=286.43..13719.38 rows=48 width=434) (actual time=15.577..1983.384 rows=2491 loops=1) 
                    ->  Nested Loop  (cost=286.00..6871.33 rows=13335 width=434) (actual time=0.843..482.864 rows=666738 loops=1) 
                          ->  Index Scan using uk_TBL_sk0_account_code on TBL_sk sk10_  (cost=0.14..12.53 rows=1 width=426) (actual time=0.005..0.052 rows=50 loops=1) 
                                Index Cond: (acco_id = 1) 
                          ->  Bitmap Heap Scan on TBL_bat bat7_  (cost=285.86..6707.27 rows=15153 width=24) (actual time=2.051..7.902 rows=13335 loops=50) 
                                Recheck Cond: (sku_id = sk10_.id) 
                                Heap Blocks: exact=241295 
                                ->  Bitmap Index Scan on ix_al_batc_sku_id  (cost=0.00..282.07 rows=15153 width=0) (actual time=1.424..1.424 rows=13335 loops=50) 
                                      Index Cond: (sku_id = sk10_.id) 
                    ->  Index Scan using ix_al_stle_batc_id on TBL_st st6_  (cost=0.42..0.50 rows=1 width=16) (actual time=0.002..0.002 rows=0 loops=666738) 
                          Index Cond: (batc_id = bat7_.id) 
                          Filter: ((date >= '2017-01-20 00:00:00'::timestamp without time zone) AND (date <= '2017-01-30 00:00:00'::timestamp without time zone)) 
                          Rows Removed by Filter: 1 
  ->  Hash  (cost=2.10..2.10 rows=110 width=13) (actual time=0.033..0.033 rows=110 loops=1) 
        Buckets: 1024  Batches: 1  Memory Usage: 14kB 
        ->  Seq Scan on TBL_bra bra4_  (cost=0.00..2.10 rows=110 width=13) (actual time=0.004..0.013 rows=110 loops=1) 
Planning time: 14.542 ms 
Execution time: 3894.793 ms

有没有人知道为什么会这样。

有没有人有类似的经历?

非常感谢你。 亲切的问候,Petar

2 个答案:

答案 0 :(得分:0)

我不确定,但我刚才有类似的情况(在ORACLE上,但我想这并不重要)。

在我的情况下,差异源于数据量之间的差异,这意味着:如果您拥有过去30天的1%数据,则使用索引。当你需要“较旧的”数据(其余99%的数据)时,它决定不使用索引并进行全面扫描(以嵌套循环的形式而不是散列连接)。

如果您确定数据分发正常,那么可以尝试收集统计数据(当时为我工作)。最终你可以开始分析这个查询的每一个和平,并从那里看到瓶颈和工作的确切部分。

答案 1 :(得分:0)

BTree索引的日期可能会有一些问题,特别是如果您要从表中删除旧数据(即删除所有超过90天的数据)。这可能导致表变得偏斜,所有行都在树的一个分支下。即使不删除旧日期,如果“新”行比“旧”行多,它仍然可能发生。

但是我没有看到您的查询计划使用st0_.date上的索引,所以我认为这不是问题。如果您可以负担得起st0_的表锁定,则可以通过对任何包含st0_.date的索引运行REINDEX操作来验证该理论。

相反,我认为您需要匹配2017-01-20至2017-01-30范围的行比2017-04-20至2017-04-30范围更多的行。两个查询中的第一个双缩进嵌套循环是相同的,因此我将忽略它。第二个双重意图节不同,在慢速查询中代价更高:

->  Materialize  (cost=286.43..13719.50 rows=48 width=434) (actual time=15.584..1986.953 rows=84560 loops=1)
      ->  Nested Loop  (cost=286.43..13719.38 rows=48 width=434) (actual time=15.577..1983.384 rows=2491 loops=1)
            ->  Nested Loop  (cost=286.00..6871.33 rows=13335 width=434) (actual time=0.843..482.864 rows=666738

vs

->  Nested Loop  (cost=2.49..3023.47 rows=1 width=434) (actual time=111.345..130.883 rows=86 loops=2474)
      ->  Hash Join  (cost=2.06..2045.18 rows=1905 width=434) (actual time=0.010..28.028 rows=54853 loops=2474)

对材料进行材料化可能是一项昂贵的操作,不一定会随估计成本而扩展。看一下https://www.postgresql.org/docs/10/static/using-explain.html,然后搜索“ Materialize”。另外请注意,慢速版本中的估计行数要高得多。

我不确定100%,但是我相信调整“ work_mem”参数可以在此区域产生一些效果(https://www.postgresql.org/docs/9.4/static/runtime-config-resource.html#GUC-WORK-MEM)。为了验证这一理论,您可以使用以下方法更改每次会话的值:

SET LOCAL work_mem = '8MB';