在SQL Server中,我们通常使用用户定义的函数来编写SQL查询。UDF接受参数并将结果作为输出返回。我们可以在编程代码中使用这些UDF,并且可以快速编写查询。我们可以独立于任何其他编程代码来修改UDF。

在SQL Server中,我们具有以下类型的用户定义函数。

    1. 标量函数:标量用户定义的函数返回单个值。您将始终具有RETURNS子句。返回值不能是文本,图像或时间戳。以下是标量函数的示例。

       
      1. Create FUNCTION dbo.ufnGetCustomerData (@CustomerID int)
      2. RETURNS varchar (50)
      3. AS
      4. BEGIN
      5. DECLARE @CustomerName varchar(50);
      6. SELECT @CustomerName = customername
      7. FROM [WideWorldImporters].[Sales].[Customers] C
      8. WHERE C.CustomerID=@CustomerID
      9. RETURN @CustomerName;
      10. END;

      传统上,标量用户定义函数不被认为是高性能的好选择,但是SQL Server 2019提供了一种提高这些标量用户定义函数的性能的方法。我们将在本文的后一部分中了解更多有关它的信息。

    2. 多语句表值函数(TVF):其语法类似于标量用户定义函数,并提供多值作为输出。由于基数估计问题,这些性能也不优化。

      SQL Server 2012提供固定的基数估计,而SQL Server 2012提供的基数估计为100。SQLServer 2017使用称为交错执行的功能改进了这些MSTVF的基数估计。

  1. 内联表值函数:内联表值函数是性能优化的函数。它们不包含表定义。此函数内部的查询批处理是一条语句,因此,当我们以批处理或循环方式使用它时,它不会提供任何性能问题。

    下面是内联表值函数的示例。

    1. USE [WideWorldImporters]
    2. GO
    3.  
    4. SET ANSI_NULLS ON
    5. GO
    6.  
    7. SET QUOTED_IDENTIFIER ON
    8. GO
    9.  
    10. CREATE FUNCTION [Application].[DetermineCustomerAccess](@CityID int)
    11. RETURNS TABLE
    12. WITH SCHEMABINDING
    13. AS
    14. RETURN (SELECT 1 AS AccessResult
    15. WHERE IS_ROLEMEMBER(N'db_owner') <> 0
    16. OR IS_ROLEMEMBER((SELECT sp.SalesTerritory
    17. FROM [Application].Cities AS c
    18. INNER JOIN [Application].StateProvinces AS sp
    19. ON c.StateProvinceID = sp.StateProvinceID
    20. WHERE c.CityID = @CityID) + N' Sales') <> 0
    21. OR (ORIGINAL_LOGIN() = N'Website'
    22. AND EXISTS (SELECT 1
    23. FROM [Application].Cities AS c
    24. INNER JOIN [Application].StateProvinces AS sp
    25. ON c.StateProvinceID = sp.StateProvinceID
    26. WHERE c.CityID = @CityID
    27. AND sp.SalesTerritory = SESSION_CONTEXT(N'SalesTerritory'))));
    28. GO

标量用户定义函数

如上所述,标量用户定义的函数不会在SQL Server中提供性能优势。因此,在本节中,我们将首先查看标量用户定义函数的性能问题,然后使用SQL Server 2019比较性能。

对于此示例,我正在运行SQL Server 2019 2.1和WideWorldImporters数据库。

将数据库兼容性级别设置为140。

  1. USE [master]
  2. GO
  3. ALTER DATABASE [WideWorldImporters] SET COMPATIBILITY_LEVEL = 140
  4. GO

使用数据库范围的配置选项清除过程缓存。这将从所有存储的执行计划或缓存中清除我们的数据库。

  1. ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE ;
  2. Go
 

现在,在WideWorldImporters数据库中创建标量用户定义函数。此函数将根据说明返回数量。执行以下脚本。

  1. CREATE OR ALTER FUNCTION Sales.SalesQuantity
  2. (@Description NVARCHAR(100))
  3. RETURNS SMALLINT
  4. AS
  5. BEGIN
  6. DECLARE @Count SMALLINT
  7.  
  8. SELECT @Count= Quantity
  9. FROM Sales.OrderLines
  10. WHERE Description=@Description;
  11.  
  12. RETURN(@Count)
  13. END;
  14. GO
 

我们可以使用类似于表列对象的功能。运行以下命令,该命令将UDF函数用作普通表列。

 
  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. SELECT
  4. Sol.orderid,
  5. sol.Description,
  6. Sales.SalesQuantity(sol.Description) as quantity,
  7. PickedQuantity
  8. FROM Sales.Orders as so
  9. JOIN Sales.OrderLines as sol
  10. on so.OrderId=sol.OrderID
  11. WHERE
  12. sol.PickingCompletedWhen >'2013-01-01 11:00:00.0000000'
  13. ORDER BY UnitPrice DESC
  14. GO

在上面的屏幕截图中,您可以看到大约花了18分钟来处理228,176条记录。

现在让我们以兼容级别150(SQL Server 2019)运行相同的查询。

  1. USE [master]
  2. GO
  3. ALTER DATABASE [WideWorldImporters] SET COMPATIBILITY_LEVEL = 150
  4. GO
  5. ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE ;
  6. Go
 

