我无法确切地说出为什么会发生这种行为,但是我相信我已经通过暴力测试开发了一个良好的行为模型。以下结论仅适用于将数据加载到单列中且整数分布得很好的情况。
首先,我尝试使用更改插入CCI的行数TOP
。我用于ID % 16000
所有测试。下图是将插入的行与压缩的行组段大小进行比较的图形:
下面是插入到CPU时间(以毫秒为单位)的行图。请注意,X轴的起点不同:
我们可以看到,行组段的大小以线性速率增长,并且使用少量的CPU直到大约1 M行。那时,行组的大小显着减小,CPU使用率显着增加。似乎我们为此压缩在CPU上付出了沉重的代价。
当插入少于1024000行时,我最终在CCI中得到一个开放的行组。但是,使用REORGANIZE
或强制压缩REBUILD
对大小没有影响。TOP
顺便说一句,我发现有趣的是,当我使用变量时,我最终得到一个开放的行组,但RECOMPILE
最终我得到了一个封闭的行组。
接下来,我通过在保持行数相同的同时更改模数值进行测试。这是插入102400行时的数据示例:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ TOP_VALUE ║ MOD_NUM ║ SIZE_IN_BYTES ║ CPU_TIME_MS ║
╠═══════════╬═════════╬═══════════════╬═════════════╣
║ 102400 ║ 1580 ║ 13504 ║ 352 ║
║ 102400 ║ 1590 ║ 13584 ║ 316 ║
║ 102400 ║ 1600 ║ 13664 ║ 317 ║
║ 102400 ║ 1601 ║ 19624 ║ 270 ║
║ 102400 ║ 1602 ║ 25568 ║ 283 ║
║ 102400 ║ 1603 ║ 31520 ║ 286 ║
║ 102400 ║ 1604 ║ 37464 ║ 288 ║
║ 102400 ║ 1605 ║ 43408 ║ 273 ║
║ 102400 ║ 1606 ║ 49360 ║ 269 ║
║ 102400 ║ 1607 ║ 55304 ║ 265 ║
║ 102400 ║ 1608 ║ 61256 ║ 262 ║
║ 102400 ║ 1609 ║ 67200 ║ 255 ║
║ 102400 ║ 1610 ║ 73144 ║ 265 ║
║ 102400 ║ 1620 ║ 132616 ║ 132 ║
║ 102400 ║ 1621 ║ 138568 ║ 100 ║
║ 102400 ║ 1622 ║ 144512 ║ 91 ║
║ 102400 ║ 1623 ║ 150464 ║ 75 ║
║ 102400 ║ 1624 ║ 156408 ║ 60 ║
║ 102400 ║ 1625 ║ 162352 ║ 47 ║
║ 102400 ║ 1626 ║ 164712 ║ 41 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
直到mod值为1600,行组段的大小才每增加10个唯一值就线性增加80个字节。这是一个有趣的巧合,BIGINT
传统上占用8个字节,而每个附加唯一值的段大小增加8个字节。超过MOD值1600时,段大小会迅速增加,直到稳定为止。
在保持模量值不变并更改插入的行数时查看数据也很有帮助:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ TOP_VALUE ║ MOD_NUM ║ SIZE_IN_BYTES ║ CPU_TIME_MS ║
╠═══════════╬═════════╬═══════════════╬═════════════╣
║ 300000 ║ 5000 ║ 600656 ║ 131 ║
║ 305000 ║ 5000 ║ 610664 ║ 124 ║
║ 310000 ║ 5000 ║ 620672 ║ 127 ║
║ 315000 ║ 5000 ║ 630680 ║ 132 ║
║ 320000 ║ 5000 ║ 40688 ║ 2344 ║
║ 325000 ║ 5000 ║ 40696 ║ 2577 ║
║ 330000 ║ 5000 ║ 40704 ║ 2589 ║
║ 335000 ║ 5000 ║ 40712 ║ 2673 ║
║ 340000 ║ 5000 ║ 40728 ║ 2715 ║
║ 345000 ║ 5000 ║ 40736 ║ 2744 ║
║ 350000 ║ 5000 ║ 40744 ║ 2157 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
看起来当插入的行数<〜64 *唯一值的数量时,我们发现压缩效果相对较差(对于mod <= 65000,每行2个字节)并且线性CPU使用率较低。当插入的行数>〜64 *唯一值的数目时,我们看到压缩效果更好,CPU使用率仍然更高。这两个状态之间存在过渡,这对我来说很难建模,但可以在图中看到。当为每个唯一值恰好插入64行时,我们看不到最大的CPU使用率。相反,我们最多只能在一个行组中插入1048576行,并且每个唯一值超过64行时,我们将看到更高的CPU使用率和压缩率。
下面是等高线图,显示了cpu时间如何随着插入的行数和唯一行数的变化而变化。我们可以看到上述模式:
下面是该段使用的空间轮廓图。在特定的点之后,我们开始看到更好的压缩,如上所述:
似乎在这里至少有两种不同的压缩算法在起作用。鉴于以上所述,在插入1048576行时我们将看到最大的CPU使用量,这是有道理的。当插入大约16000行时,我们可以看到当时CPU使用率最高的情况。1048576/64 = 16384。
我在这里上传了所有原始数据,以防有人想要对其进行分析。
值得一提的是并行计划会发生什么。我只用均匀分布的值观察到此行为。进行并行插入时,通常会存在随机性,线程通常是不平衡的。
在登台表中放入2097152行:
DROP TABLE IF EXISTS STG_2097152;
CREATE TABLE dbo.STG_2097152 (ID BIGINT NOT NULL);
INSERT INTO dbo.STG_2097152 WITH (TABLOCK)
SELECT TOP (2097152) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
该插入片段在不到一秒钟的时间内完成,压缩效果较差:
DROP TABLE IF EXISTS dbo.CCI_BIGINT;
CREATE TABLE dbo.CCI_BIGINT (ID BIGINT NOT NULL, INDEX CCI CLUSTERED COLUMNSTORE);
INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT ID % 16000
FROM dbo.STG_2097152
OPTION (MAXDOP 2);
我们可以看到不平衡线程的影响:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ OPEN ║ 13540 ║ 0 ║ 311296 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 2095872 ║
║ COMPRESSED ║ 1035036 ║ 0 ║ 2070784 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
我们可以采取各种技巧来迫使线程保持平衡并具有相同的行分布。这是其中之一:
DROP TABLE IF EXISTS dbo.CCI_BIGINT;
CREATE TABLE dbo.CCI_BIGINT (ID BIGINT NOT NULL, INDEX CCI CLUSTERED COLUMNSTORE);
INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT FLOOR(0.5 * ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) % 15999
FROM dbo.STG_2097152
OPTION (MAXDOP 2)
在这里,为模数选择一个奇数很重要。SQL Server串行扫描登台表,计算行号,然后使用循环分配将行放在并行线程上。这意味着我们将最终获得完美平衡的线程。
插入大约需要40秒,这与串行插入类似。我们得到了很好压缩的行组:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
通过从原始登台表插入数据,我们可以获得相同的结果:
DROP TABLE IF EXISTS dbo.CCI_BIGINT;
CREATE TABLE dbo.CCI_BIGINT (ID BIGINT NOT NULL, INDEX CCI CLUSTERED COLUMNSTORE);
INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT t.ID % 16000 ID
FROM (
SELECT TOP (2) ID
FROM (SELECT 1 ID UNION ALL SELECT 2 ) r
) s
CROSS JOIN dbo.STG_1048576 t
OPTION (MAXDOP 2, NO_PERFORMANCE_SPOOL);
这里,循环分布用于派生表,s
因此在每个并行线程上都对该表进行了一次扫描:
总之,当插入均匀分布的整数时,当每个唯一整数出现超过64次时,您会看到很高的压缩率。这可能是由于使用了不同的压缩算法。要实现这种压缩,CPU的成本可能很高。数据的微小变化可能导致压缩的行组段的大小发生巨大差异。我怀疑(从CPU角度来看)最糟糕的情况在野外并不常见,至少对于此数据集而言如此。并行插入时甚至更难看到。