我应该为我的聚集列存储索引表创建多少个分区?我还应该对行存储表进行分区吗?

Cyn*_*ker 9 sql-server partitioning columnstore sql-server-2016

我有一个由四个聚集列存储索引表 (CCI) 和九个行存储表组成的数据仓库。这些表仅用于分析,并且每 15 分钟从临时表中插入 CCI 数据。我希望通过添加分区和排序来优化查询性能。

该数据的所有查询都基于一个包含大约 350 个不同值的整数字段。最左边的 CCI 有 100M 条记录和 125 列。有三个子 CCI 具有相同的整数字段。CCI 2 有 1500 万条记录和 150 列,CCI 3 和 4 都有大约 3000 万条记录和 25 列。

在这 350 个不同的整数中,最左边表中的记录数分布如下:

  • 5% 大于 1M
  • 46% 大于 100K
  • 83% 大于 10K

此外,还有其他九个行存储表也连接到 CCI。它们具有涓流插入,是 CCI 的子项,它们都包含相同的整数字段。这些行存储具有相似或更小的记录量,每个 < 10 列,两个包含 LOBS,两个经常进行大规模更新(这些更新也基于 ID 字段)。

我应该做多少个分区?

我还应该对行存储表进行分区吗?

是否有我忽略的重要考虑因素?

关于我之前提到的“排序”的注意事项:

最左边的 CCI 中的日期字段通常是这些查询中的次要谓词,因此我正在考虑每四个星期左右按日期重新排序 CCI 作为维护。我将通过删除 CCI、在日期上添加聚集行存储索引、删除该索引,然后使用 MAXDOP=1 重新添加 CCI 来实现这种排序。我也在考虑通过其父级的连接键对子 CCI 进行排序。

Joe*_*ish 10

对 CCI 进行分区的好处:

  1. 可以提高查询性能,因为无论数据如何加载或修改,都可以保证最低级别的行组消除。大多数通用 SQL Server 分区指南都没有考虑到这一点。

  2. 提高了维护操作的灵活性,因为您可以在分区级别进行重建或在分区级别进行重组(在分区切换后)。您也可以将不同的分区发送到不同的文件组,但我需要提醒您这样做几乎永远不会提高性能。文件组是一项维护功能。增加文件数有时可以提高性能。根据您的存储设置,您几乎肯定希望与您的查询相关的数据分布在多个文件中以改进 I/O。

  3. 分区消除比同一列上的行组消除涵盖更多场景。例如,过滤器WHERE ID < 0 OR ID > 10不会满足行组消除的质量,但有资格进行分区消除。

  4. 在执行需要更改所有行的维护操作时,按分区循环会很有帮助。例如,假设您要向一个表中添加一个新列,该列可以从该表中的现有列派生。如果需要,分区允许您有效地将该工作拆分为多个批次。

分区 CCI 的缺点:

  1. 如果不进行维护,增量行组中的行数会急剧增加。考虑以 MAXDOP 8 加载并行插入的未分区 CCI。增量存储中最多有 4194304 行。如果表更改为具有 50 个分区,则增量存储中现在可能有 209715200 行。

  2. 用于向列存储中插入和删除的查询计划可能包含排序运算符作为 DML 运算符的子项。如果这种排序无法获得足够的内存,则最终可能会导致性能极度下降。如果使用并行插入,我建议一次只修改一个分区。

  3. 如果您不明智地选择分区函数,您最终可能会得到过小的分区。许多人会指出行组的 1048576 行限制是理想的大小,但我个人认为到达那里的好处被夸大了。如果您可以提供帮助,您可能确实希望避免使用许多小分区。

  4. 如果您的表或数据库中有太多分区,那么可能会发生不好的事情。不幸的是,这不是很好的定义,并且很难找到“太多分区”的实际含义的可靠来源。我听说并看到了查询编译时间的问题。最近在这里也有一个答案DBCC CHECKTABLE

将上述内容应用于您的场景:使用您拥有的行数,您不应该遇到任何非常糟糕的情况。对于查询性能,有些人需要非常快的查询执行时间,他们需要跳过尽可能多的行组。其他人只需要最低级别的行组消除,因为查询中完成的大部分工作都在列存储扫描之外。这使得外面的人很难为您提供分区数量的建议。对于 1 亿表,从 4 到 100 的任何值都可能是合理的。

