Postgres-更新10版以上的单条记录缓慢

时间:2019-04-03 04:25:12

标签: sql postgresql

我们有一个非常简单的表,DDL如下:

CREATE TABLE public.next_id (
  id varchar(255) NOT NULL,
  next_value int8 NOT NULL,
  CONSTRAINT next_id_pk PRIMARY KEY (id)
);

该表只有约120行,除主键外没有任何索引。

当我在DBeaver中对运行PostgreSQL 10.5或11.2的两台Linux服务器之一执行以下UPDATE查询时,大约需要50毫秒:

update NEXT_ID set next_value=next_value+1 where id='Session';

但是,如果我将DBeaver指向运行PostgreSQL 9.5.3的服务器,则平均大约需要3毫秒。

现在,如果我按如下方式创建一个FOR循环:

do $$
begin
    for i in 1..100000 loop
        update NEXT_ID set next_value=next_value+1 where id='Session';
    end loop;
end;
$$;

在所有机器上花费大约相同的时间(〜1.5s)。换句话说,错误的余量可能等于一条记录更新带来的额外延迟。

感觉语句周围的事务涉及某种开销。

如何获取有关PostgreSQL花时间的更多信息?

我已经尝试在“较慢”的服务器上对上述单个记录UPDATE进行EXPLAIN ANALYSE,并且得到以下信息:

Update on next_id  (cost=0.00..2.58 rows=1 width=36) (actual time=0.057..0.057 rows=0 loops=1)
  ->  Seq Scan on next_id  (cost=0.00..2.58 rows=1 width=36) (actual time=0.043..0.044 rows=1 loops=1)
        Filter: ((id)::text = 'Session'::text)
        Rows Removed by Filter: 125
Planning Time: 0.066 ms
Execution Time: 0.080 ms

这似乎表明该查询实际上仅需要几毫秒即可计划和执行。那么剩下的时间在哪里?

所有涉及的服务器都使用相同的数据库,并且已经在所有服务器上复制了数据库。

顺便说一句,我对让我说对主键使用VARCHAR(255)的主意不感兴趣,因为在所有服务器上都一样,这不是问题的重点


更新:我们已经注意到,速度较慢的Linux计算机与速度较快的Linux计算机之间的主要区别是文件系统。我的机器将BTRFS用于Postgres所在的文件系统,而速度更快的机器正在使用XFS。

在各种文件系统上快速搜索Postgres的人发现,有些人说在BTRFS上使用Postgres不好(不要越过信息流!)。

我们将尝试重新格式化我的计算机以使用XFS,以查看是否有区别。

同时,我仍然有兴趣将赏金给予任何可以告诉我如何记录多余时间的人。


UPDATE2:按照尼克·巴恩斯(Nick Barnes)在评论中的建议,我显式地运行了一系列BEGIN; UPDATE ...; COMMIT;语句,日志给出了以下输出:

LOG:  duration: 0.025 ms  parse <unnamed>: begin
LOG:  duration: 0.014 ms  bind <unnamed>: begin
LOG:  duration: 0.003 ms  execute <unnamed>: begin
LOG:  duration: 0.045 ms  parse <unnamed>:  update NEXT_ID set next_value=next_value+1 where id='Session'
LOG:  duration: 0.055 ms  bind <unnamed>:  update NEXT_ID set next_value=next_value+1 where id='Session'
LOG:  duration: 0.059 ms  execute <unnamed>:  update NEXT_ID set next_value=next_value+1 where id='Session'
LOG:  duration: 0.004 ms  parse <unnamed>:  commit
LOG:  duration: 0.003 ms  bind <unnamed>:  commit
LOG:  duration: 50.237 ms  execute <unnamed>:  commit

是的,尼克,开销肯定在COMMIT中。但是它在做什么?有什么方法可以在日志中获取有关在50毫秒内它正在做什么的更详细的信息?

1 个答案:

答案 0 :(得分:3)

UPDATE本身很便宜;在提交事务之前,您的新数据无需在崩溃后幸存,因此仅对内存中的缓冲区(服务器空闲时将其刷新到磁盘)进行更改。

直到您提交事务后,服务器才需要为您提供持久性保证。 Postgres使用write-ahead log(WAL)处理碰撞安全,并且当您COMMIT时,您正在等待WAL同步写入磁盘。

这使得提交延迟高度依赖于文件系统和底层硬件,并且如果您的PG10实例正在等待BTRFS完成写时复制或其他操作,那肯定可以解释您所造成的差异看到。

要确认这是原因,可以通过禁用fsync来跳过同步磁盘写入操作(尽管这样做会使您面临数据损坏的风险,因此请务必在一次性实例上对其进行测试)。一个比较安全和侵入性较小的选项是在事务开始时使用SET LOCAL synchronous_commit = off,如果您没有运行同步复制,则应该具有相同的效果。