【好文推荐】openGauss psycopg2 批量操作性能测试

news2025/1/20 21:56:36

测试版本

本测试基于 openGauss 版本的 psycopg2 驱动。

import psycopg2 as pg
>>> pg.__libpq_version__
90204
>>> pg.__version__
'2.8.6 (dt dec pq3 ext)'

测试环境

组件 说明 客户端 Rocky Linux 8 虚拟机 数据库 openGauss 3.0.3 in docker 网络 本地回路网卡 Python 3.6.8

测试接口

接口名 说明 备注 cursor.executemany(query, vars_list) 执行一个数据库操作,vars_list 列表中的所有参数会逐个被应用到query 中,每组参数都会单独封包发送给服务端。 该函数主要用于更新数据库的命令,查询返回的任何结果集都将被丢弃。在其当前实现中,此方法并不比在循环中执行execute()快。 psycopg2.extras.execute_batch(cur, sql, argslist, page_size=100) 批量执行一个数据库操作,执行的SQL和 executemany 相同,只是单个数据包发送时会发送一批SQL,数量由page_size决定。这样可以减少和服务端的通信次数 execute_batch()也可以和预处理语句(PREPARE, EXECUTE, DEALLOCATE)一起使用。 extras.execute_batch + 预处理语句 使用PREPARE提交创建一个statement,然后通过 execute_batch 提交
psycopg2.extras.execute_values(cur, sql, argslist, template=None, page_size=100, fetch=False) 将参数和SQL封装为一条SQL执行,单条SQL中参数的个数由 page_size 决定。

性能对比

INSERT

测试数据

rows executemany execute_batch prepare+execute_batch execute_values 10,000 9.782 0.707 0.501 0.266 50,000 52.979 3.123 2.637 1.226 100,000 111.504 6.831 4.557 2.125

INSERT耗时对比图

075f4c0a99f4559ae9c77c882df405ff.jpeg

INSERT 去除 executemany 对比

0c59d9bcaf0ac4bdf03f6d461be413d7.jpeg

UPDATE

测试数据

rows executemany execute_batch prepare+execute_batch execute_values 10,000 5.015 0.617 0.425 0.356 50,000 24.639 3.467 1.905 5.237 100,000 52.095 6.927 3.473 21.102

UPDATE 耗时对比图

a7546bfb9f614f0ac70c4085418f3bca.jpeg

DELETE

测试数据

(100000 条数据组耗时太久不做展示)

rows executemany execute_batch prepare+execute_batch execute_values 10,000 15.020 8.699 0.277 6.204 50,000 248.154 227.958 1.455 142.732

DELETE 耗时对比图

d6470640017e2e035212dc968ff0eb59.jpeg

性能分析

从耗时对比来看,插入、更新、删除在不同的数据量情况下性能是不同的,用户应该根据自己的业务场景来选择使用哪一种操作接口。

插入性能从低到高依次为:

executemany < execute_batch < prepare+execute_batch < execute_values

更新性能从低到高依次为:

executemany < execute_values < execute_batch < prepare+execute_batch

删除性能从低到高依次为:

executemany < execute_batch < execute_values < prepare+execute_batch

性能的高低主要是由于在向服务端发送数据包时的方式不同导致,下面以插入的SQL为例,通过 wireshark 进行抓包可以看出 psycopg2 在通信过程中不同批处理接口的封包情况。

executemany

executemany&nbsp;提交SQL的时候是逐个应用给的参数,每个SQL都单独发送给服务端

9125fbfd58dc96c7014c850d128239b5.jpeg

execute_batch

execute_batch&nbsp;接口区别于&nbsp;executemany&nbsp;的是,在发送给后端的单个请求包里的数据会一次性提交一批的SQL,这样可以减少和服务器之间通信的往返次数

90ebc19798b6d9f71cd2f106e9d69024.jpeg

prepare+execute_batch

prepare&nbsp;可以提前在数据库里面创建一个预备语句对象,在执行 prepare 语句的时候,指定的SQL已经经了解析、分析、重写,这样在后续执行 EXECUTE 时就避免了重复解析分析的工作,从而起到优化性能的作用。

415751148e27d53c4431cc1419ff93e8.jpeg

execute_values

