我建议使用一种方法2,并将搜索范围分为许多目标表。10000张桌子是一个很好的第一次尝试。例如,如果搜索“ 012345678901”,则查询将查看与以“ 0123”开头的数据关联的表。您总共仍然会有大约一万亿行,但是将数据拆分为许多表具有以下积极效果:
- 现在,所有可能的12位数字字符串都可以放入INT。
- 无论如何,构建一个1 TB字符串的搜索效率更高的表示可能都会很昂贵。使用许多表,您可以轻松并行化作业,甚至可以临时要求将更多CPU分配给您的VM。
- 您可以构建一个表作为概念证明,以确定完整字符串的查询时间和总空间要求。
- 如果您需要进行任何类型的数据库维护,您都会很高兴没有创建一个巨大的表。
在这一点上,我的主要问题是您使用压缩的行存储还是列存储。下面的代码为“ 0123”搜索空间创建一个行存储表,并向其中插入1亿行。如果字符串足够随机,那么您还可以期望每个表看到约1亿行。
DROP TABLE IF EXISTS #t;
SELECT TOP (10000) 0 ID INTO #t
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);
DROP TABLE IF EXISTS dbo.Q229892_RAW_100M_RANGE;
CREATE TABLE dbo.Q229892_RAW_100M_RANGE (
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL
);
INSERT INTO dbo.Q229892_RAW_100M_RANGE WITH (TABLOCK)
SELECT ABS(CHECKSUM(NEWID()) % 100000000),
TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT) * TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT)
FROM #t t1
CROSS JOIN #t t2
OPTION (MAXDOP 4);
DROP TABLE IF EXISTS dbo.T0123_Q229892_PAGE_COMPRESSION;
CREATE TABLE dbo.T0123_Q229892_PAGE_COMPRESSION (
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL,
PRIMARY KEY (STRING_PIECE, STR_POS)
) WITH (DATA_COMPRESSION = PAGE);
INSERT INTO dbo.T0123_Q229892_PAGE_COMPRESSION WITH (TABLOCK)
SELECT STRING_PIECE, STR_POS
FROM dbo.Q229892_RAW_100M_RANGE;
坏消息是您可能需要大约15.4 TB的完整数据集。好消息是,即使缓冲区高速缓存中没有任何相关数据,查询也只需要1毫秒,这对于与您一样大的数据集几乎总是如此。
-- 1 ms
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_PAGE_COMPRESSION
WHERE STRING_PIECE = 45678901; -- searching for '012345678901'
您可以将这些数据丢到您拥有的最便宜的存储上,并且由于查询执行的逻辑读操作很少,因此仍然可以看到良好的响应时间。
对于columnstore,您无法查找所需的数据,并且仍然极不可能将所有数据放入内存中,因此,在查询中读取尽可能少的压缩数据非常重要。我强烈建议对表进行分区。一种行之有效的简单方法是使用搜索字符串的前四位数字查找表名,然后使用后两位数字作为分区。再次使用“ 012345678901”,您将转到包含“ 0123”数据的表的分区45。100个分区是一个很好的数目,可以避免由过多的分区引起的问题,平均每个分区大约有100万行。单个行组中可容纳的最大行数为1048576,因此使用这种方法,您将尽可能减少IO。
DROP TABLE IF EXISTS dbo.Q229892_RAW_1M_RANGE;
CREATE TABLE dbo.Q229892_RAW_1M_RANGE (
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL
);
INSERT INTO dbo.Q229892_RAW_1M_RANGE WITH (TABLOCK)
SELECT TOP (1000000) ABS(CHECKSUM(NEWID()) % 1000000),
TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT) * TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);
DECLARE @IntegerPartitionFunction nvarchar(max) =
N'CREATE PARTITION FUNCTION partition100 (tinyint)
AS RANGE LEFT FOR VALUES (';
DECLARE @i int = 0;
WHILE @i < 100
BEGIN
SET @IntegerPartitionFunction += CAST(@i as nvarchar(10)) + N', ';
SET @i += 1;
END
SET @IntegerPartitionFunction += CAST(@i as nvarchar(10)) + N');';
EXEC sp_executesql @IntegerPartitionFunction;
GO
CREATE PARTITION SCHEME partition100_scheme
AS PARTITION partition100
ALL TO ([DEFAULT]);
DROP TABLE IF EXISTS dbo.T0123_Q229892_COLUMNSTORE;
-- this table must be partitioned by PART_ID!
CREATE TABLE dbo.T0123_Q229892_COLUMNSTORE (
PART_ID TINYINT NOT NULL,
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL,
INDEX CCS CLUSTERED COLUMNSTORE
) ON partition100_scheme (PART_ID);
GO
DECLARE @part_id TINYINT = 0;
SET NOCOUNT ON;
WHILE @part_id < 100
BEGIN
INSERT INTO dbo.T0123_Q229892_COLUMNSTORE WITH (TABLOCK)
SELECT @part_id, STRING_PIECE, STR_POS
FROM dbo.Q229892_RAW_1M_RANGE
OPTION (MAXDOP 1);
SET @part_id = @part_id + 1;
END;
GO
采用这种方法,整个数据集将需要大约10.9 TB。我不清楚如何缩小尺寸。在这种情况下,搜索查询会慢一些。在我的机器上大约需要25毫秒,但这主要取决于IO:
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_COLUMNSTORE
WHERE PART_ID = 45
AND STRING_PIECE = 678901; -- searching for '012345678901'
关于列存储方法的一个重要说明是10.9 TB数字是针对100%压缩数据的。在避免增量存储的同时,高效地填充此类表将具有挑战性。在此过程中的某个时刻,您最终可能会在增量存储中获得未压缩的数据,这很容易需要超过行存储方法所用的15.4 TB。