top

执行计划--Adhoc和Prepare

执行计划--Adhoc和Prepare

转载自:http://www.cnblogs.com/TeyGao/p/3531911.html?ADUIN=1815357042&ADSESSION=1390494884&ADTAG=CLIENT.QQ.5281_.0&ADPUBNO=26292

在和SQLPass讨论adhoc和Prepare时,有各自不同的观点,我来发表下我的理解,不对之处,敬请指出!

Adhoc(即席查询):没有参数化的查询计划会被标记为adhoc,adhoc不能理解为该执行计划不会被重用。

Prepared(预定义):查询中使用到参数的执行计划会被标记为Prepared.

 

在后续测试中,每次测试之前需要清除执行计划:

--清理执行计划
DBCC FREEPROCCACHE

测试语句执行结束后需要使用以下语句来查看执行计划:

复制代码
复制代码
--查看执行计划
select cp.usecounts as '使用次数',cp.cacheobjtype as '缓存类型',
cp.objtype as [对象类型],
st.text as 'TSQL',
--cp.plan_handle AS '执行计划',
qp.query_plan as '执行计划',
cp.size_in_bytes as '执行计划占用空间(Byte)'           
from sys.dm_exec_cached_plans cp
cross apply sys.dm_exec_sql_text(plan_handle) st
cross apply sys.dm_exec_query_plan(plan_handle) qp
ORDER BY[对象类型]
复制代码
复制代码


 

测试1:简单查询

--执行两遍
SELECT *FROM [TestDB].[dbo].[TB1] WHERE ID=3


执行结果:

可以看到,生成了一个Adhoc执行计划和一个Prepared执行计划,其中Adhoc执行计划被执行两次,证明Adhoc执行计划也是可以被重用的,而Prepared执行计划是由于“简单参数化”的原因生成的。

(PS:在该场景中,Adhoc执行计划最终使用的是Prepared执行计划来执行的,因此可以发现Prepared的执行计划占用的空间更多一些)

 

测试2:使用sp_executesql来实现参数化查询

--执行两遍
EXEC sp_executesql N'SELECT *FROM [TestDB].[dbo].[TB1] WHERE ID=@ID',N'@ID INT',@ID=2

执行结果:

可以看到在TSQL列里有明显的参数,因此该执行计划被标记为Prepared,同时该计划被执行两遍

测试3:使用sp_executesql来实现非参数化查询

--执行两遍
EXEC sp_executesql N'SELECT *FROM [TestDB].[dbo].[TB1] WHERE ID=3'

执行结果:

可以看到,即使使用sp_executesql,但由于TSQL里没有使用参数,因此执行计划仍然被标记为Adhoc。

 

测试4:使用sp_executesql来实现混合查询

--执行两遍
EXEC sp_executesql N'SELECT *FROM [TestDB].[dbo].[TB1] WHERE ID=3 AND C1=@C1',N'@C1 INT',@C1=3

执行结果:

可以发现,只有含有一部分的参数,执行计划就会被标记为Prepared

 

测试5:使用sp_executesql来实现混合查询2

--执行两遍
EXEC sp_executesql N'SELECT *FROM [TestDB].[dbo].[TB1] WHERE ID=3',N'@C1 INT',@C1=3

执行结果

在上面的测试中,查询根本没有使用到参数C1,但是由于整个查询里有参数,所以仍被标记为Prepared。

 

综上所述,只有查询计划里有参数,执行计划就标记为Prepared,如果没有参数,就会标记为Adhoc.

 

SQL SERVER 会在两个环节考虑是否有可重用执行计划

1>在解析SQL语句之前,对SQL语句进行hash的到一个key,使用这个key去查找是否存在现成的执行计划;

2>将SQL解析成语法树后,再使用语法树的hash key去寻找是否存在现成的执行计划。

 

为证明上述观点,我们做以下测试:

SELECT *FROM [TestDB].[dbo].[TB1]  WHERE ID=3
SELECT * FROM [TestDB].[dbo].[TB1]  WHERE ID=3

