Verimli aralıklı toplu sorgular için veritabanı?


11

Basitleştirilmiş bir örnek olarak, şöyle bir tablonuz olduğunu varsayalım:

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

Tablo yüz milyonlarca kayıt içerebilir ve sık sık böyle sorgular yapmak gerekir:

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

Bile seqdizine, her satır ile tipik bir veritabanı uygulaması bir şekilde döngü iyi durumda toplamını hesaplamak için O(n), naralığın boyutudur.

O(log(n))Sorgu başına olduğu gibi bunu verimli bir şekilde yapabilen herhangi bir veritabanı var mı ?

Burada açıklandığı gibi bir Segment Ağacı adı verilen bir veri yapısına rastladım . Tüm bu isimler genellikle veri yapısının biraz farklı bir varyasyonu olarak tanımlansa da, bazen bir aralık ağacı veya aralık ağacı olarak da adlandırılır.

Ancak, böyle bir veri yapısı uygulayan herhangi bir veritabanı rastlamak değil. Bellek içi bir yapı için sıfırdan uygulamak kolaydır, ancak kalıcı olması veya belleğe sığmayacak kadar büyük olması zor olur. Bunu mevcut bir veritabanının üstüne uygulamak için etkili bir kalıp varsa, bu da yardımcı olabilir.

Yan not: Bu yalnızca bir ek tablo değildir, bu nedenle kümülatif bir tutar tutma gibi bir çözüm bu durumda çalışmaz.


Bu, çok sayıda sütun içeren veritabanları için tipik kullanım durumudur .
mustaccio

Sütun düzenlenmiş bir veritabanı bile n satırları taramak için O (n) zamanı gerektirir. Bununla birlikte, sütun düzenlenmiş birçok veritabanı bu tür sorguları paralel hale getirmede çok iyidir, bu nedenle böyle bir veritabanında çok daha hızlı çalışacaktır.
Brian

Yanıtlar:


8

SQL Server ColumnStore dizinlerini kullanma

Tamam, sadece bir tane - kümelenmiş bir CS endeksi.

Bunu yaptığım donanım hakkında okumak isterseniz buraya gelin . Tam açıklama, blog yazısını çalıştığım şirketin web sitesinde yazdım.

Teste devam!

İşte oldukça büyük bir tablo oluşturmak için bazı genel kod. Evan ile aynı uyarı, bu oluşturmak ve dizine eklemek biraz zaman alabilir.

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

Evan basitlik için kazanır, ama daha önce bunun hakkında konuşmuştum .

İşte indeks tanımı. La ve dee ve dah.

CREATE CLUSTERED COLUMNSTORE INDEX CX_WOAHMAMA ON dbo.t1

Bir sayıya baktığımızda, her Kimlik'in eşit bir dağılımı vardır:

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

Sonuçlar:

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

...

994 5005005
995 5005005
996 5005005
997 5005005
998 5005005

Her Kimlikte ~ 5,005,005 satır olduğunda, size 10 milyon satırlık bir toplam elde etmek için oldukça küçük bir kimlik yelpazesine bakabiliriz.

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

Sonuç:

Records     Total
10010012    50015062308

Sorgu profili:

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.

Eğlence için daha büyük bir toplama:

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

Sonuçlar:

Records     Total
500500505   2501989114575

Sorgu profili:

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.

Bu yardımcı olur umarım!



2

BRIN endeksli PostgreSQL

Sıra dizini indekslenmiş olsa bile, tipik bir veritabanı uygulaması , en iyi durumda O (n) toplamını hesaplamak için her satırda döngü yapar; burada n, aralığın boyutudur.

Bu doğru değil. En azından, hiçbir iyi veritabanı bunu yapmaz. PostgreSQL , bu tür tablolarda BRIN dizinleri oluşturulmasını destekler . BRIN indeksleri çok küçüktür ve bu büyük masalarda bile koç içine sığabilir. Yüz milyonlarca satır hiçbir şey değil.

Burada, tıpkı sipariş ettiğiniz gibi 300 milyon satır tanımlandı. Uyarı oluşturmak uzun sürebilir (Süre: 336057.807 ms + dizin için 95121.809 ms).

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;

Ve şimdi...

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)

Verilen aralıktaki 5.889.135 satırı toplamak / toplamak için 1.4 saniye.

Tablo 10 GB olmasına rağmen, BRIN endeksi 304 kB'dir.

Daha hızlı

Bu hala yeterince hızlı değilse, toplamları 100 bin satır önbelleğe alabilirsiniz.

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

Artık sadece 2(1e5-1)300 milyon yerine salamura ve toplama satırlarını kullanmanız gerekecek .

Donanım

Lenovo x230, i5-3230M, 16GB RAM, 1 tb Samsung 840 SSD.


Teşekkürler, BRIN dizinlerini okuyacağım ve daha çok deneyeceğim. Bu şimdiye kadarki en iyi seçenek gibi görünüyor.
Ralf

3
Güzel öneriler, (BRIN endeksi ve materyalize görünüm). Ancak sorgu, BRIN indeksi ile bile hala O (n) 'dir. Lütfen düzenleyin ve aksini iddia etmeyin. Maddi görüş O(n), belki de daha iyi olabilir O(sqrt(n)). Materyalizasyonda kullanılacak aralıkları nasıl tanımlayacağınıza bağlıdır.
ypercubeᵀᴹ
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.