SQL Server 效能優化之——T-SQL 臨時表、表變數、UNION
這次看一下臨時表,表變數和Union命令方面是否可以被優化呢?
一、臨時表和表變數
很多資料庫開發者使用臨時表和表變數將程式碼分解成小塊程式碼來簡化複雜的邏輯。但是使用這個的後果就是可能帶來效能的損害
1. 對I/O子系統的影響 (儲存區域網路SAN 或邏輯儲存),這是由於增加了頁和頁I/O閂鎖等待,這樣等待被認為是最差的等待,這也可能會增加臨時資料庫的密集競爭進而導致高分配請求,最後可能出現全域性分配對映頁(GAM)、共享全域性對映頁(SGAM)或可用空間(PFS)癱瘓。
- 全域性分配對映頁(Global Allocation Map, GAM)用於跟蹤區的使用情況,每個GAM頁可以跟蹤64000個區或者說4GB的資料。在GAM頁中,如果某個位值為0,則表示它所對應的區已經分配給了某個物件使用,值為1時表示這個區是空閒的。
- 共享全域性分配對映頁(Shared Global Allocation Map, SGAM)功能和GAM是一樣的,所不同的就是SGAM是用來管理混合區的。不過它的點陣圖對映關係正好是相反的:在GAM中設定為1的,在SGAM中設定為0——用於代表一個空閒的區。
- 頁可用空間(Page Free Space, PFS),這種頁記錄了某個頁是否分配給了某個物件,並且記錄這個頁上有多少可用的空間,點陣圖對映值可顯示一個頁的使用率是50%、85%、95%或是95%以上。SQL Server根據這個資訊來決定是否要給一行資料分配新的空間
2. 影響CPU利用率,這是由於Cxpacket在索引不足的臨時資料庫上等待結果,如果臨時表有聚集索引和非聚集索引,這樣的現象可以被減緩。
因此,最好有限的使用臨時表。
在必須使用臨時表的情況下,可以參照一下預防措施:
- 使用臨時表(create table #Temp)而不是使用表變數(Declare @table table),這樣做的原因是可以在臨時表上使用索引。
- 使用臨時表時,用小型資料量的小表來限制性能影響。
- 如果臨時表中使用inner join , group by , order by 或 where,要確保臨時表有聚集索引或非聚集索引。
那麼,採用什麼辦法避免使用臨時表和表變數呢?
- CTE表示式(Common Table Expression, CTE)
- 子查詢
- 在資料庫架構中建立物理表,而不是在歷史資料庫中建立臨時表。
- SQL Server 2008以後,表引數是可以用的。
例子 :
首先,在新資料庫MyDemo中建立新表
1: --建立新表
2: use MyDemo
3: CREATE TABLE [dbo].[Employees](
4: [empid] [int] IDENTITY(1,1) NOT NULL,
5: [empname] [nvarchar](100) NULL,
6: [deptid] [int] NULL,
7: [Salary] [float] NULL,
8: CONSTRAINT [PK_Employees] PRIMARY KEY CLUSTERED
9: ( [empid] ASC )
10: WITH
11: (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
12: ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
13: ) ON [PRIMARY]
14: GO
15: CREATE TABLE [dbo].[Departments](
16: [deptid] [int] IDENTITY(1,1) NOT NULL,
17: [deptname] [nchar](10) NULL,
18: CONSTRAINT [PK_Departments] PRIMARY KEY CLUSTERED
19: ( [deptid] ASC )
20: WITH
21: (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
22: IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] )
23: ON [PRIMARY]
24: GO
使用表變數:
1: alter procedure Performance_Issue_Table_Variables
2: as
3: begin
4: SET NOCOUNT ON;
5: declare @table table(empid int, empname varchar (25),Department varchar (25) ,Salary int)
6: insert into @table select S.empid,S.empname,T.deptname,S.salary from Employees s inner join Departments T ON S.deptid =T.deptid
7: SELECT COUNT (empid) ,Department,Salary FROM @table GROUP BY Department,Salary HAVING Salary>2000
8: end
使用臨時表:
1: Create procedure Performance_Issue_Table_Variables
2: as
3: begin
4: SET NOCOUNT ON;
5: create table #table (empid int, empname varchar (25),Department varchar (25) ,Salary int)
6: create clustered index #table_index1 on #table (empid asc )
7: create nonclustered index #table_index2 on #table (Salary) include (Department,empid )
8: insert into #table select S.empid,S.empname,T.deptname,S.salary from Employees s
9: inner join Departments T ON S.deptid =T.deptid
10: SELECT COUNT (empid) ,Department,Salary FROM #table GROUP BY Department,Salary HAVING Salary>2000
11: DROP TABLE #table
12: end
使用CTE表示式:
1: Create procedure Performance_Solution_CTEexpression
2: as
3: begin
4: SET NOCOUNT ON;
5: With temp as
6: (
7: select S.empid,S.empname,T.deptname as Department,S.salary from Employees s inner
8: join Departments T ON S.deptid =T.deptid
9: )
10: SELECT COUNT (empid) ,Department,Salary FROM temp GROUP BY Department,Salary HAVING Salary>2000
11: end
使用表引數 表引數可通過三個步驟實現 第一,建立一個新的資料表:
1: create type Specialtable as table
2: (EmployeeID int NULL,
3: EmployeeName Nvarchar (50) Null )
接下來,建立儲存過程,並接受這個表所謂引數輸入:
1: create procedure Performance_Solution_Table_Paramters @Temptable Specialtable Readonly
2: as
3: begin
4: select * from @Temptable
5: end
6: Finally, execute the stored procedure :
7: declare @temptable_value specialtable
8: insert into @temptable_value select '1','Jone' union select '2', 'Bill'
9: exec dbo.SP_Results @temptable=@temptable_value
使用子查詢
1: Create procedure Performance_Solution_SubQuery
2: as
3: begin
4: SET NOCOUNT ON;
5: SELECT COUNT (empid) ,S.Department,Salary FROM
6: (select S.empid,S.empname,T.deptname as Department,S.salary from Employees s inner join Departments T ON S.deptid =T.deptid) S
7: GROUP BY Department,Salary HAVING Salary>2000
8: end
使用物理表
1: create table schema_table (empid int, empname varchar (25),Department varchar (25) ,Salary int)
2: create clustered index schema_table_index1 on schema_table (empid asc )
3: create nonclustered index schema_table_index2 on schema_table (Salary) include (Department,empid )
4: insert into schema_table select S.empid,S.empname,T.deptname,S.salary from Employees s inner join Departments T ON S.deptid =T.deptid
5: go
6: Create procedure Performance_Solution_PhysicalTables
7: as
8: begin
9: SET NOCOUNT ON;
10: SELECT COUNT (empid) ,Department,Salary FROM schema_table GROUP BY Department,Salary HAVING Salary>2000
11: end
二、本次的另一個重頭戲UNION 命令
使用Union命令,和使用臨時表一樣,會影響I/O子系統(如,頁和頁I/O閂鎖等待)。但是很多資料庫開發者仍然使用Union命令處理複雜的業務邏輯。
選擇/改善Union :
· 使用Case When 子句代替,它們可以做聚合和詳細的查詢
· 使用動態查詢:用強大的sp_executesq來節省每次執行查詢執行計劃,節省時間消耗。儲存過程中使用If Else 語句決定查詢語句適合的一組引數,這樣可以根據傳入儲存過程的引數控制Union的數量。
· 選擇排序語句內使用Union,使用輕量級的選擇查詢減少重量級的選擇查詢消耗的頁閂鎖等待。
例子:
使用效能較差的Union命令:
1: create procedure Poor_Performing_UnionSP
2: as
3: begin
4: SET NOCOUNT ON;
5: select S.empid,S.empname,T.deptname,S.salary from Employees s inner join Departments T ON S.deptid =T.deptid WHERE T.deptid>1 and S.salary>5000
6: UNION
7: select S.empid,S.empname,'Management deparments' as deptname,S.salary from Employees s inner join Departments T ON S.deptid =T.deptid WHERE T.deptid=1 and S.salary >10000
8: end
使用Case When語句:
1: create procedure PerformantSP_Grid_Results_Using_CaseWhen
2: AS
3: BEGIN
4: select S.empid,S.empname,
5: case when T.deptid>1 and S.salary>5000 then T.deptname
6: when T.deptid=1 and S.salary>10000 then 'Management deparments' end as deptname
7: ,S.salary
8: from Employees s inner join Departments T ON S.deptid =T.deptid
9: END
10: GO
使用Union獲得聚合結果:
1: create procedure Poor_Performing_Union_Aggregate_Results
2: as
3: begin
4: SET NOCOUNT ON;
5: select count (S.empid)as Employee_count,T.deptname,S.salary from Employees s
6: inner join Departments T
7: ON S.deptid =T.deptid WHERE T.deptid>1 and S.salary>10000 group by T.deptname,S.salary
8: en
使用Case When獲得集合結果:
1: create procedure PerformantSP_Aggregate_Results_Using_CaseWhen
2: as
3: begin
4: SET NOCOUNT ON;
5: select sum (case when T.deptid>1 and S.salary>10000 then 1 else 0 end)
6: as Employee_count2
7: ,T.deptname,S.salary
8: from Employees s inner join Departments T ON S.deptid =T.deptid
9: group by T.deptname,S.salary
10: end
期待下一篇吧!