共计 4128 个字符,预计需要花费 11 分钟才能阅读完成。
本篇内容介绍了“怎么提升 PostgreSQL 性能”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
使用 Postgres 监测慢的 Postgres 查询
在这周早些时候,一个用于我们的图形编辑器上的小表(10GB,1500 万行)的主键查询,在我们的一个(多个)数据库上发生来大的查询性能问题。
99.9% 到查询都是非常迅速流畅的,但是在一些使用大量的枚举值的地方,这些查询会需要 20 秒。花费如此多到时间在数据库上,意味着使用者必须在浏览器面前等待图形编辑器的响应。很明显只因为这 0.01% 就会造成很不好到影响。
查询和查询计划
下面是这个出问题的查询
SELECT c.key,
c.x_key,
c.tags,
x.name
FROM context c
JOIN x
ON c.x_key = x.key
WHERE c.key = ANY (ARRAY[15368196, -- 11,000 other keys --)])
AND c.x_key = 1
AND c.tags @ ARRAY[E blah
表 X 有几千行数据,表 C 有 1500 万条数据。两张表的主键值“key”都有适当的索引。这是一个非常简单清晰的主键查询。但有趣的是,当增加主键内容的数量,如在主键有 11,000 个值的时候,通过在查询语句上加上 EXPLAIN (ANALYZE, BUFFERS) 我们得到如下的查询计划。
Nested Loop (cost=6923.33..11770.59 rows=1 width=362) (actual time=17128.188..22109.283 rows=10858 loops=1)
Buffers: shared hit=83494
- Bitmap Heap Scan on context c (cost=6923.33..11762.31 rows=1 width=329) (actual time=17128.121..22031.783 rows=10858 loops=1)
Recheck Cond: ((tags @ {blah} ::text[]) AND (x_key = 1))
Filter: (key = ANY ( {15368196,(a lot more keys here)} ::integer[]))
Buffers: shared hit=50919
- BitmapAnd (cost=6923.33..6923.33 rows=269 width=0) (actual time=132.910..132.910 rows=0 loops=1)
Buffers: shared hit=1342
- Bitmap Index Scan on context_tags_idx (cost=0.00..1149.61 rows=15891 width=0) (actual time=64.614..64.614 rows=264777 loops=1)
Index Cond: (tags @ {blah} ::text[])
Buffers: shared hit=401
- Bitmap Index Scan on context_x_id_source_type_id_idx (cost=0.00..5773.47 rows=268667 width=0) (actual time=54.648..54.648 rows=267659 loops=1)
Index Cond: (x_id = 1)
Buffers: shared hit=941
- Index Scan using x_pkey on x (cost=0.00..8.27 rows=1 width=37) (actual time=0.003..0.004 rows=1 loops=10858)
Index Cond: (x.key = 1)
Buffers: shared hit=32575
Total runtime: 22117.417 ms
在结果的最底部你可以看到,这个查询总共花费 22 秒。我们可以非常直观的通过下面的 CPU 使用率图观察到这 22 秒的花费。大部分的时间花费在 Postgres 和 OS 上, 只有很少部分用于 I /O .
在最低的层面,这些查询看起来就像是这些 CPU 利用率的峰值。CPU 图很少有用,但是在这种条件下它证实了关键的一点:数据库并没有等待磁盘去读取数据。它在做一些排序,哈希以及行比较之类的事情。
第二个有趣的度量,就是距离这些峰值很近的轨迹,它们是由 Postgres“取得”的行数(本例中没有返回,就看看再忽略掉吧)。
显然有些动作在规则的有条不紊的浏览过许多行:我们的查询。
Postgres 的问题所在:位图扫描
下面是行匹配的查询计划
Buffers: shared hit=83494
- Bitmap Heap Scan on context c (cost=6923.33..11762.31 rows=1 width=329) (actual time=17128.121..22031.783 rows=10858 loops=1)
Recheck Cond: ((tags @ {blah} ::text[]) AND (x_key = 1))
Filter: (key = ANY ( {15368196,(a lot more keys here)} ::integer[]))
Buffers: shared hit=50919
Postgres 使用位图扫描表 C. 当主键的数据量小的时候,它能有效的使用索引在内存里建立位图。如果位图太大,最优查询计划就改变查询方式了。在我们这个查询中,因为主键包含的数据量很大,所以查询就使用最优(系统自己判断的)的方式去检索查询候选行,并且立即查询所有和主键匹配的数据。就是这些¨放入内存¨和¨立即查询¨花费太多的时间(查询计划中的 Recheck Cond)。
幸好只有 30% 的数据被导入到内存中,所以还不至于像从硬盘里读取那么坏。但它仍然对性能有非常明显的影响。记住,查询是非常简单的。这是一个主键查询所以没有很多明了的方式来确定它有没有戏剧性的重新架构数据库或应用程序。PGSQL-Performance mailing list 给予了我们很大的帮助.
解决方案
这是我们喜欢开源和喜欢帮助用户的另外一个原因。Tom Lane 是开源代码作者中最盛产的程序员之一,他建议我们做如下尝试:
SELECT c.key,
c.x_key,
c.tags,
x.name
FROM context c
JOIN x
ON c.x_key = x.key
WHERE c.key = ANY (VALUES (15368196), -- 11,000 other keys --)
AND c.x_key = 1
AND c.tags @ ARRAY[E blah
把 ARRAY 改成 VALUES,你能指出他们的不同点吗?
我们使用 ARRAY[…] 列举出所有的关键字以用来查询,但是这却欺骗了查询优化器。然而 Values(…) 却能够让优化器充分使用关键字索引。仅仅是一行代码的改变,并且没有产生任何语义的改变。
下面是新查询语句的写法,差别就在于第三和第十四行。
Nested Loop (cost=168.22..2116.29 rows=148 width=362) (actual time=22.134..256.531 rows=10858 loops=1)
Buffers: shared hit=44967
- Index Scan using x_pkey on x (cost=0.00..8.27 rows=1 width=37) (actual time=0.071..0.073 rows=1 loops=1)
Index Cond: (id = 1)
Buffers: shared hit=4
- Nested Loop (cost=168.22..2106.54 rows=148 width=329) (actual time=22.060..242.406 rows=10858 loops=1)
Buffers: shared hit=44963
- HashAggregate (cost=168.22..170.22 rows=200 width=4) (actual time=21.529..32.820 rows=11215 loops=1)
- Values Scan on *VALUES* (cost=0.00..140.19 rows=11215 width=4) (actual time=0.005..9.527 rows=11215 loops=1)
- Index Scan using context_pkey on context c (cost=0.00..9.67 rows=1 width=329) (actual time=0.015..0.016 rows=1 loops=11215)
Index Cond: (c.key = *VALUES* .column1)
Filter: ((c.tags @ {blah} ::text[]) AND (c.x_id = 1))
Buffers: shared hit=44963
Total runtime: 263.639 ms
查询时间从 22000ms 下降到 200ms,仅仅一行代码的改变效率就提高了 100 倍。
在生产中使用的新查询
即将发布的一段代码:
它使数据库看起来更美观轻松.
“怎么提升 PostgreSQL 性能”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!