热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

存储过程的执行信息

近来遇到一个比较困扰的优化问题,存储过程的主体逻辑如下:ALTERPROC[dbo].[DBA_TroubleShooting]ASBEGINDEC

近来遇到一个比较困扰的优化问题,存储过程的主体逻辑如下:

ALTER PROC [dbo].[DBA_TroubleShooting]
AS
BEGINDECLARE @StartTime DATETIMEDECLARE @EndTime DATETIMESELECT @StartTime=CONVERT(VARCHAR,GETDATE()-1,112),@EndTime=CONVERT(VARCHAR,GETDATE(),112)DECLARE @Spreader VARCHAR(32)DECLARE loop_cursor CURSOR FOR SELECT spreadAccount FROM dbo.SpreadStatisticsAccount ssa WITH(NOLOCK)--WHERE spreadAccount='LG0537' --分别用LG0537、LG0540测试OPEN loop_cursor;FETCH NEXT FROM loop_cursor INTO @SpreaderWHILE @@FETCH_STATUS = 0BEGIN-- 日登录用户DECLARE @DayLoginUserNum INTSELECT @DayLoginUserNum = COUNT(DISTINCT ais.UserId)FROM dbo.AccountsInfoSimple ais WITH(NOLOCK)INNER JOIN dbo.UserLoginRecord t WITH(NOLOCK) ON ais.userID = t.userIDWHERE ais.spreaderAccount = @Spreader AND ais.userType = 0AND t.loginDate BETWEEN @StartTime AND @EndTime AND t.typeCode = '02';SELECT @DayLoginUserNum --注册、登录、充值、活跃数、游戏时长...--INSERT INTO dbo.SpreadAccountStatisticsNew(...)VALUES(...,@Spreader,@DayLoginUserNum,...)FETCH NEXT FROM loop_cursor INTO @SpreaderENDCLOSE loop_cursor;DEALLOCATE loop_cursor;
END

View Code

游标统计各推广号对应的注册、登录、充值、活跃数、游戏时长等数据,并将结果插入到统计表。这里变量是在存储过程里面定义的,它的值是在存储过程的语句执行的过程中得到的。对于这种本地变量,SQL Server在编译的时候不知道它的值是多少。
我们使用代码中提供的两个推广号分别测试存储过程的执行情况,spreaderAccount='LG0537'



spreaderAccount='LG0540'



它们的执行计划相同,并不是重用了执行计划。每次都是先修改存储过程(对应缓存被清除),然后再执行存储过程。从执行计划可以看出,对于@Spreader变量的两个不同值,它对AccountsInfoSimple表的估计行数(EstimateRows)都是2978.927。但实际上LG0537只有1条记录,LG0540却有955760条记录。它们记录数直接影响UserLoginRecord的执行次数(Executes)。
一般来说,使用本地变量作出来的执行计划是一种比较"中庸"的方法,不是最快的,也不是最慢的。它对语句性能的影响,一般不会有parameter sniffing那么严重。很多时候,它还是解决parameter sniffing的一个候选方案。本例中的@Spreader是本地变量,但是它选择的执行计划并不太好。尤其是@Spreader在AccountsInfoSimple表返回记录数很多时,嵌套循环简直就是一个噩梦。
修改推广号语句where spreadAccount in('LG0537','LG0540'),实际执行计划如下:


针对每一个推广号都可以看到计算&#64;DayLoginUserNum的执行计划&#xff0c;并且会有每个操作的实际行数、执行次数信息。如果从sys.dm_exec_query_plan(These plans that are stored in cache do not contain runtime information such as the actual number of rows or actual number of executions.They are the estimated plans.<>,Page52)中取对应的执行计划&#xff0c;只有估计信息&#xff0c;不能一眼看出选择的操作是否合适&#xff1a;

查看过程中语句执行情况&#xff1a;

存储过程执行一次(ProcExCount)&#xff0c;&#64;DayLoginUserNum语句执行两次(SQLExCount)&#xff0c;从逻辑读、CPU时间、总时间可知&#xff0c;语句第二次执行占大部分开销。这里第二次的&#64;DayLoginUserNum重用第一次的语句缓存计划。
如果在日登录用户语句的末尾加上option (recompile)&#xff0c;再次执行后获取过程语句执行情况&#xff1a;

&#64;DayLoginUserNum语句显示只执行一次&#xff0c;这是由于语句重编译只保留最后一次的缓存。指定语句层次的Recompile&#xff0c;存储过程级别的计划重用还是会有的。只是在运行到那句话的时候&#xff0c;才会发生重编译。下图中执行两次带option (recompile)的存储过程&#xff1a;

注意加上option (recompile)&#xff0c;执行计划与之前不同&#xff0c;因此逻辑读、CPU时间、总时间有所差异&#xff1a;


