从客户端服务端两方面分析PostgreSQL的SQL执行时间

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
简介: 一般我们谈SQL执行时间都有意无意地把它认为是服务端执行SQL的时间。但是,有时候我们更关心从客户端看到的SQL执行总时间。比如客户在和其它数据库做性能对比的时候。 那么这个SQL执行总时间是如何构成的呢?这要分两种情况说明。

一般我们谈SQL执行时间都有意无意地把它认为是服务端执行SQL的时间。但是,有时候我们更关心从客户端看到的SQL执行总时间。比如客户在和其它数据库做性能对比的时候。

那么这个SQL执行总时间是如何构成的呢?这要分两种情况说明。

1. 无结果集返回或返回小结果集的SQL

    SQL执行总时间 = 客户端驱动处理时间 + 数据库执行SQL时间 + 通信时间

对于慢SQL,数据库执行SQL的时间占了大头,其他两项可以忽略不计。这种情况的优化也就是通常的SQL调优。

对于快SQL,并且客户端和服务端不是同一台机器的情况下,通信时间往往占了大头,其他两项可以忽略不计。而且执行这样的SQL时驱动(pgjdbc,Npgsql,libpq)和数据库间只需要发生一次通信,所以这个通信时间实际上等于两台机器上的通信延迟,也即是从客户端往服务端ping得到的延迟。普通的局域网里,通信延迟在1ms左右,对于异地系统,这个延迟可能是几十毫秒甚至更多。

对于通信延迟占了大头的情况,有一种优化措施,就是把多个SQL用";"拼到一起发给服务端。但是PostgreSQL的扩展查询协议是不支持多语句的,所以在pgjdbc(使用扩展查询协议)里,会把应用传入的多语句再拆开,依次发给服务端。
如果使用的是Npgsql驱动就可以使用这招了,下面是例子

测试程序:

点击(此处)折叠或打开

  1. DbCommand cmd = con.CreateCommand();
  2. cmd.CommandText = "select 1;select 2";
  3. DbDataReader reader = cmd.ExecuteReader();
  4. if(reader.Read())
  5. {
  6.    System.Console.WriteLine(reader.GetValue(0));
  7. }

  8. if (reader.NextResult() && reader.Read())
  9. {
  10.    System.Console.WriteLine(reader.GetValue(0));
  11. }
  12. reader.Close();

执行结果:
  1. 1
  2. 2

抓包结果:
抓包结果表明,2个SELECT只发生了一次通信。

