[原创] - Parallel Query & Bitmap
Degree of Parallelism(并行度)
一个查询使用并行来处理时,SQL Server为该查询分配多个线程,每个线程使用一个CPU进行操作。Degree of Parallelism就是SQL Server为并行查询分配的线程数量,也表明这个并行查询将使用多少个CPU进行并行处理。
Exchange Oprators(交换操作)
查询语句的执行计划中,通常是并行操作和串行操作结合在一起。并行操作要求将输入数据流(data stream)切分成多个(即degree of parallelism)部分,分别分配给各个线程进行并行处理。并行查询包括几个数据流的交换操作(exchange operator),用以管理并行计划的执行。
Distribute Steams
执行计划中显示为Parallelism/Distribute Steams。通常情况下,如果在一个串行操作之后紧接着一个并行操作,则这个并行操作将从前一个串行操作接收一个input stream。Distribute Steams就是并行查询中将单个input stream分发到多个output stream中的操作。
例如一个serial table scan产生一个4000记录的output stream,假设在这个table scan之后是一个并行操作,则在这两个操作之间必须需要一个Distribute Steams操作,向并行操作的各个线程分发input stream。如果degree of parallelism为4,SQL Server根据关键字段,将这个4000记录的stream分发成4个大致相当的stream,分别作为4个线程的input。
Distribute Streams操作之后,每一条input stream中的记录,将出现在某一个output stream中,记录的内容和格式不会发生任何变化。SQL Server自动在output stream中保留input stream中各记录的相对顺序。
通常,将使用Hashing以确定每一条输入记录被分发到哪一个output stream中。
Gather Streams
执行计划中显示为Parallelism/Gather Streams。在并行操作结束之后,输出结果保留在多个stream中。Gather Streams将并行操作的多个output stream收集到单个stream中。
Gather Streams对多个stream进行合并过程中,记录的内容和格式不会发生任何变化。如果各个input stream都是排序的,则Gather Streams的output stream也是排序的。
Redistribute Streams/Repartition Streams
执行计划中显示为Parallelism/Repartition Streams。在一系列的并行操作中,在某个并行操作之后,可能需要对各个stream进行重新组合,才能够进入下一个并行操作。
例如Merge Join、Hash Join,都需要两个input。这些操作的并行计划中,每个并行的线程都应当拥有两个独立的input,各自执行Merge Join、Hash Join操作,在并行Merge/Hash Join之后,使用Gather Streams操作,就得到完整的Merge/Hash Join的结果。如果在Merge/Hash Join前是一系列的并行计划,则在进入Merge/Hash Join时,各个并行线程拥有的stream可能不满足"独立的input"这个条件,此时就需要对进入Merge/Hash Join的各个stream进行重组,以使并行的各个线程独立的完成Merge/Hash Join中的一部分。
每一条input stream中的记录,在Repartition Streams操作之后将出现在其中一个output stream中,记录的内容和格式不会发生任何变化。如果各个input stream都是排序的,则Repartition Streams的各个output stream也是排序的。
示例
示例中要使用到2个table:
TblBuyerItem(
UserID NVARCHAR(40) NOT NULL,
OrgID NVARCHAR(40) NOT NULL,
ItemID NVARCHAR(40) NOT NULL)
这个表的Clustered Index:OrgID,ItemID,记录数为150万。
#alert_asn_shipoverdue(
ORG NVARCHAR(40) NOT NULL,
ITEM NVARCHAR(40) NOT NULL,
VENDOR NVARCHAR(40) NOT NULL)
这是个临时表,没有PK,没有任何index,记录数150。
执行的SQL如下(没有使用到TblBuyerItem的Clustered Index):
SELECT a.UserID
FROM TblBuyerItem a
INNER JOIN #alert_asn_shipoverdue b ON b.ORG=a.OrgID AND b.ITEM=a.ItemID
OPTION(MERGE JOIN)
FROM TblBuyerItem a
INNER JOIN #alert_asn_shipoverdue b ON b.ORG=a.OrgID AND b.ITEM=a.ItemID
OPTION(MERGE JOIN)
执行计划如下图(裁减掉了个别步骤,点击可以放大查看):
第一部分:
第二部分:
第一部分:
第二部分:
执行计划中,图标右下脚有个黄色小圆圈,里面有三个并行小箭头,则表明这个操作是并行执行的。
并行处理中的stream数据流如下图所示:
备注: 原图丢了,从MSDN找的这张图替代一下
上图示例degree of parallelism为3时的执行情况。执行过程介绍如下:
先在#alert_asn_shipoverdue上执行table scan,然后根据key column ORG、ITEM的值,为并行的Merge Join操作将#alert_asn_shipoverdue的数据分发到三个stream中,假设编号分别为B1、B2、B3。三个线程各自对所获得的stream创建bitmap,然后进行排序。
接下来,三个线程并行的在TblBuyerItem上执行Clustered Index Scan。操作完成后每个线程获得一个output stream,假设编号分别为A1'、A2'、A3'。现在A1'、A2'、A3'还不能与B1 、B2、B3匹配成A1'-B1、A2'-B2、A3'-B3,独立的进行Merge Join操作,因此根据key column OrgID、ItemID的值,对A1'、A2'、A3'执行Repartition Streams操作,为接下来的Merge Join重组A1'、A2'、A3',并且使用bitmap过滤记录。假设重组后的stream分别编号为A1、A2、A3,此时,三个线程分别使用A1 -B1、A2-B2、A3-B3作为输入,执行Merge Join操作。在Merge Join操作前,两个input都必须是经过排序的,因此每一个stream在进入Merge Join操作前都有一个Sort操作。
最后,Gather Streams操作从三个线程的output stream中收集合并记录集,得到完整的Merge Join结果记录集。
Parallel query并不能节约内存、CPU的资源开销,因为将stream进行Distribute、Repartition、Gather都需要消耗更多的资源。但是Parallel query也许可以节约query的执行时间。
Bitmap(位图)
在上面的示例中,可以看到Bitmap/Bitmap Create的操作。
在MANY-TO-MANY的Join中,两个表可能存在大量不匹配的记录。在上面的示例中,#alert_asn_shipoverdue的记录只有150,而TblBuyerItem的记录是1,500,000,如果直接对两个表进行 Merge Join,就必须为TblBuyerItem的1,500,000记录进行排序,这是一个成本非常高的操作。而在1,500,000记录中,只有少量记录符合匹配条件。Bitmap操作在Join操作前,快速的对数据进行一次初步的过滤,减少Join操作的开销。
Bitmap in Merge Join
仍以上面的示例来说明,在Parallelism/Distribute Streams操作之后,每个线程得到一个input stream,接下来各个线程为自己的stream创建bitmap。我们假设分配给线程1的stream编号为B1。Bitmap创建完成后,包含一系列0、1的值,为1的位代表对应于这一位,该stream中存在相应的记录;为0的位代表对应于这一位,该stream中不存在相应的记录。在 Parallelism/Repartition Streams操作时,从input stream中循环取出每条记录,先确定该记录应当进入哪一个output stream中。我们假设某一条记录被确定应当放入编号为A1的output stream,这个output stream A1将与stream B1匹配,被分配给线程1,作为线程1 Merge Join的两个输入。接下来SQL Server使用这条记录,查询与output stream A1对应的stream B1的bitmap。如果相应的位值为0,则说明这条记录在B1中不可能存在匹配的记录,因此这条记录被忽略掉,不会放入output stream A1中;如果相应的位值为1,则说明这条记录在B1中可能存在匹配的记录,该记录被放入output stream A1,继续在后续的Merge Join中进行精确的匹配。
关于bitmap具体如何创建还不清楚,猜想大致应当是如下的一个过程:使用hash算法进行操作,在查询优化期间基于#alert_asn_shipoverdue 的Join字段ORG、ITEM可能出现的唯一值数量而确定bitmap的大小。这个过程和Hash Join中确定hash table buckes数量(和大小)有点类似。执行期间,先使用这个bitmap大小的值创建bitmap,初始化各个位均为0。然后为#alert_asn_shipoverdue循环,对每一个ORG、ITEM的组合值进行hash,得到的hash value对应于bitmap中的某一个位,将这个位设为1。
基于上面bitmap的创建过程,在Parallelism/Repartition Streams操作时,某一条记录被确定进入哪一个output stream之后,即可以找到相对应stream的bitmap。对这条记录OrgID、ItemID的值,使用在bitmap创建时相同的hash算法,用得到的hash value查找对应的位。
下面看一下示例中bitmap的使用带来的效果(点击可以放大查看):
在对TblBuyerItem进行scan时,Row Count为1,543,050。在Repartition Streams操作中,WHERE:(PROBE([Bitmap1002])=TRUE)表明对bitmap的使用,Row Count 3,138。另外,从上图中可以看出,示例的SQL实际执行时使用了8 CPU。
附加说明:在上面的示例中,#alert_asn_shipoverdue很小,并且bitmap的create 和probe过程,其实已经和Hash Join差不多。事实上,让SQL Server自动选择Join Type时,使用的是Hash Join(参考[Join Type实例说明],使用的是同样的示例)。使用Hash Join时用的是serial方式,耗时4秒多;上面的示例耗时2秒;上面示例不使用并行处理(MAXDOP 1)时,耗时1分30多秒。
Merge Join中,在outer input的分支进入Merge Join前,必须有一个Sort操作,SQL Server才能使用bitmap。这是因为这个Sort操作使得整个outer input的分支处理完毕之后,才开始inner input分支的执行,最后再进入Merge Join操作。这样inner input分支执行时,才能使用在outer input分支上创建的bitmap。如果没有这个Sort操作,SQL Server会同时开始处理outer input分支和inner input分支,这样是无法使用bitmap的。
我们可以在上面的示例上做一个验证。假如在#alert_asn_shipoverdue有一个clustered index(ORG,ITEM),那示例中的SQL语句执行时会是什么状况?在(ORG,ITEM)上创建clustered index后,执行计划变成下图所示(点击可以放大查看):
对#alert_asn_shipoverdue的Table Scan变成了Clustered Index Scan,得到的stream是按照ORG、ITEM排序的。
现在,在Parallelism/Distribute Streams操作前的stream是按ORG、ITEM排序的,因此Distribute Streams的各个output stream也是按照ORG、ITEM排序的,因此这个outer input分支在Merge Join前不再需要Sort操作,这样的话就无法使用bitmap了。
从执行计划图中可以看到,inner input的分支,在TblBuyerItem的Table Scan之后,箭头的大小一直到Merge Join操作没有变化,说明在这个分支上一系列的操作中,记录数基本上没有什么变化。下图是TblBuyerItem的Table Scan和Parallelism/Repartition Streams的详细信息(点击可以放大查看):
在这个验证中,没有使用bitmap的平均执行时间是1分10秒。
Bitmap in Hash Join
Hash Join中的bitmap,总体上来讲跟Merge Join中的处理是一样的。在build input(outer input)上构造hash table时是并行处理的,构造hash table的同时也为每个build input的stream创建bitmap。当整个build阶段完成后,再开始probe阶段,因此在probe阶段执行时已经可以使用bitmap,接下来的操作就跟Merge Join中Probe Bitmap操作完全一样。
SQL Server只在Parallel Query中使用bitmap,估计是Probe Bitmap结合Parallel Query中的Repartition操作,可节约的成本比较明显。在Serial Query中,额外的去Probe Bitmap,可能对查询的执行并不能带来明显的改善。
OK,It's over now. 下面回顾一下示例的语句在各种情况下的执行效果:
参考: