富途娱乐国际货物运输公司
 
 
新闻中心
分类

新闻中心

SQLServer 性能调优方法小结
编辑:佚名 时间:2024-04-07

数据库性能优化的应用场景相当广泛,但SQL语句与业务联系紧密,代码层面的优化可能需要花费相当多的时间与精力。除了代码层面,语句执行层面的优化、更佳的SQL语句使用执行计划、运行在一个稳定高效的环境,同样是高效也更符合运维的一种优化手段。下面我分享一些SQL Server在配置方面的性能优化思路,从CPU、内存、I/O、执行计划等层面,内容包含了最大并行度、资源调控器、查询提示几个功能的介绍与配置方法。

1.介绍

最大并行度是指会话可以使用的最大线程数,对于大批量查询,例如大表的扫描,使用多个线程同时扫描能成倍地提高效率;但是对于小型查询,例如只修改小表里一行的内容,则没必要使用多个线程。

一般情况下,会话最终使用多少个线程是由查询优化器决定的(可以通过option查询子句进行干预)。查询语句提交到SQL Server后会先进行解析,然后进行优化和简化(例如子查询转为对应连接、优先应用筛选条件),生成一系列执行计划,最后根据统计信息计算开销,选择合适的执行计划。最终预估的开销决定了会话使用多少并行度。

在服务器配置选项中,我们能通过“最大并行度”、“并行的开销阈值”两个配置进行调整,最大并行度的默认值是0,即不限制并行度,最大能使用到与CPU核数相等的并行度。但是对于明显有性能问题的系统,则需要考虑调整这个高级选项进行优化:

2.配置方法

  • 检查/配置“最大并行度”设置


  • 检查/配置“最大线程数”设置


3.注意事项

以下列举了一些场景作为参考:

  • OLTP系统

单纯的OLTP系统由高并发的小事务组成,不适合使用太高的并行度,可以将最大并行度设置为1,即不开启并行查询;如果调整后明显感觉到执行时间太长,应用反应变慢,则可以逐步提高到2、4、8再进行观察。(对于这类语句执行频繁的小事务,执行计划的选择也是非常重要的优化方向,需要结合语句单独分析)

  • OLAP系统

单纯的OLAP系统由只读长事务组成,事务执行时间都较长,例如报表统计、历史数据导出。这类事务的特点是会连接大量表、读取大量数据、进行大量计算,对于语句执行效率来说并行度越高越好。尽管官方文档推荐8核以上的服务器也使用并行度8,但在没达到CPU瓶颈的情况下可以尽可能提高OLAP系统的最大并行度,或者不限制最大并行度。

  • 混合系统

实际中更常见的是读写混合的系统,在承载应用写操作的同时也承载一些小型报表的查询,这类系统则需要进行反复的调整以达到最佳的并行度设置:写操作通常开销较小,只会用1个并行度;普通的检索开销也一般不大,使用较低并行度;报表通常开销较大,会使用较高并行度。

在CPU资源有限的情况下,配置最大并行度为1可以保证最关键的写操作能获得足够的资源;但如果读操作需要使用并行来提高效率(maxdop=1时语句执行太慢),可以适当调到2并逐步增加;如果只需要提高那些执行时间很长的查询,可以提高“并行的开销阈值”,只让高开销的查询使用并行。

  • “并行的开销阈值”是一个相对值,没有单位,默认是5,只能通过一步步测试调整来选用最佳的设置。
  • 最大连接数默认值为0,但不是没上限,而是根据CPU核数递增,官方给出的计算公式为Default Max Workers + ((logical CPUs - 4) * Workers per CPU)。



例如:一个64核的SQL2016最大线程数默认为1472,默认最大并行度为64,如果一个会话引发了阻塞,被阻塞的会话并行度都很高,那么积累了几十个会话之后线程数就满了,这在繁忙的系统上可能只会花几分钟的时间。线程数满了以后新的连接无法建立,应用开始报错,直到阻塞源消失才会恢复。

这种时候普通用户无法连接数据库,我们可以通过管理员专用通道(DAC)进行连接,在连接实例的名称前加上admin:即可,例如admin:127.0.0.1,DAC连接只能同时存在1个。线程占满的根本原因还是阻塞源的处理,提高最大线程数只是一种无奈之举。

注:日常运维不建议使用DAC连接,因为DAC连接有更高的CPU优先级,服务器压力较大时有可能会抢占普通线程,引发阻塞。

  • 调整最大并行度的优点是快速,修改配置后无需停机即时生效,但无法进行颗粒度更细的资源分配,而下面的资源调控器则可以做到。

