Cara tercepat untuk memecah / menyimpan string panjang untuk fungsi charindex

8

Saya memiliki string angka 1 TB. Diberikan urutan karakter 12-digit saya ingin mendapatkan posisi awal urutan ini dalam string asli ( charindexfungsi).

Saya telah menguji ini dengan string 1GB dan substring 9 digit menggunakan SQL Server, menyimpan string sebagai varchar(max). Charindexmembutuhkan waktu 10 detik. Memecah string 1GB dalam potongan 900 byte yang tumpang tindih dan membuat tabel (StartPositionOfChunk, Chunkofstring) dengan chunkofstring dalam susunan biner, diindeks membutuhkan waktu kurang dari 1 detik. Metode yang lebih lambat untuk 10GB, 10 digit-substring naik charindex menjadi 1,5 menit. Saya ingin mencari metode penyimpanan yang lebih cepat.

Contoh

string digit: 0123456789 - substring untuk mencari 345
charindex ('345', '0123456789') memberikan 4

Metode 1 : Sekarang saya dapat menyimpan ini dalam tabel SQL Server strtable yang terdiri dari satu kolom colstrdan melakukan:

select charindex('345',colstr) from strtable

Metode 2 : atau saya bisa membuat tabel strtable2 (pos, colstr1) dengan memisahkan string asli: 1; 012 | 2; 123 | 3; 234 dan kemudian kita dapat memiliki kueri

select pos from strtable2 where colstr1='345'

Metode 3 : Saya bisa membuat tabel strtable2 (pos2, colstr2) dengan memecah string asli menjadi potongan yang lebih besar 1; 01234 | 4; 34567 | 7; 6789 dan kemudian

select pos2+charindex('345',colstr2) from strtable2 where colstr2 like '%345%'

Metode pertama adalah yang paling lambat.

Metode kedua meledakkan ukuran penyimpanan basis data!

Metode 3 : Mengatur panjang colstr2 hingga 900 byte dalam susunan biner, membuat indeks pada kolom ini membutuhkan 1 detik untuk string 1GB dan pencarian substring 9 digit. Untuk string 10GB dan 10 digit substring membutuhkan waktu 90 detik.

Ada ide lain bagaimana membuat ini lebih cepat (mungkin dengan menggunakan string terdiri dari Digit, dengan bilangan bulat Panjang, ....)?

Pencarian selalu untuk substring 12 digit dalam string 1TB digit SQL Server 2017 Developer Edition, 16 core, 16GB RAM. Tujuan utama adalah kecepatan pencarian! 10 digit dalam string 10GB (untuk pengujian kinerja).

Werner Aumayr
sumber

Jawaban:

6

Saya sarankan menggunakan rasa metode 2 dan memecah rentang pencarian menjadi banyak tabel target. 10.000 meja adalah upaya pertama yang bagus. Misalnya, jika Anda mencari "012345678901" maka kueri Anda akan melihat tabel yang terkait dengan data yang dimulai dengan "0123". Anda masih memiliki sekitar satu triliun baris secara total, tetapi memecah data menjadi banyak tabel memiliki kualitas positif berikut:

  1. Semua string 12 digit yang mungkin sekarang dapat masuk ke dalam INT.
  2. Membangun representasi pencarian yang lebih efisien dari string 1 TB Anda sepertinya akan mahal biar bagaimanapun. Dengan banyak tabel, Anda dapat dengan mudah memparalelkan pekerjaan dan bahkan sementara meminta lebih banyak CPU untuk dialokasikan ke VM Anda.
  3. Anda bisa membangun satu tabel sebagai bukti konsep untuk menentukan waktu kueri dan total kebutuhan ruang untuk string penuh.
  4. Jika Anda perlu melakukan pemeliharaan database apa pun, Anda akan senang bahwa Anda tidak membuat satu tabel besar.

Pada titik ini, pertanyaan utama bagi saya adalah apakah Anda menggunakan rowstore terkompresi atau kolomstore. Kode di bawah ini membuat tabel rowstore untuk ruang pencarian "0123" dan memasukkan 100 juta baris ke dalamnya. Jika string Anda cukup acak maka Anda juga bisa berharap untuk melihat sekitar 100 juta baris per tabel.

DROP TABLE IF EXISTS #t;

SELECT TOP (10000) 0 ID INTO #t
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.Q229892_RAW_100M_RANGE;

