`
java-mans
  • 浏览: 12023244 次
文章分类
社区版块
存档分类
最新评论

移除函数调用能有更好的性能

 
阅读更多

原文出自:

http://www.mssqltips.com/sqlservertip/2727/removing-function-calls-for-better-performance-in-sql-server/?utm_source=dailynewsletter&utm_medium=email&utm_content=headline&utm_campaign=2012726

问题:

大部分人都知道不要在where子句中调用函数,这样会影响你的性能。但是如果在SELECT中使用呢?本文将尝试移除select中的函数调用能戏剧性地增强性能,特别在返回大数据量时。

解决方案:

示例表和函数:

在此例中,我们将创建两个示例表和两个访问这些表的函数。至于填充表,你将不得不使用一种工具,Visual Studio来填充他们以提供一些合理的真实数据。在本例中,将对每个表填充20万数据。其中一件需要注意的事是,这些示例函数只返回每个买家的一笔记录。几时存在多个买家。

下面是代码:

-- Table creationlogic

CREATE TABLE[dbo].[CarSale](

[CarSaleID] [int] IDENTITY(1,1) NOT NULL,

[PurchaseDate] [smalldatetime] NOT NULL,

CONSTRAINT [PK_CarSale] PRIMARY KEYCLUSTERED ([CarSaleID] ASC)

);

CREATE TABLE[dbo].[Buyer](

[BuyerID] [int] IDENTITY(1,1) NOT NULL,

[CarSaleID] [int] NOT NULL,

[LastName] [varchar](50) NULL,

[FirstName] [varchar](100) NULL,

[CompanyName] [varchar](200) NULL,

CONSTRAINT [PK_Buyer] PRIMARY KEY NONCLUSTERED([BuyerID] ASC)

);

ALTER TABLE[dbo].[Buyer] WITH CHECK ADD CONSTRAINT[FK_Buyer_CarSale] FOREIGN KEY([CarSaleID])

REFERENCES[dbo].[CarSale] ([CarSaleID]) ON UPDATE CASCADE ON DELETE CASCADE;

CREATE CLUSTEREDINDEX [IX_Buyer_CarSalelID] ON [dbo].[Buyer]([CarSaleID] ASC);

-- Function creationlogic

CREATE FUNCTION[dbo].[fnGetBuyerFirstName]

(@CarSaleID INT)

RETURNS VARCHAR (500)

AS

BEGIN

RETURN (SELECT Top 1FirstName

FROM Buyer

WHERE CarSaleID=@CarSaleID

ORDER BY BuyerID)

END

GO

CREATE FUNCTION[dbo].[fnGetBuyerLastName]

(@CarSaleID INT)

RETURNS VARCHAR (500)

AS

BEGIN

RETURN (SELECT Top 1coalesce(LastName,CompanyName)

FROM Buyer

WHERE CarSaleID=@CarSaleID

ORDER BY BuyerID)

END

GO

原始查询:

SELECT cs.PurchaseDate,
 dbo.fnGetBuyerFirstName(cs.CarSaleID),
 dbo.fnGetBuyerLastName(cs.CarSaleID)
FROM CarSale cs
ORDER BY CarSaleID;

从上面代码中可以看出,每条记录都调用一次函数。并且查询了Buyer表两次。当CarSale表有大量数据时,这种做法并不高效。执行计划如下:


即使我们使用where子句限制查询并只查询一条数据,通过查看执行计划,如下,可以看到,依旧要对Buyer表做两次搜索。


修改后的查询:

SELECT cs.PurchaseDate,
 dbo.fnGetBuyerFirstName(cs.CarSaleID),
 dbo.fnGetBuyerLastName(cs.CarSaleID)
FROM CarSale cs
WHERE CarSaleID=5
ORDER BY CarSaleID;

值得注意的是,在这个例子中,只返回了一条记录。一下带有更广where条件从而返回更多数据的查询会变得越来越慢。

去除函数的例子:

现在移除select中的函数调用,并使用表关联来实现同样结果,其中一个是使用了where子句,另外一个没有限制:

SELECT cs.PurchaseDate,FirstName,LastName
FROM CarSale cs 
INNER JOIN (SELECT CarSaleID,MIN(BuyerID) AS SingleBuyerID FROM Buyer GROUP BY CarSaleID) m2 
ON cs.CarSaleID=m2.CarSaleID
INNER JOIN Buyer m ON m2.CarSaleID=cs.CarSaleID AND m2.SingleBuyerID=m.BuyerID
ORDER BY cs.CarSaleID;

SELECT cs.PurchaseDate,FirstName,LastName
FROM CarSale cs 
INNER JOIN (SELECT CarSaleID,MIN(BuyerID) AS SingleBuyerID FROM Buyer GROUP BY CarSaleID) m2 
ON cs.CarSaleID=m2.CarSaleID
INNER JOIN Buyer m ON m2.CarSaleID=cs.CarSaleID AND m2.SingleBuyerID=m.BuyerID
WHERE cs.CarSaleID=5
ORDER BY cs.CarSaleID;

通过查看执行计划,可以得出不用函数以后,不再需要每条记录都去重新查找。这是通过merge join来处理的。


为了确认这点,我们看看刚才去掉了函数之后的查询,通过sql Profiler的跟踪,可以得到多大的性能提升:

Query

WHERE Clause

CPU (ms)

Reads

Writes

Duration

Original

NO

10734

1239655

0

25879

YES

0

9

0

0

No Function Call

NO

578

16337

0

2457

YES

0

11

0

0

通过上面的结果可以看出,当返回的结果很大时,能从中得到相当大的好处,包括CPU、逻辑读、持续时间等。当只返回一个结果时,性能更好。

最终版本,使用CTE:

因为在本例中,使用函数来返回单独的买家,所以可以使用CTE来取得进一步的性能:

WITH summary AS (SELECT CarSaleID, 
BuyerID, 
FirstName,
 LastName,
 ROW_NUMBER() OVER(PARTITION BY CarSaleID ORDER BY CarSaleID) AS rk 
FROM Buyer) 
SELECT PurchaseDate,s.FirstName,s.LastName FROM CarSale cs INNER JOIN summary s 
ON s.CarSaleID=cs.CarSaleID WHERE s.rk = 1;

WITH summary AS (SELECT CarSaleID, 
BuyerID, 
FirstName,
 LastName,
 ROW_NUMBER() OVER(PARTITION BY CarSaleID ORDER BY CarSaleID) AS rk 
FROM Buyer) 
SELECT PurchaseDate,s.FirstName,s.LastName FROM CarSale cs INNER JOIN summary s 
ON s.CarSaleID=cs.CarSaleID WHERE s.rk = 1 AND cs.CarSaleID=5;

通过执行计划和sqlprofiler对比得到:

Query

WHERE Clause

CPU (ms)

Reads

Writes

Duration

No Function Call add WITH statement

NO

266

15796

0

1931

YES

0

6

0

0

总结:

我同意第一种方式容易实现并容易阅读,但是对性能提升来说,性能上的提升比代码量更重要。

分享到:
评论

相关推荐

    函数调用的基本原理

    在了解了函数调用的基本原理之后,我们可以更好地理解当出现栈溢出错误时,意味着函数调用的深度已经超出了程序栈可以容纳的范围。解决这个问题通常需要检查程序的递归逻辑,确保递归调用有明确的退出条件,或者重构...

    函数的几种调用机制

    此外,理解函数调用的底层机制也有助于开发者进行高效的性能优化和调试。 文档还强调了在参数传递中必须明确的两个问题:参数压栈的顺序以及堆栈的清理责任。这是通过不同调用约定来规定的,以便在函数调用和执行...

    cdecl函数调用,了解printf这样的函数调用,对比stdcall会更清楚.zip

    在C/C++编程语言中,函数调用约定(Calling Convention)是至关重要的,因为它定义了函数参数...通过对比`printf`等`cdecl`约定的函数和`stdcall`约定的函数,我们可以更好地理解这两种调用约定的工作原理和应用场景。

    函数调用与堆栈

    ### 函数调用与堆栈 #### 变量的生存期概述 在计算机编程中,尤其是在C/C++这类语言中,变量的生存期是指变量在...这样,我们可以清楚地看到栈如何随函数调用而增长和收缩,从而更好地理解函数调用与堆栈之间的关系。

    构建 View 时可能用到的代理函数调用顺序 viewdidload

    ### 构建View时可能用到的代理函数调用顺序:viewDidLoad与viewWillAppear #### 概述 在iOS开发过程中,理解视图控制器(ViewController)的生命周期及其相关的代理方法调用顺序至关重要。这些方法帮助开发者正确...

    android 不同activity切换时它们的回调函数调用逻辑的研究

    在Android应用开发中,Activity是用户界面的基本组件,它负责展示屏幕上的交互内容。当我们从一个Activity切换到另一个...通过深入理解这些机制,开发者能更好地控制Activity的显示和隐藏,提升应用的性能和用户体验。

    vc调试技巧之调用堆栈

    3. **性能优化**:有时候,通过对调用堆栈的分析可以发现程序中不必要的函数调用路径,从而进行优化。 #### 如何使用VC中的调用堆栈 1. **启动调试器**:首先确保已经设置了断点,并通过F5或其他方式启动调试器。 ...

    函数和window对象

    在JavaScript编程中,函数是核心概念之一,它们是可重用的代码块,可以接受输入(参数)并返回结果(返回值...通过深入学习这两个主题,开发者可以更高效地编写和调试JavaScript代码,从而更好地控制网页的行为和交互。

    Linux系统调用-- recv/recvfrom 函数详解

    通过分析该示例代码,可以更好地理解 `recvfrom` 的用法及其参数的意义。 通过以上内容的介绍,我们了解了 `recv` 和 `recvfrom` 在Linux系统调用中的具体用途、参数以及返回值等信息。这些知识对于开发基于网络的...

    python网络编程调用recv函数完整接收数据的三种方法

    ### Python网络编程调用recv函数完整接收数据的三种方法 在网络编程中,特别是TCP通信中,确保能够完整地接收来自远程主机的数据是非常重要的。Python的`socket`库提供了`...希望这些方法能帮助您更好地进行网络编程。

    dephi钩子函数实例源码

    同时,使用调试工具如DebugView或Visual Studio的调试器,可以帮助你更好地理解钩子函数何时被调用以及如何处理事件。 10. **代码结构**:源码通常会包含钩子的安装、卸载、回调函数的定义以及主循环中的事件处理。...

    VC窗口函数 窗口风格设定函数

    本篇将详细阐述VC中的窗口函数及其如何设置窗口风格,以便开发者能够更好地理解和应用这些技术点。 #### 一、窗口风格概述 在VC中,窗口风格(Styles)定义了窗口的外观和行为特性,通过不同的风格组合可以实现...

    HOOK跳转函数.rar

    《深入理解易语言HOOK跳转函数》 在计算机编程领域,HOOK技术是一种广泛使用的调试、监控和增强系统功能的方法。...通过实际操作和源码学习,我们可以更好地运用HOOK跳转函数,创造出更高效、更智能的软件系统。

    用hook代码实现桩函数设置功能

    为了有效地进行单元测试,有时我们需要替换或模拟某些系统函数或库函数的行为,以便更好地控制测试环境。这就是“桩函数”(Stub Function)的作用。本篇文章将深入讲解如何使用hook技术来实现桩函数的设置,特别是...

    C# 安全移除USB设备

    在IT领域,尤其是在Windows操作系统开发中,C#是一种常用的编程语言,用于构建各种应用程序,包括与硬件交互的程序。在本实例中,我们将探讨如何...通过深入研究给定的源代码实例,你将能够更好地理解和应用这些概念。

    从汇编到c 调用约定 堆栈原理

    ### 汇编与C语言调用约定及堆栈原理...通过理解调用约定和堆栈的运作机制,我们可以更好地掌握程序的执行流程,并能够更加有效地调试和优化代码。此外,这种从汇编到C语言的转换练习也是提高编程技能的重要途径之一。

    SAP HR几个常用函数及宏

    - 在进行界面设计时,为了使用户能够更好地理解每个对象的作用,通常会显示这些文本信息。 #### 7. RH_READ_OBJECT — 读取对象 **功能描述**:该函数用于读取系统中特定对象的所有信息,包括但不限于基本属性、...

    DLL函数查看 OLEVIEW

    OLEVIEW可以帮助开发者深入了解DLL的内部结构,从而更好地管理和使用这些文件。 在DLL函数查看中,以下是一些关键知识点: 1. **DLL(动态链接库)**:DLL是一种可执行文件,它包含了一组可重用的函数和资源。当多...

    打桩代码函数

    在C++环境中,打桩代码通常指的是在原代码中插入额外的逻辑来实现特定功能,如记录函数调用、监控性能指标、替换原有功能等。下面我们将深入探讨打桩代码函数在C++中的应用、实现方式以及相关的知识点。 1. **打桩...

    vue2生命周期函数(笔记_自用).html

    掌握这些生命周期函数,能让我们更好地控制Vue组件的创建、更新和销毁过程,优化应用性能和用户体验。 另外,Vue2生命周期函数中的钩子函数执行顺序非常重要,开发者应确保其符合Vue实例的生命周期流程,避免出现...

Global site tag (gtag.js) - Google Analytics