Почему pandas.read_sql с параметрами намного медленнее, чем с встроенными параметрами - PullRequest
1 голос
/ 28 октября 2019

У меня есть страница CGI в Python, которая взаимодействует с данными в 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 ()), и проверил с различными значениями. У меня есть кластеризованный индекс в столбце гидриоидов, но это не должно иметь значения, поскольку во всех трех случаях это одно и то же значение. У меня также есть два других запроса, выполняющих почти одинаковые действия (выберите * в столбце varchar с кластеризованным индексом) для других таблиц, и у них нет этой проблемы.

Единственное, что я смог придумать до сих пор, это то, что в таблице FloorPlans гидриоид всегда представляет собой последовательность чисел на данный момент (это может измениться в будущем, поскольку другие таблицы, содержащиеодин и тот же идентификатор имеет экземпляры с алфавитно-цифровыми гидроидами) и, несмотря на то, что переменная является строкой, что-то в pandas преобразует ее обратно в int перед отправкой в ​​SQL, что вызывает проблемы с запросом.

1 Ответ

1 голос
/ 28 октября 2019

Если вы используете 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

Обходной путь для панд 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'])
...