CREATE TABLE dbo.Q229892_RAW_100M_RANGE (
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL
);

INSERT INTO dbo.Q229892_RAW_100M_RANGE WITH (TABLOCK)
SELECT ABS(CHECKSUM(NEWID()) % 100000000),
TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT) * TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT)
FROM #t t1
CROSS JOIN #t t2
OPTION (MAXDOP 4);


DROP TABLE IF EXISTS dbo.T0123_Q229892_PAGE_COMPRESSION;

CREATE TABLE dbo.T0123_Q229892_PAGE_COMPRESSION (
    STRING_PIECE INT NOT NULL,
    STR_POS BIGINT NOT NULL,
    PRIMARY KEY (STRING_PIECE, STR_POS)
) WITH (DATA_COMPRESSION = PAGE);

INSERT INTO dbo.T0123_Q229892_PAGE_COMPRESSION WITH (TABLOCK)
SELECT STRING_PIECE, STR_POS
FROM dbo.Q229892_RAW_100M_RANGE;

Berita buruknya adalah untuk kumpulan data lengkap yang mungkin Anda perlukan sekitar 15,4 TB. Kabar baiknya adalah bahwa kueri hanya membutuhkan 1 ms untuk saya walaupun tidak ada data yang relevan dalam cache buffer, yang hampir selalu merupakan kasus untuk set data sebesar milik Anda.

-- 1 ms
CHECKPOINT;
DBCC DROPCLEANBUFFERS;

SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_PAGE_COMPRESSION
WHERE STRING_PIECE = 45678901; -- searching for '012345678901'

Anda mungkin dapat membuang data ini pada penyimpanan termurah yang Anda miliki dan masih melihat waktu respons yang baik karena kueri melakukan beberapa pembacaan logis.

Untuk toko kolom, Anda tidak dapat mencari data yang Anda butuhkan dan Anda masih sangat tidak mungkin untuk memasukkan semua data Anda ke dalam memori, jadi penting untuk membaca sesedikit mungkin data terkompresi dengan pertanyaan Anda. Saya sangat menyarankan untuk mempartisi tabel Anda. Salah satu pendekatan sederhana yang berfungsi dengan baik adalah menggunakan empat digit pertama dari string pencarian Anda untuk menemukan nama tabel dan dua digit berikutnya sebagai partisi. Menggunakan "012345678901" lagi, Anda akan pergi ke partisi 45 dari tabel yang menyimpan data untuk "0123". 100 partisi adalah angka yang baik untuk menghindari masalah yang disebabkan oleh terlalu banyak partisi dan Anda akan berakhir dengan rata-rata sekitar 1 juta baris untuk setiap partisi. Jumlah maksimum baris yang dapat masuk ke dalam satu grup baris adalah 1048576, jadi dengan pendekatan ini Anda akan melakukan IO sesedikit mungkin.

DROP TABLE IF EXISTS dbo.Q229892_RAW_1M_RANGE;

CREATE TABLE dbo.Q229892_RAW_1M_RANGE (
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL
);

INSERT INTO dbo.Q229892_RAW_1M_RANGE WITH (TABLOCK)
SELECT TOP (1000000) ABS(CHECKSUM(NEWID()) % 1000000),
TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT) * TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);