嵌套循环的外部表与内部表进行调换&#xff0c;使用的索引也有所改变。单独从逻辑读来看&#xff0c;语句加有option (recompile)貌似比不加所读取的页面要少。但&#xff0c;真的是这样吗&#xff1f;如果99%的推广号在AccountsInfoSimple表中只有少量匹配的记录&#xff0c;option (recompile)还合适吗&#xff1f;从前面的执行计划来看并不一定。我希望的是当推广号在AccountsInfoSimple表匹配记录很少时&#xff0c;它选择AccountsInfoSimple(外部表&#xff0c;IDX_spreaderAccount_userType)嵌套循环UserLoginRecord(内部表&#xff0c;IX_Userid)&#xff1b;当推广号在AccountsInfoSimple表匹配记录很多时&#xff0c;它使用UserLoginRecord(外部表&#xff0c;logonDate)嵌套循环AccountsInfoSimple(内部表&#xff0c;IX_AccountsInfoSimple_UserID)&#xff0c;或者两表哈希匹配得出结果。
不太明白的是&#xff0c;加有option (recompile)&#xff0c;每次执行语句为什么不能选择"聪明"点的执行计划&#xff1f;
发现存储过程只统计了一个推广号数据&#xff0c;用时却比以往400&#43;推广号还多&#xff01;这个推广号对应在AccountsInfoSimple表有千万级的数据量&#xff0c;可是之前这个推广号也包含在内&#xff0c;等待核查&#xff01;&#xff01;&#xff01;
一个推广号时

存储过程执行8次&#xff0c;&#64;DayLoginUserNum语句执行一次。可以看到&#64;DayLoginUserNum开始时间是(2016-05-13 07:25:00.363)&#xff0c;&#64;DayGameUserNum开始时间是(2016-05-13 07:54:47.530)&#xff0c;中间隔了29分钟&#xff0c;大致等于last_elapsed_time(1787197221µs)。根据总消耗与最后消耗&#xff0c;可知此时sys.dm_exec_query_stats只保留了最后一次的执行&#xff0c;汇总存储过程中的各语句的消耗小于存储过程本身的消耗。
422个推广号时

存储过程执行9次&#xff0c;&#64;DayLoginUserNum执行423次(之前有执行1次)。这是由于存储过程读取游标得到422个推广号&#xff0c;存储过程执行一次&#xff0c;对应语句执行422次。&#64;DayLoginUserNum最后执行时间是(2016-05-13 10:04:19.450)&#xff0c;&#64;DayGameUserNum最后执行时间是(2016-05-13 10:04:47.363)&#xff0c;中间只隔了28秒&#xff01;要知道&#xff0c;它们对应的是同一个推广号&#xff0c;时间竟然相差那么多&#xff01;
上面两图最大的区别是在计算&#64;DayLoginUserNum时的last_physical_reads&#xff0c;能解释的是执行一个推广号时有很多其他作业在执行&#xff0c;而且它的物理读太高&#xff0c;导致长时间的等待。执行多个推广号时&#xff0c;由于前面的推广号对应reads很低&#xff0c;对系统影响不大&#xff0c;随着其他作业的执行&#xff0c;相关数据逐渐缓存到内存&#xff0c;等到执行最后的推广号时&#xff0c;只需从内存读取数据即可。我们可以看到它们的last_logical_reads差不多。
附上05版本查看存储过程的执行情况的语句

