SQL Server – 执行计划和各种 join 方式 (Execution plan & Join Pattern)
前言
我几乎从来没有遇到过性能问题, 毕竟项目真的太小了. 一般上只要用常识去建 index 就可以了.
但是这并不阻碍我对知识的追求. 这篇是关于一些性能优化和原理的内容. 纯属学习, 希望未来有机会用到.
参考
SQL Server – 树结构 (二叉树, 红黑树, B-树, B+树) (必看)
Fish Li – 看懂SqlServer查询计划 (必看)
Sql Server中的表访问方式Table Scan, Index Scan, Index Seek (必看)
预读, 物理读, 逻辑读 (必看)
YouTube – How do SQL Indexes Work
What, When, Why?
什么是 Execution Plan?
execution plan 里头包含了 query 执行时的各种 information, 比如 IO 速度, 查找了多少 rows 等等
为什么要看 Execution Plan?
当 query 慢的时候, 可以通过分析 execution plan, 知道它为什么慢, 然后做优化.
怎样优化?
优化的方法有很多, 但绝大部分的情况加 index (索引) 就可以了.
总结
当我们发现 query 慢的时候就查看 execution plan, 然后添加 index, 通常它就会变快了.
Step by Step
开启 execution plan

跑 query
select * from Test where FirstName = '781F9AB8';
看结果

execution plan 的阅读方式是 右到左 上到下
Table Scan
table scan 是最慢的, 它就是把所有 data 都 read 出来一个一个匹配, 完全没有利用到 B+ tree 的优势.

遇到这种情况就加 index.
query "where FirstName" 想查找的是 FirstName, 那就加 index 到 FirstName.
Non Clustered Index Scan
假设我们在 FirstName 加了 non clustered index.
然后 query
select FiestName from Test;
注意, 我没有放 where 条件.
结果

这里用了一个 non clustered index scan. 虽然有 index, 但其实是很慢的. 因为 scan 就表示全部扫一遍.
只是从扫 table data 变成了扫 index. index 比 table data 小, 扫起来确实会快一些, 但依然没有利用到 B+ tree 的优势.
Clustered Index Scan
有 scan 就表示扫到完. 自然它也快不到哪去. 而且 clustered index 意味着它就是 data. 所以 clustered index scan 和 table scan 可以说是等价的.
只有在一个表没有 clustered index 的情况下 (heap table) 它才会是 table scan, 一旦有 clustered index 那就是 clustered index scan.

p.s 图中的 Parallelism 是并行执行的意思, 利用了多核 CPU
Index Seek 和 Key Lookup
在 FirstName 加上 index 以后, query 带 where
select * from Test where FirstName = 'Derrick';
结果

index seek 表示用到了 index 查找, 这个才真的有善用 B+ Tree 优势. 通常优化到这个 level 就能接受了.
另外, 之所以会有 key lookup 是因为 我 select all
B+ Tree 的 data 是放在最低层树叶的. 通过上层的 index 虽然定位到了 data. 但是依然需要去最底层 read data.
这个 key lookup 指的就是这个操作过程.
如果我把 query 换成
select FirstName from Test where FirstName = '781F9AB8';
那这个 key lookup 就没了. 因为 index 已经有 FirstName data, 就不用再到叶子节点 read data 了.
我们也可以把更多 data 写入 index 来优化掉 key lookup (但通常是没有必要的, 因为 key loopup 也没有很慢)
p.s 还有一个词叫 RID lookup (row id 的意思), 它和 key loopup 差不多意思, 只是它出现在 heap table.
Clustered Index Seek
select * from Test where FirstName = '781F9AB8';
结果

