参数化原始 sql 查询比使用实际值的查询慢得多

Parameterized raw sql query much slower than query with actual values

我正在尝试在连接到 SQL 服务器的 Django 应用程序中使用 connections[].cursor() 执行原始 sql 查询。当我在查询字符串中提供实际值时,查询执行得更快 (<1s)。

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)

但是当我在 cursor.execute() 方法中将值作为 params 提供时,查询变得更慢(2 分钟)。

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)

我还应该提到,仅当未提供条件时,在 SSMS 上执行查询实际上很慢:

 where c.column_condition = value_1 and c.column_cd_2 = value2

就好像Django发送查询时,不带参数执行(因此响应时间长),然后提供参数,过滤结果。

相关值由用户提供,因此它们会更改并且必须作为参数传递,而不是直接在查询中传递,以避免 sql 注入。 该查询也比上面给出的示例复杂得多,并且没有完全映射到模型,因此我必须使用 connection[].cursor()

这可能是参数嗅探问题。如果是这种情况,有几种解决方案。最简单的解决方案是使用查询提示。

选项 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)

选项 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)

这是一个good link供更多阅读。