1.介绍

这是一个SQL Server 2008开始的功能,可以通过登录名(函数user_name())、当前时间(函数getdate())等会话属性进行筛选,对会话使用的CPU、物理 I/O 和内存进行人为限制,保证关键功能有充足的资源可用。

开启资源调控器后(默认关闭),会话发出请求会先通过“分类器函数”进行分类,路由到相应的“工作负荷组”,每个工作负荷组都映射到一个“资源池”,再根据资源池中设置的CPU、I/O、内存阈值来决定会话的资源分配。

  • 资源池

可以看作是一个虚拟的SQL Server实例,默认有两个资源池(内部资源池和默认资源池),支持用户自行创建;

注:外部资源池定义的是外部进程的资源,如R 服务的rterm.exe、BxlServer.exe,与本次讨论的内部资源无关。

  • 工作负荷组

相当于具有分类标准的会话容器,我们可以根据工作负荷组对会话进行聚合监控。每个工作负荷组都只处于一个资源池中,默认有两个工作负荷组(内部工作负荷组和默认工作负荷组),支持用户自行创建;

  • 分类

对传入会话进行分类,分配到工作负荷组。

注:资源调控器不向专用管理员连接 (DAC) 施加任何控制。无需对在内部工作负荷组和资源池中运行的 DAC 查询进行分类。

2.创建与配置资源调控器

  • 通过图形界面创建较为直观,如需指定I/O相关的限制,则必须脚本创建,可以在图形界面生成脚本再进行修改。



这里新建了一个资源池vip_pool、工作负荷组vip_group,最小CPU预留了5%,最大不超过20%,内存无限制,资源池中还创建了工作负荷组vip_group;(注意下方脚本指定了cap_cpu_percent=20,就是说即使系统空闲也不会使用超过20%的CPU)

 USE [master]
GO
CREATE RESOURCE POOL [vip_pool] WITH(min_cpu_percent=5, 
    max_cpu_percent=20, 
    min_memory_percent=0, 
    max_memory_percent=100, 
    cap_cpu_percent=20, 
    AFFINITY SCHEDULER = AUTO
, 
    min_iops_per_volume=0, 
    max_iops_per_volume=0)
GO
USE [master]
GO
CREATE WORKLOAD GROUP [vip_group] WITH(group_max_requests=0, 
    importance=Medium, 
    request_max_cpu_time_sec=0, 
    request_max_memory_grant_percent=25, 
    request_memory_grant_timeout_sec=0, 
    max_dop=0) USING [vip_pool], EXTERNAL [default]
GO


  • 新建分类器函数(此处指定了登录名vip的会话,将路由到工作负荷组vip_group,其余会话都将在默认的default组)
 CREATE FUNCTION [dbo].[rgClassifier]() 
RETURNS sysname 
WITH SCHEMABINDING
AS
BEGIN
     DECLARE @grp_name AS sysname;
     SET @grp_name = 'default';
     IF (USER_NAME()='vip')
   begin
          SET @grp_name = 'vip_group'
      RETURN @grp_name
   end
     RETURN @grp_name;
END
GO
-- Set the classifier function for Resource Governor
ALTER RESOURCE GOVERNOR 
WITH ( 
  CLASSIFIER_FUNCTION = [dbo].[rgClassifier]
)
GO
ALTER RESOURCE GOVERNOR RECONFIGURE


  • 下面是效果演示,这里使用了一个1亿行表与100万行表连接,手动指定cpu消耗较高的hash join,对比普通登录名与vip登录名的执行时长(图1),并通过性能计数器查看对应的CPU使用情况(图2,红线default组,绿线vip_group组):





3.注意事项

  • 在所有资源池中,CPU、内存的最小值相加不能超过100,最大值的设置在系统空闲的时候是可以超出的,但是当其他资源池设置了最小值时,则一定会预留出来。如果需要限制资源池不利用空闲资源,要指定cap_cpu_percent;

  • 资源池中I/O的值不能用图形界面设置,在脚本中配置的值为min_iops_per_volume、max_iops_per_volume,注意这里的单位不是%,而是IOPS,设置为0代表不限制。存储设备差异较大,难以用数值衡量百分比,如SSD的4K随机IOPS能达到10万级别,但机械磁盘通常只有100级别。如果需要对I/O进行设置,请先做好充分测试;

