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


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

列構成データベースでも、n行をスキャンするにはO(n)時間を必要とします。とは言っても、多くの列編成データベースはそのようなクエリの並列化に非常に優れているため、そのようなデータベースでははるかに高速に実行されます。
ブライアン

回答:


8

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

まあ、まあ、1つだけ-クラスター化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

まあ、エヴァンは、簡略化のために勝利したが、私はについて話してきましたその前に。

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

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.

お役に立てれば!



2

PostgreSQLとBRINインデックス

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;

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

ハードウェア

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


よろしくお願いします。BRINインデックスの詳細を読んで実験してみます。これは今のところ最良の選択肢のように見えます。
ラルフ、

3
両方とも素晴らしい提案です(BRINインデックスとマテリアライズドビュー)。しかし、クエリは、BRINインデックスを使用しても、まだO(n)です。編集して、他に主張しないでください。マテリアライズドビューはO(n)、おそらくより良いかもしれませんO(sqrt(n))。マテリアライゼーションで使用される間隔をどのように定義するかによって異なります。
ypercubeᵀᴹ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.