更好的数据库“始终保留每个 ID 的 5 个最新条目并删除旧条目”?

lat*_*ell 6 postgresql database-design greatest-n-per-group

我有一个带有history表的 PostgreSQL 数据库,我在其中存储一个fooID(不是主键序列而是一个文本)、一个属性target和当前时间戳,每当targetfooID更改时:

CREATE TABLE history (
  fooId       text not null,
  target      text not null,
  updated_at  timestamp not null default default now()
);
Run Code Online (Sandbox Code Playgroud)

该表中有几百万个条目,每天有几千次更改。每天我都会扫描表格并保留最后 5 个条目fooId并删除所有旧条目。

DELETE ... WHERE id in ... rank() over (partiton by nr order by created_at...查询是我的问题,即工程。只是需要很长时间。

我的问题是:标准 PostgreSQL 表是解决此问题的最佳方法吗?

PostgreSQL 表分区在这里有帮助吗?我知道分区用于轻松丢弃超过 X 天的大块数据,但分区按fooId在我的情况下分区似乎会创建太多分区。

是否有NoSQL数据库会更快,因为它们存储的数据不同?

是否有其他 PostgreSQL 技巧可以帮助我以不同的方式存储数据并针对日常清除的用例进行更优化(SELECT 不是问题,它很少被查询)?

每周 1 小时的独占锁定是可以接受的。大约有 100 万个不同,fooIDs因此每个fooID.

小智 3

我能想到的一个选择是在插入新行后立即删除最旧的行。这只需要非常快速的查找,最多限制 6 行,而不是一次遍历所有行。

为了有效地做到这一点,你需要在表上有一个唯一的键:

create table history 
(
  id          serial primary key, -- to make a lookup on a single row efficient
  fooid       text not null,
  target      text not null,
  updated_at  timestamp not null default now()
);
-- to make finding the oldest row for one fooid efficient
create index on history(fooid, updated_at);
Run Code Online (Sandbox Code Playgroud)

然后创建一个触发器,仅保留 a 的最近 5 行fooid

create or replace function remove_last() 
  returns trigger
as
$$
begin
  with ranked as (
    select id, row_number() over (partition by fooid order by updated_at) as rn
    from history
    where id <> new.id
      and fooid = new.fooid 
  )
  delete from history
  where id in (select id 
               from ranked 
               where rn >= 5);
  return new;
end;
$$
language plpgsql;

create trigger remove_last_trigger 
  after insert on history
  for each row execute procedure remove_last();
Run Code Online (Sandbox Code Playgroud)

在我的笔记本电脑上,一个包含 500 万行(100 万个不同的 fooid 值)的简单测试设置显示触发开销不到一毫秒:

insert into history (fooid, target) values ('1', 'new stuff');
Run Code Online (Sandbox Code Playgroud)
查询计划                                                                                          
-------------------------------------------------- --------------------------------------------------
插入 stuff.history (成本=0.00..0.01行=1宽度=76)(实际时间=0.062..0.062行=0循环=1)
  缓冲区:共享命中=8                                                                             
  ->结果(成本=0.00..0.01行=1宽度=76)(实际时间=0.017..0.017行=1循环=1)           
        输出:nextval('history_id_seq'::regclass), '1'::text, 'new stuff'::text, now()            
        缓冲区:共享命中=1                                                                       
规划时间:0.024 ms                                                                             
触发器remove_last_trigger:时间=0.438调用=1
执行时间:0.524毫秒

我不知道您的系统中有多少开销以及您是否可以接受,但是“每天进行数千次更改”听起来不像是一个非常繁忙的系统。