用于有效范围聚合查询的数据库?


11

作为一个简化的示例,假设我有一个像这样的表:

seq | value
----+------
102 | 11954
211 | 43292
278 | 19222
499 |  3843

该表可能包含数亿条记录,我需要经常执行以下查询:

SELECT sum(value) WHERE seq > $a and seq < $b

即使seq已建立索引,典型的数据库实现也将遍历每一行以计算最佳情况下的总和O(n),其中n范围的大小是。

是否有任何数据库可以像在O(log(n))每个查询中一样有效地做到这一点?

我所遇到的数据结构称为段树所描述这里尽管所有这些名称通常都被描述为数据结构略有不同的变体,但有时也称为范围树或间隔树。

但是,我还没有遇到任何实现这种数据结构的数据库。对于内存结构,从头开始实现它很容易,但是如果必须持久或太大而无法容纳到内存中,则变得棘手。如果有一个有效的模式可以在现有数据库之上实施,那也可能会有所帮助。

旁注:这不是仅追加表,因此在这种情况下,诸如保留累计和之类的解决方案将不起作用。


这是列组织数据库的典型用例,其中有很多
mustaccio

即使是按列组织的数据库,仍然需要O(n)时间来扫描n行。也就是说,许多按列组织的数据库非常擅长并行化此类查询,因此它将在此类数据库上运行得更快。
布赖恩

Answers:


8

使用SQL Server ColumnStore索引

好吧,好吧,一个-集群的CS索引。

如果您想了解我从事此工作的硬件,请转到此处。完全披露后,我将该博客文章写在了我工作的公司的网站上。

进行测试!

这是一些用于构建很大表的通用代码。与Evan同样的警告,可能需要一段时间才能进行构建和编制索引。

USE tempdb

CREATE TABLE t1 (Id INT NOT NULL, Amount INT NOT NULL)

;WITH T (N)
AS ( SELECT X.N
     FROM ( 
      VALUES (NULL), (NULL), (NULL),
             (NULL), (NULL), (NULL),
             (NULL), (NULL), (NULL), 
             (NULL) ) AS X (N) 
           ), NUMS (N) AS ( 
            SELECT TOP ( 710000000 ) 
                    ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )) AS N
            FROM   T AS T1, T AS T2, T AS T3, 
                   T AS T4, T AS T5, T AS T6, 
                   T AS T7, T AS T8, T AS T9, 
                   T AS T10 )
INSERT dbo.t1 WITH ( TABLOCK ) (
    Id, Amount )
SELECT NUMS.N % 999 AS Id, NUMS.N % 9999 AS Amount
FROM   NUMS;

--(705032704 row(s) affected) --Aw, close enough

那么,埃文赢得了简化,但我已经谈到了之前。

这是索引定义。La,Dee和Dah。

CREATE CLUSTERED COLUMNSTORE INDEX CX_WOAHMAMA ON dbo.t1

看一下计数,每个Id都有相当均匀的分布:

SELECT t.Id, COUNT(*) AS [Records]
FROM dbo.t1 AS t
GROUP BY t.Id
ORDER BY t.Id

结果:

Id  Records
0   5005005
1   5005006
2   5005006
3   5005006
4   5005006
5   5005006

...

994 5005005
995 5005005
996 5005005
997 5005005
998 5005005

每个ID都有大约5,005,005行,我们可以查看很小范围的ID,以获取1000万行的总和。

SELECT COUNT(*) AS [Records], SUM(t.Amount) AS [Total]
FROM   dbo.t1 AS t
WHERE  t.Id > 0
       AND t.Id < 3;

结果:

Records     Total
10010012    50015062308

查询资料:

Table 't1'. Scan count 6, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 2560758, lob physical reads 0, lob read-ahead reads 0.
Table 't1'. Segment reads 4773, segment skipped 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 564 ms,  elapsed time = 106 ms.

有趣的是,更大的聚合:

SELECT COUNT(*) AS [Records], SUM(CONVERT(BIGINT, t.Amount)) AS [Total]
FROM   dbo.t1 AS t
WHERE  t.Id > 0
       AND t.Id < 101;

结果:

Records     Total
500500505   2501989114575

查询资料:

Table 't1'. Scan count 6, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 2560758, lob physical reads 0, lob read-ahead reads 0.
Table 't1'. Segment reads 4773, segment skipped 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 1859 ms,  elapsed time = 321 ms.

希望这可以帮助!



2

带有BRIN索引的 PostgreSQL

即使对seq进行了索引,典型的数据库实现也将遍历每一行以计算最佳情况下的总和O(n),其中n是范围的大小。

这不是真的。至少,没有像样的数据库可以做到这一点。PostgreSQL支持在这类表上创建BRIN索引BRIN索引非常小,即使在如此大的表上也可以放入ram中。亿万行并非全无。

在这里,定义了3亿行,就像您订购它们一样。警告创建它可能需要很长时间(时间:336057.807毫秒+索引95121.809毫秒)。

CREATE TABLE foo
AS
  SELECT seq::int, trunc(random()*100000)::int AS v
  FROM generate_series(1,3e8) AS gs(seq);

CREATE INDEX ON foo USING BRIN (seq);

ANALYZE foo;

现在...

EXPLAIN ANALYZE SELECT sum(v) FROM foo WHERE seq BETWEEN 424242 AND 6313376;
                                                                QUERY PLAN                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=1486163.53..1486163.54 rows=1 width=4) (actual time=1493.888..1493.888 rows=1 loops=1)
   ->  Bitmap Heap Scan on foo  (cost=58718.12..1471876.19 rows=5714938 width=4) (actual time=12.565..1035.153 rows=5889135 loops=1)
         Recheck Cond: ((seq >= 424242) AND (seq <= 6313376))
         Rows Removed by Index Recheck: 41105
         Heap Blocks: lossy=26240
         ->  Bitmap Index Scan on foo_seq_idx  (cost=0.00..57289.38 rows=5714938 width=0) (actual time=10.378..10.378 rows=262400 loops=1)
               Index Cond: ((seq >= 424242) AND (seq <= 6313376))
 Planning time: 0.125 ms
 Execution time: 1493.948 ms
(9 rows)

1.4秒,以汇总/累加给定范围内的5,889,135行。

尽管表为10 GB,但BRIN索引为304 kB。

甚至更快

如果仍然不够快,则可以按10万行缓存聚合。

CREATE MATERIALIZED VIEW cache_foo
AS
  SELECT seq/1e5::int AS grp, sum(v)
  FROM foo GROUP BY seq/1e5::int
  ORDER BY 1;

现在,您只需要使用brin和聚合2(1e5-1)行,而不用使用3亿行或其他任何行。

硬件

联想x230,i5-3230M,16GB RAM,1TB三星840 SSD。


谢谢,我将继续阅读并尝试使用BRIN索引。到目前为止,这确实是最好的选择。
拉尔夫

3
不错的建议(BRIN索引和实例化视图)。但是,即使具有BRIN索引的查询仍然是O(n)。请进行编辑,请勿以其他方式声明。物化的观点也许比O(n)也许更好O(sqrt(n))。取决于您如何定义实现中要使用的间隔。
ypercubeᵀᴹ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.