前言

我几乎从来没有遇到过性能问题, 毕竟项目真的太小了. 一般上只要用常识去建 index 就可以了.

但是这并不阻碍我对知识的追求. 这篇是关于一些性能优化和原理的内容. 纯属学习, 希望未来有机会用到.

参考

SQL Server – 树结构 (二叉树, 红黑树, B-树, B+树) (必看)

Fish Li – 看懂SqlServer查询计划 (必看)

Sql Server中的表访问方式Table Scan, Index Scan, Index Seek (必看)

预读, 物理读, 逻辑读 (必看)

YouTube – Join Pattern (必看)

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)的更多相关文章

  1. SQL Server 执行计划中的扫描方式举例说明

    SQL Server 执行计划中的扫描方式举例说明 原文地址:http://www.cnblogs.com/zihunqingxin/p/3201155.html 1.执行计划使用方式 选中需要执行的 ...

  2. sql server 执行计划(execution plan)介绍

    大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...

  3. SQL Server 执行计划操作符详解(2)——串联(Concatenation )

    本文接上文:SQL Server 执行计划操作符详解(1)--断言(Assert) 前言: 根据计划,本文开始讲述另外一个操作符串联(Concatenation),读者可以根据这个词(中英文均可)先幻 ...

  4. SQL Server 执行计划缓存

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/内存池/缓冲区 概述 了解执行计划对数据库性能分析很重要,其中涉及到了语句性能分析与存储,这也是写这篇文章的目的,在了解执行计划之 ...

  5. SQL Server执行计划那些事儿(3)——书签查找

    接下来的文章是记录自己曾经的盲点,同时也透漏了自己的发展历程(可能发展也算不上,只能说是瞎混).当然,一些盲点也在工作和探究过程中慢慢有些眉目,现在也愿意发扬博客园的奉献精神,拿出来和大家分享一下. ...

  6. SQL Server执行计划那些事儿(2)——查找和扫描

    接下来的文章是记录自己曾经的盲点,同时也透漏了自己的发展历程(可能发展也算不上,只能说是瞎混).当然,一些盲点也在工作和探究过程中慢慢有些眉目,现在也愿意发扬博客园的奉献精神,拿出来和大家分享一下. ...

  7. 引用:初探Sql Server 执行计划及Sql查询优化

    原文:引用:初探Sql Server 执行计划及Sql查询优化 初探Sql Server 执行计划及Sql查询优化 收藏 MSSQL优化之————探索MSSQL执行计划 作者:no_mIss 最近总想 ...

  8. SQL Server 执行计划操作符详解(3)——计算标量(Compute Scalar)

    接上文:SQL Server 执行计划操作符详解(2)--串联(Concatenation ) 前言: 前面两篇文章介绍了关于串联(Concatenation)和断言(Assert)操作符,本文介绍第 ...

  9. SQL SERVER 执行计划各字段注释

    SET SHOWPLAN_ALL使 Microsoft® SQL Server™ 不执行 Transact-SQL 语句.相反,SQL Server 返回有关语句执行方式和语句预计所需资源的详细信息. ...

  10. 学习如何看懂SQL Server执行计划(一)——数据查询篇

    一.数据查询部分 1. 看到执行计划有两种方式,对sql语句按Ctrl+L,或按Ctrl+M打开显示执行计划窗口每次执行sql都会显示出相应的执行计划 2. 执行计划的图表是从右向左看的 3. SQL ...

随机推荐

  1. WebGL管网展示(及TubeGeometry优化)

    前言 管路展示在三维场景中很常见.比如地下管网,建筑里面的水果,暖通管道等等的展示. 建立管路的方式主要两种: 通过3DMax C4D Blender等建模工具进行建模. 通过路径数据,程序生成三维管 ...

  2. workman和swoole区别和异同

    swoole是使用C语言实现的socket通信框架,workerman则是使用纯php实现的socket框架,二者进程模型上也存在很多的不同. 先说下swoole的进程模型,看一下以下解析图 mast ...

  3. PositiveSmallIntegerField、SmallIntegerField和IntegerField

    当您在Django中定义模型时,有几种不同的整数字段类型可供选择,包括PositiveSmallIntegerField.SmallIntegerField和IntegerField.以下是这三种整数 ...

  4. oeasy教您玩转vim - 13 - # 大词小词

    大词小词 回忆上节课内容 我们上次学习了 e e 代表 end 词尾 自有跳跃 还可以成倍次数的跳跃 但其实我是想以一个一个属性地跳跃,有没有方法呢? 查询帮助 没思路的话我们还是得继续查询 :h w ...

  5. Jmeter函数助手17-StringtoFile

    StringtoFile函数用于将字符串写入文件 Path to file (absolute):将写入的文件路径 String to write:要写入的字符 Append to file (tru ...

  6. 【Java】JDBC Part5 DataSource 连接池操作

    JDBC Part5 DataSource 连接池操作 - javax.sql.DataSource 接口,通常由服务器实现 - DBCP Tomcat自带相对C3P0速度较快,但存在BUG,已经不更 ...

  7. 【RabbitMQ】02 工作队列模式

    首先编写一个工作队列的生产者: 发送10条消息然后就关闭,10条消息让RabbitMQ先存着 import com.rabbitmq.client.Channel; import com.rabbit ...

  8. 【微信小程序】01 入门

    官方开发文档: https://developers.weixin.qq.com/miniprogram/dev/devtools/devtools.html 需要去微信公众平台注册开发账号: mp. ...

  9. 【REGX】正则表达式 选中空白行

    参考地址: https://www.cnblogs.com/peijyStudy/p/13201576.html VScode并列替换不够智能,我需要等行粘贴,结果SHIFT+ALT复制内容粘贴上去就 ...

  10. SSH如何通过proxy进行服务器连接

    openssh是什么这里不做解释,但凡是用过linux系统的一般都是会了解这个的,毕竟openssh都是系统自带的应用. openssh一般都是指linux上的客户端,很多linux系统自有客户端的s ...