Alex Alex - 2 months ago 34
Python Question

pyodbc calls sp_unprepare after sp_prepexec. Does that affect performance of parameterized queries?

In continuation of

SqlAlchemy+pymssql. Will raw parametrized queries use same execution plan?

I switched from pymssql to pyodbc tried to get parametrized queries sent to SQL Server. pyodbc with Microsoft driver does the trick, but something seems strange to me:

declare @p1 int
set @p1=6
exec sp_prepexec @p1 output,N'@P1 nvarchar(6),@P2 bigint,@P3 bigint,@P4 bigint',N'
FROM CC_sold
WHERE id_contract =@P1
AND id_tip_cont=@P2
AND CC_sold.anul =@P3
AND CC_sold.luna =@P4
ORDER BY CC_sold.anul, CC_sold.luna DESC
select @p1
exec sp_unprepare 6

My worries are related to this statement:

exec sp_unprepare 6

If I understand right, this cancels query execution plan and I will not get any optimization from query parameterization. Is that the case?


I did a bit of testing and you need not be concerned. pyodbc only sends a single sp_unprepare (for the last sp_prepexec executed) when the cursor object is closed. That is, it does not sp_unprepare every sp_prepexec that it sends.

Furthermore, actual timing tests reveal the difference in performance between pyodbc (which apparently can take advantage of cached execution plans) and pymssql (which apparently cannot). With the following code ...

crsr = conn.cursor()
crsr.execute("DBCC FREEPROCCACHE")
if 'pyodbc' in repr(conn):
    sql = "SELECT COUNT(*) AS n FROM table1 WHERE cola=? AND colb=? AND colc=? AND cold=?"
    sql = "SELECT COUNT(*) AS n FROM table1 WHERE cola=%s AND colb=%s AND colc=%s AND cold=%s"
t0 = time.time()
limit = 10
for a in range(limit):
    for b in range(limit):
        for c in range(limit):
            for d in range(limit):
                params = (a, b, c, d)
                crsr.execute(sql, params)
                n = crsr.fetchone()[0]
print(time.time() - t0)

... for limit = 10 the four (4) nested for loops run a total of 10,000 queries. On my Windows test machine with a local SQL Server instance, pymssql takes about 130 seconds (just over 2 minutes) to execute, while pyodbc consistently runs the same code in under 5 seconds.