如何使这个 SQL 函数更快?

Posted

技术标签:

【中文标题】如何使这个 SQL 函数更快?【英文标题】:How can I make this SQL function faster? 【发布时间】:2011-10-07 15:21:41 【问题描述】:

背景

我有以下函数,它根据结果与搜索值的匹配程度对结果进行排名:

CREATE FUNCTION [dbo].[fnGetRelevance] 
(   
    @fieldName nvarchar(50),
    @searchTerm nvarchar(50)
)

RETURNS  int
AS
BEGIN
    if (@fieldName like @searchTerm + '%') -- starts with
    begin       
        return 0
    end
    else if ((@fieldName like '%' + @searchTerm + '%') and (@fieldName not like @searchTerm + '%')) -- contains, but doesn't start with 
    begin       
        return 1
    end

    return 1
END

因此,在以下查询(NHibernate 查询)的上下文中,它基于搜索字符串 Allianz RCM BRIC Stars A 搜索共享类,它会找到 39 个结果并对它们进行排名,以便与该字符串完全匹配的结果位于顶部,其余的按字母顺序排列在它下面。

select   top 50 *
from     ShareManager.ShareClass sc
         -- and a few other tables with an inner join and a left join
where    (sc.ShareClass_Id in 
                                ( 
                                    /* filter by some business criteria which is a single 
                                       select statement that does 2 more inner joins  */ 
                                )
         and 1 = 1
         and (sc.ShareClass_Name like '%Allianz%' /* @p11 */)
         and (sc.ShareClass_Name like '%RCM%' /* @p12 */)
         and (sc.ShareClass_Name like '%BRIC%' /* @p13 */)
         and (sc.ShareClass_Name like '%Stars%' /* @p14 */)
         and (sc.ShareClass_Name like '%A%' /* @p15 */)
order by dbo.fngetrelevance(sc.ShareClass_Name, 'Allianz RCM BRIC Stars A'), sc.ShareClass_Name asc

问题

我遇到的问题是 dbo.fngetrelevance 导致我的 NHibernate 查询超时。我尝试过延长超时时间,但这不起作用,而且我认为这不是真正的问题。当我删除该功能时,它按预期工作。

SQL Server 2008 上是否有一种方法可以加快这一速度,或者使用 NHibernate 以不会超时的方式实现排名?

补充信息

我希望有人会建议我减少连接数。为了尽可能加快这些查询的速度,我们已经进行了很多优化。在修改整体架构的规模上,弄清楚如何进一步优化对我们来说将是一项巨大的努力。不幸的是,在游戏的这个阶段,我们不会获得批准,(据我目前所见,只有 1 只基金)

作为记录,这就是我在 NHibernate 中使用该函数的方式:

string querystring =
    "select sc, sctr" +
    " from ShareClass as sc" +
    // joins to 2 other tables
    " and (" + expressionTokenizer.ToResult("sc.Name") + ") " 
    + this.AddShareClassOrder(order, "sc", "sctr", searchExpression);

var result = _session.CreateQuery(querystring)
    .AddNameSearchCriteria(expressionTokenizer)
    .AddDataUniverseParameters(dataUniverseHelper)
    .SetFirstResult((pageSize * (pageNum - 1)))                    
    .SetMaxResults(pageSize)
    .List();

AddShareClassOrder 有效返回

fieldName = string.Format("dbo.fngetrelevance(1.2, '0'), 1.2", textToSearchFor, shareClassPrefix, "Name");
return String.Format(" order by 0 1", fieldName, direction);

或者,SQL 中表示的以下内容:

dbo.fngetrelevance(sc.ShareClass_Name, 'Allianz RCM BRIC Stars A'), sc.ShareClass_Name asc

【问题讨论】:

【参考方案1】:

我必须不同意此处发布的其他答案; %% 在这里使用是一个红鲱鱼,因为你没有对这个表达式进行任何过滤(在这种情况下他们肯定是正确的)。你的问题是你的UDF;就目前而言,您的 UDF 不会内联到查询中。相反,查询引擎将获取整个结果集,为每一行调用该函数,然后捕获这些结果进行排序。您需要以将内联到查询中的方式定义和使用您的函数。

有关更多信息,请参阅this article,但简短的版本是将您的功能更改为:

CREATE FUNCTION [dbo].[fnGetRelevance] 
(   
    @fieldName nvarchar(50),
    @searchTerm nvarchar(50)
)

RETURNS  table
AS
select (case when @fieldName like @searchTerm + '%' then 0 else 1 end) as Value

(我删除了您的第二个条件,因为它似乎没有必要,因为它返回与备用值相同的值。如果这是一个错误,那么如何修改上面的表达式以获得您想要的应该是相当明显的。)

然后像这样使用它:

select   top 50 *
from     ShareManager.ShareClass sc
         -- and a few other tables with an inner join and a left join
where    (sc.ShareClass_Id in 
                                ( 
                                    /* filter by some business criteria which is a single 
                                       select statement that does 2 more inner joins  */ 
                                )
         and 1 = 1
         and (sc.ShareClass_Name like '%Allianz%' /* @p11 */)
         and (sc.ShareClass_Name like '%RCM%' /* @p12 */)
         and (sc.ShareClass_Name like '%BRIC%' /* @p13 */)
         and (sc.ShareClass_Name like '%Stars%' /* @p14 */)
         and (sc.ShareClass_Name like '%A%' /* @p15 */)
order by (select Value from dbo.fngetrelevance(sc.ShareClass_Name, 'Allianz RCM BRIC Stars A')), sc.ShareClass_Name asc

我不知道如何更改您的 NHibernate 查询来执行此操作,但这不是您的问题。

【讨论】:

谢谢。这很有帮助,我也学到了一些非常有用的东西。【参考方案2】:

我猜你的函数也可以这样重写:

CREATE FUNCTION [dbo].[fnGetRelevance] 
(   
    @fieldName nvarchar(50),
    @searchTerm nvarchar(50)
)

RETURNS  int
AS
BEGIN
    if (@fieldName like @searchTerm + '%') -- starts with
    begin       
        return 0
    end
    return 1
END

因为您仅在 @fieldName 以 @searchTerm 开头时返回 0,而在所有其他情况下返回 1。

而不是调用函数

order by dbo.fngetrelevance(sc.ShareClass_Name, 'Allianz RCM BRIC Stars A'), sc.ShareClass_Name asc

您可以使用以下内容:

order by 
    case when sc.ShareClass_Name like 'Allianz RCM BRIC Stars A%'
    then 0 else 1 end, 
    sc.ShareClass_Name asc

【讨论】:

【参考方案3】:

我不确定您将如何优化此功能。

这里的主要问题是%somethings%无处不在。

一位前同事对我的描述最好,因为您可以很容易地在电话簿中找到所有以 E 开头的名字,但是找到其中包含 E 的每个名字是一个非常长期运行的过程。

您实际上是在否定您的索引。

【讨论】:

以上是关于如何使这个 SQL 函数更快?的主要内容,如果未能解决你的问题,请参考以下文章

如何使这个 for 循环更快?

如何使具有多个联合的 SQL 更快以实现高速

优化 sql 查询如何使其更快

如何使报告生成更快?(从 sql 数据库中提取数据)

SQL查询性能调优--如何使查询更快

PHP:如何使 INVOKING 内部函数更快