--存储过程的执行情况(b汇总值会小于a的实际值)
SELECT top 50 CASE when c.dbid &#61; 32767then &#39;Resource&#39; else DB_NAME(c.dbid) end DbName--,OBJECT_SCHEMA_NAME(c.objectid,c.dbid) AS SchName,OBJECT_NAME(c.objectid,c.dbid) AS ObjName,MIN(b.creation_time) AS creation_time --有时候多个计划存在于同一储存过程的缓存中,MIN(b.last_execution_time) AS last_execution_time,SUM(a.usecounts) AS UseCount,SUM(b.total_logical_reads) / SUM(a.usecounts) AS avg_logical_reads,SUM(b.total_logical_writes) / SUM(a.usecounts) AS avg_logical_writes,SUM(b.total_worker_time) / SUM(a.usecounts) AS avg_worker_time,SUM(b.total_elapsed_time) / SUM(a.usecounts) AS avg_elapsed_time
FROM sys.dm_exec_cached_plans a with(nolock)INNER JOIN (SELECT MIN(creation_time) AS creation_time,MIN(last_execution_time) AS last_execution_time,SUM(total_logical_reads) AS total_logical_reads,SUM(total_logical_writes) AS total_logical_writes,SUM(total_worker_time) AS total_worker_time,SUM(total_elapsed_time) AS total_elapsed_time,plan_handle FROM sys.dm_exec_query_stats with(nolock)GROUP BY plan_handle) bON a.plan_handle &#61; b.plan_handleCROSS APPLY sys.dm_exec_query_plan(a.plan_handle) c--CROSS APPLY sys.dm_exec_sql_text(a.plan_handle) c --用于筛选text信息
WHERE a.objtype &#61; &#39;Proc&#39;--AND c.text like &#39;Proc&#39;
GROUP BY c.dbid,c.objectid
ORDER BY SUM(b.total_logical_reads) / SUM(a.usecounts) DESC;--SQLCmd in Proc or Batch
SELECT top 500 CASE when c.dbid &#61; 32767 then &#39;Resource&#39; else DB_NAME(c.dbid) end DbName--,OBJECT_SCHEMA_NAME(c.objectid,c.dbid) AS SchName,OBJECT_NAME(c.objectid,c.dbid) AS ObjName,a.usecounts AS ProcExCount--,a.plan_handle, SUBSTRING (c.text,(b.statement_start_offset/2) &#43; 1, ((CASE WHEN b.statement_end_offset &#61; -1 THEN LEN(CONVERT(NVARCHAR(MAX), c.text)) * 2 ELSE b.statement_end_offset END - b.statement_start_offset)/2) &#43; 1) RunSQL,b.creation_time --编译计划的时间,b.last_execution_time --上次开始执行计划的时间,b.execution_count AS SQLExCount--计划自上次编译以来所执行的次数,b.last_logical_reads --上次执行计划时所执行的逻辑读取次数,b.total_logical_reads/b.execution_count avg_logical_reads,b.last_worker_time --上次执行计划所用的 CPU 时间(微秒),b.last_elapsed_time --最近一次完成执行此计划所用的时间(微秒),b.total_elapsed_time/b.execution_count avg_elapsed_time --上次完成执行此计划所用的总时间--,b.*
FROM sys.dm_exec_cached_plans a with(nolock)INNER JOIN sys.dm_exec_query_stats b with(nolock)ON a.plan_handle &#61; b.plan_handleCROSS APPLY sys.dm_exec_sql_text(b.sql_handle) c
WHERE a.objtype &#61; &#39;Proc&#39;
and OBJECT_NAME(c.objectid,c.dbid)&#61;&#39;DBA_TroubleShooting&#39;
order by a.plan_handle,b.sql_handle;--Execution Plan
SELECT top 50 CASE when b.dbid &#61; 32767 then &#39;Resource&#39; else DB_NAME(b.dbid) end DbName,OBJECT_SCHEMA_NAME(b.objectid,b.dbid) AS SchName,OBJECT_NAME(b.objectid,b.dbid) AS ObjName,a.usecounts,a.plan_handle,b.query_plan
FROM sys.dm_exec_cached_plans a with(nolock)CROSS APPLY sys.dm_exec_query_plan(a.plan_handle) b
WHERE a.objtype &#61; &#39;Proc&#39;
and OBJECT_NAME(b.objectid,b.dbid)&#61;&#39;DBA_TroubleShooting&#39;;--特定语句
select top 100 SUBSTRING (c.text,(b.statement_start_offset/2) &#43; 1, ((CASE WHEN b.statement_end_offset &#61; -1 THEN LEN(CONVERT(NVARCHAR(MAX), c.text)) * 2 ELSE b.statement_end_offset END - b.statement_start_offset)/2) &#43; 1) RunSQL,b.creation_time --编译计划的时间,b.last_execution_time --上次开始执行计划的时间,b.execution_count AS SQLExCount--计划自上次编译以来所执行的次数,b.last_logical_reads --上次执行计划时所执行的逻辑读取次数,b.total_logical_reads/b.execution_count avg_logical_reads,b.last_worker_time --上次执行计划所用的 CPU 时间(微秒),b.last_elapsed_time --最近一次完成执行此计划所用的时间(微秒),b.total_elapsed_time/b.execution_count avg_elapsed_time --上次完成执行此计划所用的总时间--,b.sql_handle,b.plan_handle--,d.query_plan,c.text
FROM sys.dm_exec_query_stats b with(nolock)
CROSS APPLY sys.dm_exec_sql_text(b.sql_handle) c
CROSS APPLY sys.dm_exec_query_plan(b.plan_handle) d
where c.text like &#39;%keywords%&#39;

View Code

存储过程里面&#xff1a;游标&#43;查询语句&#xff1b;游标&#43;查询语句抽离成存储过程&#xff1b;哪种更好&#xff1f;第二种会有parameter sniffing现象吗&#xff1f;

转:https://www.cnblogs.com/Uest/p/5489867.html



推荐阅读
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文讨论了在数据库打开和关闭状态下,重新命名或移动数据文件和日志文件的情况。针对性能和维护原因,需要将数据库文件移动到不同的磁盘上或重新分配到新的磁盘上的情况,以及在操作系统级别移动或重命名数据文件但未在数据库层进行重命名导致报错的情况。通过三个方面进行讨论。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • WhenIusepythontoapplythepymysqlmoduletoaddafieldtoatableinthemysqldatabase,itdo ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • Java学习笔记之使用反射+泛型构建通用DAO
    本文介绍了使用反射和泛型构建通用DAO的方法,通过减少代码冗余度来提高开发效率。通过示例说明了如何使用反射和泛型来实现对不同表的相同操作,从而避免重复编写相似的代码。该方法可以在Java学习中起到较大的帮助作用。 ... [详细]
author-avatar
虛情徦噫d_951
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有