DECLARE @IntegerPartitionFunction nvarchar(max) = 
    N'CREATE PARTITION FUNCTION partition100 (tinyint) 
    AS RANGE LEFT FOR VALUES (';  
DECLARE @i int = 0;  
WHILE @i < 100
BEGIN  
SET @IntegerPartitionFunction += CAST(@i as nvarchar(10)) + N', ';  
SET @i += 1;  
END  
SET @IntegerPartitionFunction += CAST(@i as nvarchar(10)) + N');';  
EXEC sp_executesql @IntegerPartitionFunction;  
GO  

CREATE PARTITION SCHEME partition100_scheme
AS PARTITION partition100  
ALL TO ([DEFAULT]);

DROP TABLE IF EXISTS dbo.T0123_Q229892_COLUMNSTORE;

-- this table must be partitioned by PART_ID!
CREATE TABLE dbo.T0123_Q229892_COLUMNSTORE (
    PART_ID TINYINT NOT NULL,
    STRING_PIECE INT NOT NULL,
    STR_POS BIGINT NOT NULL,
    INDEX CCS CLUSTERED COLUMNSTORE
) ON partition100_scheme (PART_ID);


GO

DECLARE @part_id TINYINT = 0;
SET NOCOUNT ON;
WHILE @part_id < 100
BEGIN
    INSERT INTO dbo.T0123_Q229892_COLUMNSTORE WITH (TABLOCK)
    SELECT @part_id, STRING_PIECE, STR_POS
    FROM dbo.Q229892_RAW_1M_RANGE
    OPTION (MAXDOP 1);

    SET @part_id = @part_id + 1;
END;

GO

Dengan pendekatan ini, kumpulan data lengkap akan membutuhkan sekitar 10,9 TB. Tidak jelas bagi saya bagaimana membuatnya lebih kecil. Kueri pencarian agak lambat dalam hal ini. Di komputer saya dibutuhkan sekitar 25 ms, tetapi ini sebagian besar akan tergantung pada IO:

CHECKPOINT;
DBCC DROPCLEANBUFFERS;

SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_COLUMNSTORE
WHERE PART_ID = 45
AND STRING_PIECE = 678901; -- searching for '012345678901'

Satu catatan penting pada pendekatan columnstore adalah bahwa angka 10,9 TB adalah untuk data terkompresi 100%. Akan sulit untuk mengisi meja seperti itu secara efisien sambil menghindari toko-toko delta. Kemungkinan Anda akan berakhir dengan data yang tidak terkompresi di toko delta di beberapa titik dalam proses yang bisa dengan mudah membutuhkan lebih dari 15,4 TB yang digunakan untuk pendekatan rowstore.

Joe Obbish
sumber
6

Menyimpan dan memproses 1TB data dengan hanya 16GB RAM yang tersedia dapat terbukti menjadi tantangan. 1GB per inti agak tidak seimbang, terutama untuk beban kerja semacam ini. 8GB per inti akan menjadi titik awal yang jauh lebih baik, dengan lebih diinginkan.

Yang mengatakan, saya masih tergoda untuk mencoba variasi metode 2:

Simpan semua substring 12-karakter yang mungkin seperti bigintdalam tabelstore columnstore berkerumun (dengan kompresi arsip jika ternyata bermanfaat):

CREATE TABLE dbo.Search
(
    pos bigint NOT NULL,
    fragment bigint NOT NULL,

    INDEX CCS CLUSTERED COLUMNSTORE 
        WITH (DATA_COMPRESSION = COLUMNSTORE_ARCHIVE) -- optional
);

Anda mungkin harus menerapkan beberapa cara memuat data sumber secara bertahap ke dalam tabel ini. Pastikan Anda memiliki grup baris berukuran maksimal (1.048.576 baris) di struktur kolom jadi . Lihat panduan memuat data .

Anda bisa membuat baris dalam kelipatan 1048576 dalam tabel rowstore yang tidak diindeks sebelum membuat indeks kolomstore berkerumun pada itu, kemudian beralih hasilnya langsung ke tabel utama yang dipartisi. Pendekatan yang tepat tergantung pada bagaimana Anda ingin memuat data, apakah akan ditambahkan, dan seberapa akrab Anda dengan SQL Server secara umum.

Performa yang sangat baik dimungkinkan dengan metode ini, tetapi seperti yang sering terjadi pada kolom kolom, Anda harus mencapai penghapusan partisi dan segmen yang efektif. Mempartisi pada fragmentkolom dan membangun indeks kolomstore secara berurutan sambil mengganti indeks clustered rowstore yang dikunci fragmentadalah cara untuk mencapai ini, seperti yang dicatat dalam dokumentasi yang ditautkan di atas. Ini juga akan meminimalkan kebutuhan penyimpanan, karena fragmentnilai dalam kisaran yang sama akan disimpan di segmen yang sama. Hal ini memungkinkan rebasing nilai yang efektif dan pengepakan bit.

Saat memuat, cobalah untuk membatasi string yang Anda kerjakan dalam SQL Server untuk jenis non-LOB (maks). Jika Anda menemukan bekerja dengan LOB terbaik untuk throughput, sering ada sweet spot panjang data yang dapat ditemukan, di mana kinerja menurun secara signifikan.

Bergantung pada ukuran akhir struktur dan kecepatan subsistem I / O Anda, Anda mungkin menemukan bahwa pendekatan ini menawarkan kinerja yang cukup baik secara konsisten. Meningkatkan memori yang tersedia akan meningkatkan hal-hal yang nyata.

Paul White 9
sumber