点击(此处)折叠或打开

  1. [root@node1 ~]# tcpdump -X -s 512 port 40382
  2. ...
  3. 10:34:44.476552 IP 10.167.217.175.56944 > node1.mydm.com.40382: Flags [P.], seq 1028:1053, ack 2171, win 256, length 25
  4. 0x0000: 4500 0041 0977 4000 7e06 3bac 0aa7 d9af E..A.w@.~.;.....
  5. 0x0010: c1a0 119d de70 9dbe 2912 5b2e 0d8b fa0d .....p..).[.....
  6. 0x0020: 5018 0100 a4fc 0000 5100 0000 1873 656c P.......Q....sel
  7. 0x0030: 6563 7420 313b 0d0a 7365 6c65 6374 2032 ect.1;..select.2
  8. 0x0040: 00 .
  9. 10:34:44.476724 IP node1.mydm.com.40382 > 10.167.217.175.56944: Flags [P.], seq 2171:2297, ack 1053, win 59, length 126
  10. 0x0000: 4500 00a6 bae8 4000 4006 c7d5 c1a0 119d E.....@.@.......
  11. 0x0010: 0aa7 d9af 9dbe de70 0d8b fa0d 2912 5b47 .......p....).[G
  12. 0x0020: 5018 003b b82c 0000 5400 0000 2100 013f P..;.,..T...!..?
  13. 0x0030: 636f 6c75 6d6e 3f00 0000 0000 0000 0000 column?.........
  14. 0x0040: 0017 0004 ffff ffff 0000 4400 0000 0b00 ..........D.....
  15. 0x0050: 0100 0000 0131 4300 0000 0d53 454c 4543 .....1C....SELEC
  16. 0x0060: 5420 3100 5400 0000 2100 013f 636f 6c75 T.1.T...!..?colu
  17. 0x0070: 6d6e 3f00 0000 0000 0000 0000 0017 0004 mn?.............
  18. 0x0080: ffff ffff 0000 4400 0000 0b00 0100 0000 ......D.........
  19. 0x0090: 0132 4300 0000 0d53 454c 4543 5420 3100 .2C....SELECT.1.
  20. 0x00a0: 5a00 0000 0549 Z....I
  21. ...
但是如果应用程序使用了Npgsql的Prepare(),这一招会无效,因为Prepare()后Npgsql会使用扩展查询协议。
很少有场景需要在意这么点通信延迟,而且支持多语句的简单查询协议会有其他方面的问题,所以以上只是探讨一下优化的可能性,通常不值得这么折腾。

2. 返回大量结果集的SQL


SQL执行总时间 = 客户端驱动SQL发送处理时间 + 数据库初始执行SQL时间 + 通信延迟 + Max(服务端数据Fetch时间,数据传输时间,客户端数据接受时间)

排除可以忽略的“客户端驱动SQL发送处理时间 ”和“通信延迟”,可以简化为

SQL执行总时间 =  数据库初始执行SQL时间 + Max(服务端数据Fetch时间,数据传输时间,客户端数据接受时间)

数据库初始执行SQL时间是数据库从收到SQL请求到开始发送第一条记录之间的时间。对于简单的SQL,这个时间可以忽略不计。


上面的公式中有一个Max()函数。因为数据Fetch,数据传输,数据接受这3件事情是并行做的,最终客户端看到的实行时间是它们中的最大值,而不是3个之和。对返回大量结果集的SQL,Max()中的时间经常是大头。


那么Max()中,如何确定哪个是最大值呢?
一个简单的方法,是看资源利用率(结果集要足够大,使处理时间变长,这样才能容易在资源监视器中看到资源利用率数据)。
如果服务端数据Fetch时间最大,也就是处理瓶颈,那么服务端对应的那个postgres进程的CPU利用率应该用满或接近用满。用满在CPU 利用率指标是指100%除以CPU核心数,对于4核CPU,25%即达到CPU瓶颈。
如果客户端数据接受是处理瓶颈,那么客户端应用程序进程的CPU利用率应该用满。
如果上面2个都不是瓶颈,那么数据传输就是瓶颈。
下面用一个例子说明一下。

测试环境:
客户端(VM)
 Win 64Bit
 Npgsql 2.2.3
 4核2.6G CPU
服务端(VM)
 Win 64Bit
 PostgreSQL 9.2.8
 4核2.6G CPU
网络
 延迟小于1ms
 带宽1Gbps(大文件拷贝速度可达到110MB/s)


测试程序:

点击(此处)折叠或打开

  1. DateTime start = DateTime.Now;
  2. DbCommand cmd = con.CreateCommand();
  3. cmd.CommandText = "select select c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2,c2 from tball,n100";
  4. cmd.Prepare();//Prepare()后走扩展查询协议,可以优化对某些数据类型的处理速度,比如int,double。
  5. DbDataReader reader = cmd.ExecuteReader();
  6. while (reader.Read())
  7. {
  8.    for(int i=0;ireader.FieldCount;i++)
  9.    {
  10.         var obj = reader.GetValue(i);
  11.    }
  12. }
  13. reader.Close();
  14. DateTime end = DateTime.Now;
  15. System.Console.WriteLine("escape:" + (end - start).Milliseconds);

上面的tball中有4000条记录,n100中有100条记录,c2是int型,c12为varchar(10)。通信的数据量(通过 Wireshark抓包获得):70MB。
另外,测试程序调用了Prepare(),也可以使用AlwaysPrepare=true参数,目的是为了让Npgsql以 binary格式接受数据。Npgsql默认采用text格式,采用text格式不仅通信量变大,也加重了Npgsql解析数据包的负担,会影响速度。
Npgsql社区也在考虑今后总是使用binary格式( https://github.com/npgsql/npgsql/issues/447)以提升效率。

实测结果:
执行时间:4.5s
每秒处理数据量:16MB
Npgsql CPU利用率:25%
PostgreSQL CPU利用率:5%

显然瓶颈在Npgsql上,并且发现Npgsql的处理时间主要耗在"reader.GetValue(i)"上,把这一行注释掉再测后的结果如下。
执行时间:2.5s
每秒处理数据量:28MB
Npgsql CPU利用率:11%
PostgreSQL CPU利用率:10%

既然Npgsql和PostgreSQL都不是瓶颈,那么瓶颈就在数据的网络传输上。但是26MB/s的传输速度离1Gbps的带宽上限相去甚远,这是为什么呢?
仔细一番研究,发现PostgreSQL服务端的送信BUFFER设得不够大。
src/backend/libpq/pqcomm.c中有个PQ_SEND_BUFFER_SIZE宏用来控制送信BUFFER的大小。这个送信BUFFER指的是一次send()调用发送的数据量,并不是socket的SO_SNDBUF选项, Windows上PostgreSQL把SO_SNDBUF设置成了PQ_SEND_BUFFER_SIZE的4倍,其他平台没有设置SO_SNDBUF。

PQ_SEND_BUFFER_SIZE的大小为8K,现在通过修改源码,把PQ_SEND_BUFFER_SIZE和 SO_SNDBUF设大,发现可以提高通信数据。

PQ_SEND_BUFFER_SIZE设成32K,SO_SNDBUF设成32K
执行时间:1.7s
每秒处理数据量:41MB
Npgsql CPU利用率:18%
PostgreSQL CPU利用率:14%

PQ_SEND_BUFFER_SIZE设成64K SO_SNDBUF 设成256K
执行时间:1.3s
每秒处理数据量:54MB
Npgsql CPU利用率:25%
PostgreSQL CPU利用率:20%

当PQ_SEND_BUFFER_SIZE设成64K时,瓶颈又变成Npgsql(具体而言就是Read()方法)了。这时候再从通信上抠时间已经不可能了。

3.和SQL Server的对比

SQL Server+SQLClient的组合进行相同的测试(但是不调用cmd.Prepare(),因为调了SQLClient的Prepare()性能会更差),发现SQLClient的效率比较高。
通信数据量:42MB

不删除GetValue()的测试结果
执行时间:1.5s
每秒处理数据量:28MB

性能瓶颈也在GetValue()上,把GetValue()删掉再测
执行时间:0.5s
每秒处理数据量:84MB

4.总结

1,Npgsql接受数据的效率不够高。对某些数据类型这个问题更严重,比如Datetime,对这个问题Npgsql社区也在改进中。对 本例中int和varchar,在已经Prepare()的情况下很难再提高性能了,这属于整体设计上的问题。
2, PostgreSQL在通信协议上没有尽量减小通信数据量。主要有两个问题,一是text格式传输的数据量一般会被放大,当然 驱动可以通过使用binary格式来回避这个问题。第二,传输数据时,每个数据列值的前面都有一个4字节的数据长度,而其他SQL Server只在可变长数据前面加长度。这导致本例中全int类型时,PostgreSQL的通信量将近是SQL Server的两倍。
3,PostgreSQL对送信BUFFER的设置略嫌保守,如果能提供一个可以让用户设置送信BUFFER的参数就好了。不过这个问题好像只在Windows上有,在Linux上测试发现默认的送信BUFFER设置已经很合适了
4,以上几点都不必太在意。因为很少会有场景需要对16MB/s的数据查询速度抱怨。如果真不满意可以换一个性能好的机器做客户端以及在Linux上搭PostgreSQL服务器。











相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
2月前
|
SQL 存储 数据可视化
手机短信SQL分析技巧与方法
在手机短信应用中,SQL分析扮演着至关重要的角色
|
2月前
|
SQL 数据可视化 关系型数据库
【数据库工具】DBeaver:一款免费的通用数据库工具和 SQL 客户端
【数据库工具】DBeaver:一款免费的通用数据库工具和 SQL 客户端
166 1
|
4月前
|
前端开发 Java JSON
Struts 2携手AngularJS与React:探索企业级后端与现代前端框架的完美融合之道
【8月更文挑战第31天】随着Web应用复杂性的提升,前端技术日新月异。AngularJS和React作为主流前端框架,凭借强大的数据绑定和组件化能力,显著提升了开发动态及交互式Web应用的效率。同时,Struts 2 以其出色的性能和丰富的功能,成为众多Java开发者构建企业级应用的首选后端框架。本文探讨了如何将 Struts 2 与 AngularJS 和 React 整合,以充分发挥前后端各自优势,构建更强大、灵活的 Web 应用。
65 0
|
4月前
|
SQL 数据采集 数据挖掘
为什么要使用 SQL 函数?详尽分析
【8月更文挑战第31天】
63 0
|
4月前
|
SQL 数据采集 算法
【电商数据分析利器】SQL实战项目大揭秘:手把手教你构建用户行为分析系统,从数据建模到精准营销的全方位指南!
【8月更文挑战第31天】随着电商行业的快速发展,用户行为分析的重要性日益凸显。本实战项目将指导你使用 SQL 构建电商平台用户行为分析系统,涵盖数据建模、采集、处理与分析等环节。文章详细介绍了数据库设计、测试数据插入及多种行为分析方法,如购买频次统计、商品销售排名、用户活跃时间段分析和留存率计算,帮助电商企业深入了解用户行为并优化业务策略。通过这些步骤,你将掌握利用 SQL 进行大数据分析的关键技术。
253 0
|
4月前
|
SQL 数据挖掘 BI
【超实用技巧】解锁SQL聚合函数的奥秘:从基础COUNT到高级多表分析,带你轻松玩转数据统计与挖掘的全过程!
【8月更文挑战第31天】SQL聚合函数是进行数据统计分析的强大工具,可轻松计算平均值、求和及查找极值等。本文通过具体示例,展示如何利用这些函数对`sales`表进行统计分析,包括使用`COUNT()`、`SUM()`、`AVG()`、`MIN()`、`MAX()`等函数,并结合`GROUP BY`和`HAVING`子句实现更复杂的数据挖掘需求。通过这些实践,你将学会如何高效地应用SQL聚合函数解决实际问题。
64 0
|
4月前
|
网络协议 NoSQL 网络安全
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
|
5月前
|
SQL 存储 大数据
SQL中DISTINCT关键字的使用与性能影响分析
SQL中DISTINCT关键字的使用与性能影响分析
|
7月前
|
SQL HIVE UED
【Hive SQL 每日一题】分析电商平台的用户行为和订单数据
作为一名数据分析师,你需要分析电商平台的用户行为和订单数据。你有三张表:`users`(用户信息),`orders`(订单信息)和`order_items`(订单商品信息)。任务包括计算用户总订单金额和数量,按月统计订单,找出最常购买的商品,找到平均每月最高订单金额和数量的用户,以及分析高消费用户群体的年龄和性别分布。通过SQL查询,你可以实现这些分析,例如使用`GROUP BY`、`JOIN`和窗口函数来排序和排名。
406 2
|
6月前
|
SQL 分布式计算 大数据
MaxCompute产品使用问题之如何使用MaxCompute SQL客户端删除分区数据
MaxCompute作为一款全面的大数据处理平台,广泛应用于各类大数据分析、数据挖掘、BI及机器学习场景。掌握其核心功能、熟练操作流程、遵循最佳实践,可以帮助用户高效、安全地管理和利用海量数据。以下是一个关于MaxCompute产品使用的合集,涵盖了其核心功能、应用场景、操作流程以及最佳实践等内容。