zoukankan      html  css  js  c++  java
  • 程序员眼中的 SQL Server-执行计划教会我如何创建索引?

    以前有 DBA 在身边的时候,从来不曾考虑过数据库性能的问题,但是,当一个应用程序从头到脚都由自己完成,而且数据库面对的是接近百万的数据,看着一个页面加载速度像乌龟一样,自己心里真是有种挫败感。代码的优化问题,这是属于程序员的职责范围之内,对于我来说,这一方面比较好探查些,因为都是自己熟悉的,用 EF 或 SQL Server Profiler 跟踪一下程序代码产生的 SQL,如果有问题,直接优化程序代码就可以了,如果 SQL 没问题,那就得优化数据库了,对于我来说,这是一个无人区。

    前两天,自己瞎搞了一个测试:程序员眼中的 SQL Server-非聚集索引能给我们带来什么?,因为对索引不是很熟悉,所以测试得到结果没有任何价值,甚至有些误导人,这边说声抱歉,在哪跌倒在哪爬起来。

    应用场景

    还是用商品表(Product)作为示例,表结构如下:

    存在这样一种业务场景:获取某个供应商(ProviderID),状态为已售(State 为 1)的商品列表,排序方式为生产日期(ProduceTime)降序,有可能我们应用程序在显示数据的时候用到分页,这边我们查询前 100 行。翻译为 SQL 代码:

    SELECT TOP 100 
    [ID],
    [Name],
    [Remarks],
    [ProviderID],
    [ProduceTime],
    [State]
    FROM [TestDB].[dbo].[Product]
    WHERE [ProviderID]=1 AND [State]=1
    ORDER BY [ProduceTime] DESC

    上面这个业务场景,在我们一般的应用程序中基本上都会遇到,有时候数据量不是很大的时候,我们一般不会做任何数据库优化,但是你看了下面的实践,你是否应该考虑下,为你现在的数据库加个索引呢?

    SQL Server 执行计划

    SQL Server 执行计划,是我们分析 SQL 执行情况的一大利器,通过它,我们也可以很方面的查看索引的执行,在实践之前,需要了解一些必备技能,以下知识点摘自-看懂 SqlServer 查询计划

    SQL Server 有二种索引:聚集索引和非聚集索引。二者的差别在于:【聚集索引】直接决定了记录的存放位置, 或者说:根据聚集索引可以直接获取到记录。【非聚集索引】保存了二个信息:1.相应索引字段的值,2.记录对应聚集索引的位置(如果表没有聚集索引则保存记录指针)。 因此,如果能通过【聚集索引】来查找记录,显然也是最快的。

    SQL Server 会有以下方法来查找您需要的数据记录:

    1. 【Table Scan】:遍历整个表,查找所有匹配的记录行。这个操作将会一行一行的检查,当然,效率也是最差的。
    2. 【Index Scan】:根据索引,从表中过滤出来一部分记录,再查找所有匹配的记录行,显然比第一种方式的查找范围要小,因此比【Table Scan】要快。
    3. 【Index Seek】:根据索引,定位(获取)记录的存放位置,然后取得记录,因此,比起前二种方式会更快。
    4. 【Clustered Index Scan】:和【Table Scan】一样。注意:不要以为这里有个Index,就认为不一样了。 其实它的意思是说:按聚集索引来逐行扫描每一行记录,因为记录就是按聚集索引来顺序存放的。 而【Table Scan】只是说:要扫描的表没有聚集索引而已,因此这二个操作本质上也是一样的。
    5. 【Clustered Index Seek】:直接根据聚集索引获取记录,最快!

    所以,当发现某个查询比较慢时,可以首先检查哪些操作的成本比较高,再看看那些操作在查找记录时, 是不是【Table Scan】或者【Clustered Index Scan】,如果确实和这二种操作类型有关,则要考虑增加索引来解决了。 不过,增加索引后,也会影响数据表的修改动作,因为修改数据表时,要更新相应字段的索引。所以索引过多,也会影响性能。 还有一种情况是不适合增加索引的:某个字段用0或1表示的状态。例如可能有绝大多数是1,那么此时加索引根本就没有意义。 这时只能考虑为0或者1这二种情况分开来保存了,分表或者分区都是不错的选择。

    应用分析

    我们先不建任何索引(除了主键 ID 的聚集索引),来看一下上面 SQL 代码,在 SQL Server 执行计划中的执行情况:

    可以看到,查询开销基本上被 SORT 霸占了,看到这种情况,按照正常的思维,我们首先考虑的是为 ProduceTime 创建一个非聚集索引,然后按照 DESC 排序,但有时候我们要沉下心思考一下,是不是用 ID 排序会更好呢?因为在 Product 表中,ID 为自增字段,ProduceTime 在添加的时候获取的是当前时间,在 SQL 排序中,其实 ID 和 ProduceTime 的排序效果是一样的,但是执行性能方面确实天壤之别,我们看一下执行计划就知道了:

    从上面的执行计划中,我们可以很直观的看出差别,所以在写 SQL 的时候,一定要慎重啊,这边为了方便展示,我们还是以 ProduceTime 字段进行排序,按照 ID 排序,虽然没有了 SORT 性能开销,但是发现查询记录为“Clustered Index Scan”,这是全表查询的意思,我们理想的应该是“Index Seek”或者“Clustered Index Seek”,因为这种是按照索引查询,速度最快。按照我们程序员的理解,应该创建一个非聚集索引,比如下面 IX_Product_Provider_State 索引:

    创建好之后,我们再来执行一下 SQL 代码:

    “Key Lookup(Clustered)”记录,其实还是全表进行查找,默认通过聚集索引(PK_Product),我们可能会有疑问,索引就是按照查询及排序方式创建的啊,为什么还是这种情况?这时候我们看一下 SELECT 后面的字段就知道了,我们查询显示的是 Product 表中所有字段,但是 IX_Product_Provider_State 非聚集索引,只是针对的查询条件字段,并没有吧查询显示字段包含进来,在创建索引窗口中,“索引键 列” TAB 的旁边有个“包含性 列”,我们把其他显示字段加进来,看下执行效果:

    “Index Seek”,这就是我们想要的效果,其实关于索引的创建有很多的现实问题,比如组合字段索引和单个字段索引有何不同?就像上面示例中的查询用例,如果 ProduceTime 排序在其他查询条件中也存在,是不是应该拉出来创建一个索引?还是像上面一样,和查询条件一起创建一个组合字段索引?还有一种情况就是,在一个应用程序查询中,存在单个字段的查询,也存在组合字段的查询,那这时候我们是创建单个字段索引?还是创建组合字段索引呢?这几个问题,你创建一下索引,然后用“ SQL 执行计划”试试就知道了。

    总结

    针对上面的查询用例,我个人觉得,最好的方案是:排序字段使用 ID,按照实际应用场景,提取出需要查询的字段,避免 SELECT *,这样会减少在添加“包含性 列”的字段,创建 IX_Product_Provider_State 非聚集索引,索引字段为:ProviderID 和 State,如果 State 的值不是多变的(比如值为 1 和 0),尽量不要创建 State 字段的非聚集索引。

    做完这些,你会发现,你的应用程序像飞的一样。

    ps:我要飞得更高。。。

    参考资料:

  • 相关阅读:
    HDU 1102 Constructing Roads
    HDU 1285 确定比赛名次。
    最小生成树 HDU 各种畅通工程的题,prim和kru的模板题
    HDU Jungle Roads 1301 最小生成树、
    并查集小结(转)
    HDU hdu 2094 产生冠军 拓扑排序 判定环
    模运算(转)
    拓扑排序(主要是确定环和加法) HDU 2647 Reward
    HDU 1372 Knight Moves 简单BFS
    用计算机模型浅析人与人之间沟通方式 (一)如何谈话
  • 原文地址:https://www.cnblogs.com/firstdream/p/7274125.html
Copyright © 2011-2022 走看看