前面的三个接口,不管是单个提交还是批量提交,最终都是一行数据一个SQL发送到服务端的,所以服务端需要逐个执行,而&nbsp;execute_values&nbsp;接口是会按照 page_size 分组参数后,每组参数一次性组成一个SQL进行提交。

dccc8a34b6907312071e9c6a65f5570d.jpeg

测试代码

执行方式:python test.py <api> <row> <operation>

  • <api>&nbsp;支持:&nbsp;executemany,&nbsp;execute_batch,&nbsp;prepare,&nbsp;execute_values

  • <operation>&nbsp;支持&nbsp;insert,&nbsp;update,&nbsp;delete

# coding: utf-8

# Usage: python test.py <api> <count> <operation>

import time
import sys
import psycopg2 as pg
from psycopg2.extras import execute_batch, execute_values
from contextlib import contextmanager

if sys.argv[3] == "insert":
args = [[str(i), i] for i in range(int(sys.argv[2]))]
elif sys.argv[3] == "update":
args = [[i, str(i)] for i in range(int(sys.argv[2]))]
elif sys.argv[3] == "delete":
args = [[i] for i in range(int(sys.argv[2]))]
'''
- *dbname*: the database name
- *database*: the database name (only as keyword argument)
- *user*: user name used to authenticate
- *password*: password used to authenticate
- *host*: database host address (defaults to UNIX socket if not provided)
- *port*: connection port number (defaults to 5432 if not provided)
'''
conf = {
'dbname': "postgres",
'user': 'gaussdb',
'password': '',
'host': '',
'port': 26000,
'sslmode': 'disable'
}


@contextmanager
def calc_time(s):
start = time.time()
yield
end = time.time()
print(f"{s} of '{sys.argv[3]}' cost: ", end - start)


sql_map = {
"insert": {
1: "INSERT INTO t_psycopg2_benchmark VALUES (%s, %s)",
2: "INSERT INTO t_psycopg2_benchmark VALUES ($1, $2)",
3: "INSERT INTO t_psycopg2_benchmark VALUES %s",
},
"update": {
1: "UPDATE t_psycopg2_benchmark as t SET f_value = %s WHERE t.f_key = %s",
2: "UPDATE t_psycopg2_benchmark as t SET f_value = $1 WHERE t.f_key = $2",
3: "UPDATE t_psycopg2_benchmark as t SET f_value = data.v1 FROM (VALUES %s) AS data (id, v1) WHERE t.f_key = data.id",
},
"delete": {
1: "DELETE FROM t_psycopg2_benchmark as t WHERE t.f_key=%s",
2: "DELETE FROM t_psycopg2_benchmark as t WHERE t.f_key=$1",
3: "DELETE FROM t_psycopg2_benchmark as t WHERE t.f_key IN (%s)",
}
}

def insert_data(conn):
print("* preparing data ...")
args = [[str(i), i] for i in range(int(sys.argv[2]))]
cursor = conn.cursor()
sql = "insert into t_psycopg2_benchmark values %s"
execute_values(cursor, sql, args)
conn.commit()


def main():
try:
conn = pg.connect(**conf)
print("* connect success")
except Exception as e:
print(f"connect failed: {e}")
return

cursor = conn.cursor()

sql = "drop table if exists t_psycopg2_benchmark"
cursor.execute(sql)
sql = "create table t_psycopg2_benchmark (f_key text primary key, f_value numeric)"
cursor.execute(sql)

api = sys.argv[1]
if sys.argv[3] != "insert":
insert_data(conn)

print("* benchmarking ...")
if api == "executemany":
with calc_time("executemany"):
sql = sql_map[sys.argv[3]][1]
cursor.executemany(sql, args)
conn.commit()
elif api == "execute_batch":
with calc_time("execute_batch"):
sql = sql_map[sys.argv[3]][1]
execute_batch(cursor, sql, args)
conn.commit()
elif api == "prepare":
with calc_time("execute_values"):
cursor.execute(f"PREPARE test_stmt AS {sql_map[sys.argv[3]][2]}")
if sys.argv[3] == "delete":
execute_batch(cursor, "EXECUTE test_stmt (%s)", args)
else:
execute_batch(cursor, "EXECUTE test_stmt (%s, %s)", args)
cursor.execute("DEALLOCATE test_stmt")
conn.commit()
elif api == "execute_values":
with calc_time("execute_values"):
sql = sql_map[sys.argv[3]][3]
execute_values(cursor, sql, args)
conn.commit()
else:
print(f"unknow api: {api}")

