如何降低 Postgres 中 set_bit 的成本?
Posted
技术标签:
【中文标题】如何降低 Postgres 中 set_bit 的成本?【英文标题】:How do I reduce the cost of set_bit in Postgres? 【发布时间】:2018-09-22 18:46:54 【问题描述】:我正在运行 PostgreSQL 9.6,并且正在对以下表结构进行实验:
CREATE TABLE my_bit_varying_test (
id SERIAL PRIMARY KEY,
mr_bit_varying BIT VARYING
);
为了了解如果我同时在 100,000 位数据上重置位,我可以期待多少性能,我编写了一个小的 PL/pgSQL 块,如下所示:
DO $$
DECLARE
t BIT VARYING(100000) := B'0';
idd INT;
BEGIN
FOR I IN 1..100000
LOOP
IF I % 2 = 0 THEN
t := t || B'1';
ELSE
t := t || B'0';
end if;
END LOOP ;
INSERT INTO my_bit_varying_test (mr_bit_varying) VALUES (t) RETURNING id INTO idd;
UPDATE my_bit_varying_test SET mr_bit_varying = set_bit(mr_bit_varying, 100, 1) WHERE id = idd;
UPDATE my_bit_varying_test SET mr_bit_varying = set_bit(mr_bit_varying, 99, 1) WHERE id = idd;
UPDATE my_bit_varying_test SET mr_bit_varying = set_bit(mr_bit_varying, 34587, 1) WHERE id = idd;
UPDATE my_bit_varying_test SET mr_bit_varying = set_bit(mr_bit_varying, 1, 1) WHERE id = idd;
FOR I IN 1..100000
LOOP
IF I % 2 = 0 THEN
UPDATE my_bit_varying_test
SET mr_bit_varying = set_bit(mr_bit_varying, I, 1)
WHERE id = idd;
ELSE
UPDATE my_bit_varying_test
SET mr_bit_varying = set_bit(mr_bit_varying, I, 0)
WHERE id = idd;
end if;
END LOOP ;
END
$$;
但是,当我运行 PL/pgSQL 时,它需要几分钟才能完成,我已将其范围缩小到更新表的 for 循环。由于BIT VARYING
列上的压缩,它运行缓慢吗?有什么方法可以提高性能吗?
编辑 这是一个模拟的简化示例。这实际上是因为我有数以万计的作业正在运行,每个作业都需要报告它们的状态,每隔几秒钟更新一次。
现在,我可以对其进行规范化,并创建一个“运行状态”表来保存所有工作人员及其状态,但这将涉及存储数万行。所以,我的想法是我可以使用位图来存储客户端和状态,并且掩码会按照顺序告诉我哪些已运行,哪些已完成。前面的位将用作“错误位”,因为我不需要确切知道哪个客户端失败了,只知道存在失败。
例如,您可能有 5 名工人从事一份工作。如果它们都完成了,那么状态将为“01111”,表示所有作业都已完成,并且没有一个失败。如果 2 号工人失败,则状态为“111110”,表示出现错误,除最后一名工人外,所有工人都已完成。
因此,您可以将其视为处理大量作业状态的一种人为方式。当然我有其他想法,但即使我走那条路,对于未来,我仍然想知道如何快速更新变量,因为我很好奇。
【问题讨论】:
循环更新在性能方面几乎从来都不是一个好主意。我也不明白你为什么一遍又一遍地更新同一行 这是一个模拟的最小示例。生产中实际发生的情况是,将有数千个线程同时更新该可变位行。如果完成这样一个简单的循环需要几分钟,那么我什至不想考虑它对数千个线程的作用。 一个简单循环可能会杀死任何软件。您应该知道循环创建了 100000 行并删除了其中的 99999。你是说你将同时拥有 100000 个客户吗? 我是说我可能有 10 或 15 个客户,但他们需要更新该行数千次。为了完成这项工作,每个客户端的性能必须比我看到的更新性能好得多。 让我编辑问题,看看我是否可以提供更多上下文。 【参考方案1】:如果您的问题确实是 TOAST 压缩,您可以简单地为该表禁用它:
ALTER TABLE my_bit_varying_test SET STORAGE EXTERNAL;
【讨论】:
【参考方案2】:您可以尝试使用基于集合的方法来替换第二个循环。基于集合的方法通常比循环更胖。使用generate_series()
获取索引。
UPDATE my_bit_varying_test
SET mr_bit_varying = set_bit(mr_bit_varying, gs.i, abs(gs.i % 2 - 1))
FROM generate_series(1, 100000) gs(i)
WHERE id = idd;
如果您还没有索引,也可以考虑在 my_bit_varying_test (id)
上创建索引。
【讨论】:
my_bit_varying_test 上的索引是否真的有助于更新性能?请参阅上面的评论线程。会有很多线程试图更新同一行。 @Brad:除非表很小,否则索引可能有助于检索需要更快更新的行,是的。当然,如果有帮助,您需要亲自尝试一下。以上是关于如何降低 Postgres 中 set_bit 的成本?的主要内容,如果未能解决你的问题,请参考以下文章
Docker - 如何在postgres容器中运行psql命令?