- 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 Execution Times: cpu time = 359 ms,elapsed time = 364 ms.
但是,如果我插入相同的整数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 Execution Times: cpu time = 32062 ms,elapsed time = 32511 ms.
这是一项可在多台机器上完成的可重复测试.当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
对于mod 16000插入,我在sys.dm_os_wait_stats中找不到任何有趣的内容:
- ╔════════════════════════════════════╦══════════════╗
- ║ 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 ║
- ╚════════════════════════════════════╩══════════════╝
为什么ID%16000的插入时间比ID%17000的插入时间长得多?
解决方法
由于VertiPaq是一项专有实施,因此对于您将要获得的答案的详细程度有一个明确的限制,细节是一个严密保密的秘密.即便如此,我们知道VertiPaq包含以下例程:
>值编码(缩放和/或转换值以适合少量位)
>字典编码(对唯一值的整数引用)
>运行长度编码(将重复值的运行存储为[值,计数]对)
>比特打包(将流存储在尽可能少的位)
通常,数据将是值或字典编码,然后将应用RLE或比特打包(或者在段数据的不同子部分上使用的RLE和比特打包的混合).决定应用哪种技术的过程可涉及生成直方图以帮助确定如何实现最大比特节省.
使用Windows性能分析器捕获慢速案例并使用Windows性能分析器分析结果,我们可以看到绝大部分执行时间都用于查看数据的聚类,构建直方图以及决定如何对其进行最佳分区储蓄:
对于在段中出现至少64次的值,进行最昂贵的处理.这是一种启发式方法,可以确定纯RLE何时可能是有益的.更快的情况导致不纯的存储,例如比特打包表示,具有更大的最终存储大小.在混合情况下,具有64次或更多次重复的值是RLE编码的,其余的是比特打包的.
当具有64次重复的不同值的最大数量出现在最大可能段中时发生最长持续时间,即1,048,576行,其中16,384组值各自具有64个条目.对代码的检查揭示了昂贵处理的硬编码时间限制.这可以在其他VertiPaq实现中配置,例如SSAS,但据我所知,不在sql Server中.
可以使用undocumented DBCC CSINDEX
command获取对最终存储布置的一些了解.这显示了RLE头和数组条目,RLE数据中的任何书签,以及位包数据的简要摘要(如果有的话).
有关更多信息,请参阅:
> The VertiPaq Engine in DAX,Alberto Ferrari和Marco Russo
> Microsoft Patent WO2015038442:使用DBMS引擎处理数据集
> Microsoft Patent WO2010039898:高效的大规模过滤和/或排序,用于查询基于列的数据编码结构