【问题标题】:Python takes 3ms while Spring takes 80ms to execute the select query from Azure SQL Server?Python 需要 3 毫秒,而 Spring 需要 80 毫秒才能从 Azure SQL Server 执行选择查询?
【发布时间】:2022-01-05 19:14:30
【问题描述】:

我在 Azure SQL 数据库中创建了一个简单的表

以下 Python 脚本仅需 3ms 即可从 Azure SQL Server 执行选择查询

import os
import sys
import logging, logging.handlers
import getopt
import pyodbc

# set up logging
logging.getLogger().setLevel(logging.INFO)

console = logging.StreamHandler()
console.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)s %(message)s'))
console.setLevel(logging.INFO)
logging.getLogger().addHandler(console)

logger = logging.getLogger("purgeStaleTags")

server = 'sqlsrv-01.database.windows.net'
database = 'db'
username = 'user'
password = 'p@$$word'   
driver= '{ODBC Driver 17 for SQL Server}'

logger.info("Before Connect")
connection=pyodbc.connect('DRIVER='+driver+';SERVER=tcp:'+server+';PORT=1433;DATABASE='+database+';UID='+username+';PWD='+ password+';Encrypt=yes;TrustServerCertificate=no;')
logger.info("Before Cursor")
cursor=connection.cursor()
logger.info("After Cursor")
cursor.execute("SELECT item_id FROM offer_table WHERE offer_id = 23583");
records=cursor.fetchall()
logger.info(len(records))
logger.info("After Query")

日志

>>> logger.info("Before Connect")
2022-01-05 19:00:30,638 purgeStaleTags INFO Before Connect
>>> connection=pyodbc.connect('DRIVER='+driver+';SERVER=tcp:'+server+';PORT=1433;DATABASE='+database+';UID='+username+';PWD='+ password+';Encrypt=yes;TrustServerCertificate=no;')
>>> logger.info("Before Cursor")
2022-01-05 19:00:30,871 purgeStaleTags INFO Before Cursor
>>> cursor=connection.cursor()
>>> logger.info("After Cursor")
2022-01-05 19:00:30,871 purgeStaleTags INFO After Cursor
>>> cursor.execute("SELECT item_id FROM offer_table WHERE offer_id = 23583");
<pyodbc.Cursor object at 0x7f05f45280b0>
>>> records=cursor.fetchall()
>>> logger.info(len(records))
2022-01-05 19:00:30,884 purgeStaleTags INFO 30
>>> logger.info("After Query")
2022-01-05 19:00:30,884 purgeStaleTags INFO After Query

但是下面的 spring 应用程序需要 80ms 来执行来自 Azure SQL Server 的选择查询?

Java 应用程序:

public class JDBCSample {

