sql server尽量避免滥用影响性能的标量函数

ops/2024/10/17 18:22:21/

相信很多新手学了 函数的用法就不可避免的想把学到的东西用起来,然而这个函数使用却有坑, 在实际用的时候我发现一个简单的计算封装 ,不用函数和用函数执行耗时差太多了。

能避免列上进行函数则尽量避免,这是在实际上遇到的坑 ,封装成函数和直接计算效果差太多。

行中函数(Scalar-valued functions)在 SQL Server 中的性能通常较差,主要原因是它们在查询执行过程中被视为"黑盒",使得 SQL Server 优化器无法有效优化这些函数的执行。下面是一些针对行中函数优化的建议和替代方法:

-- 原始标量值函数
CREATE FUNCTION dbo.GetDiscount (@ProductID INT)
RETURNS DECIMAL(10, 2)
AS
BEGINDECLARE @Discount DECIMAL(10, 2)SELECT @Discount = DiscountFROM ProductsWHERE ProductID = @ProductIDRETURN @Discount
END-- 转换为内联表值函数
CREATE FUNCTION dbo.GetDiscountInline (@ProductID INT)
RETURNS TABLE
AS
RETURN
(SELECT DiscountFROM ProductsWHERE ProductID = @ProductID
)

使用内联表值函数时,你可以通过 JOIN 或 CROSS APPLY 来调用它,而不会丢失性能优势。

SELECT p.ProductID, p.ProductName, d.Discount
FROM Products p
CROSS APPLY dbo.GetDiscountInline(p.ProductID) d

避免在查询中的列上使用标量函数

-- 性能较差的写法
SELECT OrderID, dbo.CalculateTax(OrderAmount) AS TaxAmount
FROM Orders-- 性能更好的写法(将计算逻辑直接写入查询)
SELECT OrderID, OrderAmount * 0.08 AS TaxAmount
FROM Orders

使用计算列(Computed Columns)

ALTER TABLE Orders
ADD TaxAmount AS OrderAmount * 0.08 PERSISTED

使用 CASE 语句代替简单的函数
如果标量函数只涉及简单的逻辑判断,可以考虑使用 CASE 语句直接在查询中实现。

-- 使用 CASE 语句替代简单函数
SELECT OrderID, CASE WHEN OrderAmount > 100 THEN OrderAmount * 0.1ELSE OrderAmount * 0.05END AS Discount
FROM Orders

消除标量子查询
标量函数在 WHERE 或 JOIN 条件中使用时会影响性能,可以考虑将其转换为 JOIN 操作。

-- 性能较差的标量子查询
SELECT OrderID
FROM Orders
WHERE dbo.GetCustomerStatus(CustomerID) = 'Active'-- 性能更好的 JOIN 替代
SELECT o.OrderID
FROM Orders o
JOIN Customers c ON o.CustomerID = c.CustomerID
WHERE c.Status = 'Active'

使用存储过程替代复杂的标量函数
对于复杂的逻辑,可以使用存储过程来代替标量函数,因为存储过程的执行效率通常较高。
标量值函数在 SQL Server 中的性能瓶颈通常可以通过以下方式解决:

总结的优化:

转换为内联表值函数(ITVF)
在查询中内联计算逻辑
使用计算列
使用 CASE 语句
使用存储过程

表值函数和内联表值函数的区别

CREATE FUNCTION dbo.GetOrdersByCustomer (@CustomerID INT)
RETURNS TABLE
AS
RETURN (SELECT OrderID, OrderDate, TotalAmountFROM OrdersWHERE CustomerID = @CustomerID
)

多语句表值函数(MSTVF)

CREATE FUNCTION dbo.GetOrdersByCustomerMulti (@CustomerID INT)
RETURNS @OrderTable TABLE
(OrderID INT,OrderDate DATETIME,TotalAmount DECIMAL(18, 2)
)
AS
BEGININSERT INTO @OrderTableSELECT OrderID, OrderDate, TotalAmountFROM OrdersWHERE CustomerID = @CustomerIDRETURN
END

性能表现

内联表值函数(ITVF):
性能更高,因为它们直接嵌入到调用查询中,与视图类似。
SQL Server 优化器能够完全展开内联表值函数,并将其优化为与查询其他部分一起执行的一个执行计划。
没有额外的计算开销,因为它不使用表变量。
多语句表值函数(MSTVF):
性能通常较差,因为 SQL Server 优化器无法提前知道函数内的具体逻辑。
由于使用了表变量,可能会影响查询性能,因为表变量不会生成统计信息,这限制了优化器的能力。
对于复杂的逻辑和多个步骤的计算,MSTVF 的灵活性更高,但执行效率往往不如 ITVF。

适用场景

内联表值函数(ITVF):
适合简单的查询逻辑。
用于那些查询不需要复杂处理逻辑的场景。
性能要求较高的情况下,应该优先选择使用 ITVF。
多语句表值函数(MSTVF):

适合复杂的业务逻辑和多步骤处理。
当需要多个 SQL 语句来生成最终结果时,可以使用 MSTVF。
如果需要在函数中执行复杂的数据操作(如条件判断、循环等),MSTVF 是更好的选择。
SQL Server 优化器支持
ITVF:因为是单个查询,优化器可以将 ITVF 中的逻辑与主查询一起优化。SQL Server 能够生成更高效的执行计划。
MSTVF:由于多语句表值函数的逻辑是一个"黑盒",优化器在执行之前无法知道其中包含的具体内容,这会导致它生成一个次优的执行计划。

为啥标量值函数尽量避免使用

