SQL Server 性能優化之——T-SQL TVF和標量函數


閱讀導航

1. TVF(-值行數Table-Valued Functions)

        a. 創建TVF

        b. 使用TVF的低性能T-SQL

        c. 使用臨時表代替TVF

2. 標量函數

3. 替代標量函數

    1). 臨時表

    2). 持久化確定的計算列

    3). 使用計划更新工作

        a. 創建標量函數

        b. 使用臨時表替換標量函數

        c. 使用持久化確定的計算列

        d. 使用計划工作代替標量函數

 

上一篇介紹了關於“臨時表、表變量和Union優化”這次轉向關注定義函數——也就是表-值函數、標量函數。

UDF(用戶定義函數,User defined Function)對於集中精力處理業務邏輯很方便,因為可以在UDF中指定一組業務邏輯,其中可以設計多個存儲過程和一些特定的查詢語句。但是,由於UDF對CPU的大量請求可能導致性能下降

 

1. TVF(-值行數Table-Valued Functions)

一般情況,當使用TVF與一個對象內聯接,如果該對象沒有索引將會導致TVF像索引掃描或表掃描一樣做掃描操作。

作為一個選擇,可以創建臨時表,臨時表上創建適當的聚集索引或非聚集索引。

詳情如下:

  • 創建適當的臨時表。
  • 根據T-SQL創建適當的聚集索引和非聚集索引。
  • 將TVF的數據插入到臨時表中。
  • 用臨時表和相關的列替換每一個TVF。
  • 在查詢語句執行結束后,刪除臨時表。

                注意,臨時表的性能提升是超過表參數,在上一篇博客中提到的,表參數不支持索引。

例子:

a. 創建TVF:

   1: use [MyDemo]
   2: go
   3: alter FUNCTION Dep_Salaries1
   4: (
   5: @empid int
   6: )
   7: RETURNS @table table
   8: (
   9: Department int,
  10: Salary_Max int,
  11: Salary_Min int
  12: )
  13: AS
  14: BEGIN
  15: declare @Department int = (select S.deptid from Employees s where s.empid=@empid)
  16: insert into @table
  17: SELECT S.deptid , max (Salary) , MIN(Salary) FROM Employees s inner join Departments T ON S.deptid =T.deptid group by S.deptid having S.deptid =@Department
  18: RETURN
  19: END
  20: GO

b. 使用TVF的低性能T-SQL:

   1: alter procedure Unperformant_SP1
   2: @empid int
   3: as
   4: begin
   5: select T.deptid as department_name , s.* from Dep_Salaries1 (@empid )S inner join Departments T ON S.Department =T.deptid
   6: end

 

c. 使用臨時表代替TVF:

   1: go
   2: alter procedure Performant_SP1
   3: @empid int
   4: as
   5: begin
   6: create table #table
   7: (
   8: Department int,
   9: Salary_Max int,
  10: Salary_Min int
  11: )
  12: create clustered index #table_index1 on #table (Department)
  13: insert into #table select * from Dep_Salaries1 (@empid )
  14: select T.deptid as department_name , s.* from #table S inner join Departments T ON S.Department =T.deptid
  15: end

在使用具體的查詢和數據時,還是應該進行必要的性能測試,發現最適合自己情況的解決方案。

2. 標量函數

標量函數,對於確定存儲過程或特定查詢語句的聚合值、累計值、差分值非常方便的,但是對性能是有損失的,尤其使用大數據,標量函數將執行每一個記錄。

 

3. 替代標量函數

1). 臨時表

使用臨時表,但是這個解決方案有一點不同於TVF的情況,這里希望完全放棄標量函數並且也不去直接使用內部T-SQL代碼。

2). 持久化確定的計算列

持久化確定的計算列值不是每次選擇都重新計算該列,而只是在創建時計算一次。因此,這時可以添加不同的T-SQL語句提高性能,因為這樣可以減少進程的開銷。

這個功能可以通過下面步驟添加:

  • 增加一個新的計算列存儲標量函數的結果。
  • 啟用這個計算列的持久化功能。
  • 在列(不管是主鍵列還是包含列)上設置適當的索引。

             但是要注意持久化功能還是有一些限制,如:

                   i. 計算列不應該使用任何其他記錄的聚合功能。

                   ii. 計算列不應該使用調用外部系統過程的功能。

                   iii. 計算列不應該使用任何其他表的其他字段的功能。

                   iv. 計算列生成最好是使用系統提供的功能,例如:Convert、Cast、Replace等等,並且開發者不能創建UDF,因為UDF通常和該功能相矛盾。

這僅僅是適用於持久化的功能,但是可以添加計算列索引,應該通過確定計算數據的精確類型(如,INT、 Bigint、 DateTime和decimal)精確列的類型。如果數據類型不精確,可以添加這些列為索引的包含列的一部分,但不是主鍵列的一部分。

3). 使用計划更新工作

如果不可能使用持久化確定的計算列,可以創建普通列並同時創建計划更新工作,更新這些列的標量函數輸出,然后用T-SQL代替標量函數並且在T-SQL中使用這些列。具體如下:

a. 創建標量函數:

   1: use [Workshops]
   2: go
   3: create FUNCTION Salary_Tax
   4: (
   5: @empid int
   6: )
   7: RETURNS float
   8: AS
   9: BEGIN
  10: declare @salary int = (select (S.salary-100) from Employees s where s.empid=@empid)
  11: RETURN @salary
  12: END
  13: GO
  14: --性能低些的標量函數
  15: Select empid ,dbo.Salary_Tax (empid) as 'SalaryWithTax' from Employees

 

b. 使用臨時表替換標量函數:

   1: Create Table #temp (Empid int primary key clustered , Salary_Tax float)
   2: Create nonclustered index #temp_Index1 on #temp (Empid ) include (Salary_Tax )
   3: insert into #temp select Empid ,(Salary-100) as salary_Tax from Employees
   4: select * from #temp

 

c. 使用持久化確定的計算列:

   1: ALTER TABLE dbo.Employees ADD Salary_Tax AS Salary-100 PERSISTED
   2: Create nonclustered index Employees_Index1 on Employees (Empid, Salary_Tax )
   3: select empid ,Salary_Tax from Employees

 

d. 使用計划工作代替標量函數:

   1: ALTER TABLE dbo.Employees ADD Salary_Tax1 float, update_flag bit
   2: ALTER TABLE dbo.Employees ADD CONSTRAINT DF_Employees_update_flag DEFAULT 0 FOR update_flag
   3: Schedule the below DML update by an appropriate frequency according to your workload
   4: Update Employees set Salary_Tax1=Salary-100 WHERE UPDATE_Flag=0
   5: Then you can include the below select query within your stored procedure.
   6: select empid , Salary_Tax1 from Employees

 

上班時間到了!

期待下一篇吧!

任何的優化的不是絕對的,只有適應自己環境才是最好的,性能測試是必要。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM