RDS SQL Server - 专题分享 - 巧用执行计划缓存之Single-used plans

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: # 背景引入 执行计划缓存是SQL Server内存管理中非常重要的特性,这篇系列文章我们探讨执行计划缓存设计中遇到的single-used plans问题,以及如何发现、如何定性和定量分析single-used plans带来的影响,最后我们使用两种方法来解决这个问题。 # 什么是Single-used Plans 要解释清楚什么是Single-used Plans,首先需要解释SQL语句

背景引入

执行计划缓存是SQL Server内存管理中非常重要的特性,这篇系列文章我们探讨执行计划缓存设计中遇到的single-used plans问题,以及如何发现、如何定性和定量分析single-used plans带来的影响,最后我们使用两种方法来解决这个问题。

什么是Single-used Plans

要解释清楚什么是Single-used Plans,首先需要解释SQL语句执行计划缓存是什么?SQL Server执行每一条SQL语句之前,会从执行计划缓存内存中查看是否存在本条语句的执行计划,如果不存在,会将其编译、选择最优路径、生成执行计划,然后会将执行计划存储在一块专门的内存区域中(这块内存叫着执行计划缓存),以供下次该语句执行直接从执行计划缓存中获取编译完毕的执行计划。以此来节约数据库系统对于查询语句编译、生成执行计划过程的性能开销,提高SQL语句的执行效率。
而Single-used Plans是指那些第一次执行后被缓存起来的执行计划,而后再也没有被重复利用过的执行计划缓存。其中ad hoc query(即席查询)就是典型的single-used plans中的一种。

如何发现Single-used Plans

从上一节我们可以大致知道,single-used plans仅会第一次被使用(从名字也可以很好理解到这一点),所以,实际上single-used plans是对SQL Server内存空间和CPU资源的浪费,对数据库性能有一定的损害。那么,我们如何来发现single-used plans呢?我们可以使用下面的查询语句:

USE master
GO

SELECT
    database_name = QUOTENAME(db.name),
    st.text,
    cp.objtype,
    cp.size_in_bytes,
    qp.query_plan,
    cp.cacheobjtype
FROM sys.dm_exec_cached_plans AS cp 
    CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
    INNER JOIN sys.databases AS db WITH(NOLOCK)
    ON st.dbid = db.database_id
    CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp
WHERE cp.cacheobjtype = N'Compiled Plan'
AND cp.objtype IN (N'Adhoc', N'Prepared')
AND cp.usecounts = 1
ORDER BY cp.size_in_bytes DESC

我的测试实例展示的查询结果如下:
01.png

执行计划缓存中有多少Single-used Plans

当我们可以有效的发现single-used plans以后,我们可能又会问:到底我的SQL Server数据库实例中,有多少执行计划缓存是属于single-used plans类型呢?可以从两个维度来分析:
Single-used plans记录总数
Single-used plans总的执行计划占用的内存空间大小
可以使用以下的查询语句来回答这个问题。

USE master
GO
SELECT
    COUNT(1) AS [total_plans], 
    CAST(SUM(size_in_bytes)/1024. AS DECIMAL(18,2)) AS [total_size_in_kb], 
    CAST(SUM((CASE WHEN usecounts = 1 THEN size_in_bytes ELSE 0 END))/1024. AS DECIMAL(12,2)) AS [total_size_single_used_in_kb], 
    SUM(CASE WHEN usecounts = 1 THEN 1 ELSE 0 END) AS [total_plans_count_single_used],
    AVG(usecounts) AS [avg_used_counts], 
    CAST(AVG(size_in_bytes)/1024. AS DECIMAL(12, 2)) AS [avg_used_size_kb]
FROM sys.dm_exec_cached_plans WITH(NOLOCK)

我的测试实例执行结果如下截图:
02.png

从这个结果我们可以得出如下single-used plans的数据:总的single-used plans记录数为73,内存空间占用大小为13912 KB。

Single-used Plans对性能的影响

就单单一条Single-used Plan来看的话,对数据库系统的性能影响很小,小到可以忽略的地步,所以,数据库执行计划缓存中,存在少量的Single-used Plans是很正常的,可以不用太关注。但是,如果Single-used Plans大量存在的话,可能就会对系统带来比较严重的性能影响。

定性分析

定性分析大量Single-used Plans对数据性能的影响,主要体现在以下几个方面:
总的执行计划缓存利用率不高:因为存在大量不能被重复利用的执行计划缓存,从而拉低了执行计划缓存整体利用率
浪费执行计划缓存中内存的开销:每一条执行计划缓存或多或少会占用一定的执行计划缓存内存空间,大量的Single-used Plans导致了内存空间的浪费
导致CPU使用率的上升:每一条SQL语句执行计划的编译、最优路径选择和执行计划缓存,这些过程都需要消耗系统CPU资源,如果大量存在Single-used Plans,会导致系统CPU使用率的上升。
举一个最为极端的例子,假设执行计划缓存中存储的所有执行计划都是Single-used Plans的话,那么导致的严重后果是:
执行计划缓存利用率就是0%,因为没有任何的执行计划被重用
执行计划缓存这种设计就毫无意义,因为缓存起来也没有被重用
浪费执行计划缓存的内存开销和CPU开销

定量分析

以上是定性分析Single-used Plans对系统性能的影响,那么到底Single-used Plans达到哪个数量级,占比多少的时候,我们需要密切关注呢?虽然微软没有官方的推荐数字,但是个人比较推荐的两个数字是2GB和50%,即:所有的Single-used Plans使用的内存空间超过2GB或者内存空间使用占比超过50%。当然最终也可能取决于SQL Server可以使用的最大内存数量。

USE master
GO

IF OBJECT_ID('tempdb..#temp') IS NOT NULL
    DROP TABLE #temp

SELECT 
    objtype AS [cachetype], 
    COUNT(1) AS [total_plans], 
    CAST(SUM(size_in_bytes)/1024. AS DECIMAL(18,2)) AS [total_size_in_kb], 
    CAST(SUM((CASE WHEN usecounts = 1 THEN size_in_bytes ELSE 0 END))/1024. AS DECIMAL(12,2)) AS [total_size_single_used_in_kb], 
    SUM(CASE WHEN usecounts = 1 THEN 1 ELSE 0 END) AS [total_plans_count_single_used],
    AVG(usecounts) AS [avg_used_counts], 
    CAST(AVG(size_in_bytes)/1024. AS DECIMAL(12, 2)) AS [avg_used_size_kb]
INTO #temp
FROM sys.dm_exec_cached_plans WITH(NOLOCK)
GROUP BY objtype


SELECT 
    [cachetype],
    [total_plans],
    [total_size_in_kb],
    [total_plans_count_single_used],
    [total_size_single_used_in_kb],
    CAST(([total_plans_count_single_used]*1.0 / [total_plans]*1.0) * 100. AS DECIMAL(12, 2)) AS [single_used_plan_count%],
    CAST([total_size_single_used_in_kb]/[total_size_in_kb] * 100. AS DECIMAL(12, 2)) AS [single_used_size%],
    [avg_used_counts],
    [avg_used_size_kb]
FROM #temp
ORDER BY [total_size_single_used_in_kb] DESC


SELECT 
    SUM([total_plans]) AS total_plan_counts,
    SUM([total_size_in_kb]) AS total_plan_size_in_kb,
    SUM([total_plans_count_single_used]) AS [total_plans_counts_single_used],
    SUM([total_size_single_used_in_kb]) AS [total_plan_size_single_used_in_kb],
    CAST(SUM([total_plans_count_single_used]) * 100. / SUM([total_plans]) AS DECIMAL(12, 2)) AS [plan_counts_single_used%],
    CAST(SUM([total_size_single_used_in_kb]) * 100. / SUM([total_size_in_kb]) AS DECIMAL(12, 2)) AS [plan_size_single_used%]
FROM #temp

执行结果如下所示:
03.png

从这个执行结果来看,在我的SQL Server测试实例上,single-used plans占用的执行计划记录数为72条,内存空间占用14016 KB;single-used plans执行计划记录数占总的百分比为39.78%,内存空间占用比例为50.59%。

解决Single-used Plans问题

从定量分析小结,发现我的测试实例,single-used plans占用执行计划缓存内存比例已经超过50%了,是解决single-used plans问题的时候到了。

清空Single-used Plans

