【问题标题】:Is there any way of improving the performance of this SQL Function?有什么方法可以提高这个 SQL 函数的性能吗?
【发布时间】:2012-02-11 12:59:38
【问题描述】:

我有一张看起来像这样的桌子

Event ID  Date       Instructor
1         1/1/2000    Person 1
1         1/1/2000    Person 2

现在我要做的是返回这些数据,以便每个事件都在一行中,并且讲师都在一列中,并使用<br> 标签(如'Person 1 <br> Person 2')进行拆分

目前我这样做的方式是使用函数

CREATE FUNCTION fnReturnInstructorNamesAsHTML
(
    @EventID INT
)
RETURNS VARCHAR(max)
BEGIN

    DECLARE @Result VARCHAR(MAX)

    SELECT 
        @result = coalesce(@result + '<br>', '') + inst.InstructorName 
    FROM
        [OpsInstructorEventsView]   inst
    WHERE
        inst.EventID = @EventID

    RETURN @result


END

然后我的主存储过程调用它像

   SELECT 
        ev.[BGcolour], 
        ev.[Event] AS name, 
        ev.[eventid] AS ID, 
        ev.[eventstart], 
        ev.[CourseType], 
        ev.[Type], 
        ev.[OtherType], 
        ev.[OtherTypeDesc], 
        ev.[eventend], 
        ev.[CourseNo], 
        ev.[Confirmed], 
        ev.[Cancelled], 
        ev.[DeviceID] AS resource_id, 
        ev.Crew, 
        ev.CompanyName , 
        ev.Notes,
        dbo.fnReturnInstructorNamesAsHTML(ev.EventID) as Names
    FROM 
        [OpsSimEventsView] ev
    JOIN
        [OpsInstructorEventsView]   inst
    ON
        ev.EventID = inst.EventID 

这非常慢,我每次调用数据库需要 4 秒。有没有办法让我提高功能的性能?它是一个相当小的函数,所以我不确定我可以在这里做什么,而且我看不到将 COALESCE 工作到主过程的 SELECT 中的方法。

任何帮助将不胜感激,谢谢。

【问题讨论】:

  • 字符串连接(以及一般的字符串函数)并不是 SQL 能够很快完成的事情。 SQL 针对基于集合的逻辑进行了优化,而不是迭代递归。
  • @Purplegoldfish:提高性能的一种简单方法是从主查询中删除与[OpsInstructorEventsView] 的连接,因为您当前没有使用其中的任何字段。
  • @MarkBannister 谢谢,我一定错过了,我在玩了一段时间后试图在一个 proc 中完成所有这些操作

标签: sql performance stored-procedures user-defined-functions


【解决方案1】:

需要注意的几点:

1) 函数的开销使它们调用起来很昂贵,尤其是在可能返回数千行的查询的 select 语句中。它必须为每个人执行该功能。考虑将函数的行为合并到您的主存储过程中,以便 SQL Server 可以更好地利用其优化器。

2) 由于您在两个表中都加入了事件 ID,因此请确保您在这两列上都有索引。我希望你这样做,因为这两个似乎都是主键列,但请确保。索引可以产生巨大的影响。

3) 将您的 coalesce 调用转换为其等效的 case 语句,以消除调用该函数的开销。

【讨论】:

  • COALESCE IS 就优化器而言,是一个 case 语句。替换它只会在查询中增加几个字符。
  • 确实如此,但是,我的目的是消除调用函数本身的开销,而不是改变它的工作方式。在 select 子句中调用函数时,总会有一些开销,但开销很小。它可能会或可能不会有所作为。
【解决方案2】:

是的,让它成为一个内联表值 SQL 函数:

 CREATE FUNCTION fnReturnInstructorNamesAsHTML 
 (  @EventID INT  ) 
 RETURNS Table   
 As 
  Return

  SELECT  InstructorName + '<br>' result 
  FROM OpsInstructorEventsView
  WHERE EventID = @EventID 
  Go

然后,在你的 SQL 语句中,像这样使用它

SELECT    ]Other stuff],
    (Select result from dbo.fnReturnInstructorNamesAsHTML(ev.EventID)) as Names   
FROM OpsSimEventsView ev   
   JOIN OpsInstructorEventsView inst   
     ON  ev.EventID = inst.EventID    

我不太清楚您在问题中显示的查询是如何将来自多行的数据连接到结果的一行中,但问题是普通的 UDF 是在每次使用时编译的,所以对于每一行在您的输出结果中,Query processopr 必须再次重新编译 UDF。对于“内联表值”UDF,这不是真的,因为它的 sql 在传递给 SQL 优化器(生成语句缓存计划的子系统)之前被折叠到外部 sql 中,因此 UDF 只编译一次。

【讨论】:

  • 我调用的函数只返回一个值,该值具有包含指定 eventID 的所有名称的连接数据,但是当我将其转换为使用您建议的方法时,每个名称仍然只能得到 1 行这是我在使用函数之前开始的数据:(
【解决方案3】:

你可以试试这样的。

SELECT 
    ev.[BGcolour], 
    ev.[Event] AS name, 
    ev.[eventid] AS ID, 
    ev.[eventstart], 
    ev.[CourseType], 
    ev.[Type], 
    ev.[OtherType], 
    ev.[OtherTypeDesc], 
    ev.[eventend], 
    ev.[CourseNo], 
    ev.[Confirmed], 
    ev.[Cancelled], 
    ev.[DeviceID] AS resource_id, 
    ev.Crew, 
    ev.CompanyName , 
    ev.Notes,
    STUFF((SELECT '<br>'+inst.InstructorName
           FROM [OpsInstructorEventsView]   inst
           WHERE ev.EventID = inst.EventID
           FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 4, '') as Names
FROM 
    [OpsSimEventsView] ev

不确定您为什么在主查询中加入OpsInstructorEventsView。我在这里删除了它,但如果您需要,您可以再次添加它。

【讨论】:

  • 谢谢,这将 260 行的性能降低到大约 1 秒,因此它比我所拥有的有了很大的改进
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-23
  • 2012-08-09
  • 2017-12-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多