База даних для ефективних сукупних запитів діапазону?


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))запиті?

Я натрапив на структуру даних під назвою Дерево сегментів, як описано тут . Також іноді називають деревом діапазону або інтервальним деревом, хоча всі ці назви часто описуються як дещо інша зміна структури даних.

Однак я не натрапив на будь-яку базу даних, яка реалізує таку структуру даних. Реалізовувати його з нуля легко для структури пам'яті, але стає складним, якщо її потрібно зберегти або занадто великий, щоб вписатись у пам'ять. Якщо є ефективний зразок для його реалізації поверх існуючої бази даних, це також може допомогти.

Побічна примітка: Це не таблиця, що додається, тому таке рішення, як збереження накопичувальної суми, не спрацює в цьому випадку.


Це типовий випадок використання для організованих стовпцями баз даних, яких існує багато .
мустаччо

Навіть у базі даних, упорядкованих стовпчиками, все одно знадобиться O (n) час для сканування n рядків. Однак, багато баз даних, упорядкованих стовпцями, дуже добре паралелізують такі запити, тому на такій базі даних вона буде працювати набагато швидше.
Брайан

Відповіді:


8

Використання індексів SQL Server ColumnStore

Ну добре, лише один - кластерний індекс CS.

Якщо ви хочете почитати про обладнання, яке я зробив це, перейдіть сюди . Повністю розкривши інформацію, я написав цю публікацію в блозі на веб-сайті компанії, в якій працюю.

На тест!

Ось загальний код для складання досить великої таблиці. Це ж попередження, як і Еван, для збирання та індексації може знадобитися певний час.

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 рядків, ми можемо переглянути досить невеликий діапазон ідентифікаторів, щоб отримати 10-мільйонну суму рядків.

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

Навіть якщо послідовність індексується, типова реалізація бази даних буде проходити цикл через кожен рядок, щоб обчислити суму в кращому випадку O (n), де n - розмір діапазону.

Що це не так. Принаймні, жодна гідна база даних цього не зробить. PostgreSQL підтримує створення індексів BRIN для таких типів таблиць. Індекси BRIN дуже малі і можуть вміщуватися в оперативні пам'ятки навіть на таких великих столах. Сотні мільйонів рядків - це нічого.

Тут 300 мільйонів рядків визначено так само, як ви їх замовили. Попередження може створити його довгий час (Час: 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 ГБ, індекс BRIN становить 304 кБ.

Ще швидше

Якщо це все ще недостатньо швидко, ви можете кешувати агрегати на 100k рядків.

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)рядки, а не 300 мільйонів чи що завгодно.

Обладнання

Lenovo x230, i5-3230M, 16 Гб оперативної пам’яті, 1 тб Samsung 840 SSD.


Дякую, я прочитаю і більше експериментую з індексами BRIN. Це виглядає як найкращий варіант поки що.
Ральф

3
Гарні пропозиції, як (індекс BRIN, так і матеріалізований вигляд). Але запит, навіть з індексом BRIN, все одно O (n). Відредагуйте та не вимагайте іншого. Матеріалізоване уявлення може бути краще , ніж O(n), можливо O(sqrt(n)). Залежить від того, як ви будете визначати інтервали, які будуть використані в матеріалізації.
ypercubeᵀᴹ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.