在进行一些性能调整时,我在Instagram的工程团队中发现了这个帖子:
在我们的一些表中,我们需要索引很长的字符串(例如,64个字符的base64标记),并且在这些字符串上创建索引最终会复制大量数据。对于这些,Postgres的功能索引功能非常有用:
CREATE INDEX CONCURRENTLY on tokens (substr(token), 0, 8)
虽然会有多个行匹配该前缀,但Postgres会匹配这些前缀,然后过滤速度很快,结果索引是我们索引整个字符串时的大小的十分之一。
这看起来是个好主意,所以我试了一下 - 我们有很多以校验和为键的项目。
我们的结果并不好。我想知道是否有其他人有运气。
首先,博客文章看起来不对:
CREATE INDEX CONCURRENTLY on tokens (substr(token), 0, 8)
不应该......
CREATE INDEX CONCURRENTLY on tokens (substr(token, 0, 8));
我们的一个字段基于40字符哈希。所以我试过了:
CREATE INDEX __speed_idx_test_8 on foo (substr(bar, 0, 8));
查询计划程序不会使用它。
所以我试过了:
CREATE INDEX __speed_idx_test_20 on foo (substr(bar, 0, 20));
查询计划程序仍然无法使用它。
然后我尝试了:CREATE INDEX __speed_idx_test_40 on foo (substr(bar, 0, 40));
然而,计划者不会使用它。
如果我们尝试禁用seq扫描怎么办?
set enable_seqscan=false;
不。
让我们回到我们原来的索引。
CREATE INDEX __speed_idx_original on foo (bar);
set enable_seqscan = True;
这很有效。
然后我想 - 也许我需要在查询中使用一个函数才能使用函数索引。所以我尝试更改查询:
旧:
select * from foo where hash = '%s';
新
select * from foo where substr(hash,0,8) = '%s' and hash = '%s';
这很有效。
有没有人知道是否可以在不添加额外搜索条件的情况下完成这项工作?我宁愿不这样做,但要看文件大小和速度改进......哇。
如果您想知道'解释分析'输出是......
-- seq scan
Seq Scan on foo (cost=10000000000.00..10000073130.77 rows=1 width=1921) (actual time=373.785..1563.551 rows=1 loops=1)
Filter: (hash = 'eae1d1728963f107fa7d8136bcf7c72572896e1d'::bpchar)
Rows Removed by Filter: 450252
Total runtime: 1563.687 ms
-- index scan
Index Scan using __speed_idx_original on foo (cost=0.00..16.53 rows=1 width=1920) (actual time=0.060..0.061 rows=1 loops=1)
Index Cond: (hash = 'eae1d1728963f107fa7d8136bcf7c72572896e1d'::bpchar)
Total runtime: 1.501 m
-- index scan with substring function
Index Scan using __speed_idx_test_8 on foo (cost=0.00..16.37 rows=1 width=1913) (actual time=0.134..0.134 rows=0 loops=1)
Index Cond: (substr((hash)::text, 0, 8) = 'eae1d172'::text)
Filter: (hash = 'eae1d1728963f107fa7d8136bcf7c72572896e1d'::bpchar)
Total runtime: 0.216 ms
答案 0 :(得分:3)
仅在WHERE子句中使用该函数时才有效。函数签名充当查询规划器的提示,即从函数返回的标量值包含在索引中。 这仅适用于不可变函数。易失性函数(在每次调用时都不会返回相同结果的函数,如rand())无法使用此方法编制索引。