很早以前,也是一提到SQL Server,就觉得它的性能没法跟Oracle相比,一提到大数据处理就想到Oracle。自己一路走来,在本地blog上记录了很多优化方面的 post,对的错的都有,没有时间系列的整理出来,这篇文章将join方法的概念稍微整理在一起,给大家个参考。通过查资料了解里面提到的各种概念,在实 际中不断验证总结,完全可以对数据库一步步深入理解下去的。
我只对SQL Server 2000比较了解,但这并不阻碍我在Oracle、MySql进行SQL调优、产品架构,因为在数据库理论原理上,各大数据库基本出入不大,对数据库的深入理解,也不会影响你架构设计思想变坏,相反给你带来的是更深层次的思考。
关于执行计划的说明
在SQL Server查询分析器的Query菜单中选择Show Execution Plan,运行SQL查询语句,在结果窗口中有Grid、Execution Plan、Messages三个Tab。看图形形式的执行计划,顺序是从右到左,这也是执行的顺序。执行计划中的每一个图标表示一个操作,每一个操作都会 有一个或多个输入,也会有一个或多个输出。输入和输出,有可能是一个物理数据表、索引数据结构,或者是执行过程中的一些中间结果集/数据结构。鼠标移动到 图标上,会显示这个操作的具体信息,例如逻辑和物理操作名称、记录的数量和大小、I/O成本、CPU成本、操作的具体表达式(参数Argument)。鼠 标移动到连接箭头上,会显示箭头起始端的操作输出结果集的记录数、记录的大小,一般情况下可以将这个输出结果集理解为箭头结束端的输入。
另 外关于执行计划的一些补充说明:1. 执行计划中显示的信息,都是一个“评估”的结果,不是100%准确的信息,例如记录数量是取自统计信息,I/O成本、CPU成本来自执行计划生成过程中基 于统计信息等得出的评估结果。2. 执行计划不一定准确,一方面受SQL Server维护的统计信息准确性的影响,另一方面SQL语句编译时刻与执行时刻的环境(内存使用状况、CPU状况等)可能会不一样。
关于统计信息、I/O成本和CPU成本的评估、SQL语句的编译和执行过程,这里不再深入。另外尽管执行计划不一定准确,但它仍是SQL语句分析最重要的依据,因为你可以理解为,绝大部分情况下,SQL Server是以这种方式来执行的。
JOIN方法说明
数据库中,象tableA inner join tableB、tableA left out join tableB这样的SQL语句是如何执行join操作的?就是说SQL Server使用什么算法实现两个表数据的join操作?
SQL Server 2000有三种方式:nested loop、merge、hash。Oracle也是使用这三种方式,不过Oracle选择使用nested loop的条件跟SQL Server有点差别,内存管理机制跟SQL Server不一样,因此查看执行计划,Oracle中nested loop运用非常多,而merge和hash方式相对较少,SQL Server中,merge跟hash方式则是非常普遍。
以SQL Server 2000为例对这三种方式进行说明,穿插在里面讲解执行计划的一些初级使用。
1. nested loop join
1.1 示例SQL
select ... from tableA inner join tableB on tableA.col1=tableB.col1 where tableA.col2=? and tableB.col2=?tableA中没有建立任何索引,tableB中在col1上有建立一个主键(聚集索引)。
1.2 算法伪代码描述
foreach rowA in tableA where tableA.col2=?{search rowsB from tableB where tableB.col1=rowA.col1 and tableB.col2=? ;if(rowsB.Count<=0)discard rowA ;elseoutput rowA and rowsB ;}
join操作有两个输入,上面例子中tableA是outer input,用于外层循环;tableB是inner input,用于循环内部。下面针对执行计划描述一下SQL Server完成这个操作的具体步骤。 %. ^ g.L
2vt [ AhVA
1.3 查看执行计划方法
移到文章最前面。
1.4 执行步骤
下 面是示例SQL的执行计划图。 nested loop操作的右边,位于上面的是outer input,位于下面的是inner input。你不能够根据join中哪个表出现在前面来确定outer input和inner input关系,而必须从执行计划中来确定,因为SQL Server会自动选择哪个作为inner input。
a) 对tableA执行Table Scan操作。这个操作的输入是tableA表中的数据,这些数据位于磁盘上,操作过程中被加载到内存;输出是符合条件的记录集,将作为b)的outer input。在这个操作中,tableA.col1=?的条件会被使用。 b) 执行上面伪代码描述的nested loop操作。对a)中的每个输出记录,执行步骤c)。 c) 对tableB执行Clustered Index Seek操作。这个操作是在nested loop循环里面执行的,输入是tableB表的聚集索引数据。它使用tableB.col1=rowA.col1和tableB.col2=?这两个条 件,从tableB的聚集索引中选择符合条件的结果。 d) 构造返回结果集。从nested loop的输出中,整理出select中指定的字段,构造最终输出结果集。 1.5 进阶说明 上面例子对inner input使用的是聚集索引,下面看一下非聚集索引的情况,加强对执行计划的理解、分析能力。 把tableB col1上的主键修改为非聚集方式,示例的SQL语句执行计划如下:
d) 执行Bookmark Lookup操作。nested loop操作的输出是一个内存数据结构,在从这个内存数据结构中整理出整个查询语句的输出结果集之前,需要处理前面的书签引用问题,Bookmark Lookup操作就是根据书签中引用的聚集索引键值或者rowid获取具体记录数据。 e) Filter过滤操作。回顾前面几个操作,在执行nested loop时只是使用非聚集索引的索引字段(tableB.col1)跟outer input的关联字段进行匹配,到目前为止还没有使用tableB.col2=?这个条件,这个操作就是使用tableB.col2=?对 Bookmark Lookup的输出进行过滤。 看 的仔细的人到这里后可能会有几个疑问,1. tableA.col2=?怎么没有一个Filter操作?2. 在1.4中为什么没有出现Filter操作?解释如下:1. 在tableA上面执行的是Table Scan操作,是直接对每条实际数据进行扫描,在这个扫描过程中可以使用tableA.col2=?这个条件进行过滤,避免一个额外的Filter操作。 鼠标移动到Table Scan操作上,从提示信息的参数(Argument)里面可以看到tableA.col2=?的条件已经被运用上了。2. 前面说过,聚集索引的根节点是实际数据记录,执行Clustered Index Seek的时候,最终也是扫描到了实际数据记录,在这个过程中运用tableB.col2=?这个条件,同样避免一个额外的Filter操作。这就是 1.4中没有Filter操作的原因。
|