zoukankan      html  css  js  c++  java
  • LeftOuterJoin和OuterApply性能比较

     

    建立测试环境:

      建立一个表Department和Employee,并向Department插入50W条记录,向Employee插入200W条记录,

    我们就拿【统计DepartmentID 从150000 至380000的每一个Department有多少个Employee】这样一问题

    来比较性能,再说他们如何使用更有效。

    Use Test
    Go
    If object_id('Employee'Is Not Null
        
    Drop Table Employee
    If object_id('Department'Is Not Null
        
    Drop Table Department
    Go
    Create Table Department
    (
        ID 
    int Identity(1,1Not Null,
        Name 
    nvarchar(50Not Null,
        
    Constraint PK_Department Primary Key(ID Asc)
    )
    Go
    Create Table Employee
    (
        ID 
    int Identity(1,1Not Null,
        DepartmentID 
    int Not Null,
        Name 
    nvarchar(50Not Null,
        
    Constraint PK_Employee Primary Key(ID Asc),
        
    Constraint FK_Employee_DepartmentID Foreign Key(DepartmentID) References Department(ID)
    )
    Create Nonclustered Index IX_Employee_DepartmentID On Employee(DepartmentID Asc)
    GO
    ;
    With
    t0 
    As(Select ID=1 Union All Select ID=1),
    t1 
    As(Select a.ID From t0 As a,t0 As b),
    t2 
    As(Select a.ID From t1 As a,t1 As b),
    t3 
    As(Select a.ID From t2 As a,t2 As b),
    t4 
    As(Select a.ID From t3 As a,t3 As b),
    t5 
    As(Select a.ID From t4 As a,t4 As b),
    t6 
    As(Select ID=Row_number() OverOrder By a.ID) From t5 As a,t5 As b)
    Insert Into Department (Name)
        
    Select N'Dept'+Rtrim(ID) 
            
    From t6 
            
    Where ID<=500000

    ;
    With
    t0 
    As(Select ID=1 Union All Select ID=1),
    t1 
    As(Select a.ID From t0 As a,t0 As b),
    t2 
    As(Select a.ID From t1 As a,t1 As b),
    t3 
    As(Select a.ID From t2 As a,t2 As b),
    t4 
    As(Select a.ID From t3 As a,t3 As b),
    t5 
    As(Select a.ID From t4 As a,t4 As b),
    t6 
    As(Select ID=Row_number() OverOrder By a.ID) From t5 As a,t5 As b)
    Insert Into Employee (DepartmentID,Name)
        
    Select ID%500000+1,N'Emp'+Rtrim(ID)
            
    From t6 
            
    Where ID<=2000000

    Go


     开始测试:

    Use Test
    Go
    Dbcc Freeproccache With No_Infomsgs
    Dbcc DropCleanBuffers With No_Infomsgs
    Go
    Set Statistics Io On
    Set Statistics Time On
    Go
    Select a.ID,a.Name,b.Qty 
        
    From Department As a
            
    Outer Apply(Select Qty=Count(*
                            
    From Employee 
                            
    Where DepartmentID=a.ID
                        ) 
    As b
        
    Where a.id Between 150000 And 380000

    Select a.ID,a.Name,b.Qty  
        
    From Department As a
            
    Left Outer Join(Select Qty=Count(*),DepartmentID 
                                
    From Employee 
                                
    Group By DepartmentID
                            ) 
    As b On b.DepartmentID=a.ID
        
    Where a.id Between 150000 And 380000

    Go
    Set Statistics Time Off
    Set Statistics Io Off


    /*

    SQL Server 分析和编译时间: 
       CPU 时间 = 31 毫秒,占用时间 = 65 毫秒。

    (230001 行受影响)
    表 'Employee'。扫描计数 230001,逻辑读取 735944 次,物理读取 159 次,预读 16 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Department'。扫描计数 1,逻辑读取 1063 次,物理读取 3 次,预读 1060 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    SQL Server 执行时间:
       CPU 时间 = 3094 毫秒,占用时间 = 6440 毫秒。

    (230001 行受影响)
    表 'Department'。扫描计数 1,逻辑读取 1063 次,物理读取 22 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Employee'。扫描计数 1,逻辑读取 1258 次,物理读取 6 次,预读 8 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    SQL Server 执行时间:
       CPU 时间 = 750 毫秒,占用时间 = 7149 毫秒。
    SQL Server 分析和编译时间: 
       CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。




    */

     从上面的例子可以看出来,使用Outer Apply   的时候,对表Employee进行230001次扫描,从缓冲区去取页的数次为

     735944,远远超过使用Left Outer Join 的次数。

    再来测试一种情况,就是在Outer Apply()里不加Where条件 ,而Left Outer Join 的 On 为1=1:

    Use Test
    Go
    Dbcc Freeproccache With No_Infomsgs
    Dbcc DropCleanBuffers With No_Infomsgs
    Go
    Set Statistics IO On
    Set Statistics Time On
    Go
    Select a.ID,a.Name,b.Qty 
        
    From Department As a
            
    Outer Apply(Select Qty=Count(*
                            
    From Employee 
                        ) 
    As b
        

    Select a.ID,a.Name,b.Qty  
        
    From Department As a
            
    Left Outer Join(Select Qty=Count(*)
                                
    From Employee 
                            ) 
    As b On 1=1

    Go
    Set Statistics Time Off
    Set Statistics Io Off

    /*

    SQL Server 分析和编译时间: 
       CPU 时间 = 16 毫秒,占用时间 = 28 毫秒。

    (500000 行受影响)
    表 'Worktable'。扫描计数 1,逻辑读取 0 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Employee'。扫描计数 1,逻辑读取 2731 次,物理读取 3 次,预读 2716 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Department'。扫描计数 1,逻辑读取 2276 次,物理读取 3 次,预读 2253 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    SQL Server 执行时间:
       CPU 时间 = 812 毫秒,占用时间 = 13823 毫秒。

    (500000 行受影响)
    表 'Worktable'。扫描计数 1,逻辑读取 0 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Employee'。扫描计数 1,逻辑读取 2731 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Department'。扫描计数 1,逻辑读取 2276 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    SQL Server 执行时间:
       CPU 时间 = 969 毫秒,占用时间 = 12327 毫秒。
    SQL Server 分析和编译时间: 
       CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。

    */


     从这里可以看出,两种方法,I/O显示信息是一致的,而且执行的时间比较接近。性能相当。

    这样性能相当的情况 ,也发生在这样的环境中:


    Use Test
    Go
    Dbcc Freeproccache With No_Infomsgs
    Dbcc DropCleanBuffers With No_Infomsgs
    Go
    Set Statistics IO On
    Set Statistics Time On
    Go
    Select a.ID,a.Name,b.Qty 
        
    From Department As a
            
    Outer Apply(Select Qty=Count(*
                            
    From Employee 
                            
    Where DepartmentID=a.ID
                        ) 
    As b
        
    Where a.ID=454514

    Select a.ID,a.Name,b.Qty  
        
    From Department As a
            
    Left Outer Join(Select Qty=Count(*),DepartmentID 
                                
    From Employee 
                                
    Group By DepartmentID
                            ) 
    As b On b.DepartmentID=a.ID
        
    Where a.ID=454514
    Go
    Set Statistics Time Off
    Set Statistics Io Off

    /*
    SQL Server 分析和编译时间: 
       CPU 时间 = 15 毫秒,占用时间 = 37 毫秒。

    (1 行受影响)
    表 'Employee'。扫描计数 1,逻辑读取 3 次,物理读取 3 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Department'。扫描计数 0,逻辑读取 3 次,物理读取 3 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    SQL Server 执行时间:
       CPU 时间 = 0 毫秒,占用时间 = 35 毫秒。

    (1 行受影响)
    表 'Employee'。扫描计数 1,逻辑读取 3 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Department'。扫描计数 0,逻辑读取 3 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    SQL Server 执行时间:
       CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
    SQL Server 分析和编译时间: 
       CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
    */

    从多次的测试中可以发现,不能滥用Outer Apply 来代替Left Outer Join。

    其实在联机帮助中Apply 定义是这样:

      ”使用 APPLY 运算符可以为实现查询操作的外部表表达式返回的每个行调用表值函数。表值函数作为右输入,外部表表达式作为左输入。通过对右输入求值来获得左输入每一行的计算结果,生成的行被组合起来作为最终输出。APPLY 运算符生成的列的列表是左输入中的列集,后跟右输入返回的列的列表。  “

      当在右边输入的是表值函数情况,我们可以考虑使用Apply,这样能为我们带来很多方便。或者条件类似为最后面测试那样,使用Outer Apply 和Left Outer Jion性能是一样的。在更复杂的环境更是要小心,需要多做测试,使用多种方法来调试才知道哪一方法好,性能更佳。

    知识点:

    在第二次测试中看到有,

    表 'Worktable'。扫描计数 1,逻辑读取 0 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    这里说明"WorkTable"含义:

      The relational engine may need to build a worktable to perform a logical operation specified in an SQL statement. Worktables are internal tables that are used to hold intermediate results. Worktables are generated for certain GROUP BY, ORDER BY, or UNION queries. For example, if an ORDER BY clause references columns that are not covered by any indexes, the relational engine may need to generate a worktable to sort the result set into the order requested. Worktables are also sometimes used as spools that temporarily hold the result of executing a part of a query plan. Worktables are built in tempdb and are dropped automatically when they are no longer needed.

       关系引擎可能需要生成一个工作表以执行 SQL 语句中指定的逻辑操作。工作表是用于保存中间结果的内部表。某些 GROUP BY、ORDER BY 或 UNION 查询中会生成工作表。例如,如果 ORDER BY 子句引用了不为任何索引涵盖的列,则关系引擎可能需要生成一个工作表以按所请求的顺序对结果集进行排序。工作表有时也用作临时保存执行部分查询计划所得结 果的假脱机。工作表在tempdb中生成,并在不再需要时自动删除。

    (完)

  • 相关阅读:
    Design Pattern: Gof
    ZT --- extern "C"用法详解 2010-08-21 19:14:12
    OCR
    Linux strace命令
    wireshark esp
    https://sourceware.org/gdb/onlinedocs/gdb/Forks.html
    12306网上买火车票选择上中下铺的方法
    gdb调试有fork的程序
    named piped tcp proxy
    bash 提示用户输入 choice
  • 原文地址:https://www.cnblogs.com/wghao/p/1422748.html
Copyright © 2011-2022 走看看