SQL Server优化器特性-位图过滤(Bitmap)

系统 1699 0
原文: SQL Server优化器特性-位图过滤(Bitmap)

一直以来,由于SQL Server中没有位图索引使得面对一些场景,从业人员在索引选择上受限,饱受诟病.其实熟悉SQL Server的朋友应该知道,SQL Server虽然没有位图索引,但在特定环境下还是会采用位图(Bitmap)过滤的,这次就为大家介绍下SQL Server的位图过滤.

概念:关于位图索引的概念我就不做过多介绍了,感兴趣的朋友可以看下wikipedia

http://en.wikipedia.org/wiki/Bitmap_index

优势:在重复率高,数据很少被更新的场景中(如一年之内的年龄,汽车车型等)过滤高效.

 

SQL Server的位图过滤采用的布隆过滤(bloom filter)方式,这里我简单说下布隆过滤的实现方式.

实现方式:通过构建一个长度X的位数组(bit array)(所有位为0),将要匹配的集合通过哈希函数映射到位数组中的相应点中(相应位为1),当判断一个值是否存在时找bit array中对应位是否为1就可以了.这个过程由SQL Server内部自己完成.

如图1-1所示,我将需要匹配的集合{神仙?,妖怪?,谢谢!}映射到bit array中,当有一条新记录{悟空..}我判断他是否在我的集合中,只需判断相应的位是否是1就可以了,图中可以看出{悟空..}并不是所有位都为1,所以悟空并不在我的集合中.

                                 图1-1

具体到SQL Server中是如何实现的呢?我们还是通过一个实例来看.

测试环境脚本

        
          USE
        
        
           AdventureWorks


        
        
          GO
        
        
          SELECT
        
        
          

    p.ProductID 
        
        
          +
        
         (a.
        
          number
        
        
          *
        
        
          1000
        
        ) 
        
          AS
        
        
           ProductID,

    p.Name 
        
        
          +
        
        
          CONVERT
        
        (
        
          VARCHAR
        
        , (a.
        
          number
        
        
          *
        
        
          1000
        
        )) 
        
          AS
        
        
           Name,

    p.ProductNumber 
        
        
          +
        
        
          '
        
        
          -
        
        
          '
        
        
          +
        
        
          CONVERT
        
        (
        
          VARCHAR
        
        , (a.
        
          number
        
        
          *
        
        
          1000
        
        )) 
        
          AS
        
        
           ProductNumber,

    p.MakeFlag,

    p.FinishedGoodsFlag,

    p.Color,

    p.SafetyStockLevel,

    p.ReorderPoint,

    p.StandardCost,

    p.ListPrice,

    p.Size,

    p.SizeUnitMeasureCode,

    p.WeightUnitMeasureCode,

    p.Weight,

    p.DaysToManufacture,

    p.ProductLine,

    p.Class,

    p.Style,

    p.ProductSubcategoryID,

    p.ProductModelID,

    p.SellStartDate,

    p.SellEndDate,

    p.DiscontinuedDate


        
        
          INTO
        
        
           T1


        
        
          FROM
        
         Production.Product 
        
          AS
        
        
           p


        
        
          CROSS
        
        
          JOIN
        
         master..spt_values 
        
          AS
        
        
           a


        
        
          WHERE
        
        
          

    a.type 
        
        
          =
        
        
          '
        
        
          p
        
        
          '
        
        
          AND
        
         a.
        
          number
        
        
          BETWEEN
        
        
          1
        
        
          AND
        
        
          50
        
        
          GO
        
        
          SELECT
        
        
           

    ROW_NUMBER() 
        
        
          OVER
        
        
           

    (

        
        
        
          ORDER
        
        
          BY
        
        
           

            x.TransactionDate,

            (
        
        
          SELECT
        
        
          NEWID
        
        
          ())

    ) 
        
        
          AS
        
        
           TransactionID,

    p1.ProductID,

    x.TransactionDate,

    x.Quantity,

    
        
        
          CONVERT
        
        (
        
          MONEY
        
        , p1.ListPrice 
        
          *
        
         x.Quantity 
        
          *
        
        
          RAND
        
        (CHECKSUM(
        
          NEWID
        
        ())) 
        
          *
        
        
          2
        
        ) 
        
          AS
        
        
           ActualCost


        
        
          INTO
        
        
           T2


        
        
          FROM
        
        
          