    public static void main(String[] args) {
        ...

        try {
            Connection conn = dataSource().getConnection();
            conn.setAutoCommit(false);

            Statement statement = conn.createStatement();

            ResultSet rs ;
            try {
                LocalDateTime executionStartTime = LocalDateTime.now();
                System.out.println("Select Query Execution Started: " + dtf.format(executionStartTime));
                
                rs = statement.executeQuery("SELECT item_id FROM offer_table WHERE offer_id = 23583");

                executionEndTime = LocalDateTime.now();
                System.out.println("Select Query Execution Completed:  " + dtf.format(executionEndTime));
            }
            catch (SQLException ex)
            {
                System.out.println("Error message: " + ex.getMessage());
                return; // Exit if there was an error
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Bean(destroyMethod = "close")
    public static DataSource dataSource(){
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        hikariConfig.setJdbcUrl("jdbc:sqlserver://....;encrypt=true;trustServerCertificate=false;");
        hikariConfig.setUsername("...");
        hikariConfig.setPassword("...");
        hikariConfig.setMaximumPoolSize(1);
        hikariConfig.setConnectionTestQuery("SELECT 1");
        hikariConfig.setPoolName("springHikariCP");

        HikariDataSource dataSource = new HikariDataSource(hikariConfig);
        
        return dataSource;
    }
}

日志

INFO  : 01.06.2022:0032 (31.802) [[]main] HikariDataSource: springHikariCP - Starting... 
INFO  : 01.06.2022:0032 (33.314) [[]main] HikariDataSource: springHikariCP - Start completed. 
Execution Started: 2022/01/06 00:32:33.436
INFO  : 01.06.2022:0032 (33.437) [[]main] HikariDataSource: springHikariCP - Starting... 
INFO  : 01.06.2022:0032 (33.927) [[]main] HikariDataSource: springHikariCP - Start completed. 
Select Query Execution Started: 2022/01/06 00:32:34.043
Select Query Execution Completed:  2022/01/06 00:32:34.122

延迟在哪里?我该如何解决这个问题,以使 Spring 应用程序花费的时间不会超过 2 毫秒。

【问题讨论】:

  • 只是检查。您是否尝试过对多个查询进行计时? Azure 可能会比 Python 进行更多的设置和初始化,但后续查询会更快。
  • 是的,我尝试了 5 个选择查询。 Python 需要 12ms 到 22ms,spring 需要 350ms
  • 尝试通过DriverManager JDBC API 连接而不是通过HikakiCP DataSource。也许是 HikariCP 的延迟加载什么的(不知道)。对 Java 程序进行微基准测试很难:通常建议在测量之前重复运行代码几分钟,以便 JVM 有机会将 Java 字节码编译为本机代码。
  • 作为健全性检查 - 您是否尝试过针对本地 SQL 服务器进行基准测试 - 您是否发现运行时有任何明显差异?
  • 对python使用这些计时方法并检查代码中的时间并检查您是否没有弄错计时选项:start_time=time.time()end_time=time.time()print('time: ' +str(end_time-start_time))

标签: python spring azure-sql-database hikaricp azure-sql-server


【解决方案1】:

TLDR:您可能将苹果与梨进行比较:您在 Python 端缺少 cursor.fetch

Java 和 Python 程序的编写风格表明您对这两种语言都有基本的了解,并且您可能正在努力提高自己的技能。这是令人钦佩的,但你的技能不足以进行性能比较。

假设开发 JVM 实现或您正在使用的驱动程序库的人比开发 Python VM 或您正在使用的 Python 库的人做得更差是不现实的。 您应该信任这两位工程师,因为参与编写 JVM 和 Python VM 实现的人员的技能水平通常非常高,流行的库也是如此。背后有一个社区,并且错误很快就会被发现。

让我们尝试一下,为了好玩,做一些调查

可重复的实验

如果我想研究这个主题,我会通过一个开源 GitHub 存储库提供一个清晰、可重现的场景,包括:

  • Python 发行版用于测试代码。你在使用 CPython 吗? 3.10.0?你在使用 PyPython 吗?您使用的是哪个版本的库?我会使用诗歌 + Docker 来确保构建可以重现
  • JVM ?开放JDK?阿祖利?什么版本?您使用的是哪个版本的 Azure SQL Server JDBC 库?我会使用 Gradle、Maven 或任何你喜欢的构建工具,以及 Docker

然后,我会确保我使用的工具可以让我获得可靠的性能指标。例如,Python 是解释型的,但如果该库是作为您的架构的轮子提供的,它可能依赖于编译的 C 实现,这可能比单次执行时的 JVM 实现快得多。但是,JVM 重复执行代码会激活 JIT 编译器,该编译器将 Java 字节码编译为机器代码,从而提高性能。如您所见,了解性能需要了解两种语言的执行方式,这不是初学者级别的技能。

不同的语义

作为拥有十多年 Java 经验的人,我可以说今天没有人直接使用 JDBC API 与关系数据库进行交互。十多年前人们开始使用 ORM,今天他们可能使用 JOOQ 之类的工具。

在快速阅读 pyodbc 文档后,我认为 pyodbc.cursorResultSet 是两种不同的数据结构。游标不会立即检索数据,需要调用fetch 方法。您正在比较从数据库中提取所有记录(在 Java 中)所需的时间与仅在结果中提取记录数(在 Python 中)所需的时间。第一个操作需要传输更多数据,因此需要更长的时间。

================ 老================

您遇到这些性能问题的原因有很多,包括不同的驱动程序和配置。

阅读代码的一个明显区别是,在一种情况下,您正在检索连接,而在另一种情况下,您正在创建连接。 HikariCP 是 Java 中已知的连接池实现,它针对生产工作负载进行了优化。相反,您的 python 代码将在每次执行时创建一个连接,如果在 for 循环中执行,则会导致对服务器的 DoS 攻击。

我会尝试从 Java 进程中删除连接池并再次对代码进行基准测试。

【讨论】:

  • 没有 HikariCP 需要 400 毫秒来执行。
  • 查看更新的答案
  • 他使用fetchall。如果 JDBC 很慢,再多的 ORM 也不会让它更快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-19
  • 1970-01-01
  • 1970-01-01
  • 2014-01-01
  • 2017-02-24
  • 1970-01-01
相关资源
最近更新 更多