using $Partition function to improve query performance

7 performance sql-server partitioning sql-server-2014 query-performance

I have tables which are partitioned based on a INT column.

I see some queries that are using $Partition function to compare partition number instead of comparing the actual field data.

For example instead of saying:

select *
from T1 
    inner join T2 on T2.SnapshotKey = T1.SnapshotKey
Run Code Online (Sandbox Code Playgroud)

they have been written like below:

select *
from T1 
    inner join T2 on $Partition.PF_Name(T2.SnapshotKey) = $Partition.PF_Name(T1.SnapshotKey)
Run Code Online (Sandbox Code Playgroud)

where PF_Name is the name of partition function.

我看到对这些查询的评论说这样做是为了提高性能,当我运行这两个查询时,我看到执行时间和执行计划不同。我不确定这两个查询有何不同。

这是一个真正的查询:

-- this takes about 9 seconds 
select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703

-- this takes about 5 seconds
select sum(PrinBal)
from fpc
    inner join gl on $Partition.SnapshotKeyPF(gl.SnapshotKey) = $Partition.SnapshotKeyPF(fpc.SnapshotKey)
where fpc.SnapshotKey = 201703
Run Code Online (Sandbox Code Playgroud)

下面是真实查询的执行计划:

You can see the execution plan here

抱歉,我什至无法上传经过消毒的执行计划,因为上传是由我们的网络监控的,这可能违反了政策。

问题是:为什么执行计划不同,为什么第二个查询更快。

如果有人可以分享任何想法,我很感激。只是想知道他们为什么不同。不过越快越好。

这发生在 SQL Server 2014 上。如果我在 SQL Server 2012 上运行相同的程序,结果会有所不同,第一个查询的执行速度会更快!

Pau*_*ite 9

为什么执行计划不同

第一次查询

select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703
Run Code Online (Sandbox Code Playgroud)

优化器知道:

  • gl.SnapshotKey = fpc.SnapshotKey; 和
  • fpc.SnapshotKey = 201703

所以它可以推断:

  • gl.SnapshotKey = 201703

就像你写的一样:

select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703
and gl.SnapshotKey = 201703
Run Code Online (Sandbox Code Playgroud)

优化器也可以使用字面值 201703 来确定分区 ID。对于两个SnapshotKey谓词(一个给定,一个推断),这意味着优化器知道两个表的分区 ID。

更进一步,SnapshotKey现在可以在两个表上使用文字值 (201703) ,连接谓词:

  • gl.SnapshotKey = fpc.SnapshotKey

简化为:

  • 201703 = 201703; 或者干脆
  • true

这意味着根本没有连接谓词。结果是逻辑交叉连接。使用最接近的可用 T-SQL 语法表达最终执行计划,就好像你写了:

SELECT
    CASE
        WHEN SUM(Q1.c) = 0 THEN NULL
        ELSE SUM(Q1.s)
    END
FROM 
(
    SELECT c = COUNT_BIG(*), s = SUM(GL.PrinBal)
    FROM dbo.gl AS GL
    WHERE GL.SnapshotKey = 201703
    AND $PARTITION.PF(GL.SnapshotKey) = $PARTITION.PF(201703)
) AS Q1
CROSS JOIN
(
    SELECT Dummy = 1
    FROM dbo.fpc AS FPC
    WHERE FPC.SnapshotKey = 201703
    AND $PARTITION.PF(FPC.SnapshotKey) = $PARTITION.PF(201703)
) AS Q2;
Run Code Online (Sandbox Code Playgroud)

第二次查询

select sum(PrinBal)
from fpc
    inner join gl on $Partition.PF(gl.SnapshotKey) = $Partition.PF(fpc.SnapshotKey)
where fpc.SnapshotKey = 201703
Run Code Online (Sandbox Code Playgroud)

优化器无法再推断出关于 的任何信息gl.SnapshotKey,因此不再可能对第一个查询进行简化和转换。

实际上,除非每个分区确实只包含一个SnapshotKey,否则不能保证重写会产生相同的结果。

同样,使用最接近的可用 T-SQL 语法表达生成的执行计划:

SELECT
    CASE
        WHEN SUM(Q2.c) = 0 THEN NULL
        ELSE SUM(Q2.s)
    END
FROM 
(
    SELECT
        Q1.PtnID,
        c = COUNT_BIG(*),
        s = SUM(Q1.PrinBal)
    FROM 
    (
        SELECT GL.PrinBal, PtnID = $PARTITION.PF(GL.SnapshotKey)
        FROM dbo.gl AS GL
    ) AS Q1
    GROUP BY
        Q1.PtnID
) AS Q2
CROSS APPLY
(
    SELECT Dummy = 1
    FROM dbo.fpc AS FPC
    WHERE
        $PARTITION.PF(FPC.SnapshotKey) = Q2.PtnID
        AND FPC.SnapshotKey = 201703
) AS Q3;
Run Code Online (Sandbox Code Playgroud)

这次没有逻辑交叉连接。相反,分区 id 上有一个相关的连接(一个应用)。


为什么第二个查询更快。

从所提供的信息很难评估这一点。使用基于提供的查询和计划图像的模拟数据和表,我发现第一个查询在每种情况下都优于第二个查询。

使用不同语法表达的相同查询通常会产生不同的执行计划,这仅仅是因为优化器从不同的点开始,并在找到合适的执行计划之前以不同的顺序探索选项。计划搜索并非详尽无遗,并且并非所有可能的逻辑转换都可用,因此最终结果可能会有所不同。如上所述,无论如何,这两个查询不一定表达相同的要求(至少考虑到优化器可用的信息)。

另外请注意,SQL Server 2012(以及在较小程度上,2014)中的初始列存储实现有许多限制,尤其是在优化方面。通过升级到更新版本(最好是最新版本),您可能会获得更好、更一致的结果。如果您要使用分区,则尤其如此。

我当然不会建议你养成使用 重写连接的习惯$PARTITION,除非作为最后的手段,并且对你正在做的事情有非常深入的了解。

这就是我在无法看到架构或计划细节的情况下所能说的。