(

    
        
        
          SELECT
        
        
          

        p.ProductID, 

        p.ListPrice,

        
        
        
          CASE
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          26
        
        
          =
        
        
          0
        
        
          THEN
        
        
          26
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          25
        
        
          =
        
        
          0
        
        
          THEN
        
        
          25
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          24
        
        
          =
        
        
          0
        
        
          THEN
        
        
          24
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          23
        
        
          =
        
        
          0
        
        
          THEN
        
        
          23
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          22
        
        
          =
        
        
          0
        
        
          THEN
        
        
          22
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          21
        
        
          =
        
        
          0
        
        
          THEN
        
        
          21
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          20
        
        
          =
        
        
          0
        
        
          THEN
        
        
          20
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          19
        
        
          =
        
        
          0
        
        
          THEN
        
        
          19
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          18
        
        
          =
        
        
          0
        
        
          THEN
        
        
          18
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          17
        
        
          =
        
        
          0
        
        
          THEN
        
        
          17
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          16
        
        
          =
        
        
          0
        
        
          THEN
        
        
          16
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          15
        
        
          =
        
        
          0
        
        
          THEN
        
        
          15
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          14
        
        
          =
        
        
          0
        
        
          THEN
        
        
          14
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          13
        
        
          =
        
        
          0
        
        
          THEN
        
        
          13
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          12
        
        
          =
        
        
          0
        
        
          THEN
        
        
          12
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          11
        
        
          =
        
        
          0
        
        
          THEN
        
        
          11
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          10
        
        
          =
        
        
          0
        
        
          THEN
        
        
          10
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          9
        
        
          =
        
        
          0
        
        
          THEN
        
        
          9
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          8
        
        
          =
        
        
          0
        
        
          THEN
        
        
          8
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          7
        
        
          =
        
        
          0
        
        
          THEN
        
        
          7
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          6
        
        
          =
        
        
          0
        
        
          THEN
        
        
          6
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          5
        
        
          =
        
        
          0
        
        
          THEN
        
        
          5
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          4
        
        
          =
        
        
          0
        
        
          THEN
        
        
          4
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          3
        
        
          =
        
        
          0
        
        
          THEN
        
        
          3
        
        
          WHEN
        
         p.productid 
        
          %
        
        
          2
        
        
          =
        
        
          0
        
        
          THEN
        
        
          2
        
        
          ELSE
        
        
          1
        
        
          END
        
        
          AS
        
        
           ProductGroup

    
        
        
          FROM
        
        
           bigproduct p

) 
        
        
          AS
        
        
           p1


        
        
          CROSS
        
        
           APPLY

(

    
        
        
          SELECT
        
        
          

        transactionDate,

        
        
        
          CONVERT
        
        (
        
          INT
        
        , (
        
          RAND
        
        (CHECKSUM(
        
          NEWID
        
        ())) 
        
          *
        
        
          100
        
        ) 
        
          +
        
        
          1
        
        ) 
        
          AS
        
        
           Quantity

    
        
        
          FROM
        
        
          

    (

        
        
        
          SELECT
        
        
          DATEADD
        
        (dd, 
        
          number
        
        , 
        
          '
        
        
          20050101
        
        
          '
        
        ) 
        
          AS
        
        
           transactionDate,

            NTILE(p1.ProductGroup) 
        
        
          OVER
        
        
           

            (

                
        
        
          ORDER
        
        
          BY
        
        
          number
        
        
          

            ) 
        
        
          AS
        
        
           groupRange

        
        
        
          FROM
        
        
           master..spt_values

        
        
        
          WHERE
        
        
           

            type 
        
        
          =
        
        
          '
        
        
          p
        
        
          '
        
        
          

    ) 
        
        
          AS
        
        
           z

    
        
        
          WHERE
        
        
          

        z.groupRange 
        
        
          %
        
        
          2
        
        
          =
        
        
          1
        
        
          

) 
        
        
          AS
        
         x
      
View Code

实例Code

      
        select
      
      
        *
      
      
        from
      
        t1 
      
        inner
      
      
        join
      
        t2 
      
        on
      
       t1.productid
      
        =
      
      
        t2.ProductID


      
      
        where
      
       t1.ProductID
      
        <
      
      
        1510
      
    

执行计划如图1-2所示,再扫描t2表时实际上通过t1表的匹配结果集生成bit array(bitmap1008)进行过滤,从而使得20多万的数据可以高效过滤,进而提升语句的整体效率.

 

                                                             图1-2

