目次
最近使用到sqlserver数据库并要对查询语句进行调优,之前接触的不多,搜索网上一些帖子和查阅微软msdn资料对sqlserver的查询计划才大致了解了一些,用这篇文章做个总结。本文主要侧重对查询计划的理解,尤其是对微软复杂的运算函数的理解,如理解有误请指出。
如何查看查询计划
关于查询计划的说明
 
具体查询计划分析
初步分析计划
关于表扫描

分析sqlserver查询计划

Jun 07, 2016 pm 03:35 PM
sqlserver 分析する お問い合わせ プラン

最近使用到sqlserver数据库并要对查询语句进行调优,之前接触的不多,搜索网上一些帖子和查阅微软msdn资料对sqlserver的查询计划才大致了解了一些,用这篇文章做个总结。本文主要侧重对查询计划的理解,尤其是对微软复杂的运算函数的理解,如理解有误请指出

最近使用到sqlserver数据库并要对查询语句进行调优,之前接触的不多,搜索网上一些帖子和查阅微软msdn资料对sqlserver的查询计划才大致了解了一些,用这篇文章做个总结。本文主要侧重对查询计划的理解,尤其是对微软复杂的运算函数的理解,如理解有误请指出。

如何查看查询计划

先介绍一下如何查看查询计划。使用【SQL Server Management Studio】进行查询,大致有如下几个方式看查询计划

1、set showplan_all on 在查询前显示计划显示形式为每个查询步骤一行

2、set statistics profile on 在查询后显示计划并包括每个查询步骤的扫描行数和执行次数

3、选中sql语句按Ctrl + L(同工具栏显示预估的查询计划按钮) 以图形方式显示查询计划这个还可以使用setshowplan_xml on命令后者生成的xmlSQL Server Management Studio打开就是图形查询计划

 

关于查询计划的说明

在msdn上找到set showplan_all on返回结果各列的说明

 

分析sqlserver查询计划

 

 

具体查询计划分析

初步分析计划

开启set showplan_all on

执行如下sql

SELECT dbll.N_SZJY GBM, sum(data.N_SL) NNum

FROM DB_SHARE.dbo.DA_JGXT_VW_QBF_FFJL data

left join DB_SHARE.dbo.T_DBLL dbll

ON dbll.C_ZFBH = data.ZFBH and dbll.D_KSRQ  data.D_RQ and (dbll.D_JSRQ >= data.D_RQ ORdbll.D_JSRQ IS NULL)

where data.D_RQ >= '2012-07-02 00:00:00.0' and data.D_RQ  '2012-08-01 23:59:59.0'

GROUP BY dbll.N_SZJY

 

执行计划左半部分

 

分析sqlserver查询计划

 

 接上图计划的右半部分如下:

 

分析sqlserver查询计划

 

 

Sqlserver的执行计划一出来给人一种特别复杂的感觉有木有,其中命令特别多,还可以看到我们没有写出来的语句比如Expr1006Expr1012Bmk1003这都是什么啊?

一步一步来先看看执行计划中每个列的说明

msdn有对执行计划所有运算符的说明

http://msdn.microsoft.com/zh-cn/library/ms191158(v=sql.105)

根据msdn说明,对上面的执行计划分析如下,对于整个计划树的执行是从下到上,从叶子到根的,最上边是整个查询语句:

