SQL Server 性能调优 之执行计划(Execution Plan)调优

执行计划中的三种 Join 策略

SQL Server 存在三种 Join 策略:Hash Join,Merge Join,Nested Loop Join。

Hash Join:用来处理没有排过序/没有索引的数据,它在内存中把 Join 两边数据(的关联key)分别建立一个哈希表。例如有以下的查询语句,关联的两张表没有建立索引,执行计划将显示为Hash Join。

    SELECT  
      sh.*  
    FROM  
      SalesOrdHeaderDemo AS sh  
    JOIN  
      SalesOrdDetailDemo AS sd  
    ON  
      sh.SalesOrderID=sd.SalesOrderID  
    GO  

Merge Join:用来处理有索引的数据,它比Hash Join轻量化。我们为前面两张表的关联列建立索引,然后再次上面的查询,执行计划将变更为Merge Join

    CREATE UNIQUE CLUSTERED INDEX idx_salesorderheaderdemo_SalesOrderID ON SalesOrdHeaderDemo (SalesOrderID)  
    GO  
    CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderlID ON SalesOrdDetailDemo (SalesOrderID,SalesOrderDetailID)  
    GO  

Nested Loop Join:在满足Merge Join的基础上,如果某一边的数据较少,那么SQL Server 会把数据较少的那个作为外部循环,另一个作为内部循环来完成Join处理。继续前面的例子为查询语句加上WHERE语句来减少 Join 一边的数据量,执行计划显示为Nested Loop Join。

    SELECT  
      sh.*  
    FROM  
      SalesOrdHeaderDemo AS sh  
    JOIN  
      SalesOrdDetailDemo AS sd  
    ON  
      sh.SalesOrderID=sd.SalesOrderID  
    WHERE  
      sh.SalesOrderID=43659  

执行计划中的(table/index scan)的改进

在许多场合我们需要在一张包含许多数据的表中提取出一小部分数据,此时应当避免Scan,因为扫描处理会遍历每一行,这是相当耗时耗力的。下面我们来看一个例子:

    SELECT  
      sh.SalesOrderID  
    FROM  
      SalesOrdHeaderDemo AS sh  
    JOIN  
      SalesOrdDetailDemo AS sd  
    ON  
      sh.SalesOrderID=sd.SalesOrderID  
    WHERE  
      sh.OrderDate='2005-07-01 00:00:00.000'  
    GO  

这里写图片描述

图中的红圈标出了table scan,并且执行计划也智能得建议建立索引。我们先尝试在SalesOrdHeader 表上建立一个索引:

    CREATE UNIQUE CLUSTERED INDEX idx_salesorderheaderdemo_SalesOrderID ON SalesOrdHeaderDemo (SalesOrderID)  
    GO  

然后再次执行相同的查询语句,执行计划变成以下的模样:
这里写图片描述

table scan 变为了 Index Scan,继续给另一张表也加上索引:

    CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderlID ON SalesOrdDetailDemo (SalesOrderID,SalesOrderDetailID)  
    GO  

执行计划发生以下的变化:

这里写图片描述

虽然不能说 Scan 比 Seek 差,但绝大多数的场合(尤其是在许多数据中查找少量数据时)Seek 是更好的选择。举例来说如果你有一个上亿条数据的表,你要取其中的100条,那么你应当保证其采用 Seek,但如果你需要取出其中绝大多数(比如95%)的数据时,Scan 可能更好。(有较权威的文章给出了这个阀值为30%,即取出超过30%数据时 scan 更高效;反之则 Seek 更好)

另外你可能注意到两张表上都建立了索引但一张表在执行计划中表现为 Clustered index scan,而另一张表现为 Clustered index seek,我们期待的不是两个 Clustered index seek 吗?这是因为前一张表没有断言(predicate),而后一张表通过 ON 关键字对SalesOrderID 进行了断言限制。

执行计划中的 Key Lookup

为了后续的示例,我们先在同一张表上建立两个不同的索引:

    CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderlID ON SalesOrdDetailDemo (SalesOrderID,SalesOrderDetailID)  
    GO  
    CREATE NONCLUSTERED INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo(ModifiedDate)  
    GO  

执行以下的查询:

SELECT  
  ModifiedDate  
FROM SalesOrdDetailDemo  
  WHERE ModifiedDate='2005-07-01 00:00:00.000'  
GO 