也许有人会说,既然Bitmap过滤如此强悍为什么这个运算符在日常执行计划中并不常见呢?的确SQL Server在Bitmap过滤上有限制.只有在并行hash join,merge join的情形中才会使用这个技术(实际串行计划hash join中也有可能采用,但不显示).

其实位图过滤(位图索引)的应用场景我感觉还是不少的,由于 SQL Server没有位图索引,针对优化器自身使用的Bitmap 过滤又有种种限制,这个限制了这个优秀算法的使用空间,为此我还专门给微软SQL Server团队提了建议,建议放宽/可控bitmap过滤的使用.

注: 关于位图索引的使用,大家可以参考oracle中的技术文档

http://www.oracle.com/technetwork/articles/sharma-indexes-093638.html

   关于布隆过滤器的使用可以参考wikipedia

http://en.wikipedia.org/wiki/Bloom_filter

 

也许大家有疑问既然SQL Server中Bitmap这么不容易出现,那对我们调优还有什么帮助呢?

这个给大家讲个我们实际生产过程中的应用.在我写的 SQL Server优化技巧之SQL Server中的"MapReduce" 博客中,不少朋友对我调整的那个系统参数兴趣极大:),这里大概讲下相关的调整过程.

背景:双11活动中,公司网站访问量明显增加,发现某台数据库实例资源消耗上升明显.通过DMV捕获其中消耗资源的语句发现资源大多被个别高并发的语句消耗.

语句执行计划截图图2-1

                              图2-1

可以看出绝大多数消耗被Sort占据.

由于Sort是典型的计算密集型操作,消耗CPU的同时消耗大量内存.

在没有溢出到tempdb的sort采用的算法是快速排序,内存消耗将至少是排序结果集的200%以上,本例中单条查询的内存消耗在600MB以上,高并发,加上语句执行周期长(2s以上)使得单条语句长期占用内存,影响Buffer Pool的稳定,进而影响吞吐.同时带来不好的用户体验.

通过对语句实际分析,发现如果采用并行执行,优化器是可以利用Bitmap过滤,进而改善整体查询.

语句执行计划截图图2-2

                          图2-2

可以看出在并行执行计划中由于采用了Bitmap过滤,使得并行响应时间缩短为不到0.3s,同时CPU时间缩短为1s并且内存的消耗由600MB+减少至不到300M,这样减少资源使用的同时也提升了用户体验,并且由于响应时间不到0.3s使得查询内存的占用时间明显缩短,保证了Buffer Pool的稳定,进而确保吞吐基本稳定.

 

调整方案的抉择

实际上要优化器针对某些查询使用并行执行计划,我们是有几种方案供选择的

Plan Guide, Trace Flag , cost threshold for parallelism

 

由于当时的语句是个复杂的拼串语句,在query cache中发现针对相关语句存在不少不同的query_hash,此时如果使得Plan Guide调整复杂,不确定因素多,因此未采用.

 

针对特定的语句采用Trace flag(8649)对特定语句调整其实是最具针对性的,但是考虑到代码中实际上是需要研发同事参与的,在特定的时间窗口(双11)能不给别人找事儿就是运维人员最主要的出发点(同时也是运维人员价值的侧面直观体现).

 

因此决定采用并行阈值,使系统自动出发并行,并调整合适的并行度.调整并行阈值时我当时并未采用一般的二分法进行定位调整,考虑到并行阈值调整是实例级调整,会清空plan cache,影响很大,多一次调整就多一次性能抖动(甚至多一次意外).这时在一个时间段内我对实例的高消耗,出镜率高的查询进行采样,分别统计他们的subtree cost,进而大概确定了最小影响的阈值区间,并进行调整.由于本人人品不错:),一次调整就OK了.

之后CPU下降明显,访问量继续升高.

 

结语:无论是日常,还是特殊时段的运维,都需要我们确保头脑冷静的同时依靠自己掌握的知识选择最合理的解决方案.

 

/*******************************************************************/

再次奉上我儿子小蓝天的靓照.

小宝贝出生了,压力增加,动力更强了,哪些朋友如果有SQL Server相关的培训或是优化,架构等方面的需求可以联系我.为了小蓝天,为了家要更拼些.

SQL Server优化器特性-位图过滤(Bitmap)


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论