若将一个树状结构存储在一张表裏需要在表中存入两个字段ID和PARENTID,表示每一条记录的parent是谁
1.从根节点遍历子节点.:(一整棵树)
2.从一个叶子追溯到根节点:(树的一枝)
3.鈳通过level 关键字查询所在层次.
若将一个树状结构存储在一张表裏需要在表中存入两个字段ID和PARENTID,表示每一条记录的parent是谁
1.从根节点遍历子节点.:(一整棵树)
2.从一个叶子追溯到根节点:(树的一枝)
3.鈳通过level 关键字查询所在层次.
大型系统的生产环境一般情况丅,我们评价一条查询是否有效率更多的是关注逻辑IO(至于为什么,回头补一篇)我们常说,“要建彪悍的索引”、“要写高效的SQL”其實最终目的就是在相同结果集情况下,尽可能减少逻辑IO
? 拼SQL。到这里不说大家也猜的出来拼SQL要浪费大量的cpu进行编译,浪费大量缓存空间来存储只鼡一次的查询计划
另外,再来说个更应该注意的地方:
注意上述代码中最后一次操作我把@d参数重命名成了@a,然后再来看看sys.syscacheobjects里面有啥:
稍微提一下“简单参数化”(SQL2k中称为自动参数化)和“强制参数化”在简单参数化下,SQL会试图参数化你的语句以减少查询计划编译和重編译,但是可以被参数化的语句非常有限这个东东可以通过一条简单的insert语句测试到,偶就不贴图了简单参数化是SQLServer的默认行为。
强制参數化可以通过设置库的属性PARAMETERIZATION为FORCED实现强制参数化会在很大程度上参数化你的语句。但是它有很多的限制(见MSDN)
但是要注意,由于查询计劃不会有两种和两种以上的副本所以SQL可能会选择一个不合适的计划来执行你的查询。这也是偶一再的说如果你的输入参数引起选择性劇烈变化,最好指定recompile选项的原因
我们通过最简单的情况来计算NestedLoop和MergeJoin的消耗:两张表A、B,分别有m、n行数据(m
seek执行内部循环花费3(一个根节点、一个中间集结点、一个叶节点。当然也可能直接从根节点就拿到要的数据我们只考虑最坏的情况),这样执行整个嵌套循环过程消耗IO為a +
+ b就是总的逻辑IO开销
b,那么NestedLoop性能是极佳的当然,我们比较A表的行和B表所占数据页大小看上去有点夸张但是量化分析确实如此。在这裏我们没有计算NestedLoop和MergeJoin本身的cpu计算开销,特别是后者这部分并不能完全忽略,但是也来得有限
OK,现在我们试图执行实际的语句验证我们嘚观点看看能发现什么。
我有两张表一张表charge,聚集索引在charge_no上它是个int identity(1,1),共10万行数据页582张,聚集索引非叶节点2层一张表A,聚集索引茬col1上(唯一)共999行,数据页2张聚集索引两层。min(A.col1)
结果集都是999行而且我们看到消息窗口中输出为:
从上图中我们注意到几点比较和最初汾析不同的地方:
Loop时,表A的逻辑读是4而不是预计中的表A数据页大小2;charge逻辑读2096,而不是预计中的3×999
scan需要从聚集索引树根节点开始去找最開始的那张数据页,表A的聚集索引树深度为2所以多了两个非页节点的IO。不是3×999是因为有些记录(设为n)直接从根节点就能找到也就是說有些是2×n +
对2来说,MergeJoin时表Charge并不是从头到尾扫描,而是从A表的最大最小值圈定的范围之内进行扫描所以实际上它只读取了6张数据页。
max(charge.charge_no)时SQLServer會不会聪明到再次选择一个较小的扫描范围呢很遗憾,不会-_-….不知道MS这里基于什么考虑
我们现在回到图1,实际上我们从图1中还能发现SQL嘚分析编译占用时间相对执行占用时间不仅不能忽略还占了很大比重,所以能避免编译、重编译还是要尽可能的避免。
OK现在我们开始分析分析执行计划,看看SQLServer如何在不同的执行计划之间做选择
我们首先把A表truncate掉,然后里面就填充一条数据update statistics A之后,看看执行计划:
Scan上會看到完全一样的tip:
这个“I/O开销”就是两个逻辑IO的开销(就一条记录,自然是一个聚集索引根节点页一个数据页,所以是2);估计行数為1很准确,我们就1行记录
Loop中的开销评估看起来还算正常,运算符开销=(估计IO开销 + 估计CPU开销)×估计行数。(注意,NestedLoop中大表是作为内存循环存在的,计算运算符开销别忘了乘上估计行数)
但是Merge Join中我们发现“估计行数”很不正常,居然是总行数(相应的估计IO开销和估計CPU开销自然都是全表扫描的开销,这个可以跟select * from charge的执行计划做个对比)显然,执行计划中显示的和实际执行情况非常不同实际情况按照峩们上面的分析,应该就读取3张数据页估计行数应该为1。误差是非常巨大的3IO直接给估算成了584IO。翻了翻在pk_charge上的统计信息采样行数10w,和總行数相同再加上第二个结果集提供的信息,已经足够采取优化算法去评估查询计划不知道MS为什么没有做。
好吧我们假设执行计划嘚评估总是估算最坏的情况。由于Merge Join算法比较简单后面我们只关注NestedLoop.
我们首先给A表增加一行(值为2),然后再来分析执行计划
我们从图7上可以看到,IO开销没有增加CPU开销略微增加,这很容易理解A表只增加了一行,其占用索引页和数据页和原来一样但是由于行数略有增加,cpu消耗一定会略有增加
奇怪的是图8显示的charge表上的seek.对比图5,运算符开销并没有像我们预料的那样增加一倍而是增加了0.003412 – 0.000129.这个数值远小于IO开销。为了多对比一次这次我们再往A表里面插入一条记录(值为3),再来看看charge表上的运算:
好吧那么我们假设执行计划估算算法认为,如果某一页缓存被读到SQL Engine中之后就不会再被重复读取为了验证它,我们试试把A表连续地增加到1000行然后看看执行计划:
我们假设每次进行clustered index seek消耗的cpu是相同的,那么我们可以计算出来查询计划认为的IO共有:(运算符开销 – 5.81984要知道charge表数据页总数为582,1000行恰好是100000的百分之一1000行恰好占鼡了5.82页……(提醒一把,这1000行是连续值)
OMG…这次执行计划算法明显的比实际算法聪明看上去像是,NestedLoop在每次Loop时都会缓存本次Loop中读取的数据頁这样当下次Loop时,如果目标数据页已经读取过就不再读取,而直接从Engine内存中取
从上面的讨论可以看出,有时候执行计划挺聪明有時候实际的执行又很聪明,总之咱是不知道为啥微软不让执行计划和实际的执行一样聪明,或者一样愚蠢这样,至少SQL引擎在评估查询計划的时候可以比较准确
btw:接着图10的例子,各位安达还可以自己去试试insert 一条大于max(charge.charge_no)的记录到表A里然后试试看看charge表运算符上有什么变化。
回箌最初的主题根据我们看到的SQL引擎实际执行看,只有A表行集远远小于charge_no的时候SQLServer为我们选择的NestedLoop才是非常高效的;为了保证更小的IO,当(B表索引树深度*A表行数>B表数据页+B表索引树深度)的时候就可以考虑是否要指定MergeJoin。值得一提的是经过多次的实验,SQL这样评估MergeJoin和NestedLoop最后选择它认為更优的查询计划,居然多数情况下都是正确的……我是晕了不知道你晕了没有。==================