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,要确保临时表有聚集索引或非聚集索引。

那么,采用什么办法避免使用临时表和表变量呢?

  1. CTE表达式(Common Table Expression, CTE
  2. 子查询
  3. 在数据库架构中创建物理表,而不是在历史数据库中创建临时表。
  4. 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

期待下一篇吧!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

Entity Framework Core 实现MySQL 的TimeStamp/RowVersion 并发控制

将通用的序列号生成器库 从SQL Server迁移到Mysql 遇到的一个问题,就是TimeStamp/RowVersion并发控制类型在非Microsoft ...

36180
来自专栏各种机器学习基础算法

常用但容易忘记的sql语句(sql server为主)

1.说明:随机取出10条数据 a.Sql Server: select top 10 * from tablename order by newid() b....

30890
来自专栏转载gongluck的CSDN博客

ADO大总结

内容比较乱,作为草稿,对现有的ado数据库操作函数方法进行汇总。 小函数 m_pRecordset->RecordCount//取得记录数量 全局变量 #i...

31080
来自专栏.NET技术

经典SQL语句大全之基础

1、说明:创建数据库 CREATE DATABASE database-name

15510
来自专栏大数据和云计算技术

MongoDB系列6:MongoDB索引的介绍

1、前言 和关系型数据库一样,MongoDB的索引可以提高查询执行效率。索引就好比书中的目录,可以快速定位书中某一页。适当的索引查询,优化器可以快速地返回结果集...

550100
来自专栏逸鹏说道

SQL Server 索引和表体系结构(包含列索引)

包含列索引 概述 包含列索引也是非聚集索引,索引结构跟聚集索引结构是一样,有一点不同的地方就是包含列索引的非键列只存储在叶子节点;包含列索引的列分为键列和非键列...

33680
来自专栏乐沙弥的世界

SQL基础--> 序列(SEQUENCE)、同义词(SYNONYM)

--=============================================

10720
来自专栏恰童鞋骚年

《MSSQL2008技术内幕:T-SQL语言基础》读书笔记(下)

  所谓透视(Pivoting)就是把数据从行的状态旋转为列的状态的处理。其处理步骤为:

14820
来自专栏xcywt

学习SQLite之路(四)

20160621 更新 参考: http://www.runoob.com/sqlite/sqlite-tutorial.html 1. SQLite   a...

23280
来自专栏云霄雨霁

数据库完整性总结

20140

扫码关注云+社区

领取腾讯云代金券