python - 为什么带有参数的 pandas.read_sql 比带有内联参数的要慢得多
问题描述
我在 python 中有 cgi 页面,它使用 pandas 与 SQL Server 中的数据进行交互。
概括
查询是基于用户与来自另一个查询的一些其他数据的交互运行的。这些都是使用pandas.read_sql()
函数加载的。由于某种原因,第二个查询的运行速度比它在 python 中与直接在数据库上运行时(在 SQL Server Management Studio 中)时的运行速度要慢得多。经过一些测试,我发现当我使用 传递参数时查询要慢得多params=[p]
,这是我最初正在做的并且更愿意这样做,而不是在查询中内联(下面的代码)。我不确定为什么会这样,并认为那里的某个人可能有一个想法。
代码
#Method 1: using param=[]
query = "select * from FloorPlans where hydroid = ? order by plan_date desc"
t1 = datetime.datetime.now()
df2 = pd.read_sql(query, conn, params=[row["HydroID"]])
t2 = datetime.datetime.now()
print(t2-t1)
#Method 2: inline
query = "select * from FloorPlans where hydroid = '" + row["HydroID"] + "' order by plan_date desc"
t3 = datetime.datetime.now()
df2 = pd.read_sql(query, conn)
t4 = datetime.datetime.now()
print(t4-t3)
时代
方法 1:~210.0 秒
方法 2:~0.05 秒
在 SSMS 中:~0.04 秒
有谁知道为什么会发生这种情况?我已经检查以确保 param 方法按预期发送字符串(通过将其包装在 str() 中)并且我检查了各种值。我在 hydrooid 列上有一个聚集索引,但这无关紧要,因为它在所有三种情况下都是相同的值。我还有其他两个查询在其他表上做几乎相同的事情(在带有聚集索引的 varchar 列上选择 *),它们没有这个问题。
到目前为止,我唯一能想到的是,在 FloorPlans 表中,Hydroid 目前始终是一个数字序列(这可能会在未来发生变化,因为包含相同标识符的其他表具有带有字母数字的实例hydroids) 并且尽管确保变量是字符串,但 pandas 中的某些内容在发送到 SQL 之前将其转换回 int,这会导致查询出现问题。
解决方案
如果您使用的是 Python_3,那么所有字符串都是 Unicode。Python代码...
sql = "SELECT * FROM MillionRows WHERE varchar_column = ?"
crsr.execute(sql, 'record012345')
... 在 SQL Server 上被处理为
declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@P1 nvarchar(24)',N'SELECT * FROM MillionRows WHERE varchar_column = @P1',N'record012345'
select @p1
请注意,参数值是 Unicode:nvarchar(24)
现在,如果我们检查 SSMS 中等效查询的实际执行计划......
SELECT * FROM MillionRows WHERE varchar_column = N'record012345'
... 我们看
Physical operation: Index Scan
Actual Number of Rows: 1
Number of Rows Read: 1000000
另一方面,如果我们运行一个使用varchar
值的查询......
SELECT * FROM MillionRows WHERE varchar_column = 'record012345'
...执行计划向我们展示
Physical operation: Index Seek
Actual Number of Rows: 1
Number of Rows Read: 1
不同之处在于第一个查询必须nvarchar
针对索引执行扫描(隐式转换)值varchar
,而第二个查询能够执行搜索而不是扫描。
原始 Python 代码的修复方法是使用setinputsizes来指定查询参数应该是varchar
...
sql = "SELECT * FROM MillionRows WHERE varchar_column = ?"
crsr.setinputsizes([(pyodbc.SQL_VARCHAR, 25)])
crsr.execute(sql, 'record012345')
.. 在 SQL Server 上被处理为
declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@P1 varchar(25)',N'SELECT * FROM MillionRows WHERE varchar_column = @P1','record012345'
select @p1
pandas 的一种解决方法read_sql_query
是CAST
SQL 查询本身的参数值
sql = "SELECT * FROM MillionRows WHERE varchar_column = CAST(? AS varchar(25))"
df = pd.read_sql_query(sql, engine, params=['record012345'])
推荐阅读
- delphi - 如何使用 Delphi 在其他应用程序中检测 CTRL+A
- keycloak - 如何通过 kcadm.sh 更新 keycloak 用户的属性?
- xsd - 针对多个 xsd 的 XML 验证
- css - 如何在 vuetify 中更改弹出对话框的位置
- r - 在 conda env 中使安装不适用于 R4.0.2
- syntax-highlighting - 与全局变量相比,编辑器可以突出显示/标记局部变量吗?
- java - Hibernate 在本地开发机器上启动缓慢 - 两个 DEBUG 日志行之间有 1 分钟的延迟
- gnuplot - gnuplot - 将 x 轴上的 unix 时间戳格式化为日期 - 时间
- php - 创建包含原始数组值作为键和值的关联数组
- docker - Jenkins:如何保持容器运行并将工件从容器复制到主机