尽管有足够的内存来完全缓存,但 PostgreSQL 8.4.4 中的大型连接查询性能不佳

ehs*_*nul 6 postgresql linux database-performance

我怎样才能使这篇文章中描述的查询更快,特别是通过使用可用的 RAM 来创建 PostgreSQL?- 请注意,我已尝试适当地配置 Effective_cache_size 和 shared_buffers。见下文。

背景

我必须定期将大约 2.6 亿行表 (coreg_master) 加入进来的新数据。我已经对表进行了分区,以允许每个分区都适合 RAM。当然,我也设置了适当的索引。但是,当将分区表与其他(小得多)表分开连接时,它会在磁盘上进行完全随机的 IO。这是由于对大表的嵌套循环索引扫描造成的,这真的很慢,因为我们没有很好的磁盘设置。

我希望它使用所有可用的 RAM 来缓存大型分区表,我认为这应该由 Linux 内核/文件系统本身完成。但是它仍然不会将表加载到 RAM 中,尽管它适合。我猜这是因为访问模式不是顺序的,因此不会触发缓存?我不知道。查询计划和配置参数如下。

表结构

这是coreg_master的分区之一,我的大表。分区表被命名为 coreg_a、coreg_b 等。

\d coreg_a
                                   Table "public.coreg_a"
   Column    |       Type        |                         Modifiers                         
-------------+-------------------+-----------------------------------------------------------
 id          | integer           | not null default nextval('coreg_master_id_seq'::regclass)
 first_name  | character varying | 
 last_name   | character varying | 
 phone       | character varying | 
 city        | character varying | 
 zip         | integer           | 
 address     | character varying | 
 dob         | date              | 
 ip          | character varying | 
 source      | character varying | 
 gender      | character varying | 
 state       | character varying | 
 record_date | date              | 
 email       | character varying | 
Indexes:
    "coreg_a_name" btree (lower(first_name::text), lower(last_name::text))
Check constraints:
    "coreg_a_first_name_check" CHECK (first_name::text >= 'a'::text AND first_name::text < 'b'::text)
Inherits: coreg_master
Run Code Online (Sandbox Code Playgroud)

下面是表appendable_24的一个分区,一个表被coreg_masterjoin的例子。它也以与 coreg_master 相同的方式分区,因此实际上 coreg_a 与 appendable_24_a 等连接,一次一个。

\d appendable_24_a
       Table "public.appendable_24_a"
   Column   |       Type        | Modifiers 
------------+-------------------+-----------
 line_num   | integer           | not null
 first_name | character varying | 
 last_name  | character varying | 
 address    | character varying | 
 state      | character varying | 
 zip        | integer           | 
Indexes:
    "appendable_24_a_name_index" btree (lower(first_name::text), lower(last_name::text))
Check constraints:
    "appendable_24_a_first_name_check" CHECK (first_name::text >= 'a'::text AND first_name::text < 'b'::text)
Inherits: appendable_24
Run Code Online (Sandbox Code Playgroud)

询问 & EXPLAIN ANALYZE

下面是对最小连接(表根据 first_name 列的第一个字母进行分区)的解释分析的输出,因为它不需要很长时间。但是,每个分区上所有连接的查询计划都相同,因此它也应该代表较大的连接(注意,我做ANALYZE了表,总时间实际上是 20 秒,但结果是这里更快被缓存):

explain analyze SELECT
          coreg_x.phone,
          coreg_x.email,
          coreg_x.record_date,
          appendable_24_x.line_num
        FROM appendable_24_x INNER JOIN coreg_x ON
          lower(appendable_24_x.first_name) = lower(coreg_x.first_name) AND
          lower(appendable_24_x.last_name) = lower(coreg_x.last_name) AND
          (coreg_x.phone IS NOT NULL OR coreg_x.email IS NOT NULL) AND
          similarity(lower(appendable_24_x.address), lower(coreg_x.address)) > 0.7
      ; 
                                                                                       QUE
RY PLAN 

------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------
-----
 Nested Loop  (cost=0.01..640.49 rows=875 width=39) (actual time=9.990..53.839 rows=29 loo
ps=1)
   Join Filter: (similarity(lower((appendable_24_x.address)::text), lower((coreg_x.address
)::text)) > 0.7::double precision)
   ->  Seq Scan on appendable_24_x  (cost=0.00..1.80 rows=80 width=34) (actual time=0.009.
.0.111 rows=80 loops=1)
   ->  Index Scan using coreg_x_name on coreg_x  (cost=0.01..7.95 rows=1 width=64) (actual
 time=0.024..0.137 rows=44 loops=80)
         Index Cond: ((lower((coreg_x.first_name)::text) = lower((appendable_24_x.first_na
me)::text)) AND (lower((coreg_x.last_name)::text) = lower((appendable_24_x.last_name)::tex
t)))
         Filter: ((coreg_x.phone IS NOT NULL) OR (coreg_x.email IS NOT NULL))
 Total runtime: 53.950 ms
(7 rows)
Run Code Online (Sandbox Code Playgroud)

一些统计信息、配置参数和其他数据

  • PostgreSQL 版本:8.4.4
  • 操作系统:CentOS 5.5 版(最终版)
  • 文件系统:ext3
  • 总可用内存:8GB
  • 共享缓冲区 = 2GB
  • 有效缓存大小 = 7200MB
  • 完整的运行时配置通过show allhttp : //pastie.org/1159746
  • 最大的分区核心表 (coreg_j) 大小:~4900MB
  • 对应的行数:~3200万
  • 对应的 (first_name, last_name) 索引大小:~1000MB
  • 第二个分区表(appendable_24_j)大小:~1800kB
  • appendable_24_j 中的行数:~25,000

eev*_*var 2

只有足够的内存来缓存数据库的一小部分,并且正如您发布的查询计划所示,之前访问的数据/相关索引的部分确实被缓存了。(Postgres 不缓存查询结果)。

53 毫秒的运行时间并不算太差,而且我不确定未缓存数据上的 20 秒是否意味着 PG 选择了错误的查询计划。毕竟,仅相关索引就有 1GB 大,但查看慢速查询的分析输出会很有趣。

您可以尝试调整规划器成本,看看这是否对最坏情况下的性能有任何影响(如果这是您的问题)。

您可能还想稍微增加您的maintenance_work_mem,即使这没有任何关系。

  • 是的,我同意存在随机 IO,但我不确定这是一个问题。根据你的计划成本,PG认为这是最便宜的选择。你尝试调整它们吗?启动 psql 会话,解释您的查询,“set random_page_cost = 10;”,然后再次解释。不断提高它直到查询计划发生变化,然后解释分析。它真的会跑得更快吗?-- 如果确实如此,则说明您的 random_page_cost 太低,或者 pg 对小表的统计信息很差。 (2认同)