为什么带参数的 pandas.read_sql 比带内联参数的速度慢得多
Why is pandas.read_sql with params so much slower than with inline params
我在 python 中有一个 cgi 页面,它使用 pandas 与 SQL 服务器中的数据进行交互。
总结
查询 运行 基于用户与来自另一个查询的一些其他数据的交互。这些都是使用 pandas.read_sql()
函数加载的。出于某种原因,第二个查询 运行ning 比在 python 中与直接在数据库 运行 中(在 SQL 服务器中)进行比较时应该慢得多管理工作室)。经过一些测试后,我发现当我使用 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)
次
方法一:~210.0 秒
方法 2:~0.05 秒
在 SSMS 中:~0.04 秒
有人知道为什么会这样吗?我已经检查以确保 param 方法按预期发送字符串(通过将其包装在 str() 中)并且我已经检查了各种值。我在 hydrioid 列上有一个聚集索引,但这无关紧要,因为它在所有三种情况下都是相同的值。我还有两个其他查询在其他 tables 上做几乎相同的事情(select * 在具有聚集索引的 varchar 列上)并且他们没有这个问题。
到目前为止我唯一能想到的是,在 FloorPlans table 中,hydrioid 目前始终是一个数字序列(这在未来可能会改变,因为其他 tables 包含相同的标识符有字母数字水螅体的实例)并且尽管确保变量是一个字符串,pandas 中的某些东西在发送到 SQL 之前将其转换回 int,这导致查询出现问题。
如果您使用 Python_3,那么所有字符串都是 Unicode。 Python 代码 ...
sql = "SELECT * FROM MillionRows WHERE varchar_column = ?"
crsr.execute(sql, 'record012345')
... 在 SQL 服务器上被处理为
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
不同之处在于第一个查询必须针对 varchar
索引对(隐式转换的)nvarchar
值进行扫描,而第二个查询能够执行寻找而不是扫描。
原始 Python 代码的修复是使用 setinputsizes 指定查询参数应该是 varchar
...
sql = "SELECT * FROM MillionRows WHERE varchar_column = ?"
crsr.setinputsizes([(pyodbc.SQL_VARCHAR, 25)])
crsr.execute(sql, 'record012345')
.. 在 SQL 服务器上被处理为
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'])
我在 python 中有一个 cgi 页面,它使用 pandas 与 SQL 服务器中的数据进行交互。
总结
查询 运行 基于用户与来自另一个查询的一些其他数据的交互。这些都是使用 pandas.read_sql()
函数加载的。出于某种原因,第二个查询 运行ning 比在 python 中与直接在数据库 运行 中(在 SQL 服务器中)进行比较时应该慢得多管理工作室)。经过一些测试后,我发现当我使用 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)
次
方法一:~210.0 秒
方法 2:~0.05 秒
在 SSMS 中:~0.04 秒
有人知道为什么会这样吗?我已经检查以确保 param 方法按预期发送字符串(通过将其包装在 str() 中)并且我已经检查了各种值。我在 hydrioid 列上有一个聚集索引,但这无关紧要,因为它在所有三种情况下都是相同的值。我还有两个其他查询在其他 tables 上做几乎相同的事情(select * 在具有聚集索引的 varchar 列上)并且他们没有这个问题。
到目前为止我唯一能想到的是,在 FloorPlans table 中,hydrioid 目前始终是一个数字序列(这在未来可能会改变,因为其他 tables 包含相同的标识符有字母数字水螅体的实例)并且尽管确保变量是一个字符串,pandas 中的某些东西在发送到 SQL 之前将其转换回 int,这导致查询出现问题。
如果您使用 Python_3,那么所有字符串都是 Unicode。 Python 代码 ...
sql = "SELECT * FROM MillionRows WHERE varchar_column = ?"
crsr.execute(sql, 'record012345')
... 在 SQL 服务器上被处理为
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
不同之处在于第一个查询必须针对 varchar
索引对(隐式转换的)nvarchar
值进行扫描,而第二个查询能够执行寻找而不是扫描。
原始 Python 代码的修复是使用 setinputsizes 指定查询参数应该是 varchar
...
sql = "SELECT * FROM MillionRows WHERE varchar_column = ?"
crsr.setinputsizes([(pyodbc.SQL_VARCHAR, 25)])
crsr.execute(sql, 'record012345')
.. 在 SQL 服务器上被处理为
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'])