使用此已修改的兼容性级别再次运行查询。

在SQL Server 2019中,您可以注意到查询仅用19秒就完成了处理228,176行,而SQL Server 2016中则是12分钟。相对而言,处理这些结果非常快。

现在让我们在SQL Server 2016和SQL Server 2019数据库兼容性级别中再次运行查询,并捕获统计数据IO和统计时间以及要与之进行比较的实际执行计划。

SQL Server 2019统计信息输出

(受影响的228176行)
表'OrderLines'。扫描计数5,逻辑读857,物理读0,预读0,lob逻辑读180,lob物理读0,lob预读0。
表'OrderLines'。段读取为2,段跳过0。
表为“工作表”。扫描计数227,逻辑读730037,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'Workfile'。扫描计数0,逻辑读0,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。

(受影响的1行)

SQL Server执行时间:
   CPU时间= 15287毫秒,经过的时间= 18782毫秒。
SQL Server解析和编译时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。

SQL Server执行时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。

SQL Server 2016统计信息输出

SQL Server解析和编译时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。
SQL Server执行时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。
SQL Server解析和编译时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。
SQL Server执行时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。

SQL Server执行时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。

(受影响的228176行)

表“工作表”。扫描计数0,逻辑读0,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'OrderLines'。扫描计数1,逻辑读3504,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。

(受影响的1行)

SQL Server执行时间:
   CPU时间= 1071719 ms,经过的时间= 1109655 ms。
SQL Server解析和编译时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。
SQL Server执行时间:
   CPU时间= 0毫秒,经过的时间= 0毫秒。

如果将执行级别与兼容性级别140(SQL Server 2017)和兼容性级别150(SQL Server 2019)进行比较,则可以看到CPU时间和运行时间有了显着改善。

在下表中,您可以看到Scalar用户定义函数与SQL Server 2019的性能优化比较

SQL Server兼容性级别140

SQL Server兼容性级别150

性能改进

CPU – 1,071,719毫秒

CPU 15,287毫秒

1,056,432毫秒

经过时间1,109,655毫秒

经过时间18,782毫秒

1,090,873毫秒

执行计划比较

在本节中,我们将比较在使用SQL Server 2019及更高版本运行标量UDF时的执行计划。

以下是我们以兼容级别140(SQL Server 2017或更低版​​本)运行标量UDF时的实际执行计划

在估算的执行计划中,我们将对此有一个更清晰的认识。在下面的计划中,它显示了查询和UDF函数的单独执行计划。因此,SQL Server在SQL Server 2017之前将标量UDF函数视为一个单独的标识。每次调用此UDF函数时,SQL Server都需要做额外的工作。

现在,如果我们查看SQL Server 2019中的实际估计计划,我们将获得一个较早执行计划的复杂计划,如下图所示。在SQL Server 2019中,由于内联功能,它能够将UDF操作合并到单个查询计划中。

在此执行计划中,它在下部区域显示了UDF。您可以看到多个运算符来执行查询。SQL Server 2019内联标量UDF以优化查询的执行。它不会将此UDF视为单独的标识,但是会像正常的select语句一样运行它。因此,我们获得了CPU,内存,执行时间等方面的性能优势。

我们可以检查SQL Server是否能够内联特定标量UDF。SQL Server 2019在sys.sql_modules中引入了新列is_inlineable。运行以下查询,并在其中传递标量函数名称。在下面的查询中,我们的UDF显示值1,这意味着SQL Server 2019可以倾斜此函数。

  1. select o.name, sm.is_inlineable
  2. from sys.sql_modules sm
  3. join sys.objects o on sm.object_id = o.object_id
  4. where o.name = 'SalesQuantity';
 

在SQL Server 2019中内嵌标量UDF的条件

如果满足以下条件,则SQL Server 2019可以内联标量函数。

  1. UDF不是分区函数
  2. 没有引用任何表变量
  3. 没有使用任何计算列。
  4. 不能在Group By子句中调用UDF
  5. 可以在标量UDF中使用以下构造
    • Declare
    • Select
    • If/ else
    • Return
    • Exits or IsNull
  1. UDF没有包含任何与时间有关的函数。

使用SQL Server 2019 UDF倾斜功能的不同方法。

我们可以通过以下方式在SQL Server 2019中使用此UDF内联功能。

  • 将数据库兼容性级别设置为150(SQL Server 2019)
  • 我们可以使用新的数据库处理配置来控制此行为。您可以在下面的数据库作用域配置列表中看到,我们有了新的选项TSQL_SCALAR_UDF_INLINING

    1. --Run the below query to turn on Scalar UDF Inlining in a particular database
    2. ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = ON;
    3.  
    4. -- Run the below query to turn off Scalar UDF Inlining in a particular database
    5. ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;
     
  • 我们还可以通过指定查询提示来禁用特定查询的标量UDF内联:

    1. DISABLE_TSQL_SCALAR_UDF_INLINING
     

    在此示例中,我们使用以下参数禁用了UDF内联

    1. 'OPTION(USE HINT ('DISABLE_TSQL_SCALAR_UDF_INLINING'))'

    注意:如果我们已为标量UDF内联启用数据库作用域配置,并使用查询提示在查询级别禁用此功能,则SQL Server将优先提示数据库作用域配置或兼容性级别设置。

  • 创建标量UDF时,我们可以指定也关闭此功能。我们需要在以下示例中指定WITH INLINE = OFF

    默认情况下,在SQL Server 2019中启用了标量UDF内联,因此,我们不需要指定参数WITH INLINE = ON。如果需要,可以指定它。