执行计划如下图,他利用了我们先前建立在 ModifiedDate 字段上的 Non-Clustered Index,生成为一个Index Seek 处理。

这里写图片描述

我们改造一下查询语句,SELECT 中多加两个字段:

SELECT  
  ModifiedDate,  
  SalesOrderID,  
  SalesOrderDetailID  
FROM SalesOrdDetailDemo  
WHERE ModifiedDate='2005-07-01 00:00:00.000'  
GO 

执行计划如下图,基本没变:

这里写图片描述

上面选出的字段不是属于 Non-Clustered Index 就是属于 Clustered Index,如果再增加几个其他的字段呢?

    SELECT  
      ModifiedDate,  
      SalesOrderID,  
      SalesOrderDetailID,  
      ProductID,  
      UnitPrice  
    FROM SalesOrdDetailDemo  
    WHERE ModifiedDate='2005-07-01 00:00:00.000'  
    GO  

乖乖,执行计划一下多了两个处理(Key Lookup, Nested Loop):

这里写图片描述

Key Lookup 是一个繁重的处理,我们可以使用关键字 WITH 来指定使用 Clustered Index,以此回避Key Lookup。

    SELECT  
      ModifiedDate,  
      SalesOrderID,  
      SalesOrderDetailID,  
      ProductID,  
      UnitPrice  
    FROM SalesOrdDetailDemo WITH(INDEX=idx_SalesDetail_SalesOrderlID)  
    WHERE ModifiedDate='2005-07-01 00:00:00.000'  
    GO  

执行计划应声而变成为一个 Clustered Index Scan:
这里写图片描述

前文提过 Scan 似乎也不是一个很好的处理,那么矮子里拔高个,使用 SET STATISTICS IO ON 来比较一下:

    SET STATISTICS IO ON  
    GO  

    SELECT  
      ModifiedDate,  
      SalesOrderID,  
      SalesOrderDetailID,  
      ProductID,  
      UnitPrice  
    FROM SalesOrdDetailDemo  
    WHERE ModifiedDate='2005-07-01 00:00:00.000'  
    GO  

    SELECT  
      ModifiedDate,  
      SalesOrderID,  
      SalesOrderDetailID,  
      ProductID,  
      UnitPrice  
    FROM SalesOrdDetailDemo WITH(INDEX=idx_SalesDetail_SalesOrderlID)  
    WHERE ModifiedDate='2005-07-01 00:00:00.000'  
    GO  

    SELECT  
      ModifiedDate,  
      SalesOrderID,  
      SalesOrderDetailID,  
      ProductID,  
      UnitPrice  
    FROM SalesOrdDetailDemo WITH(INDEX=idx_non_clust_SalesOrdDetailDemo_ModifiedDate)  
    WHERE ModifiedDate='2005-07-01 00:00:00.000'  
    GO  

这里写图片描述

比较下来,采用了 clustered index 的查询表现最差,另外 SET STATISTICS IO 输出的数据中clustered index 的查询在 logical reads 上花费了更多的时间。

看起来采用 non-clustered index + Key Lookup 执行计划表现还不错,但如果能回避 Key Lookup 就完美了,我们来把 non-clustered index 修改一下,用 INCLUDE 关键字在索引中包含其他的字段:

    DROP INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo  
    GO  
    CREATE NONCLUSTERED INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo(ModifiedDate)  
    INCLUDE  
    (  
      ProductID,  
      UnitPrice  
    )  
    GO  

    -- 清下缓存,仅用于开发环境!  
    DBCC FREEPROCCACHE  
    DBCC DROPCLEANBUFFERS  
    GO  

再次执行之前的查询:

SELECT  
  ModifiedDate,  
  SalesOrderID,  
  SalesOrderDetailID,  
  ProductID,  
  UnitPrice  
FROM SalesOrdDetailDemo  
WHERE ModifiedDate='2005-07-01 00:00:00.000'  
GO 

这里写图片描述

