PostgreSQL以FIFO方式限制表行

时间:2019-03-10 09:35:54

标签: sql postgresql

我有一个表,其中包含PK ID,两个FK(两个其他ID)和一个时间戳。 我希望对该表进行限制,以便每个FK组合只能有有限的行数。 例如,如果有十多个具有相同FK的行,则应删除该时间戳中最旧的行。

当前,解决方案的想法是在插入之前触发一个触发器,该触发器应检查表中是否存在多于要插入的FK相同的行数。如果存在最旧的字段,则应删除时间戳。

有人可以帮助我实现这一点吗?

2 个答案:

答案 0 :(得分:1)

该问题的另一种解决方案是使用数组列而不是行和INSERT INTO ... ON CONFLICT UPDATE ...支持。

在线示例:https://www.db-fiddle.com/f/2y46V6EEVJLQ5cPNTDAUPy/0

结构

CREATE TABLE test_rr (
    id serial primary key,
    fk_1 integer not null,
    fk_2 integer not null,
    latest timestamptz[] not null
);

CREATE UNIQUE INDEX idx_unique_rr ON test_rr (fk_1, fk_2);

正在上传数据

INSERT INTO test_rr (fk_1, fk_2, latest)
    VALUES (1, 2, array[current_timestamp])
    ON CONFLICT (fk_1, fk_2) DO UPDATE SET latest = (array_cat(EXCLUDED.latest, test_rr.latest))[:10];

选择条目

SELECT id, fk_1, fk_2, unnest(latest) AS ts FROM test_rr WHERE fK_1 = 1 AND fk_2 = 2;

...导致:

 id  | fk_1 | fk_2 |             ts
-----+------+------+-------------------------------
 652 |    1 |    2 | 2019-03-10 13:28:57.806489+01
 652 |    1 |    2 | 2019-03-10 13:28:56.670678+01
 652 |    1 |    2 | 2019-03-10 13:28:55.470668+01
 652 |    1 |    2 | 2019-03-10 13:28:54.174111+01
 652 |    1 |    2 | 2019-03-10 13:28:52.878719+01
 652 |    1 |    2 | 2019-03-10 13:28:51.3748+01
 652 |    1 |    2 | 2019-03-10 13:28:49.886457+01
 652 |    1 |    2 | 2019-03-10 13:28:48.190317+01
 652 |    1 |    2 | 2019-03-10 13:28:46.350833+01
 652 |    1 |    2 | 2019-03-10 13:11:50.506323+01
(10 rows)

代替timestamptz[],您还可以创建自己的类型来支持更多列:

CREATE TYPE my_entry_data AS (ts timestamptz, data varchar);

CREATE TABLE test_rr (
    id serial primary key,
    fk_1 integer not null,
    fk_2 integer not null,
    latest my_entry_data[] not null
);

CREATE UNIQUE INDEX idx_unique_rr ON test_rr (fk_1, fk_2);

-- ...
INSERT INTO test_rr (fk_1, fk_2, latest)
  VALUES (1, 2, array[(current_timestamp,'L')::my_entry_data])
  ON CONFLICT (fk_1, fk_2) DO UPDATE
    SET latest = (array_cat(EXCLUDED.latest, test_rr.latest))[:10];

SELECT id, fk_1, fk_2, tmp.ts, tmp.data
FROM test_rr, unnest(latest) AS tmp -- LATERAL function call
WHERE fK_1 = 1 AND fk_2 = 2;

但是,负载测试必须显示这实际上是否比触发器或其他方法快。至少这样做的好处是,仅更新行而不插入+删除行,这样可以节省一些I / O。

答案 1 :(得分:0)

您应该使用带有触发器的单独的摘要表,该表将包含列fk1fk2count,其中包含check count<=N和{{ 1}}。

在向原始表中插入行之前,应检查(fk1, fk2)的值,如果该值已达到极限,则应首先删除最旧的行。如果您不想在应用程序中执行此操作,可以使用触发器来完成。

您必须记住:

  • 如果您在同一事务中更改fk1或fk2值或删除具有相同fk1和fk2的多行,则必须确保以指定的顺序执行此操作(例如,按id排序),否则可能会出现死锁;
  • 在同一笔交易中,您添加的count行最多不能超过N行-首先删除的行将不够;
  • 如果添加具有相同(fk1, fk2)的多行,将会导致性能下降(并行度更差)。

如果有许多(如100多个)具有相同的(fk1, fk2)的行,那么简单的触发器只会检查行数并在插入之前删除最旧的触发器,这可能会很慢。同样,当并行执行多个插入操作时,它可能允许太多行。