我在1.83 GHz Intel Core Duo Mac Mini上运行PostgreSQL 8.3,内存为1GB,Mac OS X 10.5.8。我在PostgreSQL数据库中存储了一个巨大的图形。它由160万个节点和3000万个边缘组成。我的数据库架构如下:
CREATE TABLE nodes (id INTEGER PRIMARY KEY,title VARCHAR(256));
CREATE TABLE edges (id INTEGER,link INTEGER,PRIMARY KEY (id,link));
CREATE INDEX id_idx ON edges (id);
CREATE INDEX link_idx ON edges (link);
表格边缘的数据类似于
id link
1 234
1 88865
1 6
2 365
2 12
...
因此它为每个节点存储id x到id y的传出链接。
搜索所有外发链接的时间还可以:
=# explain analyze select link from edges where id=4620;
QUERY PLAN
---------------------------------------------------------------------------------
Index Scan using id_idx on edges (cost=0.00..101.61 rows=3067 width=4) (actual time=135.507..157.982 rows=1052 loops=1)
Index Cond: (id = 4620)
Total runtime: 158.348 ms
(3 rows)
但是,如果我搜索到某个节点的传入链接,那么数据库的速度会慢100倍(尽管传入的链接数量只比传出链接的数量高5到10倍):
=# explain analyze select id from edges where link=4620;
QUERY PLAN
----------------------------------------------------------------------------------
Bitmap Heap Scan on edges (cost=846.31..100697.48 rows=51016 width=4) (actual time=322.584..48983.478 rows=26887 loops=1)
Recheck Cond: (link = 4620)
-> Bitmap Index Scan on link_idx (cost=0.00..833.56 rows=51016 width=0) (actual time=298.132..298.132 rows=26887 loops=1)
Index Cond: (link = 4620)
Total runtime: 49001.936 ms
(5 rows)
我试图强制Postgres不要通过
使用位图扫描=# set enable_bitmapscan = false;
但是传入链接的查询速度没有提高:
=# explain analyze select id from edges where link=1588;
QUERY PLAN
-------------------------------------------------------------------------------------------
Index Scan using link_idx on edges (cost=0.00..4467.63 rows=1143 width=4) (actual time=110.302..51275.822 rows=43629 loops=1)
Index Cond: (link = 1588)
Total runtime: 51300.041 ms
(3 rows)
我还将共享缓冲区从24MB增加到512MB,但它没有帮助。所以我想知道为什么我对传出和传入链接的查询显示出这样的不对称行为?我选择的索引有问题吗?或者我应该更好地创建第三个表,其中包含id为x的节点的所有传入链接?但这将浪费磁盘空间。但是,由于我是SQL数据库的新手,也许我在这里缺少一些基本的东西?
答案 0 :(得分:5)
我想这是因为磁盘上的同一密钥记录的“密度”。 我认为具有相同id的记录存储在密集(即,少量块)中,并且具有相同链接的记录存储在稀疏(即,分布到大量块)中。 如果您按照id的顺序插入了记录,则可能会发生这种情况。
假设: 1.有10,000条记录, 2.它们按照(id,link)=(1,1),(1,2),...,(1,100),(2,1)......等顺序存储。 3. 50个记录可以存储在一个区块中。
在上面的假设中,块#1~#3由记录(1,1)〜(1,50),(1,51)〜(1,100)和(2,1)〜(2)组成。 ,50)。
当您SELECT * FROM edges WHERE id=1
时,只需加载和扫描2个区块(#1,#2)。
另一方面,SELECT * FROM edges WHERE link=1
需要50个块(#1,#3,#5,...),即使行数相同。
答案 1 :(得分:3)
我认为habe是对的。
填写表格后,您可以使用cluster link_idx on edges; analyze edges
进行检查。现在第二个查询应该很快,首先应该很慢。
要快速查询这两个查询,您必须使用第二个表进行非规范化,如您所提议的那样。只需记住在加载数据后对第二个表进行聚类和分析,以便链接到节点的所有egdes将进行物理分组。
如果您不会一直查询这个并且您不想存储和备份第二个表,那么您可以在查询之前临时创建它:
create temporary table egdes_backwards
as select link, id from edges order by link, id;
create index edges_backwards_link_idx on edges_backwards(link);
您不必对此临时表进行集群,因为它将在创建时进行物理排序。它对一个查询没有意义,但可以连续帮助多个查询。
答案 2 :(得分:3)
如果您需要良好的性能并且可以在没有外键约束的情况下处理(或使用触发器手动实现它们),请尝试intarray和intagg扩展模块。而不是边表在节点表上有一个outedges integer[]
列。这将增加约140MB的表,所以整个事情仍然可能适合内存。对于反向查找,要么在outedges列上创建一个GIN索引(另外280MB),要么只添加一个inedges列。
Postgresql具有相当高的行开销,因此朴素边表将导致仅用于表的1G空间,而对于索引则为1.5。给定数据集大小,如果使用整数数组来存储关系,则很有可能将其中的大部分放在缓存中。这将使任何查找都非常快。我看到大约0.08ms的查找时间,以获得给定节点的任一方向的边缘。即使你不能将它全部放在内存中,你仍然会在内存中占很大比例,而且缓存区域也会更好。
答案 3 :(得分:1)
您的问题似乎与磁盘相关。 Postgres必须读取索引匹配的元组才能查看该行是否可见(这不能从索引中完成,因为它不包含必要的信息)。
如果您有大量已删除的行和/或更新的行,VACUUM ANALYZE(或简称为ANALYZE)将有所帮助。首先运行它,看看你是否有任何改进。
CLUSTER也可能有所帮助。根据您的示例,我会说使用link_idx作为集群密钥。 “CLUSTER边缘使用link_idx”。它可能会降低您的id查询的性能(您的id查询可能很快,因为它们已经在磁盘上排序)。记得在CLUSTER之后运行ANALYZE。
接下来的步骤包括微调内存参数,添加更多内存或添加更快的磁盘子系统。
答案 4 :(得分:-1)
你有没有试过在www.neo4j.org这样做?这在图数据库中几乎是微不足道的,并且应该在ms范围内为您的用例提供性能。