SQL Server 效能優化之——T-SQL TVF和標量函式
上一篇介紹了關於“臨時表、表變數和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
上班時間到了!
期待下一篇吧!
任何的優化的不是絕對的,只有適應自己環境才是最好的,效能測試是必要。