我想将PostgreSQL表用作文档的一种工作队列。每个文档都有一个ID,并存储在另一个包含大量附加列的普通表中。但是这个问题是关于为工作队列创建表。
我想为这个队列创建一个没有OID的表,只有一列:文档的ID为整数。如果此工作队列表中存在文档的ID,则表示具有该ID的文档是脏的,并且必须进行一些处理。 如果主文档表中的每个文档条目只有一个脏位,那么额外的表应避免出现VACUUM和死元组问题以及出现的事务死锁。
我系统的许多部分都会将文档标记为脏,因此会插入ID以处理该表。这些插入将在一个事务中用于许多ID。我不想使用任何类型的嵌套事务,似乎没有任何类型的INSERT IF NOT EXISTS命令。我宁愿在表格中有重复的ID。因此,该表中唯一的列必须可以重复。
处理工作队列的进程将删除所有进程ID,因此会处理重复项。 (顺便说一句:下一步还有另一个队列,所以关于竞争条件,这个想法应该是干净的,没有问题)
但我也希望按顺序处理文件:总是先处理ID较小的文件。
因此,我希望有一个索引可以帮助ID列上的LIMIT和ORDER BY,它是工作队列表中唯一的列。 理想情况下,我只有一列,这应该是主键。但主键不能有重复,所以我似乎不能这样做。
没有索引,ORDER BY和LIMIT会很慢。
我可以在该列上添加正常的二级索引。但我担心PostgreSQL会在光盘上添加第二个文件(PostgreSQL会为每个附加索引执行此操作)并对该表使用双倍数量的光盘操作。
最好的事情是什么? 添加一个随机的虚拟列(如OID),以使主键不会抱怨重复?我必须在队列表中浪费这个空间吗?
或者添加第二个索引是无害的,它会成为直接在主元组btree中的主要索引吗?
我要删除上面的所有内容并留下以下内容吗?最初的问题令人分心,并且包含太多不相关的信息。
我想在PostgreSQL中有一个包含这些属性的表:
我想不出能解决所有问题的解决方案。
我的唯一解决方案会在最后一个要点上妥协:添加一个覆盖整数的PRIMARY KEY以及一个虚拟列,如OID,时间戳或SERIAL。
另一种解决方案是使用假设的INSERT IF NOT EXISTS,或嵌套事务或带有WHERE的特殊INSERT。所有这些解决方案都会在插入时添加btree查询。 它们也可能导致死锁。
答案 0 :(得分:3)
你说
我系统的许多部分都会将文档标记为脏,因此 插入要处理到该表的ID。因此必须重复 可能的。
和
具有相同ID的5行与1或10行具有相同的含义 相同的ID:它们表示具有该ID的文档是脏的。
您不需要重复。如果此表的唯一目的是识别脏文档,则包含文档ID号的单行就足够了。没有令人信服的理由允许重复。
如果您需要跟踪插入该行的进程,或者在插入行时按顺序排序,则每个ID号的单行不就足够了,但是单个列不足以那首先。所以我确信主键约束或唯一约束对你来说很好。
其他进程必须忽略重复键错误,但这很简单。这些进程无论如何都必须捕获错误 - 除了重复键之外还有很多东西可以防止insert语句成功。
允许重复的实现。 。
create table dirty_documents (
document_id integer not null
);
create index on dirty_documents (document_id);
在该表中插入100k ID号进行测试。这必然需要更新索引。 (Duh。)包括一堆副本。
insert into dirty_documents
select generate_series(1,100000);
insert into dirty_documents
select generate_series(1, 100);
insert into dirty_documents
select generate_series(1, 50);
insert into dirty_documents
select generate_series(88000, 93245);
insert into dirty_documents
select generate_series(83000, 87245);
在我的桌面上花了不到一秒钟,这没什么特别的,它运行着三个不同的数据库服务器,两个Web服务器,并播放Rammstein CD。
选择第一个脏文档ID号进行清理。
select min(document_id)
from dirty_documents;
document_id
--
1
仅需0.136毫秒。现在让我们删除文档ID为1的每一行。
delete from dirty_documents
where document_id = 1;
花了0.272毫秒。
让我们重新开始吧。
drop table dirty_documents;
create table dirty_documents (
document_id integer primary key
);
insert into dirty_documents
select generate_series(1,100000);
花了500毫秒。让我们再次找到第一个。
select min(document_id)
from dirty_documents;
花费.054毫秒。这大约是使用允许重复的表的一半时间。
delete from dirty_documents
where document_id = 1;
也花了.054毫秒。这大约比另一张桌快50倍。
让我们重新开始,尝试一个未编制索引的表格。
drop table dirty_documents;
create table dirty_documents (
document_id integer not null
);
insert into dirty_documents
select generate_series(1,100000);
insert into dirty_documents
select generate_series(1, 100);
insert into dirty_documents
select generate_series(1, 50);
insert into dirty_documents
select generate_series(88000, 93245);
insert into dirty_documents
select generate_series(83000, 87245);
获取第一份文件。
select min(document_id)
from dirty_documents;
花了32.5毫秒。删除这些文件。 。
delete from dirty_documents
where document_id = 1;
花了12毫秒。
这一切都花了我12分钟。 (我使用了秒表。)如果你想知道将会有什么性能,可以构建表并编写测试。
答案 1 :(得分:2)
在线之间阅读,我认为你正在尝试实现工作排队系统。
停止。现在
工作排队很难。在关系DBMS中排队工作非常困难。人们提出的大多数“聪明”解决方案最终都会在没有实现锁定的情况下序列化锁定工作,或者在并发操作中存在令人讨厌的错误。
使用现有的消息/任务排队系统。 ZeroMQ,RabbitMQ,PGQ等等有很多可供选择,它们具有(a)工作和(b)高效的显着优点。您很可能需要运行外部帮助程序进程或服务器,但关系数据库模型的局限性往往需要这样做。
你似乎想象的方案,就我所能想到的最好,听起来就像在故障处理,插入/删除比赛等方面会遇到无望的并发问题。真的,做不是< / em>尝试自己设计,特别是当你没有很好地掌握底层的并发性和性能问题时。