在新硬件上将Postgres从 8.3.8 升级到 9.4.1 。一组有代表性的查询表明,新系统的性能范围从1倍到3倍不等。但是,我们的高负荷区域之一总是较慢。
EXPLAIN
输出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
…btree (pid);
…btree (lower("substring"(value, 1, 1000)) text_pattern_ops, fid);
…btree (lower("substring"(value, 1, 1000)), fid);
改变以下范围并没有改善这种情况......
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%
的类似结果。
在我们放弃这几年之前,我想知道我是否还可以采取更多措施来优化这些重要案例。
SELECT p.pid
FROM prime p
INNER JOIN data d ON p.pid = d.pid
WHERE LOWER(substring(d.value,1,1000)) LIKE '%something%'
AND p.tid IN (1,2,3)
AND p.deleted = FALSE
AND p.ppid IN (28226, 53915, 83421, 82118397, 95513866)
简化和消毒。
\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。
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
使用enable_bitmapscan = off @ 362 ms(产生与预期相同的计划)
早些时候我也尝试过enable_indexscan = off @ 491 ms(当然触发了不同的计划)
是的,pg 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
SELECT pid
FROM data d
JOIN prime p USING (pid)
WHERE LOWER(substring(d.value,1,1000)) LIKE '%something%'
AND p.ppid IN (28226, 53915, 83421, 82118397, 95513866)
AND p.tid IN (1, 2, 3)
AND p.deleted = FALSE;
lower(substring(d.value,1,1000)) = 355 ms
lower(left(d.value,1000)) = 343 ms (~3% faster over multiple tests, I'll take that!)
为了处理未锚定的情况,我们使用运算符类" text_pattern_ops"来获得第二个索引。
我们之前已经评估了多列GIN指数,但没有实现预期的收益。复杂,因为A)满足acl,状态和类似的多个标准,B)需要点击"确切的短语"这需要结果短语重新检查。我对长期使用全文方法持乐观态度,我们迄今为止所尝试的食谱并不比它更快或更稳定。 老派BTREE方法;爱好。
GIN试验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)
GIN试验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
我们已经有关于" ppid,删除,tid"的质量指数,对不起,原来并不清楚。
答案 0 :(得分:2)
错误查询计划的最常见原因是统计数据或成本设置不能很好地代表现实:
如果随机访问实际上与顺序访问一样快,则random_page_cost = 1.0
的设置才有意义,只有当您的数据库完全驻留在RAM中时才会这样。具有80M和750M行的表的数据库可能太大了。如果我的假设是正确的,那么提高成本设置可能会解决问题。至少尝试 1.1
,可能更多。运行测试以找到设置中的最佳位置。
通常我会先跑:
SET enable_bitmapscan = off;
在第9.4页的当前会话中再次测试。棘手的部分是您的查询可能需要两者:索引和位图索引扫描。我需要查看查询。
random_page_cost
的极低设置有利于通过位图索引扫描进行索引扫描。如果该成本设置具有误导性,则会得到较差的查询计划。
关于dba.SE的相关问题的答案有更多解释:
您的表格设计已经简化,但通常最好不要在整数列之间放置一个boole column *,因为这样会浪费磁盘空间来填充。更好:
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;
left(d.value,1000)
比substring(d.value,1,1000)
更短更快(需要pg 9.1 +)。
text_pattern_ops
索引仅对与LIKE
匹配的左锚定模式有用。你的表达没有锚定。 (我已经看到你也在使用锚定模式。)为此,使用由附加模块pg_trgm
提供的三元组GIN索引 非常 < / strong>使用大表更快,特别是第9.4页(改进的GIN索引)。
要在下面的GIN索引中包含integer
列pid
,请先安装附加模块btree_gin
,它提供必要的GIN运算符类。每个数据库运行 :
CREATE EXTENSION btree_gin;
做出一些假设,这对你的查询来说是完美的。 data
上的多列trigram 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;
在这里谈论数量级。
答案 1 :(得分:0)
首先从&#34;数据表&#34;生成最大可能结果集的方法略有不同通过Common Table Expression(CTE),然后返回到素数以通过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 ms
我将进一步评估CTE的意外影响。