第一种方法是手动清理single-used plans,当然,你也可以很暴力的将所有执行计划缓存清空,从而也就清理了single-used plans,但是这样会误杀很多有用的无辜的执行计划缓存,从而导致这些执行计划重编译,影响性能。我们推荐使用下面的方法,仅清空哪些single-used plans,方法如下:

USE master
GO

DECLARE
    @plan_handle varbinary(64)
;

DECLARE
     cur_single_used_plan_handle CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT cp.plan_handle
FROM sys.dm_exec_cached_plans AS cp WITH(NOLOCK)
WHERE usecounts = 1

OPEN cur_single_used_plan_handle
FETCH NEXT FROM cur_single_used_plan_handle INTO @plan_handle
WHILE @@FETCH_STATUS = 0
BEGIN
    PRINT @plan_handle

    DBCC FREEPROCCACHE (@plan_handle); 

    FETCH NEXT FROM cur_single_used_plan_handle INTO @plan_handle
END

CLOSE cur_single_used_plan_handle
DEALLOCATE cur_single_used_plan_handle
GO

optimize for ad hoc workloads配置选项

除了手动清理single-used plans执行计划缓存外,另外一个更强大的功能是修改SQL Server 实例级别的配置选项optimize for ad hoc workloads。为了解决single-used plans带来的问题,微软从SQL Server 2008开始引入了这个全新选项,当打开这个选项以后,一个ad hoc的查询语句在第一次执行的时候,系统会创建一个“compiled plan stub”,并不会将执行计划缓存起来,只有当第二次该语句再被执行的时候,执行计划才会被缓存在内存中,从而避免了single-used plans带来的问题。配置这个选项的方法很简单,参见以下语句:

EXEC sys.sp_configure 'show advanced options', 1
RECONFIGURE
GO

EXEC sys.sp_configure 'optimize for ad hoc workloads', 1
RECONFIGURE
GO

最后总结

这篇文章讨论了执行计划缓存中的single-used plans带来的问题,如何发现,如何去定量和定性分析,以及最后怎么解决single-used plans的问题。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
SQL 关系型数据库 MySQL
mysql 简单的sql语句,入门级增删改查
介绍MySQL中的基本SQL语句,包括数据的增删改查操作,使用示例和简单的数据表进行演示。
mysql 简单的sql语句,入门级增删改查
|
1月前
|
SQL 关系型数据库 MySQL
|
30天前
|
SQL 运维 关系型数据库
MySQL 运维 SQL 备忘
MySQL 运维 SQL 备忘录
46 1
|
1月前
|
SQL 存储 关系型数据库
SQL文件导入MySQL数据库的详细指南
数据库中的数据转移是一项常规任务,无论是在数据迁移过程中,还是在数据备份、还原场景中,导入导出SQL文件显得尤为重要。特别是在使用MySQL数据库时,如何将SQL文件导入数据库是一项基本技能。本文将详细介绍如何将SQL文件导入MySQL数据库,并提供一个清晰、完整的步骤指南。这篇文章的内容字数大约在
133 1
|
1月前
|
SQL Oracle 关系型数据库
Oracle SQL:了解执行计划和性能调优
Oracle SQL:了解执行计划和性能调优
52 1
|
19天前
|
SQL 关系型数据库 MySQL
MySql5.6版本开启慢SQL功能-本次采用永久生效方式
MySql5.6版本开启慢SQL功能-本次采用永久生效方式
33 0
|
19天前
|
SQL 关系型数据库 MySQL
mysql编写sql脚本:要求表没有主键,但是想查询没有相同值的时候才进行插入
mysql编写sql脚本:要求表没有主键,但是想查询没有相同值的时候才进行插入
30 0
|
1月前
|
存储 SQL 关系型数据库
MySQL查询数据库锁表的SQL语句
MySQL查询数据库锁表的SQL语句
83 1
|
1月前
|
SQL 存储 关系型数据库
mysql 数据库空间统计sql
mysql 数据库空间统计sql
45 0
|
1月前
|
SQL 存储 关系型数据库
mysql SQL必知语法
本文详细介绍了MySQLSQL的基本语法,包括SELECT、FROM、WHERE、GROUPBY、HAVING、ORDERBY等关键字的使用,以及数据库操作如创建、删除表,数据类型,插入、查询、过滤、排序、连接和汇总数据的方法。通过学习这些内容,读者将能更好地管理和操
14 0