Memfilter data yang dipesan dengan konversi baris

8

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 @MaxRowsakan berkisar antara 500.000 dan 2.000.000 tergantung pada seberapa banyak klien akan menginginkan datanya.


Saya telah mencoba berbagai pendekatan:

  1. Pengindeksan pada (Id, RV):
    CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);

Menggunakan indeks, kueri mencari baris tempat RV = @Cursormasing-masing Idmasuk @Ids, baca baris berikut lalu gabungkan hasilnya dan urutkan.
Efisiensi kemudian tergantung pada posisi @Cursornilai 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 @Cursormendekati 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.

  1. Pengindeksan di RV:
    CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);

Menggunakan indeks, kueri mencari baris di mana RV = @Cursorkemudian 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.

  1. 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.

Paciv
sumber
Crosspost dengan stackoverflow.com/questions/11586004/… . Saya telah menghapus versi Oracle untuk saat ini karena saya telah menemukan bahwa ORA_ROWSCN tidak dapat diindeks (dan hampir tidak melalui tampilan terindeks yang terwujud).
Paciv
Bagaimana bidang tanggal cocok? Bisakah baris dengan ID dan Tanggal tertentu diperbarui dalam tabel? Dan jika demikian, apakah tanggalnya juga diperbarui (seperti cap waktu tambahan?)
8kb
Sepertinya untuk upaya GetData (), urutan oleh harus menyertakan Id (urutan oleh RV, Id). Bisakah Anda mengomentari menggunakan indeks (Rv, Id)? Juga menggunakan ">" max rowversi dari panggilan sebelumnya sepertinya akan melewatkan rekaman di antara potongan jika baris memiliki konversi baris yang sama (bukankah itu mungkin?).
crokusek
@ 8kb: pernyataan pembaruan yang berjalan di atas meja hanya memodifikasi Valuekolom. @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.
Paciv
Bisakah klien menerima hasil dalam urutan (Id, Rv) dan memberikan argumen LastId selain argumen LastRowVersion untuk menghilangkan pengurutan RV di seluruh id? Komentar saya sebelumnya semua didasarkan pada asumsi bahwa RV memiliki duplikat. Indeks yang difilter per klien tampak menarik.
crokusek

Jawaban:

5

Salah satu solusinya adalah aplikasi klien mengingat maksimum rowversionper ID. Jenis tabel yang ditentukan pengguna akan berubah menjadi:

CREATE TYPE
    dbo.guid_list_tbltype
AS TABLE 
    (
    Id      uniqueidentifier PRIMARY KEY, 
    LastRV  rowversion NOT NULL
    );

Permintaan dalam prosedur kemudian dapat ditulis ulang untuk menggunakan APPLYpola (lihat artikel SQLServerCentral saya bagian 1 dan bagian 2 - diperlukan login gratis). Kunci untuk kinerja yang baik di sini adalah ORDER BY- ia menghindari pra-pengambilan tak teratur pada loop bersarang bergabung. Hal RECOMPILEini diperlukan untuk memungkinkan pengoptimal untuk melihat kardinalitas variabel tabel pada waktu kompilasi (mungkin menghasilkan rencana paralel yang diinginkan).

ALTER PROCEDURE dbo.GetData

    @IDs        guid_list_tbltype READONLY,
    @MaxRows    bigint

AS
BEGIN

    SELECT TOP (@MaxRows)
        d.Id,
        d.[Date],
        d.Value,
        d.RV
    FROM @Ids AS i
    CROSS APPLY
    (
        SELECT
            d.*
        FROM dbo.Data AS d
        WHERE
            d.Id = i.Id
            AND d.RV > i.LastRV
    ) AS d
    ORDER BY
        i.Id,
        d.RV
    OPTION (RECOMPILE);

END;

Anda harus mendapatkan rencana permintaan pasca-eksekusi seperti ini (perkiraan paket akan menjadi serial):

rencana permintaan

Paul White 9
sumber
Benar, salah satu solusi perubahan desain adalah membuat klien mengingat 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.
Paciv
2

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:

CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, VersionNumber) INCLUDE(Date, Value);

Tentu saja, kita perlu memastikan bahwa VersionNumber dimulai dengan satu dan tidak ada celah. Ini mudah dilakukan dengan kendala.

AK
sumber
Apakah maksud Anda global atau ID lokal VersionNumber? Bagaimanapun, saya tidak dapat melihat bagaimana itu akan membantu dengan pertanyaan, dapatkah Anda menjelaskan lebih lanjut?
Paciv
0

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:

  • Tambahkan 8 Bytes ke setiap record di Indeks Clustered Anda.
  • Simpan 16 Bytes pada setiap record di setiap Indeks Non-Clustered.
  • Apa yang Anda miliki adalah "Kunci Alami": UniqueIdentifyer (16 Bytes) dengan DateTime (8 Bytes).
  • Itu 24 Bytes di setiap Catatan Indeks untuk referensi kembali ke Indeks Clustered!
  • Inilah sebabnya mengapa kami memiliki Kunci Pengganti sebagai Integer Bertambah Yang Lebih Kecil.


Atur BigInt PK ( DataID ) baru Anda untuk menggunakan Clustered-Index:
Ini akan:

  • Pastikan catatan yang paling baru dibuat diletakkan di dekat bagian akhir.
  • Memungkinkan Pengindeksan lebih cepat dengan Indeks Non-Clustered lainnya.
  • Izinkan untuk ekspansi di masa depan sebagai FK ke Tabel lain.


Buat Non-Clustered-Indeks sekitar (Tanggal, Id).
Ini akan:

  • Mempercepat kueri yang paling sering Anda gunakan.
  • Anda bisa menambahkan "Nilai", tetapi itu akan meningkatkan ukuran indeks Anda, yang membuatnya lebih lambat.
  • Saya sarankan mencobanya di dalam dan di luar Indeks untuk melihat apakah ada perbedaan besar dalam kinerja.
  • Saya sarankan untuk tidak menggunakan "Sertakan" jika Anda menambahkannya.
  • Tempelkan pada seperti itu (Tanggal, Id, Nilai) - tetapi hanya jika pengujian Anda menunjukkannya meningkatkan kinerja.


Buat Indeks Non-Clustered di (RV, ID).
Ini akan:

  • Selalu jaga Indeks Anda sekecil mungkin.
  • Kecuali Anda melihat keuntungan kinerja yang sangat besar dengan memiliki Tanggal dan Nilai dalam Indeks Anda, saya sarankan Anda membiarkannya untuk menghemat ruang disk. Cobalah tanpa mereka terlebih dahulu.
  • Jika Anda menambahkan Tanggal atau Nilai, jangan gunakan "Sertakan", sebagai gantinya tambahkan mereka ke urutan Indeks.
  • Berkat DataID Menambah Sisipan baru ke dalam PK Clustered Anda, RV Anda baru-baru ini biasanya akan muncul di dekat akhir (kecuali jika Anda memperbarui petak data dari masa lalu sepanjang waktu).
MikeTeeVee
sumber