在 Postgres 中向系统目录添加索引

Ser*_*gey 5 postgresql index information-schema system-tables

我遇到的情况与此处描述的情况非常相似:

我有一个 SaaS 情况,我在单个数据库中使用 1000 多个模式(每个模式包含相同的表,只是每个租户的数据不同)。我使用了模式,以便共享应用程序服务器可以为所有模式共享到单个数据库的连接。一切正常。

而且,虽然应用程序本身似乎运行良好,但一些涉及系统目录的查询非常缓慢。此外,psql的自动完成完全没用,\dt而且速度很慢。

特别是,我需要使用以下方法计算每个模式的磁盘大小:

SELECT sum(pg_total_relation_size(c.oid)) AS size
FROM   pg_namespace n
JOIN   pg_class     c ON c.relnamespace = n.oid
WHERE  n.nspname = 'abbiecarmer'
AND    c.relkind = 'r';
Run Code Online (Sandbox Code Playgroud)

这很慢。查看查询计划,我看到

Aggregate  (cost=136903.16..136903.17 rows=1 width=4) (actual time=1024.420..1024.420 rows=1 loops=1)             
  ->  Hash Join  (cost=8.28..136902.86 rows=59 width=4) (actual time=143.247..1016.749 rows=60 loops=1)           
        Hash Cond: (c.relnamespace = n.oid)                                                                       
        ->  Seq Scan on pg_class c  (cost=0.00..133645.24 rows=866333 width=8) (actual time=0.045..943.029 rows=879788 loops=1)                                                                                                    ?
              Filter: (relkind = 'r'::"char")                                                                     
              Rows Removed by Filter: 2610112                                                                     
        ->  Hash  (cost=8.27..8.27 rows=1 width=4) (actual time=0.032..0.032 rows=1 loops=1)                      
              Buckets: 1024  Batches: 1  Memory Usage: 1kB                                                        
              ->  Index Scan using pg_namespace_nspname_index on pg_namespace n  (cost=0.00..8.27 rows=1 width=4)(actual time=0.029..0.030 rows=1 loops=1)                                                                        ?
                    Index Cond: (nspname = 'abbiecarmer'::name)                                                   
Total runtime: 1024.476 ms                                                                                        
Run Code Online (Sandbox Code Playgroud)

如果我理解正确的话,这说明 90% 的查询时间都花在了顺序扫描pg_class关系上。

我停止了 postmaster,以单用户模式运行后端并添加了以下索引:

create index pg_class_relnamespace_index on pg_class(relnamespace);
REINDEX INDEX pg_class_relnamespace_index;

create index pg_class_reltablespace_index on pg_class(reltablespace);
REINDEX INDEX pg_class_reltablespace_index;
Run Code Online (Sandbox Code Playgroud)

(我也有数千个表空间)。现在查询速度提高了大约 100 倍,并且计划看起来更好:

Aggregate  (cost=846.91..846.92 rows=1 width=4) (actual time=10.609..10.610 rows=1 loops=1)                       
  ->  Nested Loop  (cost=0.00..846.61 rows=60 width=4) (actual time=0.069..0.320 rows=60 loops=1)                 
        ->  Index Scan using pg_namespace_nspname_index on pg_namespace n  (cost=0.00..8.27 rows=1 width=4) (actual time=0.023..0.024 rows=1 loops=1)                                                                              ?
              Index Cond: (nspname = 'abbiecarmer'::name)                                                        
        ->  Index Scan using pg_class_relnamespace_index on pg_class c  (cost=0.00..837.59 rows=75 width=8) (actual time=0.043..0.271 rows=60 loops=1)                                                                             ?
              Index Cond: (relnamespace = n.oid)                                                                 
              Filter: (relkind = 'r'::"char")                                                                    
              Rows Removed by Filter: 102                                                                        
Total runtime: 10.696 ms                                                                                         
Run Code Online (Sandbox Code Playgroud)

然而,在上面的帖子中,作为 Postgres 核心贡献者之一的 Tom Lane 说:

这里有很多问题,特别是您创建索引的会话不会知道它的存在(因此在这种情况下,之后可能建议在 pg_class 上重新索引)。 我仍然认为你会疯狂地在生产数据库上尝试它,但是......

我还担心在 Postgres 9.0 和 9.1 中似乎完全禁用了系统目录的修改(我使用的是 9.2) - 我想这是有原因的吗?

所以,问题是:在 Postgres 中向系统目录添加索引有什么问题,如果我(最终)在生产系统上这样做,我会疯吗?

Erw*_*ter 5

毕竟,诊断并没有离题太远。显然,目录并没有做好处理数千个模式的准备。

不幸的是,您正在尝试使用信息架构中的视图,这可能会非常缓慢。这些是涉及许多表的复杂视图,以产生完全符合标准的状态。只需查看EXPLAIN ANALYZEpgAdmin中的输出或图形表示即可获得印象。

直接使用目录表

而且,虽然项目没有任何保证,但主要版本的基本元素pg_namespacepg_class也不太可能改变。

试试这个查询。应该快开箱:

SELECT pg_total_relation_size(c.oid) AS size
FROM   pg_namespace n
JOIN   pg_class     c ON c.relnamespace = n.oid
WHERE  n.nspname = :schema_name
AND    c.relkind = 'r';
Run Code Online (Sandbox Code Playgroud)

或者也许更快一点:

SELECT pg_total_relation_size(c.oid) AS size
FROM   pg_class c 
WHERE  c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = :schema_name)
AND    c.relkind = 'r';
Run Code Online (Sandbox Code Playgroud)

-> SQLfiddle 演示(包括更新)

如果你要使用索引,就第一个pg_class分指数

CREATE INDEX pg_class_relnamespace_idx on pg_class(relnamespace)
WHERE relkind = 'r';
Run Code Online (Sandbox Code Playgroud)

更小、更快、更不容易引起问题,因为索引本身并不包含在索引中。

您在问题中的第二个索引可能是复制/粘贴工件。您没有提到表空间在起作用,您的查询或查询计划也没有显示任何与之相关的内容。

create index pg_class_reltablespace_index on pg_class(reltablespace);
Run Code Online (Sandbox Code Playgroud)

必须是:

CREATE INDEX pg_namespace_nspname_idx on pg_namespace(nspname);
Run Code Online (Sandbox Code Playgroud)

但是,我当然不会像 Tom Lane 那样声称对系统目录的了解几乎一样多。如果他说你会疯狂地在一个高效的系统中尝试这个,那么无论如何你都会疯狂地去做。

然后,汤姆再次写道:

我仍然认为你会疯狂地在生产数据库上尝试它,但是......

大胆强调我的。这告诉我,他并不完全反对,只是不愿意保证任何事情。这使它不那么疯狂。我仍然不会推荐它。