web-dev-qa-db-ja.com

効率的な範囲集計クエリのためのデータベース?

簡単な例として、次のようなテーブルがあるとします。

_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))のように、これを効率的に実行できるデータベースはありますか?

here で説明されているように、セグメントツリーと呼ばれるデータ構造に遭遇しました。 これらの名前はすべて、データ構造のわずかに異なるバリエーションとして説明されることが多いですが、範囲ツリーまたは間隔ツリーとも呼ばれます

しかし、そのようなデータ構造を実装するデータベースに出会ったことはありません。インメモリ構造の場合、最初から実装するのは簡単ですが、永続化する必要がある場合や、メモリに収まりきらない場合は注意が必要です。これを既存のデータベースの上に実装するための効率的なパターンがある場合、それも役立ちます。

補足:これは追加専用のテーブルではないため、累積合計を維持するなどの解決策はこの場合機能しません。

11
Ralf

SQL Serverの使用 ColumnStore インデックス

まあ、まあ、1つだけです。クラスター化されたCSインデックスです。

私がこれを行ったハードウェアについてお読みになりたい場合は、 over here に進んでください。完全に開示し、私が働いている会社のウェブサイトにそのブログ投稿を書きました。

テストに進みます!

これは、かなり大きなテーブルを作成するための一般的なコードです。 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

さて、エヴァンは単純さのために勝ちます、しかし私は以前に that について話しました。

これがインデックスの定義です。ラとディーとダー。

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を調べて、1,000万行の合計を取得できます。

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.

お役に立てれば!

8
Erik Darling

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;
_

これで、3億行などではなく、brinおよび集約2(1e5-1)行を使用するだけで済みます。

ハードウェア

Lenovo x230、i5-3230M、16GB RAM、1TB Samsung 840 SSD。

2
Evan Carroll