测试结果:

两条语句中有一个空格的差别,因此会生成两个adhoc执行计划,但是只会生成一个Prepared执行计划,表明这两个Adhoc执行计划最终都使用该Prepeared的执行计划。
Adhoc执行计划会调用Prepared执行计划,但Prepared执行计划不会调用Adhoc执行计划,这是两者的另一区别。

 

 

误区:Adhoc会导致重编译,Adhoc就是影响性能,就是需要把Adhoc查询改成Prepared查询

这个是初学者很容易犯的误区,容易把问题一刀切,由于我们需要在查询里使用到不同的变量,如"WHERE ID=1"和"WHERE ID=2"这样的语句,会生成不同的adhoc的执行计划,每个执行计划生成会消耗CPU资源,并需要占用buffer pool里的内存,当频繁执行这些类似但又不相同的SQL语句时,就会浪费大量的资源,因此需要将之参数化,共用一个执行计划,尤其在执行复杂SQL(如四五个表做连接查询)时,查询优化器需要分析生成很多执行计划并选择一种比较合理的执行计划来执行,消耗很多CPU资源并延长总的SQL执行时间,共用一个执行计划会大大提升系统性能。

当然,参数化也有其切点,在数据分布不均或参数变动对查询影响巨大的情况下,参数化反而会导致系统异常,如果“WHERE ID>@ID”语句,当ID=10000000时返回一条数据,而当ID=1是返回10000000条数据,前者适合索引查找,后者适合全表扫描,如果两者使用同一个执行计划,并会导致系统性能严重下降,此时Adhoc反而更适合。

此外,还有一种情况,当查询语句特别简单,简单到编译几乎不消耗资源时,SQL SERVER会选择不保存这些语句的执行计划。

 

 

在分析执行计划问题时,需要考虑以下问题:

1>系统是否有过多的adhoc执行计划占用大量内存

2>这些adhoc的执行频率和相似度

3>是否可以改写这些adhoc执行计划的SQL

4>是否可以使用'optimize for ad hoc workloads'来优化

5>是否可以使用'强制参数化'

 

推荐阅读:http://www.cnblogs.com/TeyGao/p/3526804.html


 

 PrepareCommand / Enterprise Library 

