当我注意到我的一些插入花费的时间比预期的要长时,我正在制作一个涉及 CCI 的演示。要重现的表定义:
DROP TABLE IF EXISTS dbo.STG_1048576; CREATE TABLE dbo.STG_1048576 (ID BIGINT NOT NULL); INSERT INTO dbo.STG_1048576 SELECT TOP (1048576) 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);
对于测试,我从暂存表中插入所有 1048576 行。这足以完全填充一个压缩的行组,只要它由于某种原因没有被修剪。
如果我插入所有 mod 17000 的整数,它需要不到一秒钟的时间:
TRUNCATE TABLE dbo.CCI_BIGINT; INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK) SELECT ID % 17000 FROM dbo.STG_1048576 OPTION (MAXDOP 1);
SQL Server 执行时间:CPU 时间 = 359 毫秒,运行时间 = 364 毫秒。
但是,如果我插入相同的整数 mod 16000,它有时会花费 30 多秒:
TRUNCATE TABLE dbo.CCI_BIGINT; INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK) SELECT ID % 16000 FROM dbo.STG_1048576 OPTION (MAXDOP 1);
SQL Server 执行时间:CPU 时间 = 32062 毫秒,运行时间 = 32511 毫秒。
这是已在多台机器上完成的可重复测试。随着 mod 值的变化,经过的时间似乎有一个清晰的模式:
MOD_NUM TIME_IN_MS 1000 2036 2000 3857 3000 5463 4000 6930 5000 8414 6000 10270 7000 12350 8000 13936 9000 17470 10000 19946 11000 21373 12000 24950 13000 28677 14000 31030 15000 34040 16000 37000 17000 563 18000 583 19000 576 20000 584
如果您想自己运行测试,请随时修改我在此处编写的测试代码。
我在 sys.dm_os_wait_stats 中找不到 mod 16000 插入的任何有趣内容:
╔════════════════════════════════════╦══════════════╗ ║ wait_type ║ diff_wait_ms ║ ╠════════════════════════════════════╬══════════════╣ ║ XE_DISPATCHER_WAIT ║ 164406 ║ ║ QDS_PERSIST_TASK_MAIN_LOOP_SLEEP ║ 120002 ║ ║ LAZYWRITER_SLEEP ║ 97718 ║ ║ LOGMGR_QUEUE ║ 97298 ║ ║ DIRTY_PAGE_POLL ║ 97254 ║ ║ HADR_FILESTREAM_IOMGR_IOCOMPLETION ║ 97111 ║ ║ SQLTRACE_INCREMENTAL_FLUSH_SLEEP ║ 96008 ║ ║ REQUEST_FOR_DEADLOCK_SEARCH ║ 95001 ║ ║ XE_TIMER_EVENT ║ 94689 ║ ║ SLEEP_TASK ║ 48308 ║ ║ BROKER_TO_FLUSH ║ 48264 ║ ║ CHECKPOINT_QUEUE ║ 35589 ║ ║ SOS_SCHEDULER_YIELD ║ 13 ║ ╚════════════════════════════════════╩══════════════╝
为什么插入 forID % 16000比插入 for 花费的时间长得多ID % 17000?
ID % 16000
ID % 17000
在许多方面,这是预期的行为。任何一组压缩例程都将具有广泛的性能,具体取决于输入数据分布。我们期望用数据加载速度来换取存储大小和运行时查询性能。
由于 VertiPaq 是专有实现,而且细节是严密保密的,因此您在这里获得的答案的详细程度有一定的限制。即便如此,我们确实知道 VertiPaq 包含以下例程:
通常,数据将被值或字典编码,然后应用 RLE 或位打包(或 RLE 和位打包的混合用于段数据的不同子部分)。决定应用哪些技术的过程可能涉及生成直方图,以帮助确定如何实现最大的比特节省。
使用 Windows Performance Recorder 捕获缓慢的情况并使用 Windows Performance Analyzer 分析结果,我们可以看到绝大多数执行时间都花在了查看数据的集群、构建直方图以及决定如何最佳分区上储蓄:
最昂贵的处理发生在段中出现至少 64 次的值。这是一种确定纯RLE 何时可能有益的启发式方法。更快的情况会导致不纯的存储,例如位压缩表示,最终存储大小更大。在混合情况下,具有 64 次或更多重复的值是 RLE 编码的,其余的是位压缩的。
当具有 64 次重复的最大数量的不同值出现在最大可能的段中时,即 1,048,576 行具有 16,384 组值,每组具有 64 个条目时,持续时间最长。代码检查揭示了昂贵处理的硬编码时间限制。这可以在其他 VertiPaq 实现中配置,例如 SSAS,但据我所知不能在 SQL Server 中配置。
可以使用未记录的DBCC CSINDEX命令获得对最终存储安排的一些了解。这显示了 RLE 标头和数组条目、RLE 数据中的任何书签以及位包数据(如果有)的简要摘要。
DBCC CSINDEX