为什么使用并行查询?
- 改善大查询的响应时间
- 对终端用户完全透明
- 适用于数据仓库工作负载 (少量大查询)
- 线性可扩展性
- 不适合OLTP工作负载 (大量小查询)
并行性可用于减少单个查询的响应时间。但是,并行性带来了成本:它增加了与执行查询相关的开销。虽然这种开销相对较小,但它确实使并行性不适合小型查询(特别是对于OLTP查询)
- 并行性不是免费的
- 增加查询执行的开销
- 可能会降低整体服务器吞吐量
SQL Server如何并行化查询?
SQL Server通过将输入数据水平分区为大致相等大小的集合,为每个CPU分配一组,然后在每个集合上执行相同的操作(例如,聚合,连接等)来并行化查询。
例如,假设我们要使用两个CPU来执行恰好在整数列上分组的哈希聚合。我们创建两个线程(每个CPU一个)。每个线程执行相同的散列聚合运算符。我们可以通过发送行来分区输入数据,其中逐行的列是奇数到一个线程的行和逐列的行甚至是另一个线程的行。只要属于一个组的所有行都由一个散列聚合运算符和一个线程处理,我们就会得到正确的结果。
这种并行查询执行方法既简单又可扩展。在上面的示例中,两个散列聚合线程都是独立执行的。这两个线程不需要以任何方式通信或协调它们的工作。如果我们想要提高并行度(DOP),我们可以简单地添加更多线程并调整我们的分区功能。(实际上,我们使用散列函数来分配散列聚合的行。散列函数处理任何数据类型,任意数量的逐列和任意数量的线程。)
请注意,这种并行方法与“管道”并行性不同,其中多个不相关的运算符在不同的线程中并发运行。虽然SQL Server经常将不同的运算符放在不同的线程中,但这样做的主要原因是允许在数据从一个运算符流向另一个运算符时对数据进行重新分区。使用流水线并行性,并行度和线程总数将限制为运算符的数量。
谁决定是否并行化查询?
查询优化器决定是否应该并行执行查询。与大多数其他决策一样,此决定基于成本。处理很多行的复杂且昂贵的查询比处理非常少的行的简单查询更有可能产生并行计划。
SQL Server中禁止并行的一些操作
SQL Server并不是所有操作都可以并行的,有些操作符会导致整个执行计划无法并行,而有些会使得某些分支(branch)无法并行,下面罗列出相关的操作符对并行的影响,这些感兴趣的朋友可以自行测试.
整个执行计划无法并行
T-SQL scalar functions
更新表变量数据
访问系统表
动态游标
某些分支不能并行
TOP(global)
2012以前窗口函数(Row_Number())
Multi -Statement Function
Backward scan
CTE递归
谁决定并行度(DOP)?
DOP不是缓存的编译计划的一部分,可能会随着每次执行而更改。我们通过考虑服务器上的CPU数量,“最大并行度”和“最大工作线程数”由sp_configure设置(仅在“show advanced options”设置打开时可见)来确定执行开始时的DOP。简而言之,我们选择一个最大化并行性的DOP,同时确保我们不会耗尽工作线程。如果选择MAXDOP 1,我们将删除所有并行度迭代器,并使用单个线程将查询作为串行计划运行。
请注意,并行查询使用的线程数可能超过DOP(跟oracle类似)。如果在运行并行查询时检查sys.sysprocesses,则可能会看到比DOP更多的线程。如上所述,如果我们需要在两个运算符之间重新分配数据,我们将它们放在不同的线程中。DOP确定每个运算符的线程数,而不是每个查询计划的线程总数。查询使用的所有线程都被分配给同一组DOP调度程序,并且查询仅使用DOP CPU,而不管线程的总数。
https://blogs.msdn.microsoft.com/craigfr/2006/10/11/introduction-to-parallel-query-execution/
术语及解释
Schedulers, Workers, 以及Tasks
Schedulers
Schedulers是指SQL Server中SQLOS识别到的硬件环境中的逻辑CPU, 它可能在物理上是CPU,处理器核心,或者可能是在核心内运行的多个硬件线程之一(超线程)。它是管理SQL Server计算工作的基本单位,
主要目的是允许SQL Server精确控制其自己的线程调度,而不是依赖于Windows操作系统使用的通用算法。每个调度程序确保在任何给定时刻只有一个协作执行的线程可运行(就操作系统而言),这具有重要的好处,例如减少上下文切换,以及减少对Windows内核的调用次数。
有关调度程序的信息显示在系统动态管理视图(DMV)sys.dm_os_schedulers中。
Tasks
Tasks实际就是SQL Server中的工作单元,本质上就是一个函数指针(C++),用于指向需要执行的代码.比如LazyWriter实际上就是去调用相应的Function(sqlmin!Lazywriter()).
Workers 和Threads
如果说Tasks是指向工作的地方,那么Workers是处理相应的工作,它绑定到Windows线程用于计算.
对于大多数实际用途,Worker是一个Thread。Worker(Thread)在其整个生命周期中绑定到特定的Schedulers。有关Worker的信息显示在DMV sys.dm_os_workers中。
可以说一条SQL请求SQL Request=Task+Worker
最终回到我们的并行查询,实际就是多个tasks在多个schedulers上同时运行,提升响应速度
执行上下文
如果说task描述了要完成的工作,则执行上下文就是工作发生的地方。每个任务都在单个执行上下文中运行,由sys.dm_os_tasks DMV中的exec_context_id列标识(您还可以在向后兼容性视图sys.sysprocesses中使用ecid列查看执行上下文)。
The Parallelism Operator并行操作员(别名Exchange)
并行(或exchanges )迭代器在查询执行中实现了并行性。优化器在线程之间的边界处放置exchanges ; exchanges 在线程之间移动行,它是SQL Server用于将并行计划的执行上下文连接在一起的“粘合剂”。
exchanges 迭代器的独特之处在于它实际上是两个迭代器:生产者和消费者。
- 生产者, 连接输入端的线程
- 消费者, 连接输出端的线程
我们将生产者放在查询子树的根(通常称为分支)。生产者从其子树中读取输入行,将行组装成数据包,并将这些数据包路由到适当的消费者。我们将使用者放在下一个查询子树的叶子上。消费者从其生产者接收数据包,从这些数据包中删除行,并将行返回到其父迭代器。例如,以并行度(DOP)2运行的重新分区交换由两个生产者和两个消费者组成:
请注意,虽然大多数迭代器之间的数据流是基于拉取的(迭代器在准备好另一行时调用其子级上的GetRow),但exchange 生产者和使用者之间的数据流是基于推送的。也就是说,生产者用行填充数据包并将其“推送”给消费者。此模型允许生产者和消费者线程独立执行。
有多少种不同类型的exchange ?
我们可以根据生产者和/或消费者线程的数量对交换进行分类:一对多、多对多、多对一
聚合流交换之后的运算符串行运行,而它之前的运算符并行运行。任何并行计划中的根交换始终是聚合交换,因为任何查询计划的结果必须最终汇集回单个连接线程以返回给客户端。
distribute streams exchange 与汇聚流交换相反:其之后并行运行,而之前串行运行。
下图展示了流聚合内部构造
在生产者与消费者的模型中,数据是通过一定规则由生产者流动到消费者那里。我们可以根据交换从生产者到消费者的行的方式对交换进行分类。我们将此属性称为交换的“分区类型”。分区类型仅对重新分区或分发流交换有意义, SQL Server支持5种分区类型:
|
类型 |
描述 |
|
Hash |
最常见,通过评估当前行中的一个或多个列值的散列函数来选择消费者。 |
|
轮循 |
每个新行以固定顺序发送给下一个使用者。 |
|
广播 |
每一行被发送给所有消费者。 |
|
请求 |
每一行被发送给第一个请求的消费者。这是唯一的分区类型,其中行由交换运算符内的使用者从生产者中提取。 |
|
范围 |
为每个消费者分配一个非重叠的值范围。特定输入列所属的范围决定了哪个消费者获得该行。 |
需求和范围分区类型比前三种常见的要少得多,而且一般只在分区表上运行的查询计划可见。在需求类型划分搭配使用连接到一个分区ID分配到下一个工作线程。的范围分割型被使用,例如,创建分区索引时。在图形查询计划中可以看到所使用的分区类型以及该过程中使用的任何列值:
保留排序顺序
有了生产者和消费者的数据使用方式,你可能觉得还差点什么吧?没错,数据是否是有序的呢,SQL Server中exchange分别Merge Exchange 和Non-Merge Exchange,分别对应数据在exchange时是否要求有序,当然大家也都知道了有序的成本Merge Exchange明显高于无序
可选地,exchange可以配置为保留排序顺序。这对于进入交换的行已经按照对后来的运算符有用的方式排序(遵循先前的排序,或者从索引读取的结果)的计划非常有用。如果交换没有保留排序顺序,优化器必须在交换后引入额外的Sort操作符以重新建立所需的顺序。需要排序输入的公共运算符包括Stream Aggregate,Segment和Merge Join。图11显示了一个保留顺序的Repartition Streams交换:
图11: 保留顺序的重新分配流
到达exchange的三个输入的行按排序顺序排序(排序,即从各个worker的角度来看)。保持顺序的交换(称为合并交换)确保其输出端的工作人员以相同的排序顺序接收行(当然,分配通常是不同的)。
一个收集流交换也可以保存排序顺序,如果需要的话(和分发流交换已经没有其他的选择,如果你想想看)。在任何情况下,如果交换是一个合并交换,exchange有一个' Order By '属性,如图12所示:
图12:合并交换的“排序依据”属性
请注意,合并交换本身不执行任何排序; 它仅限于保留到达其输入的行的排序顺序。合并交换的效率可能远低于非顺序保留的交易,并且与某些性能问题相关。
并行执行计划例子
---串行执行
select COUNT(*)
from dbo.bigTransactionHistory option(maxdop 1)
--并行执行
select COUNT(*)
from dbo.bigTransactionHistory option(maxdop 2)
通过观察上面两条简单语句的执行计划可以发现,在预估Subtree cost中 (资源消耗量)实际上单CPU与双CPU相比只是CPU预估减半,IO预估不变.如图2-1-a
让我们真正的进入并行执行计划,每个并行执行计划都有一个主的交换操作(root exchange),即图形执行计划中最左面的Gather stream运算符(后面会讲到)而所有的并行执行计划都会有一个固定的串行部分—即最左边Gather stream运算符中所有靠左的部分(包含Gather中消费者,后面会讲到生产-消费模型)的所有操作,他是由主线程Thread Zero控制,同时它的执行上下文也是context zero,如图2-2所示
而并行区域,顾名思义就是root exchange所有靠右的部分,而并行部分又有可能有多个分支,每个分支都可以同时执行(分支有自己的tasks),分支自身可以是并行,也可以是串行.但分支不会使用主线程thread zero.关于分支大家可以用如下语句自己看下相关执行计划属性(SQL Server 2012版本及之后版本可以显现) 如图2-3所示
如图2-3-2,我的最大并行度设置为4,有三个branches,而这里我使用的线程数就是4*3=12,再加上一个主线程 thread zero 这个并行查询我所使用的线程总数为13个.
select a.productid,count_big(*) as rows
from dbo.bigproduct as a
inner join dbo.bigtransactionhistory as b
on a.productid=b.productid
where a.ProductID between 1000 and 5000
group by a.productid
order by a.productid
而并行和串行的区别联系,大家可以理解成下图2-4
主线程在串行中实际就是他的执行线程
图2-3中,并行的表扫描后聚合,实际上是等于两个串行的表扫描聚合.这个提到并行表扫描table scan(range scan)就稍微展开下:在SQL Server 2005及以前版本中并行扫描实际上是
” parallel page supplier”及每个线程扫描的单位是数据页,扫描多少有数据页中含有的数据行多少决定,而在SQL Server 2008及以后是”Range Enumerators” 每个线程扫描的数据实际上是由数据的区间分布决定的,至此也就有了我们在08及以后版本中经常看到的 “access_methods_dataset_parent”闩锁等待,你可能会疑问怎么高版本还带来新问题,实际上在 ” parallel page supplier”中的问题也是不少的,比如在快照隔离级别下的数据页扫描确认问题等等,总得来说还是更高效了.这里需要大家注意的是并行扫描只支持前滚扫描(forward),不支持反向扫描(backward)
看到这里不少朋友可能有疑惑了,上文中又是串行,又是并行,又是多tasks,到底什么时候用串行,什么时候并行,用多少个tasks啊,实际上SQL Server实例级有两个设置:并行阈值”cost threshold for parallelism” 即当Subtree cost(前面提到的估计资源消耗)大于设定值时优化器才会触发并行优化,进而可能生成并行执行计划.二:最大并行度(max degree of parallelism)用于设置分支(branches)中到底可以多少个CPU同时工作.这里给大家画个简单的图,大家就一目了然了.如图2-4
如何开启
SQL Server能够隐式使用并行性来加速SQL查询。它是如何做到的,以及你如何确定它是这样做的,对我们大多数人来说并不完全是显而易见的。
强制使用并行
法1
Trace flag 8649 在SQL Server中可以不触发并行而手动指定并行,注意这个标记是无官方文档记录,勿轻易使用.使用时只需在查询最好加上query hint:Option(querytraceon 8649)即可
USE MSSQLTipsDemo
GO
SELECT PP.[ProductID]
,[Name]
,[ProductNumber]
,PTH.ActualCost
,PTH.TransactionType
FROM [MSSQLTipsDemo].[Production].[Product] PP
JOIN [MSSQLTipsDemo].[Production].TransactionHistory PTH
ON PP.ProductID =PTH.ProductID
WHERE PP.SellEndDate <GETDATE()-2 AND MakeFlag =1 and Weight >148
OPTION(QUERYTRACEON 8649)
法2
从SQL Server 2016 SP1开始,OPTION(USE HINT(''))查询提示被引入作为OPTION(QUERYTRACEON)查询提示语句的替代,而不需要具有sysadmin权限来执行,并且您提供了提示名称而没有需要记住跟踪标志号。SQL Server 2016 SP1的累积更新2附带了一个新提示,用于强制执行特定查询的并行计划。
可以重写上一个查询以使用新的ENABLE_PARALLEL_PLAN_PREFERENCE查询级别提示,如下所示。
USE MSSQLTipsDemo
GO
SELECT PP.[ProductID]
,[Name]
,[ProductNumber]
,PTH.ActualCost
,PTH.TransactionType
FROM [MSSQLTipsDemo].[Production].[Product] PP
JOIN [MSSQLTipsDemo].[Production].TransactionHistory PTH
ON PP.ProductID =PTH.ProductID
WHERE PP.SellEndDate <GETDATE()-2 AND MakeFlag =1 and Weight >148
OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'))
如果遇到如下错误,说明版本不够
执行计划
如何在查询级别指定最大并行度?
SELECT *
FROM Pubs.dbo.Authors
OPTION(MAXDOP 1);
配置并行索引操作
-
并行索引执行和 MAXDOP 索引选项适用于下列 Transact-SQL 语句:
- CREATE INDEX
- ALTER INDEX REBUILD
- DROP INDEX(只适用于聚集索引。)
- ALTER TABLE ADD (索引) CONSTRAINT
- ALTER TABLE DROP (聚集索引) CONSTRAINT
- 不能在 ALTER INDEX REORGANIZE 语句中指定 MAXDOP 索引选项。
- ALTER INDEX IX_ProductVendor_VendorID ON Purchasing.ProductVendor
- REBUILD WITH (MAXDOP=8);
- GO
- CREATE INDEX IX_ProductVendor_NewVendorID
- ON Purchasing.ProductVendor (BusinessEntityID)
- WITH (MAXDOP=8);
- GO
https://docs.microsoft.com/zh-cn/sql/relational-databases/indexes/configure-parallel-index-operations?view=sql-server-2017
显示计划
SQL Server包括showplan中的所有上述属性(图形,文本和XML)。
说到showplan,在图形showplan中,您还可以通过在运算符图标上查找一个小的并行度符号,一目了然地看出哪些运算符并行运行(即哪些运算符在启动交换和停止交换之间):
并行度的控制
SQL Server中有并行相关的一些设置,主要有两个:并行阈值及最大并行度.
对于在SQL Server引擎内执行的操作,您必须查看如何设置 并行 度的 成本阈值和 最大并行度,因为它们可能会阻止您使用并行性。默认值为5,但Microsoft建议将其更改为50,然后根据需要进行调整。
相关参数,哪些与oracle类似?
parallel_min_time_threshold 预估执行时间 vs Cost threshold for parallelism
parallel_max_servers vs “Max degree of parallelism”(MaxDOP)定义SQL Server可以并行使用多少处理器来执行单个处理器SQL查询
并行的问题
以下截图来自oracle,但sqlserver的问题类似
自动并行的问题
自适应并行的问题
使用并行应注意的问题
数据分布不均,预估,碎片等问题
导致CXPACKET等待以及过多无谓IO,应对方式创建临时对象,更新统计信息,整理碎片等.
nested loop Join导致的随机IO,及nested loop join预读问题等
冷数据中使用并行nested join可能导致实例的IO稳定性受影响,面对具体场景应酌情使用.应对方式可以关闭nested loop预读,而nested loop预读时SQL Server也会试图将随机IO转化为连续IO,如具体应用合理应接受并行nested loop join.
线程饥饿问题(worker thread starvation)
前面我们说过,线程的授予是按照分支(branches)及并行度授予的,如果并行度高,此时复杂的查询下分支又很多,这个时候可能针对某个查询分配过多线程,加之这类查询又高并发,则这时出现线程饥饿的几率就大大增加了.具体生产中,这个应引起我们的注意.这里给大家举个简单的实例,感兴趣的同学可以自己测试下.这个查询有5个分支,分支所申请的线程就是5*16共80个!如图5-1
并行死锁
并行执行提升查询响应时间,提高用户体验已经被我们所熟知了,但正如我一直强调的,任何事物均有利弊,我们要做的重点是权衡.并行死锁在并行执行中也会偶尔出现,官方给出的解释是SQL Server的”BUG”,你只需将查询的MAXDOP调整为1,死锁就会自动消失,但有时我们还应追溯其本质.这里用一个实例为大家说明下并行死锁的原因,以便我们更好的利用并行.
最常见的并行等待事件——CXPACKET
根据生产者消费者模型来看分析是:
CXPACKET Waits=Class eXchange PACKET
针对生产者:所有的 Buffer packets 都已经被填满,无法继续生产(填充)
针对消费者:所有的Buffer packets都是空的,没有数据可以消费
Cxpacket常见产生原因
http://blog.jobbole.com/104173/
http://www.cnblogs.com/shanksgao/p/5497106.html
使用AWS RDS参数组设置SQL Server配置选项
https://www.mssqltips.com/sqlservertip/5329/setting-sql-server-configuration-options-with-aws-rds-parameter-groups/
如何在SQL Server 2016中强制执行并行执行计划
https://www.mssqltips.com/sqlservertip/4939/how-to-force-a-parallel-execution-plan-in-sql-server-2016/
SQL Server并行性概述
https://www.mssqltips.com/sqlservertip/5169/sql-server-parallelism-overview/
MAXDOP计算器
http://dbamastery.com/performance-tuning/maxdop-calculator/
SQL Server中的“最大并行度”的配置建议
https://www.cnblogs.com/kerrycode/p/4692496.html
理解和使用SQL Server中的并行
https://www.cnblogs.com/wenBlog/p/5795695.html
https://www.red-gate.com/simple-talk/sql/learn-sql-server/understanding-and-using-parallelism-in-sql-server/