查询提示可以对当前语句的执行计划进行干涉,但在通常情况下,查询优化器选择的执行计划已经足够高效,只推荐利用查询提示进行性能分析,或者用在一些特殊的语句上。

1.表提示(WITH子句)

这里只介绍一些常用项,详细使用方法参考官方文档:docs.microsoft.com/zh-c

  • NOLOCK

查询不加表S锁,可能造成脏读,不强调一致性的报表类语句可以使用,防止读表时阻塞写操作,很常用。但对于AlwaysOn辅助副本,即使加了WITH(NOLOCK)也会在库级别添加SCH-S锁防止数据库被修改,这个锁会阻塞redo线程,引发主从延迟,最根本的解决方法还是优化语句,避免单个语句长时间执行。

同类的提示还有HOLDLOCK、PAGLOCK、ROWLOCK、TABLOCK、UPDLOCK、XLOCK等,适用于各种需要保证结果一致性的地方。

  • INDEX(<index_name>)

强制使用特定的索引,不推荐用,如果索引被删除则语句会执行失败,查询优化器没走最佳索引通常是另有原因,例如统计信息偏差太大、计算开销的误差太大。

同类的提示还有FORCESEEK、FORCESCAN,不推荐用在生产,理由同上。

WITH子句用法是加在表名后,例如:

select * from msdb.dbo.sysjobs as a with(nolock)

2.OPTION子句

这里只介绍一些常用项,详细使用方法参考官方文档:docs.microsoft.com/zh-c

  • MAXDOP

指定当前语句的最大并行度。

第一节提到可以在整个实例层面配置最大并行度,对于单个查询则可以使用查询提示OPTION(MAXDOP 1)来覆盖全局设置。

  • QUERYTRACEON

仅对当前查询开启追踪标志,会覆盖全局设置。

例如OPTION(QUERYTRACEON 8649)可以将并行开销阈值降为0,即强制使用并行计划,更多追踪标志参考官方文档:docs.microsoft.com/zh-c

  • RECOMPILE

强制重新生成执行计划。

生成执行计划会带来额外的消耗,对于大型的查询语句,带来的收益可能远大于重新生成执行计划的开销;但对于执行频繁的小查询,还有其他查询提示可以干预执行计划的生成,不推荐使用。

  • FAST <integer_value>

快速返回前N行,然后查询会继续执行直至生成完整的结果。

  • MAXRECURSION <integer_value>

指定当前查询的最大递归数,覆盖全局设置,防止进入无限循环。

  • OPTIMIZE FOR

针对特定参数生成执行计划,需要详细统计业务访问的构成,一般不建议干涉执行计划。

当缓存中存在有效的执行计划时,语句会直接沿用现有的执行计划来避免生成执行计划的性能消耗,但对于参数化的语句,例如存储过程内的语句,每次的参数可能不一样,但执行计划会使用同一个,而这个执行计划是根据第一次执行的时候传入的参数选择的,未必对于其他参数也是最优解。

例如下方语句,则参数@city_name使用值'Seattle'而非初始值,参数@postal_code使用统计数据而非初始值

 CREATE PROCEDURE dbo.RetrievePersonAddress
@city_name NVARCHAR(30), 
 @postal_code NVARCHAR(15)
AS
SELECT * FROM Person.Address 
WHERE City = @city_name AND PostalCode = @postal_code 
OPTION ( OPTIMIZE FOR (@city_name = 'Seattle', @postal_code UNKNOWN) );


注:存储过程sp_create_plan_guide也可以达到类似的效果,具体用法参考官方文档:docs.microsoft.com/zh-c

  • MERGE JOIN

强制语句使用MERGE JOIN,仅适用性能优化排查,或者明确使用场景的语句。

同类型的还有:

{ HASH | ORDER }GROUP

{ CONCAT | HASH | MERGE }UNION

{ LOOP | MERGE | HASH }JOIN

FORCE ORDER

  • 表提示也可以写在OPTION子句中,官方文档中有例子。

OPTION子句用法是加在整个查询语句后,例如:

select * from msdb.dbo.sysjobs as a with(nolock) join msdb.dbo.sysjobhistory as b with(nolock) on a.job_id = b.job_id where a.[name] = 'syspolicy_purge_history'
option(maxdop 1)


分享到:
富途娱乐国际货物运输公司

分享到:

400-123-4567
Copyright © 2012-2018 富途娱乐国际货物运输公司 版权所有 非商用版本
 

平台注册入口