【发布时间】:2019-07-27 03:21:30
【问题描述】:
我正在寻找一种高效的方法来检索 SQL 中每组数据的前两行。我有一个非常大的数据表(大约 100 亿行)。每行数据由四个维度(构成主键)描述,表由其中一个维度(主键的最后一列)进行分区。
-- Medium table (2 to 3 million rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableA] (
[colA] [int] NOT NULL PRIMARY KEY
,[valueA] [int]
);
-- Small table (<1000 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableB] (
[colB] [int] NOT NULL PRIMARY KEY
,[valueB] [int]
);
-- Small table (<10000 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableC] (
[colC] [int] NOT NULL PRIMARY KEY
,[valueC] [int]
);
-- Small table (100 to 200 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableD] (
[colD] [int] NOT NULL PRIMARY KEY
,[grouperD] [int] NOT NULL
,[dateD] [date]
);
CREATE PARTITION FUNCTION [pfColD](int) AS RANGE RIGHT FOR VALUES (1, 2, 3, ..., n);
CREATE PARTITION SCHEME [psColD] AS PARTITION [pfColD] TO ([PRIMARY], [PRIMARY], [PRIMARY], ..., [PRIMARY]);
-- Large table (~10 billion rows)
CREATE TABLE [bigDatabase].[dbo].[factBigTable] (
[colA] [int] NOT NULL
,[colB] [int] NOT NULL
,[colC] [int] NOT NULL
,[colD] [int] NOT NULL
,[datum] [float] NULL
,PRIMARY KEY (
[colA] ASC
,[colB] ASC
,[colC] ASC
,[colD] ASC
)
) ON psColD([colD]);
另一个要求是我只需要随时为数据子集执行此操作。为了表示需要定位的数据,我们使用临时表进行过滤。
CREATE TABLE #filter (
[colA] [int] NOT NULL
,[colB] [int] NOT NULL
,PRIMARY KEY (
[colA] ASC
,[colB] ASC
)
);
我在网上找到了一些其他解决方案,建议使用行号选择前 2 位,如下所示:
-- Get the most recent two data points for each group of data
SELECT *
FROM (
SELECT big.*
,dimD.[grouperD]
,ROW_NUMBER() OVER (
PARTITION BY dimD.[grouperD], big.[colA], big.[colB], big.[colC]
ORDER BY dimD.[dateD] DESC
) AS rowNumber
FROM [bigDatabase].[dbo].[factBigTable] AS big
INNER JOIN [smallDatabase].[dbo].[dimTableD] AS dimD
ON big.[colD] = dimD.[colD]
INNER JOIN #filter
ON big.[colA] = #filter.[colA]
AND big.[colB] = #filter.[colB]
) AS bigDataRanked
WHERE rowNumber <= 2;
这确实让我得到了我正在寻找的确切数据;但是,它非常慢!
此时我已经尝试了许多不同的解决方案,但所有解决方案的执行速度都比我想要的要慢。值得注意的是,由于数据的性质,并不是每个维度组合都有数据。有些组合非常稀疏。
我尝试过的一种算法在纸面上看起来很棒,但由于数据的稀疏性,最终表现非常缓慢。这个想法是:
- 缓存每个组的列表。即,[grouperD]、[colA]、[colB] 和 [colC]。跟踪为每个组找到的行。
- [colD] 上的光标,按 [dateD] 排序。当每组找到 2 行时停止。
- 从 [factBigTable] 中选择与找到的行数小于 2 的组匹配的行。缓存结果。
- 对于缓存的结果,增加找到的行数。
- 将缓存的结果移动到临时表以供以后使用。
- 继续循环中的下一个 [colD]。
每个循环都执行得相对较快,因为 SQL 能够在大多数查询上使用 PK 搜索。但是,我的一些组的最大 [colD] 非常低,因此循环必须迭代多次。
到目前为止,我发现的最快的解决方案在纸面上看起来很糟糕,但最终表现最好。然而;它仍然比我想要的慢,而且扩展性很差。
- 对于我们关心的数据子集(即加入过滤器),缓存每个组的所有主键。
- 选择并缓存每个组的最大 [colD]。
- 从 PK by group 列表中删除最大值。
- 再次选择并缓存每个组的最大 [colD]。获得第二个到最大值 [colD]。
- 使用 max 和 second to max 缓存键来查找我们需要的所有行。
是否有人对如何快速检索我正在寻找的行有任何其他想法?这绝不需要在单个查询中完成。我可以根据需要使用尽可能多的临时表或临时表来快速获取数据。此外,我愿意添加索引或其他数据模型更改。我不希望 - 仅仅因为表太大,任何更改都可能意味着重要的存储考虑 - 但是,如果这是唯一的方法,那么我会让它工作。
【问题讨论】:
-
您是否对有效的查询进行了解释?可能只需要一个索引。
-
使用游标绝对不是一个好的选择。循环非常低效,因为它基本上必须运行 100 亿次查询才能查看每一行。我和@Hogan 在一起,索引几乎可以肯定是这里的最佳选择。
-
您可能会通过搜索“top n in group”获得一些好主意 Itzik Ben Gan 展示了各种解决方案 here 以及其他 similar questions 以及其他问题的详细答案dba.stackexchange.com。我更倾向于在 dba.stackexchange.com 上询问如何对具有这种大小和结构的表进行分区,以允许针对它编写有效和高效的查询。
标签: sql sql-server tsql database-performance