Saya memiliki tabel data SQL dengan struktur berikut:
CREATE TABLE Data(
Id uniqueidentifier NOT NULL,
Date datetime NOT NULL,
Value decimal(20, 10) NULL,
RV timestamp NOT NULL,
CONSTRAINT PK_Data PRIMARY KEY CLUSTERED (Id, Date)
)
Jumlah Id yang berbeda berkisar dari 3000 hingga 50.000
. Ukuran tabel bervariasi hingga lebih dari satu miliar baris.
Satu Id dapat mencakup antara beberapa baris hingga 5% dari tabel.
Permintaan tunggal yang paling dieksekusi pada tabel ini adalah:
SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate
Saya sekarang harus menerapkan pengambilan data tambahan pada subset Id, termasuk pembaruan.
Saya kemudian menggunakan skema permintaan di mana pemanggil menyediakan konversi baris tertentu, mengambil blok data dan menggunakan nilai konversi baris maksimum dari data yang dikembalikan untuk panggilan berikutnya.
Saya telah menulis prosedur ini:
CREATE TYPE guid_list_tbltype AS TABLE (Id uniqueidentifier not null primary key)
CREATE PROCEDURE GetData
@Ids guid_list_tbltype READONLY,
@Cursor rowversion,
@MaxRows int
AS
BEGIN
SELECT A.*
FROM (
SELECT
Data.Id,
Date,
Value,
RV,
ROW_NUMBER() OVER (ORDER BY RV) AS RN
FROM Data
inner join (SELECT Id FROM @Ids) Ids ON Ids.Id = Data.Id
WHERE RV > @Cursor
) A
WHERE RN <= @MaxRows
END
Di mana @MaxRows
akan berkisar antara 500.000 dan 2.000.000 tergantung pada seberapa banyak klien akan menginginkan datanya.
Saya telah mencoba berbagai pendekatan:
- Pengindeksan pada (Id, RV):
CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);
Menggunakan indeks, kueri mencari baris tempat RV = @Cursor
masing-masing Id
masuk @Ids
, baca baris berikut lalu gabungkan hasilnya dan urutkan.
Efisiensi kemudian tergantung pada posisi @Cursor
nilai relatif .
Jika dekat dengan akhir data (dipesan oleh RV) kueri bersifat instan dan jika tidak kueri dapat memakan waktu hingga menit (jangan pernah biarkan berjalan sampai akhir).
masalah dengan pendekatan ini adalah yang @Cursor
mendekati akhir data dan pengurutannya tidak menyakitkan (bahkan tidak diperlukan jika kueri mengembalikan lebih sedikit baris daripada @MaxRows
) baik itu jauh di belakang dan kueri harus mengurutkan @MaxRows * LEN(@Ids)
baris.
- Pengindeksan di RV:
CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);
Menggunakan indeks, kueri mencari baris di mana RV = @Cursor
kemudian membaca setiap baris membuang ID yang tidak diminta sampai mencapai @MaxRows
.
Efisiensi kemudian tergantung pada% Id yang diminta ( LEN(@Ids) / COUNT(DISTINCT Id)
) dan distribusinya.
Lebih banyak Id yang diminta% berarti lebih sedikit baris terbuang yang berarti pembacaan lebih efisien, lebih sedikit Id% yang diminta berarti lebih banyak baris yang dibuang yang berarti lebih banyak dibaca untuk jumlah baris yang sama dihasilkan.
Masalah dengan pendekatan ini adalah bahwa jika Id yang diminta hanya berisi beberapa elemen, mungkin harus membaca seluruh indeks untuk mendapatkan baris yang diinginkan.
- Menggunakan indeks yang difilter atau tampilan yang diindeks
CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
WHERE Id IN (/* list of Ids for specific client*/);
Atau
CREATE VIEW vDataClient1 WITH SCHEMABINDING
AS
SELECT
Id,
Date,
Value,
RV
FROM dbo.Data
WHERE Id IN (/* list of Ids for specific client*/)
CREATE UNIQUE CLUSTERED INDEX IDX_IDRV ON vDataClient1(Id, Rv);
Metode ini memungkinkan pengindeksan sempurna dan rencana eksekusi permintaan tetapi datang dengan kerugian: 1. Secara praktis, saya harus menerapkan SQL dinamis untuk membuat indeks atau tampilan dan memodifikasi prosedur meminta untuk menggunakan indeks atau tampilan yang tepat. 2. Saya harus mempertahankan satu indeks atau tampilan oleh klien yang ada, termasuk penyimpanan. 3. Setiap kali klien harus mengubah daftar Id yang diminta, saya harus menghapus indeks atau melihat dan membuatnya kembali.
Sepertinya saya tidak dapat menemukan metode yang sesuai dengan kebutuhan saya.
Saya mencari ide yang lebih baik untuk mengimplementasikan pengambilan data tambahan. Ide-ide itu bisa menyiratkan pengerjaan ulang skema permintaan atau skema basis data meskipun saya lebih suka pendekatan pengindeksan yang lebih baik jika ada.
sumber
Value
kolom. @crokusek: Tidak akan memesan dengan RV, ID bukannya RV hanya menambah beban kerja tanpa bantuan, saya tidak mengerti alasan di balik komentar Anda. Dari apa yang saya baca, RV harus unik kecuali memasukkan data secara khusus ke dalam kolom itu, yang aplikasi tidak.Jawaban:
Salah satu solusinya adalah aplikasi klien mengingat maksimum
rowversion
per ID. Jenis tabel yang ditentukan pengguna akan berubah menjadi:Permintaan dalam prosedur kemudian dapat ditulis ulang untuk menggunakan
APPLY
pola (lihat artikel SQLServerCentral saya bagian 1 dan bagian 2 - diperlukan login gratis). Kunci untuk kinerja yang baik di sini adalahORDER BY
- ia menghindari pra-pengambilan tak teratur pada loop bersarang bergabung. HalRECOMPILE
ini diperlukan untuk memungkinkan pengoptimal untuk melihat kardinalitas variabel tabel pada waktu kompilasi (mungkin menghasilkan rencana paralel yang diinginkan).Anda harus mendapatkan rencana permintaan pasca-eksekusi seperti ini (perkiraan paket akan menjadi serial):
sumber
MAX(RV)
per Id (atau sistem berlangganan di mana aplikasi internal mengingat semua pasangan Id / RV) dan saya menggunakan pola ini untuk klien lain. Solusi lain adalah memaksa klien untuk selalu mengambil semua Id (yang membuat masalah pengindeksan sepele). Itu masih tidak mencakup pertanyaan kebutuhan khusus: Pengambilan bertahap dari subset Id dengan hanya satu counter global yang disediakan oleh klien.Jika memungkinkan, saya akan mendesain ulang tabel. Jika kita dapat memiliki VersionNumber sebagai bilangan bulat tambahan tanpa celah, bahwa tugas mengambil potongan berikutnya adalah pemindaian rentang yang benar-benar sepele. Yang kita butuhkan hanyalah indeks berikut:
Tentu saja, kita perlu memastikan bahwa VersionNumber dimulai dengan satu dan tidak ada celah. Ini mudah dilakukan dengan kendala.
sumber
VersionNumber
? Bagaimanapun, saya tidak dapat melihat bagaimana itu akan membantu dengan pertanyaan, dapatkah Anda menjelaskan lebih lanjut?Apa yang akan saya lakukan:
Dalam hal ini, PK Anda harus menjadi Bidang Identitas "Kunci Pengganti" yang ditambahkan secara otomatis.
Karena Anda sudah berada dalam miliaran, sebaiknya menggunakan BigInt.
Sebut saja DataID .
Ini akan:
Atur BigInt PK ( DataID ) baru Anda untuk menggunakan Clustered-Index:
Ini akan:
Buat Non-Clustered-Indeks sekitar (Tanggal, Id).
Ini akan:
Buat Indeks Non-Clustered di (RV, ID).
Ini akan:
sumber