I am trying to execute a raw sql query with connections[].cursor() in a Django app that connects to SQL server. The query executes much faster (<1s) when I provide the actual vlaues in the query string.
from django.db import connections
with connections['default'].cursor() as cursor:
cursor.execute("""
select c.column1 as c1
, ve.column2 as c2
from view_example c
left join view_slow_view ve on c.k1 = ve.k2
where c.column_condition = value_1 and c.column_cd_2 = value2
""")
result = dictfetchall(cursor)
But when I provide the values as params in the cursor.execute() method, the query becomes much slower (2 minutes).
from django.db import connections
with connections['default'].cursor() as cursor:
cursor.execute("""
select c.column1 as c1
, ve.column2 as c2
from view_example c
left join view_slow_view ve on c.k1 = ve.k2
where c.column_condition = %s and c.column_condition_2 = %s
""", [value_1, value_2])
contracts_dict_lst = dictfetchall(cursor)
I should also mention that the query is actually slow when executed on SSMS ONLY IF a condition is NOT provided:
where c.column_condition = value_1 and c.column_cd_2 = value2
It is as if when Django sends the query, it is executed without the parameters (hence the long response time) and then the parameters are provided so the result is filtered.
The values in question are provided by the user, so they change and have to be passed as params and not directly in the query to avoid sql injection. The query is also much more complex than the example given above and doesn't map cleanly to a model so I have to use connection[].cursor()
This is probably parameter sniffing issue. If that's the case, there are couple of solutions. The easiest solution is using query hint.
Option 1:
from django.db import connections
with connections['default'].cursor() as cursor:
cursor.execute("""
select c.column1 as c1
, ve.column2 as c2
from view_example c
left join view_slow_view ve on c.k1 = ve.k2
where c.column_condition = %s and c.column_condition_2 = %s
OPTION(RECOMPILE) -- add this line to your query
""", [value_1, value_2])
contracts_dict_lst = dictfetchall(cursor)
Option 2:
from django.db import connections
with connections['default'].cursor() as cursor:
cursor.execute("""
declare v1 varchar(100) = %s -- declare variable and use them
declare v2 varchar(100) = %s
select c.column1 as c1
, ve.column2 as c2
from view_example c
left join view_slow_view ve on c.k1 = ve.k2
where c.column_condition = v1 and c.column_condition_2 = v2
""", [value_1, value_2])
contracts_dict_lst = dictfetchall(cursor)
This is a good link for more reading.