這次看一下臨時表,表變量和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: end
使用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
期待下一篇吧!