冷门的PostgreSQL性能优化来了:利用事务链减少与SERVER端的往返开销

文摘   2024-07-17 06:00   天津  

前言

本文是一篇PostgreSQL比较冷门的优化小技巧, 通过事务链(transaction chain)的使用,来减少与Server端交互次数,从而达到提高性能的目的。

分析

使用PostgreSQL或任何关系数据库实现业务应用程序,通常相当于执行事务序列。给定事务是否以COMMIT或ROLLBACK结束并不重要,因为在这两种情况下,下一个事务在前一个事务完成后立即启动。此外,对于大多数应用程序,前一个事务和下一个事务具有相似的特征,例如它们的隔离级别。从本质上讲,你最终得到的是一个看起来如下的模式:

START TRANSACTION;
-- workload of 1st transaction
COMMIT;
START TRANSACTION;
-- workload of 2nd transaction
ROLLBACK;
START TRANSACTION;
-- workload of 3rd transaction
COMMIT;

在上面的SQL脚本中,您可以看到三个后续事务和总共六个语句(每个事务两个),但是您可以想象更长的事务序列。事情是这样的:每个语句都需要一个单独的服务器往返通信来执行。这是没有办法的,即使三个示例事务是空的——不包含任何语句——也没有什么区别。

监控连接状态

与其相信我的话,我们可以很容易地证明服务器往返通信确实发生了。为了进行这个实验,我们需要两个连接到同一个PostgreSQL服务器(例如,两个psql会话)。第一个连接使用postgres数据库,第二个连接使用demo数据库。

好了,现在我们可以使用pg_stat_activity,这是一个属于pg_catalog模式的内置视图,在第一个连接查询PostgreSQL关于使用demo数据库的第二个连接的状态:

postgres=# SELECT state FROM pg_stat_activity WHERE datname='demo';
 state
-------
 idle
(1 row)

正如您所看到的,连接当前处于空闲状态(idle),因为它没有做任何事情。然而,如果我们在第二个连接上启动一个事务,我们可以看到该连接的状态立即变为事务中的空闲状态(idle in transaction)。

demo=# START TRANSACTION;
START TRANSACTION
postgres=# SELECT state FROM pg_stat_activity WHERE datname='demo';
        state
---------------------
 idle in transaction
(1 row)

这证明了START TRANSACTION语句导致了与服务器之间的往返通信,因为如果没有这样做,第一个连接将无法看到第二个连接的状态更改。如果我们用COMMITROLLBACK结束事务,情况也是一样的,在这种情况下,连接立即恢复到空闲状态(idle):

demo=*# COMMIT;
COMMIT
postgres=# SELECT state FROM pg_stat_activity WHERE datname='demo';
 state
-------
 idle
(1 row)

往返通信(round-trips)的开销

因此,长话短说,事务的开始和结束可能会造成很大的开销。具体来说,如果满足以下任何一个条件:

  • 到服务器的往返时间很慢。通常情况下,如果客户机和服务器之间的网络距离很远,则会出现这种情况。

  • 有许多事务的平均运行时间很短。这是因为对于较短的事务,开销占总运行时的百分比更高。

这样来看,我们如何才能减少往返开销并提高性能呢?

事务进行链化 (transaction chaining)

事务链化,隐约可以进行某种程度的优化。

SQL标准有一个内置的解决方案,由PostgreSQL实现:AND CHAIN参数。该参数可用于COMMITROLLBACK语句,其效果如下:

如果提供了AND CHAIN参数,则提交(或回滚)当前事务,并且立即启动具有相同特征(例如,ISOLATION LEVEL)的后续事务。

因此,如果我们将此应用于原始示例,我们可以将服务器往返次数基本上减少50%(从n减少到n/2+1)。

START TRANSACTION;
-- workload of 1st transaction
COMMIT AND CHAIN;
-- workload of 2nd transaction
ROLLBACK AND CHAIN;
-- workload of 3rd transaction
COMMIT;

我们可以做同样的实验来证明这和预期的一样有效。我再次使用两个连接,一个使用postgres数据库,另一个使用demo数据库。最初,demo数据库连接是空闲的,但是一旦我们启动一个新事务,它的状态就会变成事务中空闲(idle in transaction)。

demo=# START TRANSACTION;
START TRANSACTION
postgres=# SELECT state FROM pg_stat_activity WHERE datname='demo';
        state
---------------------
 idle in transaction
(1 row)

那么,如果demo连接执行COMMIT AND CHAIN语句会发生什么呢? 正如我所说,PostgreSQL会立即启动一个后续事务,所以我们不会看到状态变化。

demo=# COMMIT AND CHAIN;
COMMIT AND CHAIN
postgres=# SELECT state FROM pg_stat_activity WHERE datname='demo';
        state
---------------------
 idle in transaction
(1 row)

当我们执行ROLLBACK and CHAIN时,我们会得到完全相同的行为——没有明显的状态改变。

demo=# ROLLBACK AND CHAIN;
ROLLBACK AND CHAIN
postgres=# SELECT state FROM pg_stat_activity WHERE datname='demo';
        state
---------------------
 idle in transaction
(1 row)

最后,当我们发出一个普通的COMMITROLLBACK命令时,状态会变回初始空闲状态。

demo=# COMMIT;
COMMIT
postgres=# SELECT state FROM pg_stat_activity WHERE datname='demo';
 state
-------
 idle
(1 row)

希望你能从这篇文章里得到启发,享受到其中的乐趣。

总结:

文中的内容确实蛮有意思的。在实际的应用开发过程中,把一些频繁调用的小事务(commit, rollback),可以使用 (commit and chain,  rollback and chain) 链接起来,就能达到交互次数极大减少的目的。

如果抽象起来,

interface Connection {
    void commit(boolean chained);
    default void commit() {
        commit(false);
    }
    void rollback(boolean chained);
    default void rollbavck() {
        rollback(false);
    }
}

这个想想就蛮有意思的。性能优化无止境。配合PostgreSQL本身的特性,在应用层就可以不断地挖掘和利用那些特性,进行适当的优化。有一个说法是,应用层出问题的比例大概就是80%或以上。

参考:

Using Transaction Chaining to Reduce Server Round-Trips
https://bugfactory.io/articles/using-transaction-chaining-to-reduce-server-round-trips/

我是【Sean】,  欢迎大家长按关注并加星公众号:数据库杂记。有好资源相送,同时为你提供及时更新。已关注的朋友,发送0、1到7,都有好资源相送。

往期导读: 
1. PostgreSQL中配置单双向SSL连接详解
2. 提升PSQL使用技巧:PostgreSQL中PSQL使用技巧汇集(1)
3. 提升PSQL使用技巧:PostgreSQL中PSQL使用技巧汇集(2)
4. PostgreSQL SQL的基础使用及技巧
5. PostgreSQL开发技术基础:过程与函数

AustinDatabases
关于数据库相关的知识分享
 最新文章