I downloaded the source code for the Enterprise Library code blocks from codeplex (http://entlib.codeplex.com/) the other day and I saw a procedure named "PrepareCommand".  The XML comment for this command says "Assigns a transaction to the command and discovers parameters if needed".  However, when I look at the code, it doesn't appear to be doing anything at all with parameters, just the setup of the transaction/command/connection.  

My question is, am I missing something or is the XML comment / code not corresponding with what it's actually doing.  

        /// <summary>
        /// <para>Assigns a <paramref name="connection"/> to the <paramref name="command"/> and discovers parameters if needed.</para>
        /// </summary>
        /// <param name="command"><para>The command that contains the query to prepare.</para></param>
        /// <param name="connection">The connection to assign to the command.</param>
        protected static void PrepareCommand(DbCommand command,
                                             DbConnection connection)
        {
            if (command == null) throw new ArgumentNullException("command");
            if (connection == null) throw new ArgumentNullException("connection");
            command.Connection = connection;
        }

        /// <summary>
        /// <para>Assigns a <paramref name="transaction"/> to the <paramref name="command"/> and discovers parameters if needed.</para>
        /// </summary>
        /// <param name="command"><para>The command that contains the query to prepare.</para></param>
        /// <param name="transaction">The transaction to assign to the command.</param>
        protected static void PrepareCommand(DbCommand command,
                                             DbTransaction transaction)
        {
            if (command == null) throw new ArgumentNullException("command");
            if (transaction == null) throw new ArgumentNullException("transaction");

            PrepareCommand(command, transaction.Connection);
            command.Transaction = transaction;
        }


 自己学习写的sqlhelp类,请高手找找问题和低效的地方

public class SqlHelp
    {
        private SqlConnection connection = null;
        public static readonly string connectionString = ConfigurationManager.ConnectionStrings["GoodsManager"].ToString();
 
        /// <summary>
        /// 打开连接
        /// </summary>
        /// <returns>sqlconnection</returns>
        private void OpenConnection()
        {
            if (connection.State != ConnectionState.Open)
                connection.Open();
        }
 
        /// <summary>
        /// 关闭连接
        /// </summary>
        private void CloseConnection()
        {
            if (connection == null)
                return;
            if (connection.State == ConnectionState.Open)
                connection.Close();
        }
 
 
        /// <summary>
        /// 执行数据读取
        /// </summary>
        /// <param name="cmdType">CommandType属性</param>
        /// <param name="cmdText">CommandText属性</param>
        /// <param name="commandParameters">参数</param>
        /// <returns>SqlDataReader</returns>
        public SqlDataReader ExecuteReader(CommandType cmdType, string cmdText, params SqlParameter[] commandParameters)
        {
            connection = new SqlConnection(connectionString);
            SqlCommand comm = new SqlCommand();
            OpenConnection();
            try
            {
                PrepareCommand(connection, comm, cmdType, cmdText, commandParameters);//对sqlcommand属性进行填充
                SqlDataReader sdr = comm.ExecuteReader(CommandBehavior.CloseConnection);
                comm.Parameters.Clear();//清除参数
                return sdr;
            }
            catch
            {
                CloseConnection();
                throw;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cmdType">CommandType属性</param>
        /// <param name="cmdText">CommandText属性</param>
        /// <param name="commandParameters">参数</param>
        /// <returns>object</returns>
        public object ExecuteScalar(CommandType cmdType, string cmdText, params SqlParameter[] commandParameters)
        {
            using (connection = new SqlConnection(connectionString))
            {
                SqlCommand comm = new SqlCommand();
                PrepareCommand(connection, comm, cmdType, cmdText, commandParameters);//对sqlcommand属性进行填充
                OpenConnection();
                object obj = comm.ExecuteScalar();
                comm.Parameters.Clear();
                return obj;
            }
 
        }
        /// <summary>
        /// 执行对数据库的删改操作
        /// </summary>
        /// <param name="cmdType">CommandType属性</param>
        /// <param name="cmdText">CommandText属性</param>
        /// <param name="commandParameters">参数</param>
        /// <returns>执行情况回执,int</returns>
        public int ExecuteNonQuery(CommandType cmdType, string cmdText, params SqlParameter[] commandParameters)
        {
            using (connection = new SqlConnection(connectionString))
            {
                SqlCommand comm = new SqlCommand();
                PrepareCommand(connection, comm, cmdType, cmdText, commandParameters);//对sqlcommand属性进行填充
                OpenConnection();
                int flag = comm.ExecuteNonQuery();
                comm.Parameters.Clear();//清除参数
                return flag;
            }
 
        }
 
        /// <summary>
        /// 对command属性填充PrepareCommand
        /// </summary>
        /// <param name="cmd">command</param>
        /// <param name="cmdType">CommandType属性</param>
        /// <param name="cmdText">CommandText属性</param>
        /// <param name="commandParameters">参数</param>
        public void PrepareCommand(SqlConnection connection, SqlCommand cmd, CommandType cmdType, string cmdText, params SqlParameter[] commandParameters)
        {
            //给sqlcommand属性赋值
            cmd.Connection = connection;
            cmd.CommandText = cmdText;
            cmd.CommandType = cmdType;
            if (commandParameters != null)//判别是否传入参数
            {
                foreach (SqlParameter commandParameter in commandParameters)
                {
                    cmd.Parameters.Add(commandParameter);
                }
            }
        }
 
    }
 
每次都新建连接有问题么?还有其中有必要每个方法都是用try catch么?
posted @   桦仔  阅读(430)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
点击右上角即可分享
微信分享提示