if sys.argv[3] != "delete":
cursor.execute("delete from t_psycopg2_benchmark")
conn.commit()


if __name__ == "__main__":
main()



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/991351.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《DevOps实践指南》- 读书笔记(三)

DevOps实践指南 Part 3 第一步 &#xff1a;流动的技术实践9. 为部署流水线奠定基础9.1 按需搭建开发环境、测试环境和生产环境9.2 应用统一的代码仓库9.3 使基础设施的重建更容易9.4 运行在类生产环境里才算“完成”9.5 小结 10. 实现快速可靠的自动化测试10.1 对代码和环境做…

C#__资源访问冲突和死锁问题

/// 线程的资源访问冲突&#xff1a;多个线程同时申请一个资源&#xff0c;造成读写错乱。 /// 解决方案&#xff1a;上锁&#xff0c;lock{执行的程序段}:同一时刻&#xff0c;只允许一个线程访问该程序段。 /// 死锁问题&#xff1a; /// 程序中的锁过多&#xf…

【数据结构】二叉树的顺序结构-堆

【数据结构】二叉树的顺序结构-堆 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储&#xff0c;需要注意的是这里的堆和操作系统虚拟进程地址空间…

Direct LiDAR-Inertial Odometry

DLIO Runing 运行效果&#xff1a; <video id“video” controls""src“data/dlio_ss.mp4” height“500” preload“none”> 论文 摘要 难点&#xff1a;快速运动 or 穿越不规则地形时降低精度&#xff0c;通常过于简单的方法而过高的计算量。本方案提出…

go-zero直连与etcd服务注册中心

go-zero中直连方式 在使用grpc是最重要的就是pb文件了&#xff0c;生成的pb文件&#xff0c;通过pb文件可以生成grpc的客户端和服务端&#xff0c;那么客户端和服务端就可以直连了&#xff0c;再次基础上可以引入etcd实现服务注册。 所有的代码都需要开发者编写&#xff0c;包…

如何面对未来的迷茫和热爱?

很多人并没有明确的热爱的事&#xff0c;就是按照一般规划安安稳稳地上学工作&#xff0c;并在一个不那么爱也不怎么讨厌的工作岗位上度过大部分人生。 首先&#xff0c;我必须说&#xff0c;我并不认为这有什么不妥。或许大部分人并不热爱自己的工作&#xff0c;但他们对自己的…

全球市场争夺战:如何提升品牌在海外市场的竞争力?

随着全球化的不断发展&#xff0c;越来越多的企业将目光投向了海外市场&#xff0c;希望能够在国际舞台上获得更大的发展机会。然而&#xff0c;海外市场的竞争激烈&#xff0c;如何有效地提升品牌在海外市场的竞争力成为了一个关键的问题。本文Nox聚星将和大家从多个方面探讨&…

CAR-NK治疗的商业化之旅

自然杀伤细胞NK细胞是一种重要的免疫效应细胞&#xff0c;能识别并杀伤病毒感染细胞和肿瘤细胞&#xff0c;不过NK细胞本身难以准确识别肿瘤细胞。科学家通过基因工程修饰&#xff0c;在NK细胞表面表达能够和肿瘤特定抗原结合的嵌合抗原受体CAR。跨膜结构域将CAR结构锚定在NK细…

DNS、ICMP协议和NAT技术

文章目录 1. DNS1.1 域名简介 2. NAT技术2.1 NAPT2.2 NAT技术的缺陷2.3 NAT和代理服务器 3. ICMP协议3.1 ping命令 4. 总结 1. DNS TCP/IP中使用IP地址和端口号来确定网络上的一台主机的一个程序&#xff0c;但是IP地址不方便记忆&#xff0c;于是人们发明了一种叫主机名的东西…

视频监控汇聚平台EasyNVR安防视频平台新版本无法对接到EasyNVS平台并报错login error,该如何解决?