标量值函数(Scalar-valued functions)在 SQL Server 中的性能往往较差,通常建议尽量避免使用。原因如下:

1. 逐行执行

标量值函数在查询中被调用时,会对每一行数据逐一执行。这种逐行处理(Row-by-row execution)方式会导致性能显著下降,尤其是当查询结果集非常大时。相比之下,SQL Server 通常更擅长处理批量操作。

示例:假设有一个返回税额的标量函数 dbo.CalculateTax:

sql

SELECT OrderID, dbo.CalculateTax(OrderAmount) AS TaxAmount
FROM Orders

在此查询中,如果 Orders 表有一百万行记录,SQL Server 会为每一行调用一次 CalculateTax 函数,导致性能极差。

2. 阻碍查询优化器优化

SQL Server 的查询优化器在生成查询计划时,无法有效地优化标量值函数。标量函数的逻辑对于优化器来说是一个“黑盒”,无法提前知道函数内的执行逻辑,因此优化器无法进行充分的优化。这就限制了查询的性能提升。

相比之下,内联表值函数(Inline Table-Valued Functions, ITVF)中的逻辑会被直接嵌入到查询计划中,优化器可以根据整体查询来选择最优的执行计划。

3. 不会生成执行计划并行化

标量值函数通常会导致查询计划的并行化被禁用。SQL Server 优化器会倾向于将使用标量函数的查询设计为单线程执行,这在处理大量数据时,会显著降低性能。

4. 隐藏了真正的计算成本

标量值函数中的操作很容易被忽略,因为它们的执行是隐藏在函数调用中的。这使得查询执行时间的分析和调优变得更加困难。使用标量函数时,开发者可能低估了计算成本,从而导致性能问题。

5. 带来额外的上下文切换开销

标量值函数在执行时会频繁地在 SQL Server 的上下文和函数自身的上下文之间进行切换。每次调用函数时都需要这种开销,在处理大量数据时,这种开销会被放大,从而影响查询性能。

替代方案

为了避免标量值函数的性能问题,可以考虑以下替代方案:

使用内联表值函数(ITVF):它们的性能更好,因为优化器可以将它们直接嵌入到主查询中进行优化。

将计算逻辑直接写在查询中:将简单的计算逻辑内联到查询中,避免使用函数封装。

使用计算列(Computed Columns):对于简单的计算,可以在表中创建计算列,并根据需要为其创建索引。

使用 CASE 语句:对于简单的条件判断,CASE 语句可以替代标量函数,实现相同的逻辑。


http://www.ppmy.cn/ops/126268.html

相关文章

Android——通过MediaStore查询图片

查询图片: private void loadImageList() {String[] columns new String[]{MediaStore.Images.Media._ID, // 编号MediaStore.Images.Media.TITLE, // 标题MediaStore.Images.Media.SIZE, // 文件大小MediaStore.Images.Media.DATA, // 文件路径};Cursor cursor g…

UI自动化测试示例:python+pytest+selenium+allure

重点应用是封装、参数化: 比如在lib文件夹下,要存储封装好的方法和必要的环境变量(指网址等) 1.cfg.py:封装网址和对应的页面 SMP_ADDRESS http://127.0.0.1:8234SMP_URL_LOGIN f{SMP_ADDRESS}/login.html SMP_URL_DE…

Centos7系统Python3.11.2版本安装

开发依赖包需要依赖于python3.11环境,但是目前python环境为3.6,于是需要修改python环境为3.11版本 Python 3.11 在 CentOS 7 中没有。我们将从源代码安装它 安装依赖包 yum -y update systemctl reboot yum -y install epel-release yum install wget…

SQL INNER JOIN:深入解析与实际应用

SQL INNER JOIN:深入解析与实际应用 引言 在关系型数据库管理系统中,SQL(Structured Query Language)是一种用于管理和操作数据库的标准编程语言。SQL INNER JOIN 是一种常用的查询技术,用于结合两个或多个数据库表中的相关行。本文将深入探讨 SQL INNER JOIN 的概念、语…

公寓智能水电系统

什么是公寓智能水电系统?简单来说,公寓智能水电系统是一种利用物联网技术,将家中的水电设备连接起来,实现远程监控与自动控制的解决方案。它包括但不限于智能电表、智能水表、漏电保护器、漏水检测器等硬件设施,以及配套的软件平…

根据Vue对比来深入学习React 下 props 组件传值 插槽 样式操作 hooks 高阶组件 性能优化

文章目录 函数组件的特点props组件间的传值父传子看上例子传父兄弟组件传值祖先组件传值 插槽基础插槽具名插槽作用域插槽 样式操作**CSS Modules** 生命周期useRef常用hookuseStateuseEffectuseContextuseReduceruseMemouseCallback 高阶组件什么时候使用 react性能问题和优化…

iOS 18升级:避免常见陷阱,顺利完成升级

随着iOS 18的发布,许多用户都希望尽快体验到新系统带来的新功能和改进。然而,升级过程可能会因为准备工作不足或对步骤的不熟悉而变得复杂。本文旨在为用户提供一个清晰的升级指南,确保升级过程既平滑又安全。 升级前的准备工作 在开始升级之…

力扣(leetcode)每日一题 3158 求出出现两次数字的 XOR 值 |位运算

3158. 求出出现两次数字的 XOR 值 题干 给你一个数组 nums ,数组中的数字 要么 出现一次,要么 出现两次。 请你返回数组中所有出现两次数字的按位 XOR 值,如果没有数字出现过两次,返回 0 。 示例 1: **输入&#…