clustered index seek 是最快的. 即使是 select all 也不需要 key lookup 就可以直接定位获取 data 了.
clustered index 和 non clustered index 的区别是, clustered 是 data 本身在磁盘物理的排序. 一个表只能有一个 clustered index 一种排法.
non clustered index 则是目录只有 key 没有 data, 一个表可以做很多目录, 查找时先找目录, 然后再去 read data.
总结
通常
table scan > clustered index scan > index scan > index seek > clustered index seek
scan 表示扫到完, 不管是扫 data 还是 index 都慢
seek 表示利用了 B+ Tree 二分查找, 所以快
loopup 是因为 non clustered index 只保存了 target column data, 其余 row column data 在叶节点, 所以 seek 了之后还需要 read other data from leaf.
为什么说是 "通常" 呢?
因为并不是所有情况下都是这样的. 有时候表小, 直接 scan table 反而更快.
又比如 bit column, 就是男/女. 这种情况你即使 seek 用上了 B+ Tree 二分查找也快不到哪去.
所以我们做优化的步骤是先看哪一个 query 慢.
快的 query, 哪怕它是 table scan 也不用管它.
发现慢的 query 后, 就看它是 scan 还是 seek. 然后尝试加 index 让它变成其它的方式. then 在跑跑看是否有提速.
预读, 物理读, 逻辑读 (read-ahead reads, logical reads, physical reads)
除了看 execution plan 的 scan, seek 这些. 还有一个重要的指标是看缓存.
我们知道 IO 是很慢的 (从磁盘读取数据), 因为这个是物理操作, 需要移动磁头,
而从内存或缓存中读取数据是很快的, 因为不算物理操作, 它只是通通电.
所以我们要尽量确保 query 的时候不要从磁盘读取数据.
Clear cache
为了测试方便, 我们每次 query 的时候先 clear 干净 cache
DBCC DROPCLEANBUFFERS --清空执行计划缓存
DBCC FREEPROCCACHE --清空数据缓存
开启 statistics (侦测工具)
SET STATISTICS IO ON; -- OFF 就关闭
query
select * from Test where FirstName = '0000007B';
结果

SQL Server 读取最小单位是 page, 一个 page = 8kb.
physical reads 3 表示从磁盘读了 3 次, 也就是 3 x 8kb = 24kb 的数据.
logical reads 是从缓存读取
read-ahead reads 是预读. 当 query 发出后, SQL Server 会先制作执行计划, 与此同时为了不浪费时间, 它会先去预读一些数据.
当执行计划做好后, 就执行. 这时如果 logical reads 不能满足所有结果, 那么就会去 physical reads.
当我们第二次运行相同 query 就会发现 physical reads 0. 因为之前的数据已经被缓存了 (除非我们跑上面的 clear cache command)
总结
physical reads 太多肯定就慢, 至于如何优化...我不知道, 看着办呗.
Join and Loop Pattern
除了 read row data, 连表也很讲究性能. SQL Server 有 3 种连表方式.
这 3 种方式在不同的前提下, 性能表现会不一样, SQL Server 会依据不同情况选择不同的方式.
比如表大小, 表是否已经有排序等等
Nested Loop Join
nested loop join 的过程是 for loop A 表, 拿每一条 row 再去 for loop B 表做 match.
假设 A, B 表都有 100 rows. 那么 100 x 100 就需要 10000 次 compare.

这种方式适合用在 A 表比较小, B 表有 index 的情况. 这 2 点可以大大降低找的次数.
Merge Join
merge join 的过程是先拿 AB 表做一个排序 (注意排序是很慢的)
然后 loop A 表, 拿 row 去 match B 表.
由于有排序了, 所以不需要检索所有的 B rows. 只要匹配到比较大的就可以回到 A 表下一个了.

这种方式适合用在 AB 表已经有排序好的情况 (比如 AB 表都有相同的 index)
Hash Join
hash join 的过程是先拿 AB 表每一条 row 做哈希算法.然后放入哈希表.
哈希的特色是可以算出位置. 相同位置的就表示同值.

