-1

我想使用 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 中有一个具有以下属性的表:

  • 一列有一个整数
  • 允许重复
  • 列上高效的 ORDER BY+LIMIT
  • INSERT 不应在该表或任何类型的唯一索引中执行任何查询。INSERT 应该只为该表的主文件/主 btree 找到最佳页面,并将行插入到其他行之间,按 ID 排序。
  • INSERT 将批量发生,并且不能失败,预计磁盘已满等。
  • 此表不应有额外的 btree 文件,因此没有二级索引
  • 行不应占用太多空间,例如没有 OID

我想不出解决所有这些问题的解决方案。

我唯一的解决方案是在最后一个要点上妥协:添加一个覆盖整数的主键和一个虚拟列,如 OID、时间戳或序列。

另一种解决方案要么使用假设的 INSERT IF NOT EXISTS,要么使用嵌套事务或带有 WHERE 的特殊 INSERT。所有这些解决方案都会在插入时添加对 btree 的查询。它们也可能导致死锁。

(也在这里发布:https ://dba.stackexchange.com/q/45126/7788 )

4

2 回答 2

3

你说

我系统的许多部分会将文档标记为脏,因此会将要处理的 ID 插入到该表中。因此必须可以重复。

具有相同 ID 的 5 行与具有相同 ID 的 1 或 10 行具有相同的含义:它们意味着具有该 ID 的文档是脏的。

你不需要重复。如果此表的唯一目的是识别脏文档,则包含文档 ID 号的单行就足够了。没有令人信服的理由允许重复。

如果您需要跟踪哪个进程插入了该行,或者在插入行时对行进行排序,则每个 ID 号的单行是不够的但首先单列是不够的。所以我确信主键约束或唯一约束对你来说很好。

其他进程必须忽略重复的键错误,但这很简单。无论如何,这些进程都必须捕获错误——除了重复键之外,还有很多事情会阻止插入语句成功。


允许重复的实现。. .

create table dirty_documents (
  document_id integer not null
);

create index on dirty_documents (document_id);

在该表中插入 100k 个 ID 号进行测试。这必然需要更新索引。(杜。)包括一堆重复。

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; 

花费了 0.054 毫秒。这大约是使用允许重复的表所用时间的一半。

delete from dirty_documents
where document_id = 1;

还花了 0.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 分钟。(我使用了秒表。)如果您想知道性能如何,请构建表格并编写测试。

于 2013-06-23T21:19:16.300 回答
2

在字里行间阅读,我认为您正在尝试实现工作排队系统。

停止。现在。

工作排队很难。关系 DBMS 中的工作排队非常困难。人们提出的大多数“聪明”解决方案最终都会在他们没有意识到的情况下序列化锁上的工作,或者他们在并发操作中存在令人讨厌的错误。

使用现有的消息/任务排队系统。ZeroMQ、RabbitMQ、PGQ 等等等等等等等等。有很多可供选择,它们具有(a)工作和(b)高效的显着优势。您很可能需要运行外部辅助进程或服务器,但关系数据库模型的限制往往使这变得必要。

您似乎正在设想的方案,尽我所能猜测,听起来它在故障处理、插入/删除竞争等方面会遇到无可救药的并发问题。真的,不要尝试自己设计,尤其是当您对潜在的并发性和性能问题没有很好的掌握时。

于 2013-06-24T02:40:22.150 回答