Indeks fragmentasi sambil terus memproses

10

SQL Server 2005

Saya harus dapat terus memproses sekitar 350 juta catatan dalam tabel 900 juta catatan. Kueri yang saya gunakan untuk memilih catatan yang akan diproses menjadi sangat terfragmentasi saat saya memproses dan saya harus menghentikan pemrosesan untuk membangun kembali indeks. Model data semu & kueri ...

/**************************************/
CREATE TABLE [Table] 
(
    [PrimaryKeyId] [INT] IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    [ForeignKeyId] [INT] NOT NULL,
    /* more columns ... */
    [DataType] [CHAR](1) NOT NULL,
    [DataStatus] [DATETIME] NULL,
    [ProcessDate] [DATETIME] NOT NULL,
    [ProcessThreadId] VARCHAR (100) NULL
);

CREATE NONCLUSTERED INDEX [Idx] ON [Table] 
(
    [DataType],
    [DataStatus],
    [ProcessDate],
    [ProcessThreadId]
);
/**************************************/

/**************************************/
WITH cte AS (
    SELECT TOP (@BatchSize) [PrimaryKeyId], [ProcessThreadId]
    FROM [Table] WITH ( ROWLOCK, UPDLOCK, READPAST )
    WHERE [DataType] = 'X'
    AND [DataStatus] IS NULL
    AND [ProcessDate] < DATEADD(m, -2, GETDATE()) -- older than 2 months
    AND [ProcessThreadId] IS NULL
)
UPDATE cte
SET [ProcessThreadId] = @ProcessThreadId;

SELECT * FROM [Table] WITH ( NOLOCK )
WHERE [ProcessThreadId] = @ProcessThreadId;
/**************************************/

Konten data ...
Sementara kolom [Tipe Data ] diketik sebagai CHAR (1), sekitar 35% dari semua catatan sama dengan 'X' dan sisanya sama dengan 'A'.
Dari hanya catatan di mana [Jenis Data] sama dengan 'X', sekitar 10% akan memiliki nilai NOT NULL [DataStatus].

Kolom [ProcessDate] dan [ProcessThreadId] akan diperbarui untuk setiap catatan yang diproses.
Kolom [DataType] diperbarui ('X' diubah menjadi 'A') sekitar 10% dari waktu.
Kolom [DataStatus] diperbarui kurang dari 1% dari waktu.

Untuk saat ini solusi saya adalah memilih kunci utama dari semua catatan untuk diproses menjadi tabel pemrosesan yang terpisah. Saya menghapus kunci saat saya memprosesnya sehingga sebagai fragmen indeks saya berurusan dengan lebih sedikit catatan.

Namun, ini tidak sesuai dengan alur kerja yang saya inginkan sehingga data ini diproses terus menerus, tanpa intervensi manual dan downtime yang signifikan. Saya mengantisipasi downtime setiap tiga bulan untuk pekerjaan rumah tangga. Tapi sekarang, tanpa tabel pemrosesan yang terpisah, saya tidak bisa melewati pemrosesan bahkan setengah dari set data tanpa fragmentasi menjadi begitu buruk sehingga mengharuskan berhenti dan membangun kembali indeks.

Adakah rekomendasi untuk pengindeksan atau model data yang berbeda? Apakah ada pola yang perlu saya teliti?
Saya memiliki kontrol penuh terhadap model data dan perangkat lunak proses sehingga tidak ada yang salah.

Chris Gallucci
sumber
Satu pemikiran juga: indeks Anda tampaknya salah urutan: seharusnya paling selektif untuk paling selektif. Jadi ProcessThreadId, ProcessDate, DataStatus, DataType mungkin?
gbn
Kami telah mengiklankannya di obrolan kami. Pertanyaan yang sangat bagus chat.stackexchange.com/rooms/179/the-heap
gbn
Saya memperbarui kueri untuk menjadi representasi seleksi yang lebih akurat. Saya menjalankan beberapa thread bersamaan. Saya telah mencatat rekomendasi pesanan selektif. Terima kasih.
Chris Gallucci
@ChrisGallucci Datang untuk ngobrol kalau bisa ...
JNK

Jawaban:

4

Apa yang Anda lakukan adalah Anda menggunakan tabel sebagai antrian. Pembaruan Anda adalah metode dequeue. Tetapi indeks berkerumun di atas meja adalah pilihan yang buruk untuk antrian. Menggunakan tabel sebagai Antrian sebenarnya memberlakukan persyaratan yang cukup ketat pada desain tabel. Indeks berkerumun Anda harus menjadi urutan dequeue, dalam hal ini mungkin ([DataType], [DataStatus], [ProcessDate]). Anda dapat menerapkan kunci utama sebagai nonclustered kendala. Jatuhkan indeks yang tidak berkerumun Idx, karena kunci yang dikelompokkan mengambil perannya.