结论:

在本文中,我们探讨了由于内联导致标量UDF的显着性能改进。这些增强功能会给开发人员和管理员带来微笑。

SQL Server 2019 中标量用户定义函数性能的改进的更多相关文章

  1. Sql server 浅谈用户定义表类型

    1.1 简介 SQL Server 中,用户定义表类型是指用户所定义的表示表结构定义的类型.您可以使用用户定义表类型为存储过程或函数声明表值参数,或者声明您要在批处理中或在存储过程或函数的主体中使用的 ...

  2. SQL Server 2019 新版本

    2019 年 11 月 4 日,微软在美国奥兰多举办的 Ignite 大会上发布了关系型数据库 SQL Server 的新版本.与之前版本相比,新版本的 SQL Server 2019 具备以下重要功 ...

  3. SQL Server 2019 新函数Approx_Count_Distinct

    2019年11月4日微软发布了2019正式版,该版本有着比以往更多强大的新功能和性能上的优势,可参阅SQL Server 2019 新版本. SQL Server 2019具有一组丰富的增强功能和新功 ...

  4. 微软SQL Server 2019 全新发布,更新内容亮点都在这里了

    IT之家11月7日消息 在Microsoft   Ignite 2019 大会上,微软正式发布了新一代数据库产品SQL Server 2019.使用统一的数据平台实现业务转型SQL Server 20 ...

  5. SQL Server 2019企业版和标准版的区别?

    来源公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485400&idx=1&a ...

  6. 应用C#和SQLCLR编写SQL Server用户定义函数

    摘要: 文档阐述使用C#和SQLCLR为SQL Server编写用户定义函数,并演示用户定义函数在T-SQL中的应用.文档中实现的 Base64 编码解码函数和正则表达式函数属于标量值函数,字符串分割 ...

  7. 调试SQL Server的存储过程及用户定义函数

    分类: 数据库管理 2005-06-03 13:57 9837人阅读 评论(5) 收藏 举报 sql server存储vb.net服务器sql语言 1.在查询分析器中调试 查询分析器中调试的步骤如下: ...

  8. SQL Server中授予用户查看对象定义的权限

    SQL Server中授予用户查看对象定义的权限   在SQL Server中,有时候需要给一些登录名(用户)授予查看所有或部分对象(存储过程.函数.视图.表)的定义权限存.如果是部分存储过程.函数. ...

  9. 【翻译】Flink Table Api & SQL — 用户定义函数

    本文翻译自官网:User-defined Functions  https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/tabl ...

随机推荐

  1. ArrayList :货物库存管理(遍历)

        package com.oracle.demo01; import java.util.ArrayList; import java.util.Scanner; public class De ...

  2. isopod dsl 框架管理kubernetes 配置

    isopod 是一个包含了丰富能力的dsl 框架我们可以不用编写yaml 文件来进行k8s 管理 说明 语法类似python,目前移植内置了一些不错的功能kube 方法 vault 集成,helm 集 ...

  3. BZOJ 1802: [Ahoi2009]checker

    题目描述 若有两个红格相邻 第一问的答案为0,所有位置上的棋子都可以通过在这两个格子上放棋子得到 第二设f[i]表示想让第i个格子上有棋子需要放的棋子数 若没有,第一问答案为偶数格子上白格的个数,第二 ...

  4. JavaScript var、let、const

    var申明的变量是有作用域的 如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量: 'use strict'; function foo() { var x = 1; ...

  5. 【09NOIP提高组】Hankson 的趣味题(信息学奥赛一本通 1856)(洛谷 1072)

    题目描述 Hanks 博士是BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫Hankson.现在,刚刚放学回家的Hankson 正在思考一个有趣的问题.今天在课堂上,老师讲解了如何求 ...

  6. robotframework 文档

    http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#post-processing-outputs

  7. Java8之list<entity>获取实体的某一字段

    示例 List<String> titles = titleList.stream().map(e -> e.get(ConstantUtil.TITLE)).collect(Col ...

  8. ppt VBA 实现随机抽题

    目录 目标/最终效果 关于VBA VBA简单示例 检查环境 步骤 VBA实现随机抽题 todo challenge 目标/最终效果 目标是制作一个ppt,实现随机抽题,具体描述: 第一页幻灯片中:点击 ...

  9. Shell命令行提示定制

    /******************************************************************************* * Shell命令行提示定制 * 说明 ...

  10. Java NIO 堆外内存与零拷贝

    一.直接缓存 这个例子的区别就是 ByteBuffer.allocateDirect(512); 进入allocateDirect方法 进入DirectByteBuffer构造函数 Native方法: ...