Postgres 10 - 查询因 order by 而变慢

Aru*_*run 5 postgresql postgresql-performance

我正在运行一个查询,例如

select id from students where school_id='67153fb1-8f79-441d-a747-ca3778cf6d3d';
Run Code Online (Sandbox Code Playgroud)

在桌子上看起来像

                Table "public.students"
          Column       |            Type             |             Modifiers              
    -------------------+-----------------------------+------------------------------------
     id                | uuid                        | not null default gen_random_uuid()
     school_id        | uuid                        | 
Indexes:
    "students_pkey" PRIMARY KEY, btree (id)
    "students_school_id_idx" btree (school_id)
Run Code Online (Sandbox Code Playgroud)

select 语句的查询计划与 where 类似,如下所示:

explain select id from students where school_id='67153fb1-8f79-441d-a747-ca3778cf6d3d';
                                            QUERY PLAN                                            
--------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on students  (cost=581.83..83357.10 rows=24954 width=16)
   Recheck Cond: (school_id = '67153fb1-8f79-441d-a747-ca3778cf6d3d'::uuid)
   ->  Bitmap Index Scan on students_school_id_idx  (cost=0.00..575.59 rows=24954 width=0)
         Index Cond: (school_id = '67153fb1-8f79-441d-a747-ca3778cf6d3d'::uuid)
Run Code Online (Sandbox Code Playgroud)

这相当快。

现在我们将 order by 添加到带有 id 的查询中,这会降低查询的性能。(这样的查询是由 Rails 生成的,例如有一些条件的 Student.first)

explain select id from students where school_id='67153fb1-8f79-441d-a747-ca3778cf6d3d' order by id asc limit 1;
                                                 QUERY PLAN                                                 
------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.43..488.51 rows=1 width=16)
   ->  Index Scan using students_pkey on students  (cost=0.43..12179370.22 rows=24954 width=16)
         Filter: (school_id = '67153fb1-8f79-441d-a747-ca3778cf6d3d'::uuid)
Run Code Online (Sandbox Code Playgroud)

如何提高查询结果的返回速度?目前表中约有 4990731 条记录,耗时超过 2 分钟!它在带有 db.t2.medium 实例的 RDS 上运行。

运行后更新Analyze students;

explain select id from students where school_id='67153fb1-8f79-441d-a747-ca3778cf6d3d' order by id asc limit 1;
                                                       QUERY PLAN                                                    
    -----------------------------------------------------------------------------------------------------------------
     Limit  (cost=8.46..8.46 rows=1 width=16)
       ->  Sort  (cost=8.46..8.46 rows=1 width=16)
             Sort Key: id
             ->  Index Scan using students_school_id_idx on students  (cost=0.43..8.45 rows=1 width=16)
                   Index Cond: (school_id = '67153fb1-8f79-441d-a747-ca3778cf6d3d'::uuid)

    explain analyze select id from students where school_id='67153fb1-8f79-441d-a747-ca3778cf6d3d' order by id asc limit 1;
                                                                          QUERY PLAN                                                                         
    -----------------------------------------------------------------------------------------------------------------------------------------------------------
    Limit  (cost=8.46..8.46 rows=1 width=16) (actual time=1.853..1.855 rows=1 loops=1)
     ->  Sort  (cost=8.46..8.46 rows=1 width=16) (actual time=1.851..1.852 rows=1 loops=1)
           Sort Key: id
           Sort Method: quicksort  Memory: 25kB
           ->  Index Scan using students_school_id_idx on students  (cost=0.43..8.45 rows=1 width=16) (actual time=1.841..1.843 rows=1 loops=1)
                 Index Cond: (school_id = '67153fb1-8f79-441d-a747-ca3778cf6d3d'::uuid)
    Planning time: 0.145 ms
    Execution time: 1.874 ms
Run Code Online (Sandbox Code Playgroud)

Lau*_*lbe 3

ORDER BYPostgreSQL 认为,通过按排序顺序扫描行并丢弃行,直到找到具有正确 的行,可以更快地避免 的排序school_id

这可能比预期花费的时间更长有两个原因:

  1. 表统计信息已关闭,并且 PostgreSQL 高估了该 的行数school_id

    计算新的统计数据,可能具有更高的 值default_statistics_target,以验证这是否是问题所在:

    ANALYZE students;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 具有正确值的(许多)行都school_id恰好具有相当高的id,因此 PostgreSQL 必须扫描比预期更多的行,直到找到匹配项。

    在这种情况下,您应该修改该ORDER BY子句,以便 PostgreSQL 不能使用错误的索引:

    ... ORDER BY id + 0
    
    Run Code Online (Sandbox Code Playgroud)