Bagian penting lain dari teka-teki adalah menjaga ukuran baris konstan selama pemrosesan. Anda telah mendeklarasikan ProcessThreadIdsebagai VARCHAR(100)yang menyiratkan baris tumbuh dan menyusut seperti sedang 'diproses' karena nilai bidang berubah dari NULL ke non-null. Pola tumbuh-dan-susutkan pada baris ini menyebabkan perpecahan dan fragmentasi halaman. Saya tidak dapat membayangkan ID utas 'VARCHAR (100)'. Gunakan tipe panjang tetap, mungkin sebuah INT.

Sebagai catatan tambahan, Anda tidak perlu keluar dalam dua langkah (UPDATE diikuti oleh SELECT). Anda dapat menggunakan klausa OUTPUT, seperti yang dijelaskan dalam artikel yang ditautkan di atas:

/**************************************/
CREATE TABLE [Table] 
(
    [PrimaryKeyId] [INT] IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
    [ForeignKeyId] [INT] NOT NULL,
    /* more columns ... */
    [DataType] [CHAR](1) NOT NULL,
    [DataStatus] [DATETIME] NULL,
    [ProcessDate] [DATETIME] NOT NULL,
    [ProcessThreadId] INT NULL
);

CREATE CLUSTERED INDEX [Cdx] ON [Table] 
(
    [DataType],
    [DataStatus],
    [ProcessDate]
);
/**************************************/

declare @BatchSize int, @ProcessThreadId int;

/**************************************/
WITH cte AS (
    SELECT TOP (@BatchSize) [PrimaryKeyId], [ProcessThreadId] , ... more columns 
    FROM [Table] WITH ( ROWLOCK, UPDLOCK, READPAST )
    WHERE [DataType] = 'X'
    AND [DataStatus] IS NULL
    AND [ProcessDate] < DATEADD(m, -2, GETDATE()) -- older than 2 months
    AND [ProcessThreadId] IS NULL
)
UPDATE cte
SET [ProcessThreadId] = @ProcessThreadId
OUTPUT DELETED.[PrimaryKeyId] , ... more columns ;
/**************************************/

Selain itu saya akan mempertimbangkan untuk memindahkan item yang berhasil diproses menjadi tabel, arsip, yang berbeda. Anda ingin tabel antrian Anda melayang-layang di dekat ukuran nol, Anda tidak ingin mereka tumbuh karena mereka mempertahankan 'riwayat' dari entri lama yang tidak dibutuhkan. Anda juga dapat mempertimbangkan mempartisi dengan [ProcessDate]sebagai alternatif (mis. Satu partisi aktif saat ini yang bertindak sebagai antrian dan menyimpan entri dengan NULL ProcessDate, dan partisi lain untuk semua yang bukan nol. Atau beberapa partisi untuk bukan-nol jika Anda ingin menerapkan efisien menghapus (beralih) untuk data yang telah melewati periode retensi yang diamanatkan. Jika semuanya menjadi panas Anda dapat mempartisi dengan tambahan[DataType] jika memiliki selektivitas yang cukup, tetapi desain itu akan sangat rumit karena membutuhkan partisi dengan kolom yang dikomputasi tetap (kolom komposit yang menempel bersama [DataType] dan [ProcessingDate]).

Remus Rusanu
sumber
3

Saya akan mulai dengan memindahkan bidang ProcessDatedan Processthreadidke tabel lain.

Saat ini, setiap baris yang Anda pilih dari indeks yang cukup luas ini juga perlu diperbarui.

Jika Anda memindahkan kedua bidang tersebut ke tabel lain, volume pembaruan Anda di tabel utama dipotong 90%, yang seharusnya menangani sebagian besar fragmentasi.

Anda masih memiliki fragmentasi di tabel BARU, tetapi akan lebih mudah untuk mengelola di tabel yang lebih sempit dengan data yang jauh lebih sedikit.

JNK
sumber
Ini dan secara fisik memisahkan data berdasarkan [Tipe Data] harus membuat saya berada di tempat yang saya inginkan. Saya saat ini dalam tahap desain (desain ulang sebenarnya) ini sehingga akan butuh waktu sebelum saya mendapatkan kesempatan untuk menguji drive perubahan ini.
Chris Gallucci