这下完美了,因为我们的查询字段都包含在索引中,所以执行计划最终被优化为 Index Seek。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 好的,以下是一个简单的 SQL Server Execution Plan 示例: 假设有一个名为“Orders”的表,其中包含订单信息。我们想要查看最近30天内的所有订单,并按订单日期排序。以下是一个SQL查询和它的Execution Plan: ``` SELECT OrderID, OrderDate, CustomerID, TotalAmount FROM Orders WHERE OrderDate >= DATEADD(day, -30, GETDATE()) ORDER BY OrderDate DESC ``` Execution Plan如下: ``` |--Sort(ORDER BY:([Orders].[OrderDate] DESC)) |--Clustered Index Scan(OBJECT:([Orders].[PK_Orders] AS [Orders])) ``` 这个Execution Plan的第一行是一个Sort操作,它使用了ORDER BY子句指定的列进行排序。第二行是一个Clustered Index Scan操作,它从表中检索数据并扫描索引。我们可以看到,这个Execution Plan使用了表的主键索引(PK_Orders)来扫描数据。 总的来说,这个Execution Plan的步骤是:使用主键索引扫描数据,然后按照OrderDate列的值进行排序。最后,将结果返回给用户。 ### 回答2: Execution Plan执行计划)是SQL Server中的一个非常重要的概念,它是由查询优化器生成的一组指令,用于确定如何执行查询语句以获取最佳性能。下面是一个使用Execution Plan来编写SQL Server的演示过程: 1. 首先,我们创建一个简单的表来模拟数据表,在表中插入一些数据: ```sql CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, Name NVARCHAR(100), Department NVARCHAR(50), Salary DECIMAL(18, 2) ) INSERT INTO Employees VALUES (1, 'John Doe', 'Sales', 5000) INSERT INTO Employees VALUES (2, 'Jane Smith', 'Marketing', 6000) INSERT INTO Employees VALUES (3, 'Mike Johnson', 'IT', 7000) ``` 2. 接下来,我们编写一个简单的查询语句,用于查询工资低于6000的员工: ```sql SELECT Name, Department, Salary FROM Employees WHERE Salary < 6000 ``` 3. 在SQL Server Management Studio(SSMS)中打开一个新的查询窗口,粘贴以上查询语句。 4. 在查询窗口中,点击工具栏上的"显示执行计划"按钮,或者使用快捷键Ctrl+M来启用执行计划。 5. 执行查询语句,SSMS将生成并显示查询的执行计划执行计划中包含多个操作符,例如扫描表(Table Scan)、索引扫描(Index Scan)、排序(Sort)等等。这些操作符代表了在执行查询时涉及到的不同操作。 6. 阅读执行计划,可以了解查询的执行流程,以及每个操作的代价估算、实际数据行数等信息。特别是,可以观察到是否存在性能瓶颈或者优化的空间。 通过使用Execution Plan,我们可以更好地了解查询语句SQL Server中的执行情况,以便进行性能优化。除了简单的查询语句Execution Plan还可以用于复杂查询的优化、索引的设计和优化以及其他数据库相关的性能问题的调优。同时,根据Execution Plan可以判断数据库是否需要优化或者重构,以提高系统的性能和响应速度。 ### 回答3: Execution Plan执行计划)对于SQL Server性能优化非常重要。它可以帮助我们了解查询语句在数据库中的执行过程,并且提供了优化性能的建议。 下面是使用Execution Plan编写SQL Server的一个demo的示例: 首先,假设我们有一个名为"Employees"的表,包含员工的信息,包括员工ID、姓名、部门ID等字段。我们的目标是查询所有部门为部门ID为1的员工的姓名。 我们可以使用以下SQL查询语句来实现这个目标: ```sql SELECT Name FROM Employees WHERE DepartmentID = 1; ``` 接下来,在SQL Server Management Studio中,我们打开查询窗口并输入以上查询语句。然后点击工具栏上的"Include Actual Execution Plan"按钮,执行查询。 在结果窗口下方的"Execution Plan"选项卡中,我们可以看到查询的执行计划执行计划通常以图形化的方式呈现,包括各个操作符、数据流等。我们可以通过查看执行计划,了解查询的执行顺序、操作符的代价估算等信息,以及是否存在性能瓶颈。 在我们的示例中,执行计划显示了一个简单的查询计划,表明查询使用了索引扫描(Index Scan)操作符来获取结果。 此外,执行计划还可以提供一些性能优化的建议。例如,如果我们的查询中存在缺失的索引,执行计划会建议我们创建适当的索引来提高查询性能。 因此,在使用Execution Plan编写SQL Server的demo时,我们可以通过查看执行计划来了解查询的执行情况,并根据其建议来优化查询性能。这是一个非常有用的工具,能够帮助我们进行SQL Server性能调优

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值