这种方式适合用在, 你需要完全找出 AB 表所有匹配的情况. 因为有些时候我们可能只需要找出几个 A 表匹配.
那么 nested loop 会比较合适. hash 一定要全部 row 都做完才能出结果, 不像 nested loop 找一个是一个.
总结
三种方式都有它适合的场景. 同样的, 我们做优化首先是看它是否慢, 然后才想办法 (加 index) 让它换一个方式试试看是否提速.
小 tips
在做分析时, 经常需要一些 command
DBCC DROPCLEANBUFFERS --清空执行计划缓存
DBCC FREEPROCCACHE --清空数据缓存
SET STATISTICS IO ON;
SET STATISTICS TIME ON; left join TestChild WITH (INDEX([IX_TestChild_TestId])) --强制使用 index -- 强制 join pattern
left hash join
left merge join
left loop join
SQL Server – 执行计划和各种 join 方式 (Execution plan & Join Pattern)的更多相关文章
- SQL Server 执行计划中的扫描方式举例说明
SQL Server 执行计划中的扫描方式举例说明 原文地址:http://www.cnblogs.com/zihunqingxin/p/3201155.html 1.执行计划使用方式 选中需要执行的 ...
- sql server 执行计划(execution plan)介绍
大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...
- SQL Server 执行计划操作符详解(2)——串联(Concatenation )
本文接上文:SQL Server 执行计划操作符详解(1)--断言(Assert) 前言: 根据计划,本文开始讲述另外一个操作符串联(Concatenation),读者可以根据这个词(中英文均可)先幻 ...
- SQL Server 执行计划缓存
标签:SQL SERVER/MSSQL SERVER/数据库/DBA/内存池/缓冲区 概述 了解执行计划对数据库性能分析很重要,其中涉及到了语句性能分析与存储,这也是写这篇文章的目的,在了解执行计划之 ...
- SQL Server执行计划那些事儿(3)——书签查找
接下来的文章是记录自己曾经的盲点,同时也透漏了自己的发展历程(可能发展也算不上,只能说是瞎混).当然,一些盲点也在工作和探究过程中慢慢有些眉目,现在也愿意发扬博客园的奉献精神,拿出来和大家分享一下. ...
- SQL Server执行计划那些事儿(2)——查找和扫描
接下来的文章是记录自己曾经的盲点,同时也透漏了自己的发展历程(可能发展也算不上,只能说是瞎混).当然,一些盲点也在工作和探究过程中慢慢有些眉目,现在也愿意发扬博客园的奉献精神,拿出来和大家分享一下. ...
- 引用:初探Sql Server 执行计划及Sql查询优化
原文:引用:初探Sql Server 执行计划及Sql查询优化 初探Sql Server 执行计划及Sql查询优化 收藏 MSSQL优化之————探索MSSQL执行计划 作者:no_mIss 最近总想 ...
- SQL Server 执行计划操作符详解(3)——计算标量(Compute Scalar)
接上文:SQL Server 执行计划操作符详解(2)--串联(Concatenation ) 前言: 前面两篇文章介绍了关于串联(Concatenation)和断言(Assert)操作符,本文介绍第 ...
- SQL SERVER 执行计划各字段注释
SET SHOWPLAN_ALL使 Microsoft® SQL Server™ 不执行 Transact-SQL 语句.相反,SQL Server 返回有关语句执行方式和语句预计所需资源的详细信息. ...
- 学习如何看懂SQL Server执行计划(一)——数据查询篇
一.数据查询部分 1. 看到执行计划有两种方式,对sql语句按Ctrl+L,或按Ctrl+M打开显示执行计划窗口每次执行sql都会显示出相应的执行计划 2. 执行计划的图表是从右向左看的 3. SQL ...
随机推荐
- Linux 文件夹和文件操作【Linux 常用命令系列一】
〇.前言 本文首先介绍了 Linux 中文件的结构,将全部文件夹罗列并介绍了大概的用途: 然后通过实例介绍了文件夹相关的常用操作,仅供参考. 一.Linux 系统的文件结构 列一下系统全部文件夹: / ...
- win10配置 sshkey
win10配置 sshkey 一:生成sshkey 打开powershell 或者 cmd 输入命令: ssh-keygen -t rsa 1 提示输入文件名 [不输入,使用默认文件名,回车] 2 提 ...
- vscode element-plus/lib/theme-chalk/index.css报错路径找不到
vscode element-plus/lib/theme-chalk/index.css报错路径找不到 import { createApp } from 'vue' import './styl ...
- [oeasy]python0073_进制转化_eval_evaluate_衡量_oct_octal_八进制
进制转化 回忆上次内容 上次了解的是 整型数字类变量 integer 前缀为i 添加图片注释,不超过 140 字(可选) 整型变量 和 字符串变量 不同 整型变量 是 直接存储二进制形 ...
- [oeasy]python0144_try的完全体_否则_else_最终_finally
try的完全体 回忆上次内容 上次细化了主控程序(main.py) 导入(get_fruits.py) 处理(process.py) 输出(output.py) 使用了 try 结构 try ...
- 机器学习:详解迁移学习(Transfer learning)
详解迁移学习 深度学习中,最强大的理念之一就是,有的时候神经网络可以从一个任务中习得知识,并将这些知识应用到另一个独立的任务中.所以例如,也许已经训练好一个神经网络,能够识别像猫这样的对象,然后使用那 ...
- PointNet笔记
可能遇到的问题 在windows上运行pointnet的代码时,可能会遇到一些问题: 1.比如提示OSError: no file with expected extension, 这是因为可视化的s ...
- 顺序表_C
// Code file created by C Code Develop #include "ccd.h" #include "stdio.h" #incl ...
- 【Vue】Re02 指令:第一部分
一.v-once指令 用于固定一次性赋值,后续Vue实例的赋值更改将不再对v-once指令的元素有效 <!DOCTYPE html> <html lang="en" ...
- 中国的AI领域发展的重大不足 —— 数据缺少,尤其是自然语言领域(NLP)、大模型领域
全世界公开可用的语言文本中绝大部分是英文文本,其中中文文本只有1.5% 相关: China's Betting Big On Artificial Intelligence. Could The US ...