Postgres-单记录更新速度超过10版

Dun*_*ear 7 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)
);
Run Code Online (Sandbox Code Playgroud)

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

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

update NEXT_ID set next_value=next_value+1 where id='Session';
Run Code Online (Sandbox Code Playgroud)

但是,如果我将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;
$$;
Run Code Online (Sandbox Code Playgroud)

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

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

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

我尝试EXPLAIN ANALYSE在“较慢”的服务器上对上述单条记录UPDATE进行操作,我得到以下信息:

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
Run Code Online (Sandbox Code Playgroud)

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

所涉及的所有服务器都使用已在所有服务器上复制的同一数据库。

顺便说一句,我对告诉我使用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
Run Code Online (Sandbox Code Playgroud)

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

Nic*_*nes 3

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

直到您提交事务后,服务器才需要为您提供持久性保证。Postgres 使用预写日志(WAL) 来处理崩溃安全,当您执行此操作时COMMIT,您正在等待 WAL 同步写入磁盘。

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

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