12、【RID Lookup(OBJECT:([DB_SHARE].[dbo].[T_DBLL] AS [dbll]), SEEK:([Bmk1003]=[Bmk1003]), WHERE:([DB_SHARE].[dbo].[T_DBLL].[D_JSRQ] as [dbll].[D_JSRQ]>=[DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[D_RQ] as [data].[D_RQ] OR [DB_SHARE].[dbo].[T_DBLL].[D_JSRQ] as [dbll].[D_JSRQ] IS NULL) LOOKUP ORDERED FORWARD)

这是一个书签查找步骤,,RIDrecord id Lookup是使用行标示符在堆上进行书签查找,SEEK后面有一个[Bmk1003]=[Bmk1003],看命名是一个书签,在11行的DefinedValues列有对此的定义。

 

11、【Index Seek(OBJECT:([DB_SHARE].[dbo].[T_DBLL].[I_DBLL_ZFBH_KSRQ] AS [dbll]), SEEK:([dbll].[C_ZFBH]=[DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[ZFBH] as [data].[ZFBH] AND [dbll].[D_KSRQ]

这是使用I_DBLL_ZFBH_KSRQ索引在T_DBLL表检索数据,SEEK部分是检索的具体条件,此步骤定义了Bmk100311步应该先于12执行,看来在同一层级下顺序是从上到下的。

11步骤和12步骤的意思是先用I_DBLL_ZFBH_KSRQ索引检索数据,再使用书签查找的方式获取每一行的其他数据。因为I_DBLL_ZFBH_KSRQ是一个非聚集索引,它只包含索引列的数据,实际上对T_DBLL检索出的数据列还包括N_SZJYD_JSRQ,前者要进行Group,后者是进行与DA_JGXT_VW_QBF_FFJLD_RQ的比对,查询语句涉及到非聚集索引不包含的列时就要通过书签查找或聚集索引查找来提取非索引列(T_DBLL此时没有聚集索引,只能用书签查找的方式)。

 

10、【Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1003]))

一次嵌套循环连接,将1112步骤数据连接起来,11步骤的数据作为Outer,在进行书签查找的父步骤一般都是Nested Loops Join,这基本符合内表较大且有索引的条件。

 

9、【Table Scan(OBJECT:([DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL] AS [data]), WHERE:([DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[D_RQ] as [data].[D_RQ]>='2012-07-02 00:00:00.000' AND [DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[D_RQ] as [data].[D_RQ]

DA_JGXT_VW_QBF_FFJL表全表扫描数据,条件是D_RQ在一个时间段内。显然这是需要增加索引的

 

8、【Parallelism(Repartition Streams, RoundRobin Partitioning)

这是什么呢?看看微软古板的说明“Parallelism 运算符执行分发流、收集流和对流重新分区逻辑操作。Argument 列可以包含一个 PARTITION COLUMNS:() 谓词和一个以逗号分隔的分区列的列表。Argument 列还可以包含一个 ORDER BY:() 谓词,以列出分区过程中要保留排序顺序的列。Repartition Streams 运算符处理多个流并生成多个记录流。记录的内容和格式不会改变”。大约是这样,Parallelism表示查询会被并行执行(如果服务器负荷太高可能最终不会并行),这算是sqlserver的一个优化处理,如果服务器处理多任务能力强这就会比串行更有效率,这个并行应该是表示和其他任务的关系,由于10步骤会依赖8步骤的数据,8步骤应该先于10步骤执行

 

7、【Nested Loops(Left Outer Join, OUTER REFERENCES:([data].[ZFBH], [data].[D_RQ], [Expr1012]) WITH UNORDERED PREFETCH)

一次嵌套循环连接,它将810两个步骤得到的数据连接,Outer表是DA_JGXT_VW_QBF_FFJL,因OUTER REFERENCES中的字段都是属于该表。

这里出现了Expr1012,这是个神秘的列,联系上下语句,这个列应该是T_DBLLN_SZJY,因为后面要用此列数据进行分组。

 

6、【Sort(ORDER BY:([dbll].[N_SZJY] ASC))

使用N_SZJY列排序,后面要进行Group,这里必须先进行一次排序

 

5、【Stream Aggregate(GROUP BY:([dbll].[N_SZJY]) DEFINE:([partialagg1007]=COUNT_BIG([DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[N_SL] as [data].[N_SL]), [partialagg1009]=SUM([DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[N_SL] as [data].[N_SL])))

名词解释,“Stream Aggregate 运算符按一列或多列对行分组,然后计算查询返回的一个或多个聚合表达式。此运算符的输出可供查询中的后续运算符引用和/或返回到客户端。Stream Aggregate 运算符要求输入在组中按列进行排序。如果由于前面的 Sort 运算符或已排序的索引查找或扫描导致数据尚未排序,优化器将在此运算符前面使用一个 Sort 运算符

继续根据微软生硬的解释进行分析,这行的处理就是进行一个分组,因为GROUP BY dbll.N_SZJY一句而产生此行处理。其中还有一个COUNT_BIG,这是一个类似COUNT的函数,区别是前者返回bigint后者返回int,不过语句中只有个sum没有count,为什么还要计算呢?跳过先。

 

4、【Parallelism(Gather Streams, ORDER BY:([dbll].[N_SZJY] ASC))

又一个并行查询。“Gather Streams 运算符仅用在并行查询计划中。Gather Streams 运算符处理几个输入流并通过组合这几个输入流生成单个记录输出流。不更改记录的内容和格式。如果此运算符保留顺序,则所有的输入流都必须有序。如果输出已排序,则参数列包含一个 ORDER BY:() 谓词和正在排序的列名称。 Gather Streams函数把输入流组合,不过4行只有一个子节点,我理解到4之前仍存在两个输入流,分别来自DA_JGXT_VW_QBF_FFJL表和T_DBLL表,上面的步骤对两个输入流分别有索引条件过滤和分组,但仍然没有整合,在Gather Streams步骤将两个输入流数据整合起来,那么之后的数据看到的就是一个输入流了

3、【Stream Aggregate(GROUP BY:([dbll].[N_SZJY]) DEFINE:([globalagg1008]=SUM([partialagg1007]), [globalagg1010]=SUM([partialagg1009])))

再次进行流聚合。这里和第5步骤的不同在哪里

5步骤DefinedValues中有如下定义

[partialagg1007]=COUNT_BIG([DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[N_SL] as [data].[N_SL]), [partialagg1009]=SUM([DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[N_SL] as [data].[N_SL])

而此步骤执行了

[globalagg1008]=SUM([partialagg1007]), [globalagg1010]=SUM([partialagg1009])

这就是它们的不同,此步骤再次用N_SZJY分组对之前的计算结果求和,这应该是个避免整合流后分组数据出现重复的操作。

2、【Compute Scalar(DEFINE:([Expr1006]=CASE WHEN [globalagg1008]=(0) THEN NULL ELSE [globalagg1010] END))

Compute Scalar的意思是计算标量。标量,相对于向量而言,无方向数据,就是计算一个数值。这个步骤sqlserver优化器执行了一个case whenglobalagg1008是针对每个N_SZJYcountN_SL),看来之前自动执行count是为了此步,globalagg1010是针对每个N_SZJYsumN_SL),对照我们的语句是SELECT dbll.N_SZJY GBM, sum(data.N_SL) NNum此步骤的意图看来sqlserver会对sum特殊情况的检测,对于整个语句而言就是如果T_DBLL表中N_SZJY列有数据而DA_JGXT_VW_QBF_FFJLN_SL都为null,那么sum就返回null

 

1、【整条语句】。作为查询计划树的根节点,在计划列表中大多数列都是Null,这个计划的估算行是12行,仅供参考,实际查询的结果是1

 

关于表扫描

Sql Server 会有以下方法来查找您需要的数据记录:
1. 
Table Scan】:遍历整个表,查找所匹配的记录行。这个操作将会一行一行的检查,当然,效率也是最差的。
2. 
Index Scan】:根据非聚集索引,扫描索引的全部记录,查找所匹配的记录行,匹配的条件可从查询计划的Argument 列中看到。比第一种方式的查找范围要小(B+索引叶子之间有指针类似链表),因此比【Table Scan】要快。
3. 
Index Seek】:根据非聚集索引,定位(获取)记录的存放位置,然后取得记录(这会使用B+索引查找树的定位算法,基本一条记录2-4IO,取决于表数据量产生的索引树高度),这个方式比起前二种方式会更快。
4. 
Clustered Index Scan】:根据聚集索引扫描全部记录。这个的效率要根据实际情况分析。出现这个步骤可能是效率很差的表现,因为如果条件中的列没有索引,数据库引擎在提取数据的时会考虑进行优化,基于磁盘顺序读比随机读快的原理,数据按照聚集索引的顺序存放,那么用聚集索引来提取数据是一种对更差方式的优化。

比如DA_JGXT_VW_QBF_FFJL表有715455条记录,

如下记录返回8条记录,优化器使用Clustered Index Scan

select * from DB_SHARE.dbo.DA_JGXT_VW_QBF_FFJL where N_ID = 14000

执行时间10s,计划如下,IO消耗是7.21多,此时使用聚集索引扫描来顺序提取数据,这个步骤在这里就是避免更差的随机磁盘读取

 

分析sqlserver查询计划

  

如下语句返回24条记录优化器使用Clustered Index Seek

select * from DB_SHARE.dbo.DA_JGXT_VW_QBF_FFJL where D_RQ = '2012-07-25 00:00:00.0'

执行时间0s毫秒级,计划如下,IO消耗是0.000232

 

分析sqlserver查询计划

  

 而直接使用select top 10 * from DB_SHARE.dbo.DA_JGXT_VW_QBF_FFJL查询计划也会使用Clustered Index Scan

select top 10 * from DB_SHARE.dbo.DA_JGXT_VW_QBF_FFJL

|--Top(TOP EXPRESSION:((10)))

|--Clustered Index Scan (OBJECT:([DB_SHARE].[dbo].[DA_JGXT_VW_QBF_FFJL].[I_DA_JGXT_VW_QBF_FFJL_RQ]))

对于这个无条件的语句这个计划已经是最优的了
5. 
Clustered Index Seek】:根据聚集索引获取记录,不解释,最快!

 

优化Table Scan

使用日期过滤数据,对D_RQ建立聚集索引 

CREATE CLUSTERED INDEX I_DA_JGXT_VW_QBF_FFJL_RQ ON dbo.DA_JGXT_VW_QBF_FFJL(D_RQ)

 

再次查看执行计划

左半部分:

 

 分析sqlserver查询计划

 

右半部分

 

分析sqlserver查询计划

  

Table Scan变为Clustered Index Seek,看右半部分计划中的EstimateRowsEstimateIOEstimateCPUTocalSubTreeCost等都有很大提升

优化RID Lookup

前面提到RID Lookup是使用非聚集索引时提取了索引外的列产生的一种操作,中文解释为书签查找。

微软有一篇专门的文章http://blogs.msdn.com/b/craigfr/archive/2006/06/30/652639.aspx对此作出了解释。每次书签查找会产生一次随机IO,随机IO对于磁盘来说是比较耗费资源的,虽然sqlserver优化器认为这个比不用索引的消耗小些因而选择了这个方式,但可能的情况下我们还是要考虑优化。

优化书签查找的方式大致两种,一种是给目前已经使用的索引加入要查询的列,使得查询的列都在索引中;另一种是使索引成为聚集索引。那么可以考虑创建(C_ZFBH, D_KSRQ, D_JSRQ, N_SZJY)4键联合索引或将(C_ZFBH,D_KSRQ)的索引改为聚集索引。

我先采用了聚集索引优化,查询计划的IOCPUCost都有所提升,主要是TotalSubTreeCost一项提升较多。按照微软对此项的说明为查询开销,这是一个综合的数值,一般这个开销较小的更好,不过也不绝对。

  

分析sqlserver查询计划

 

那么如果用4键联合索引呢?

我发现对比两者的查询计划相差很小,于是我用了set statistics profile on来查看实际的执行情况,这个开关比前面的计划多两列,会返回每个步骤的实际扫描行数和执行次数

列名

说明

Rows

各运算符生成的实际行数

Executes

运算符执行的次数

先看聚集索引,本次执行耗费8s

  

分析sqlserver查询计划

 

 

再看4键联合索引,本次执行耗费12s,值得一提的是此时两个索引都存在,是sqlserver优化器选择了4键联合索引

 

分析sqlserver查询计划

 

 

相比之下,后者的TotalSubTreeCost较小因IO少,但是前者实际扫描的行数较少,且执行时间更短。我在执行前已使用了dbcc dropcleanbuffersdbcc freeproccache清除缓存,不过我使用的数据库是虚拟机,在执行效率上经常有波动,后者的执行时间长可能因为索引还没有全部加载到内存中,实际测试时有时后者的时间更短。不过鉴于D_JSRQ是存在空值并且检索的时候都要使用(D_JSRQ>日期 or D_JSRQ is null)这样的条件,前者可能更好。

 

看懂Sqlserver查询计划

http://www.cnblogs.com/fish-li/archive/2011/06/06/2073626.html

msdn逻辑运算符和物理运算符引用

http://msdn.microsoft.com/zh-cn/library/ms191158(v=sql.105)

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

mdfファイルをsqlserverにインポートする方法 mdfファイルをsqlserverにインポートする方法 Apr 08, 2024 am 11:41 AM

インポート手順は次のとおりです。 MDF ファイルを SQL Server のデータ ディレクトリ (通常は C:\Program Files\Microsoft SQL Server\MSSQL\DATA) にコピーします。 SQL Server Management Studio (SSMS) でデータベースを開き、[アタッチ] を選択します。 「追加」ボタンをクリックして、MDF ファイルを選択します。データベース名を確認し、「OK」ボタンをクリックします。

指定されたオブジェクトが sqlserver データベースにすでに存在するという問題を解決する方法 指定されたオブジェクトが sqlserver データベースにすでに存在するという問題を解決する方法 Apr 05, 2024 pm 09:42 PM

SQL Server データベースに既に存在する同じ名前のオブジェクトについては、次の手順を実行する必要があります。 オブジェクトの種類 (テーブル、ビュー、ストアド プロシージャ) を確認します。 IF NOT EXISTS を使用すると、オブジェクトが空の場合に作成をスキップできます。オブジェクトにデータがある場合は、別の名前を使用するか、構造を変更してください。既存のオブジェクトを削除するには、DROP を使用します (注意してください。バックアップを推奨します)。スキーマの変更をチェックして、削除または名前変更されたオブジェクトへの参照がないことを確認します。

sqlserver サービスを開始できない場合の対処方法 sqlserver サービスを開始できない場合の対処方法 Apr 05, 2024 pm 10:00 PM

SQL Server サービスの開始に失敗した場合の解決手順は次のとおりです。 エラー ログを確認して、根本原因を特定します。サービス アカウントにサービスを開始する権限があることを確認してください。依存関係サービスが実行されているかどうかを確認します。ウイルス対策ソフトウェアを無効にします。 SQL Server のインストールを修復します。修復が機能しない場合は、SQL Server を再インストールします。

SQLサーバーのポート番号を確認する方法 SQLサーバーのポート番号を確認する方法 Apr 05, 2024 pm 09:57 PM

SQL Server のポート番号を表示するには: SSMS を開いてサーバーに接続します。オブジェクト エクスプローラーでサーバー名を見つけ、右クリックして [プロパティ] を選択します。 「接続」タブで、「TCP ポート」フィールドを表示します。

SQLserver データベースはどこにありますか? SQLserver データベースはどこにありますか? Apr 05, 2024 pm 08:21 PM

SQL Server データベース ファイルは、通常、次のデフォルトの場所に保存されます。 Windows: C:\Program Files\Microsoft SQL Server\MSSQL\DATALinux: /var/opt/mssql/data データベース ファイルの場所は、データベース ファイル パスを変更することでカスタマイズできます。設定。

sqlserverで誤って削除したデータベースを回復する方法 sqlserverで誤って削除したデータベースを回復する方法 Apr 05, 2024 pm 10:39 PM

SQL Server データベースを誤って削除した場合は、次の手順を実行して回復できます: データベース アクティビティの停止、ログ ファイルのバックアップ、データベース ログの確認、回復オプション: バックアップからの復元、トランザクション ログからの復元、DBCC CHECKDB の使用、3 番目の使用パーティーツール。データ損失を防ぐために、データベースを定期的にバックアップし、トランザクション ログを有効にしてください。

インストールが失敗した場合に sqlserver を削除するにはどうすればよいですか? インストールが失敗した場合に sqlserver を削除するにはどうすればよいですか? Apr 05, 2024 pm 11:27 PM

SQL Server のインストールが失敗した場合は、次の手順に従ってクリーンアップできます。 SQL Server をアンインストールする レジストリ キーを削除する ファイルとフォルダーを削除する コンピューターを再起動する

12306 過去のチケット購入記録の確認方法 過去のチケット購入記録の確認方法 12306 過去のチケット購入記録の確認方法 過去のチケット購入記録の確認方法 Mar 28, 2024 pm 03:11 PM

12306 チケット予約アプリの最新バージョンをダウンロードします。誰もが非常に満足している旅行チケット購入ソフトウェアです。行きたい場所に行くのに非常に便利です。ソフトウェアには多くのチケット ソースが提供されています。本物のチケットを渡すだけで済みます。 - 氏名認証によるオンラインチケット購入 全ユーザー 旅行券や航空券を簡単に購入でき、さまざまな割引が受けられます。また、チケットを入手するための事前予約も開始できます。ホテルや特別な車の送迎も予約できます。これを使用すると、ワンクリックで行きたい場所に行き、チケットを購入できます。旅行がより簡単で便利になり、すべての人に旅行体験を提供します編集者はオンラインで詳細を説明するようになり、12306 人のユーザーに過去のチケット購入記録を表示する方法が提供されます。 1. Railway 12306 を開き、右下隅の [My] をクリックして、[My Order] をクリックします。 2. 注文ページで [Paid] をクリックします。 3. 有料ページにて

See all articles