SQL优化:基本概念(索引调优、统计信息、查询调整、资源调控)
1、索引碎片
/*======================================================= 注意:所有的参数都是以当前数据库来计算的,所以必须指定完全限定。 模式影响如何收集碎片数据: LIMITED:扫描堆所有的页,对于索引,只扫描叶级别上面的父级别页 SAMPLED:收集堆或索引中1%采样率的数据 DETAILED:扫描所有页,最精确但速度最慢 当制定null或default时,等同于limited模式。 ===================================================*/ select DB_NAME(d.database_id), OBJECT_NAME(d.object_id), i.name, --索引名称 d.index_type_desc, d.alloc_unit_type_desc, d.index_depth, --索引的深度 d.index_level, --索引当前级别 --索引的逻辑碎片,或 IN_ROW_DATA 分配单元中堆的区碎片 d.avg_fragmentation_in_percent, --IN_ROW_DATA分配单元的叶级别中的碎片数 d.fragment_count, --IN_ROW_DATA 分配单元的叶级别中的一个碎片的平均页数 d.avg_fragment_size_in_pages, --对于索引,平均百分比应用于 IN_ROW_DATA 分配单元中 b 树的当前级别 --对于堆,表示 IN_ROW_DATA 分配单元中所有数据页的平均百分比 d.avg_page_space_used_in_percent, d.page_count, --索引或数据页的总数 d.record_count, --总记录数 d.min_record_size_in_bytes, --最小记录大小(字节) d.max_record_size_in_bytes, --最大记录大小(字节) d.avg_record_size_in_bytes, --平均记录大小(字节) --压缩页的数目 d.compressed_page_count, --分配单元中将被虚影清除任务删除的虚影记录数 d.ghost_record_count, --由分配单元中未完成的快照隔离事务保留的虚影记录数 d.version_ghost_record_count, --堆中具有指向另一个数据位置的转向指针的记录数 d.forwarded_record_count from sys.dm_db_index_physical_stats (db_id('wcc'), --数据库id null, --对象id:数据库名称.架构.对象名称 null, --索引id null, --分区号 '' --模式 )d inner join sys.indexes i on d.object_id = i.object_id and d.index_id = i.index_id
2、索引使用情况
create table txt(id int primary key,v varchar(10)) create index idx_txt_v on txt (v) insert into txt values(1,'a'), (2,'b'), (3,'c') select v from txt --idx_txt_v索引中的user_scans会显示1 --user_updates会显示1 select DB_NAME(d.database_id), OBJECT_NAME(d.object_id), i.name, user_seeks, user_scans, user_lookups, user_updates --通过用户查询执行的更新次数 from sys.dm_db_index_usage_stats d inner join sys.indexes i on d.object_id = i.object_id and d.index_id = i.index_id where database_id = DB_ID('test2') --添加数据后,user_updates会显示2 insert into txt values(4,'a'), (5,'b'), (6,'c')
丢失的索引
SELECT TOP 30 ROUND(s.avg_total_user_cost * s.avg_user_impact * ( s.user_seeks + s.user_scans ), 0) AS [Total Cost] , s.avg_total_user_cost * ( s.avg_user_impact / 100.0 ) * ( s.user_seeks + s.user_scans ) AS Improvement_Measure , DB_NAME() AS DatabaseName , d.[statement] AS [Table Name] , equality_columns , inequality_columns , included_columns FROM sys.dm_db_missing_index_groups g INNER JOIN sys.dm_db_missing_index_group_stats s ON s.group_handle = g.index_group_handle INNER JOIN sys.dm_db_missing_index_details d ON d.index_handle = g.index_handle WHERE s.avg_total_user_cost * ( s.avg_user_impact / 100.0 ) * ( s.user_seeks + s.user_scans ) > 10 ORDER BY [Total Cost] DESC , s.avg_total_user_cost * s.avg_user_impact * ( s.user_seeks + s.user_scans ) DESC
3、统计信息
--创建有主键的表,会自动建立聚集索引,自动生成索引所对应的统计信息 create table txt(id numeric(10,0) primary key, v varchar(20), vv int ) --建立非聚集索引后自动生成索引所对应的统计信息 create index txt_v on txt(v) insert into txt select object_id, type_desc, schema_id from sys.objects where LEN(type_desc) < 20 --1.1创建统计信息,通过扫描表或索引视图中的所有行来计算统计信息 create statistics txtStats on dbo.txt(v,vv) with fullscan --1.2采用为10%的行 create statistics txtStatsPercent on dbo.txt(v,vv) with sample 10 percent --1.3采样为100行 create statistics txtStatsRow on dbo.txt(v,vv) where id < 1000 --使用筛选谓词创建的统计信息 with sample 100 rows, norecompute --覆盖数据库选项选项AUTO_STATISTICS_UPDATE --查询优化器将完成statistics_name的任何正在进行中的统计信息更新 --并禁用将来的更新 --2.更新表或索引视图的统计信息 --2.1更新表中为某些列建立的统计信息 update statistics txt(txtStats) update statistics txt(txtStats) with sample 50 percent update statistics txt(txtStatsRow) with resample, --使用最近的采样速率更新每个统计信息 norecompute --查询优化器将完成此统计信息更新并禁用将来的更新 --2.2更新索引的统计信息 update statistics txt(txt_v) with fullscan --2.3更新表的所有统计信息 update statistics txt with all --2.4更新表中所有为某一些列建立的统计信息 update statistics txt(txt_v) with columns --2.5更新表中所有为索引建立的统计信息 update statistics txt(txt_v) with index /*============================================== 调用CREATE STATISTICS语句以便对于不是统计信息对象中第一列的列创建单列统计信息。 创建单列统计信息会增加直方图的数目,这可能会改进基数估计、查询计划和查询性能。 统计信息对象的第一列具有直方图;其他列不具有直方图. 在查询执行时间很重要并且不能等待查询优化器以生成单列统计信息时, sp_createstats对于基准确定之类的应用程序十分有用。 在大多数情况下,无需使用sp_createstats, 而是由查询优化器根据需要生成单列统计信息, 以便在AUTO_CREATE_STATISTICS选项为ON时改进查询计划. ========================================================*/ --3.1参数值默认都是NO. --@indexonly:仅对位于现有索引中并且不是任何索引定义中的第一列的列创建统计信息 --@fullscan: 将CREATE STATISTICS语句与FULLSCAN选项一起使用 --@norecompute:将CREATE STATISTICS语句与NORECOMPUTE选项一起使用 exec sp_createstats @indexonly ='indexonly', @fullscan ='fullscan', @norecompute = 'norecompute' --3.2对当前数据库中所有用户定义表和内部表运行UPDATE STATISTICS --将使用默认的抽样来更新统计信息 exec sp_updatestats @resample = 'no' --使用UPDATE STATISTICS语句的RESAMPLE选项来更新统计信息 exec sp_updatestats @resample = 'resample' /*==================================================== 4.显示表或索引视图的当前查询优化统计信息 查询优化器使用统计信息来估计查询结果中的基数或行数, 查询优化器就可以创建高质量的查询计划。 例如,查询优化器可以使用基数估计,在查询计划中选择索引查找运算符而不是索引扫描运算符, 避免消耗大量资源的索引扫描,提高查询性能。 对于表,统计信息是根据索引或表列的列表创建的。 统计信息包含一个带有统计信息的相关元数据的标题、 一个用于度量各列之间的相关性的密度向量、 一个带有统计信息第一个键列中的值的分布的直方图。 数据库引擎可以使用统计信息中的任何数据计算基数估计。 DBCC SHOW_STATISTICS 根据统计信息对象中存储的数据显示标题、直方图和密度向量。 使用以下语法,您可以指定表或索引视图以及target(目标索引名称、统计信息名称或列名) DBCC SHOW_STATISTICS ( table_or_indexed_view_name , target ) [ WITH [ NO_INFOMSGS ] < option > [ , n ] ] < option > :: = STAT_HEADER | DENSITY_VECTOR | HISTOGRAM | STATS_STREAM ======================================================*/ DBCC SHOW_STATISTICS('dbo.txt','txtStats') DBCC SHOW_STATISTICS('dbo.txt','vv') with HISTOGRAM --5.删除某个表的统计信息 drop statistics txt.txtStatsRow, txt.txtStatsPercent --表.统计信息名
统计信息的更新时间:
--1.更新日期,列名 select stats_date(s.object_id,s.stats_id) 统计信息的最新更新的日期, c.name as 列名, * from sys.stats s inner join sys.stats_columns sc on s.object_id = sc.object_id and s.stats_id = sc.stats_id inner join sys.columns c on s.object_id = c.object_id and sc.column_id = c.column_id where s.object_id = object_id('xx') --2.或者直接这样 sp_helpstats 'xx'
那么在更新某个表的统计信息时,会导致阻塞问题吗?
通过实验发现,在更新某个表的统计信息时,会对表加上X锁,这个肯定会导致阻塞问题:
4、查询调整
create table txt(id numeric(10,0) primary key, v varchar(20), vv int ) create index txt_v on txt(v) insert into txt select object_id, type_desc, schema_id from sys.objects where LEN(type_desc) < 20 /*================================================= 1.1动态sql语句很容易导致SQL注入,与存储过程不同的是, 动态sql、常规即席查询在每次执行时都会生成新的执行计划, 所以查询的性能不稳定. ===================================================*/ exec ('select * from txt'); /*=============================================== 1.2创建可重复使用的、只有查询参数不同的查询计划, 来处理动态sql的性能问题. 参数是类型安全的,不能以非指定的数据类型使用, 也就是参数类型与字段类型必须一致,否则会报错. =================================================*/ exec sp_executesql @statement = N'select * from txt where id > @id and vv > @vv', @params = N'@id numeric(10,0), @vv int', --定义为@id int时会报错 @id = 10000, @vv = 2 --2.强制SQL Server使用特定的查询计划 set statistics xml on select * from txt where id > 1000 and vv > 2 set statistics xml off select * from txt option( use plan '<ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.1" Build="10.50.1600.1"> <BatchSequence><Batch><Statements> <StmtSimple StatementText="SELECT * FROM [txt] WHERE [id]>@1 AND [vv]>@2" StatementId="1" StatementCompId="1" StatementType="SELECT" StatementSubTreeCost="0.00429444" StatementEstRows="42.2945" StatementOptmLevel="TRIVIAL" QueryHash="0xA4E0AA4B0A87F88B" QueryPlanHash="0x3325250D8A42F500"> <StatementSetOptions QUOTED_IDENTIFIER="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" NUMERIC_ROUNDABORT="false"/> <QueryPlan DegreeOfParallelism="1" CachedPlanSize="16" CompileTime="1" CompileCPU="1" CompileMemory="136"><RelOp NodeId="0" PhysicalOp="Clustered Index Seek" LogicalOp="Clustered Index Seek" EstimateRows="42.2945" EstimateIO="0.00386574" EstimateCPU="0.0004287" AvgRowSize="34" EstimatedTotalSubtreeCost="0.00429444" TableCardinality="292" Parallel="0" EstimateRebinds="0" EstimateRewinds="0"> <OutputList><ColumnReference Database="[test2]" Schema="[dbo]" Table="[txt]" Column="id"/><ColumnReference Database="[test2]" Schema="[dbo]" Table="[txt]" Column="v"/> <ColumnReference Database="[test2]" Schema="[dbo]" Table="[txt]" Column="vv"/> </OutputList><RunTimeInformation><RunTimeCountersPerThread Thread="0" ActualRows="5" ActualEndOfScans="1" ActualExecutions="1"/> </RunTimeInformation><IndexScan Ordered="1" ScanDirection="FORWARD" ForcedIndex="0" ForceSeek="0" NoExpandHint="0"><DefinedValues> <DefinedValue><ColumnReference Database="[test2]" Schema="[dbo]" Table="[txt]" Column="id"/> </DefinedValue><DefinedValue><ColumnReference Database="[test2]" Schema="[dbo]" Table="[txt]" Column="v"/></DefinedValue><DefinedValue> <ColumnReference Database="[test2]" Schema="[dbo]" Table="[txt]" Column="vv"/></DefinedValue></DefinedValues> <Object Database="[test2]" Schema="[dbo]" Table="[txt]" Index="[PK__txt__3213E83F4D1564AE]" IndexKind="Clustered"/> <SeekPredicates><SeekPredicateNew><SeekKeys> <StartRange ScanType="GT"><RangeColumns> <ColumnReference Database="[test2]" Schema="[dbo]" Table="[txt]" Column="id"/></RangeColumns><RangeExpressions><ScalarOperator ScalarString="CONVERT_IMPLICIT(numeric(10,0),[@1],0)"><Identifier> <ColumnReference Column="ConstExpr1003"><ScalarOperator> <Convert DataType="numeric" Precision="10" Scale="0" Style="0" Implicit="1"> <ScalarOperator><Identifier><ColumnReference Column="@1"/> </Identifier></ScalarOperator></Convert></ScalarOperator> </ColumnReference></Identifier></ScalarOperator> </RangeExpressions></StartRange></SeekKeys></SeekPredicateNew> </SeekPredicates><Predicate><ScalarOperator ScalarString="[test2].[dbo].[txt].[vv]>CONVERT_IMPLICIT(int,[@2],0)"><Compare CompareOp="GT"> <ScalarOperator><Identifier><ColumnReference Database="[test2]" Schema="[dbo]" Table="[txt]" Column="vv"/></Identifier></ScalarOperator><ScalarOperator> <Identifier><ColumnReference Column="ConstExpr1004"><ScalarOperator> <Convert DataType="int" Style="0" Implicit="1"><ScalarOperator> <Identifier><ColumnReference Column="@2"/></Identifier> </ScalarOperator></Convert></ScalarOperator></ColumnReference> </Identifier></ScalarOperator></Compare></ScalarOperator> </Predicate></IndexScan></RelOp><ParameterList> <ColumnReference Column="@2" ParameterCompiledValue="(2)" ParameterRuntimeValue="(2)"/> <ColumnReference Column="@1" ParameterCompiledValue="(1000)" ParameterRuntimeValue="(1000)"/></ParameterList></QueryPlan> </StmtSimple></Statements></Batch></BatchSequence></ShowPlanXML>') --3.不修改应用程序,应用查询提示 exec sp_create_plan_guide @name = 'plan_guide_txt', @stmt = 'select * from txt inner join sys.objects o on o.object_id = txt.id', @type = 'sql', @module_or_batch = null, @params = null, @hints = 'option(merge join)' --当下面的这段代码与上面的代码完全一样(包括空格,回车)时,系统才会应用查询提示 select * from txt inner join sys.objects o on o.object_id = txt.id --4.从缓存创建计划指南 begin select * from txt inner join sys.objects o on o.object_id = txt.id select * from sys.objects end go --找到计划句柄 select e.text, d.statement_start_offset, d.plan_handle --计划句柄 from sys.dm_exec_query_stats d cross apply sys.dm_exec_sql_text(d.sql_handle)e where e.text like '%from txt%' /*======================================================== 从计划句柄建立计划指南 参数@statement_start_offset:在批处理中可能会有多条语句, 此参数指出批处理中创建计划指南的语句,在批处理中的开始偏移。 如果指定为NULL,那么会为批处理中的所有语句建立计划指南 ==========================================================*/ exec sp_create_plan_guide_from_handle @name = 'plan_guide_txt_handle', @plan_handle = 0x060018007BA32F074021425A020000000000000000000000, @statement_start_offset = 14 --5.使用计划指南把非参数化查询参数化 --5.1运行多条类似的查询 select * from txt where id = 8 select * from txt where id = 9 --5.2通过查询缓存的计划所对应的sql文本,发现很多都是相同的 --而且大部分的objtype都是proc,adhoc,prepared. SELECT * FROM SYS.dm_exec_cached_plans E CROSS APPLY SYS.dm_exec_sql_text(E.plan_handle)EE WHERE EE.text LIKE '%select * from txt where id =%' declare @sqltext nvarchar(max) declare @parameter nvarchar(max) --5.3获取查询的参数化形式以及查询的参数,放入变量中 exec sp_get_query_template @querytext = N'select * from txt where id = 8', @templatetext= @sqltext output, @parameters = @parameter output --5.4使用模板来创建计划指南 exec sp_create_plan_guide @name = 'plan_guide_txt_template', @stmt = @sqltext, @type = 'template', @module_or_batch = null, @params = @parameter, @hints = 'option(parameterization forced)' --5.5再次查询发现多条执行计划已经变为一条,usecounts计数增加 SELECT * FROM SYS.dm_exec_cached_plans E CROSS APPLY SYS.dm_exec_sql_text(E.plan_handle)EE WHERE EE.text LIKE '%select * from txt where id =%' --6.显示计划指南的元数据 select p.plan_guide_id, p.name, p.create_date, p.modify_date, p.is_disabled, --是否禁用计划指南 p.query_text, --创建计划指南所依据的查询文本 p.scope_type, --3个类型:object,sql,template p.scope_type_desc, p.scope_object_id , --如果类型为object,那么指出对象id scope_batch, --如果scope_type为SQL,则为批处理文本。 --如果批处理类型不是SQL,则其值为NULL p.parameters, --定义与计划指南关联的参数列表的字符串 p.hints --与计划指南关联的 OPTION 子句提示 from sys.plan_guides p /*============================================================= 7.验证指定计划指南的有效性。返回计划指南应用于其查询时遇到的第一条错误消息。 如果计划指南有效,则将返回一个空的行集。在更改数据库的物理设计后,计划指南可能会变为无效。 例如,如果计划指南指定了特定索引并且随后将该索引删除,则查询将不能再使用该计划指南。 通过验证计划指南,可确定优化器是否能够在不进行修改的情况下直接使用该指南。 例如,基于函数的结果,可决定删除该计划指南并重新调整查询或修改数据库设计, 例如,重新创建计划指南中指定的索引。 ===============================================================*/ select pp.msgnum, --错误消息的 ID pp.severity, --消息的严重级别 pp.state, --错误的状态号,用于指示发生错误的代码位置 pp.message --错误的消息正文 from sys.plan_guides p cross apply sys.fn_validate_plan_guide(p.plan_guide_id) pp /*============================================== 8.删除或者禁用计划指南 disable,disable all:禁用(所有)计划指南 enable,enable all:启用(所有)计划指南 drop,drop all:删除(所有)计划指南 注意:当指定all时,不能指定计划指南名称。 ================================================*/ exec sp_control_plan_guide @operation = 'disable', --禁用计划指南 @name = 'plan_guide_txt' --删除所有计划指南 exec sp_control_plan_guide @operation = 'drop all'
5、资源调控
在SQL Server 2008中引入了使用资源调控器来限制工作负荷的资源消耗。在SQL Server内部包含了两个资源池:默认和内部。内部资源池不能修改,并且使SQL Server的活动不受资源的限制。默认资源池可以把连接请求,连接、配置资源调控器,默认情况下没有限制,之后可以修改。
一个或多个工作组可以绑定到一个资源池,使用工作负荷组可以定义资源池中重要的请求、最大授予内存比、最大以秒为单位的cpu时间、最大授予内存超时时间、最大并行度、同时执行的请求的最大数量。工作负荷组也包含:内部和默认工作组。默认工作组用在没有被任何分类器用户定义函数覆盖的任何请求上。多个工作负荷组可以关联到一个资源池,但是一个工作负荷组不能关联到多个资源池。
在创建用户定义工作负荷组和绑定到资源池后,可以创建一个帮助确定:进入的SQL Server连接和请求属于哪个工作负荷组的分类器用户定义函数。
--1.创建资源池 --创建应用程序资源池 create resource pool app_query with ( MIN_CPU_PERCENT = 25, MAX_CPU_PERCENT = 75, MIN_MEMORY_PERCENT = 25, MAX_MEMORY_PERCENT = 75 ) --创建即席查询资源池 CREATE RESOURCE POOL adhoc_query with ( min_cpu_percent = 5, max_cpu_percent = 25, min_memory_percent = 5, max_memory_percent = 25 ) --修改即席查询资源池 alter resource pool adhoc_query with ( min_memory_percent = 10, max_memory_percent = 50 ) --2.创建工作负荷组 --创建应用程序a的工作负荷组 create workload group app_a with ( importance = high, request_max_memory_grant_percent = 75, request_max_cpu_time_sec = 75, request_memory_grant_timeout_sec = 120, max_dop = 8, group_max_requests = 8 ) using app_query --使用应用程序资源池 --创建应用程序b的工作负荷组 create workload group app_b with ( importance = low, request_max_memory_grant_percent = 50, request_max_cpu_time_sec = 50, request_memory_grant_timeout_sec = 360, max_dop = 1, group_max_requests = 4 ) using app_query --使用应用程序资源池 --创建即席查询工作负荷组 create workload group adhoc_user with ( importance = low, request_max_memory_grant_percent = 100, request_max_cpu_time_sec = 120, request_memory_grant_timeout_sec = 360, max_dop = 1, group_max_requests = 5 ) using adhoc_query --修改工作负荷组 alter workload group app_b with ( importance = medium ) --3.分类器函数必须在master数据库中创建 use master go --返回工作负荷组名称,系统会根据工作组名称,将连接定位到工作组所对应的资源池 create function dbo.wc_classifier() returns sysname with schemabinding as begin declare @resource_group_name sysname if SUSER_SNAME() in ('sa') set @resource_group_name = 'app_a' if SUSER_SNAME() in ('sa') set @resource_group_name = 'app_b' if HOST_NAME() in ('abc') set @resource_group_name = 'adhoc_query' if @resource_group_name is null set @resource_group_name = 'default' return @resource_group_name end go --设置资源调度器的分类器函数,并且重新配置 alter resource governor with ( classifier_function = dbo.wc_classifier ) go --为了启用配置,需要执行reconfigure alter resource governor reconfigure go --4.查看元数据 --资源池的元数据 select pool_id, min_cpu_percent, max_cpu_percent, min_memory_percent, max_memory_percent from sys.resource_governor_resource_pools --负荷工作组元数据 select group_id, name, pool_id, importance, request_max_memory_grant_percent, request_max_cpu_time_sec, request_memory_grant_timeout_sec, max_dop, group_max_requests from sys.resource_governor_workload_groups --显示资源调控器的配置信息,包含了:分类器函数,是否启用 select classifier_function_id, OBJECT_NAME(classifier_function_id, DB_ID('master') ), --函数名 is_enabled --是否启用 from sys.resource_governor_configuration --5.删除资源池,工作负荷组,分类器函数 use master go drop workload group app_a drop workload group app_b drop workload group adhoc_user drop resource pool adhoc_query drop resource pool app_query --禁用设置 alter resource governor disable --设置不在使用分类器函数 alter resource governor with ( classifier_function = null ) --删除分类器函数 drop function dbo.wc_classifier