安防监控系统EasyNVR视频云存储平台可实现设备接入、实时直播、录像、检索与回放、视频云存储、视频分发等视频能力服务&#xff0c;可覆盖全终端平台&#xff08;pc、手机、平板等终端&#xff09;&#xff0c;在智慧工厂、智慧工地、智慧社区、智慧校园等场景中有大量落地应用…

leetcode 671. 二叉树中第二小的节点(java)

二叉树中第二小的节点 题目描述DFS 深度优先遍历代码演示 题目描述 难度 - 简单 leetcode 671. 二叉树中第二小的节点 给定一个非空特殊的二叉树&#xff0c;每个节点都是正数&#xff0c;并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话&#xff0c;那么…

SpringMVC增删改查(CRUD)的实现

目录 前言 一、前期准备 1.pom.xml---依赖与插件的导入 2.jdbc.properties---数据库连接 3.log4j2.xml---日志文件 4.spring-mybatis 5.spring-context 6.spring-mvc 二、增删改查的实现 1.model与mapper层的生成 2.biz层 3.工具类 4.controller层 三、测试结果 总…

MySQL——select语句的简单介绍和查询时常用的参数

select语句详解 基本的select语句 select 要查询的列名 from 要查询的表 where 限制条件; 如果要查询表的所有内容&#xff0c;则把要查询的列名用—个星号*号表示(之间的案例中都已经使用过)&#xff0c;代表要查询表中所有的列。 而大多数情况&#xff0c;我们只需要查看…

Linux——(第七章)文件权限管理

目录 一、基本介绍 二、文件/目录的所有者 1.查看文件的所有者 2.修改文件所有者 三、文件/目录的所在组 1.修改文件/目录所在组 2.修改用户所在组 四、权限的基本介绍 五、rwx权限详解 1.rwx作用到文件 2.rwx作用到目录 六、修改权限 一、基本介绍 在Linux中&…

[docker]笔记-portainer的使用

1、安装完成后输入ip加端口号打开网页&#xff0c;并再相应位置输入初始密码&#xff0c;初始密码自行设置。 2、进入主页后可以看到如下图标&#xff1a; 3、选择docker环境&#xff0c;即可展示目前docker信息 可以看到目前有1个容器&#xff0c;3个卷和4个镜像&#xff0c…

【C++ • STL】一文带你走进string

文章目录 一、STL简介二、标准库中的string类三、string类的常用接口说明2.1 string类对象的常见构造2.2 string类对象的访问及遍历操作2.2.1 元素访问2.2.2 迭代器 2.3 string类对象的容量操作2.4 string类对象的修改操作2.5 string类非成员函数 四、总结 ヾ(๑╹◡╹)&#x…

西门子PLC如何与多个三菱PLC建立无线通信?

对一个大型工厂&#xff0c;由于生产线的不断改造、新老流程的不断更新&#xff0c;这些PLC系统往往是由不同的制造商提供的。那么在智慧工厂的实现中&#xff0c;常会遇到不同品牌PLC之间需要进行相互通讯的情况。由于场地和生产能效的原因&#xff0c;在后期的系统改造中&…

docker从零部署jenkins保姆级教程(上)

jenkins&#xff0c;基本是最常用的持续集成工具。在实际的工作中&#xff0c;后端研发一般没有jenkins的操作权限&#xff0c;只有一些查看权限&#xff0c;但是我们的代码是经过这个工具构建出来部署到服务器的&#xff0c;所以我觉着有必要了解一下这个工具的搭建过程以及简…

【Java 基础篇】Java 异常处理指南:解密异常处理的关键技巧

异常是 Java 编程中不可避免的一部分。无论你是刚刚入门 Java 编程&#xff0c;还是已经有一定经验&#xff0c;了解异常处理都是非常重要的。本篇博客将向你介绍 Java 中异常的基础知识&#xff0c;帮助你理解什么是异常、为什么需要异常处理以及如何在代码中处理异常。 什么…

最新SQL注入漏洞原理及与MySQL相关的知识点

点击星标&#xff0c;即时接收最新推文 本文选自《web安全攻防渗透测试实战指南&#xff08;第2版&#xff09;》 点击图片五折购书 SQL注入漏洞简介 SQL注入是指Web应用程序对用户输入数据的合法性没有判断&#xff0c;前端传入后端的参数是攻击者可控的&#xff0c;并且参数被…