我有大量的表" INSERT一次",然后是只读的表。 ie:在记录的初始INSERT
之后,永远不会有任何UPDATE
或DELETE
语句。因此,磁盘上表的数据碎片很少。
我现在考虑在每个表中添加needs_action
布尔字段。此字段只会更新一次,并且会在慢速/定期的基础上完成。作为MVCC的结果,当VACUUM
在UPDATE
之后出现(时间更慢)时,表格变得非常碎片化,因为它清除了最初插入的元组,随后它们被新的回填插入。
简而言之:添加此单曲"始终更新一次"字段将表格从设计上最小化分割,到设计高度分散。
是否有一些有效实现此单needs_action
记录标记的方法可以避免产生的表碎片?
< 现在有一些背景/补充信息...... >
到目前为止考虑的一些选项......
冒着使这个问题变得庞大(因此被忽视?)的风险,下面是目前已经考虑过的一些选项:
只需将列添加到每个表格中,执行UPDATE
并且不要担心产生的碎片,直到它确实被证明是一个问题。
创建一个独立的跟踪表(对于每个表),只包含A)来自主表的PK,以及B)needs_action
标志。使用主表中的AFTER INSERT
触发器在跟踪表中创建记录
强制needs_action
字段为HOT更新以避免元组复制
WHERE needs_action = TRUE
上需要一个索引似乎排除了这个选项,但也许有另一种方法可以快速找到它们?使用表填充因子(50?)为不可避免的UPDATE
UPDATE
的空间,因此将其保留在同一页面中UPDATE
,这似乎会使表包装分数永远保持在50%并占用两倍的存储空间?我还没有100%理解这个选项......还在学习。在主表记录中查找特殊/魔法字段/位,可以在没有MVCC影响的情况下进行翻转。
WHERE needs_action = TRUE
部分索引的其他快速查找机制)在postgres之外存储needs_action
(例如:作为<table_name>:needs_copying
redis中的PK列表)以避免因mvcc而导致的碎片。
redis_fdw
触发器中使用AFTER INSERT
(或其他一些fdw?)可以保持原子状态?我需要了解更多有关fdw功能的信息......看起来我能找到的所有fdw都是只读的。运行带有背景碎片整理/压缩的精美视图,如this fantastic article
中所述只需跟踪需要在postgres表中复制的ID / PK
DELETE
记录RPUSH
离线redis列表(但肯定是 ACID)还有其他选择吗?
有关推动此问题的具体用例的更多信息......
我对如何避免这种碎片的一般情况感兴趣,但在目前的用例中还有更多内容:
SELECT
查询将跨越广泛的表格范围(不仅仅是最近的数据),范围从单个结果记录到100k + 答案 0 :(得分:1)
您可以尝试使用继承的表。此方法不直接支持表的PK,但可以通过触发器解决。
CREATE TABLE data_parent (a int8, updated bool);
CREATE TABLE data_inserted (CHECK (NOT updated)) INHERITS (data_parent);
CREATE TABLE data_updated (CHECK (updated)) INHERITS (data_parent);
CREATE FUNCTION d_insert () RETURNS TRIGGER AS $$
BEGIN
NEW.updated = false;
INSERT INTO data_inserted VALUES (NEW.*);
RETURN NULL;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER d_insert BEFORE INSERT ON data_parent FOR EACH ROW EXECUTE PROCEDURE d_insert();
CREATE FUNCTION d_update () RETURNS TRIGGER AS $$
BEGIN
NEW.updated = true;
INSERT INTO data_updated VALUES (NEW.*);
DELETE FROM data_inserted WHERE (data_inserted.*) IN (OLD);
RETURN NULL;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER d_update BEFORE INSERT ON data_inserted FOR EACH ROW EXECUTE PROCEDURE d_update();
-- GRANT on d_insert to regular user
-- REVOKE insert / update to regular user on data_inserted/updated
INSERT INTO data_parent (a) VALUES (1);
SELECT * FROM ONLY data_parent;
SELECT * FROM ONLY data_inserted;
SELECT * FROM ONLY data_updated;
INSERT 0 0
a | updated
---+---------
(0 rows)
a | updated
---+---------
1 | f
(1 row)
a | updated
---+---------
(0 rows)
UPDATE data_parent SET updated = true;
SELECT * FROM ONLY data_parent;
SELECT * FROM ONLY data_inserted;
SELECT * FROM ONLY data_updated;
UPDATE 0
a | updated
---+---------
(0 rows)
a | updated
---+---------
(0 rows)
a | updated
---+---------
1 | t
(1 row)
答案 1 :(得分:1)
我只想将fillfactor降低到默认值100以下。
根据行的大小,使用80或90之类的值,这样一些新行仍然适合该块。更新后,旧行可以被“修剪”并通过下一个事务进行碎片整理,以便可以重用该空间。
50的值似乎太低了。没错,这会为同时更新的块中的所有行留出空间,但这不是你的用例,对吗?