您可以尝试使用分区中的不同行数测试一些查询,以查看性能如何变化。这可以通过创建表的副本或通过故意偏斜在一个表上创建分区函数并更改过滤所依据的 ID 来模拟。如果您采用什么结果来获得足够好的查询性能并验证加载数据不会有任何问题,那么您应该很好。

行存储与问题无关,或者更确切地说,它们是一个完全不同的问题。分区不是提高行存储查询性能的正确工具。我已经看到仅通过对列存储表进行分区并单独保留行存储表来提高系统性能。


Cyn*_*ker 8

更新已将分区一直用于生产:

为聚集列存储索引 (CCI) 确定正确的分区是一个非常定制的过程。如果选择了错误的分区,性能和压缩将比非分区方案更差。

因为我要对四个 CCI 进行分区,所以我选择了记录最少的 CCI,并将其记录数除以 1,048,576(理想的 CCI 行组大小)。我使用该商数作为我建议的分区数。然后我根据该方案运行记录计数查询以返回每个分区的实际行数。这一步是为了确保分区之间的记录分布合理。有。幸运的我。

一个障碍出现了:这个生产分析过程帮助我得到了正确的分区数量,但仅限于生产。我的较低环境比生产环境小得多。所选的分区级别将数据切片得如此精细,以至于我根本没有完整的行组。较低的数据库变得更大,查询时间保持不变。IO 确实大幅下降,我不得不反复指出这一点,因为该计划的收益受到质疑。在我投入生产之前,很难证明分区真的会有帮助。

结果:分区在生产中取得了巨大的成功。IO 大幅下降,我的查询时间减少了 70% 或更多。我也有更多的选项来管理这些小块的表。

一些注意事项:选择正确的字段进行分区。如果您的查询必须导航大量分区,您可能会发现性能下降。此外,我还留有增长空间,将分区和范围添加到我的分区函数中,以处理现在不存在但总有一天会存在的数据。

仅来自本地测试的原始答案:

自从提出这个问题以来,我一直在做更多的研究和本地的 POC。有人建议我在答案中分享这个 POC。

在我的 POC 中,我选择使用以下分区函数:

CREATE PARTITION FUNCTION [MyIntPF](int) 
AS RANGE LEFT 
FOR VALUES (
    N'50'
    , N'100'
    , N'150'
    , N'200'
    , N'250'
    , N'300'
    , N'350'
    , N'400'
    , N'450'
    , N'500'
);

CREATE PARTITION SCHEME [MyIntPS] 
AS PARTITION [MyIntPF] 
TO (
    [MyInt050fg]
    , [MyInt100fg]
    , [MyInt150fg]
    , [MyInt200fg]
    , [MyInt250fg]
    , [MyInt300fg]
    , [MyInt350fg]
    , [MyInt400fg]
    , [MyInt450fg]
    , [MyInt500fg]
    , [MyInt000fg]
);
Run Code Online (Sandbox Code Playgroud)

该函数为每个分区分配了 50 个 MyInts,有一点增长空间。

请记住,我在 PROD CCI 中的 170M 记录中有大约 350 个不同的 MyInt。David Browne 建议分区中的最小记录大小为 1M,这对于优化 CCI 压缩段是有意义的。由于两个原因,我犯了更大的错误。第一个原因是为了避免创建一个 100 分区的 POC 怪物。第二个是我假设 1M 适用于分区中的每个表。我正在对四个列存储进行分区,其中最小的有 25M 记录。如果我把它分成 100 块,它永远不会实现完整的部分。

在我的本地开发数据库中,我在最左边的 CCI 中有 220 万条记录,甚至比子 CCI 中的记录还要少。这为创建真实的 PROD 复制带来了问题。我真的应该优先考虑一些额外的时间来为此创建一个大的本地数据库,但与此同时,这里是本地分区的 IO 之前/之后的结果。我从我最左边的 CCI 中查询了一个基于 MyInt = 单个值的聚合。

未分区

扫描计数 1,逻辑读取 0,物理读取 0,预读读取 0,lob 逻辑读取 1548, 
lob 物理读取 0,lob 预读读取 44。
段读取 4,段跳过 0。

分区

扫描计数 1,逻辑读取 0,物理读取 0,预读读取 0,lob 逻辑读取 268, 
lob 物理读取 0,lob 预读读取 0。
段读取 1,段跳过 0。

正如预期的那样,SQL Server 能够在使用 MyInt 相等谓词的查询中跳过除我的分区之外的所有分区。

我正在继续努力,随着事情的进展,我应该有时间在这里更新。