Sürekli işlerken indeks parçalanması


10

SQL Server 2005

900M kayıt tablosunda yaklaşık 350M kayıtları sürekli olarak işleyebilmem gerekiyor. Ben işlem yapmak için kayıtları seçmek için kullandığım sorgu işlem olarak kötü parçalanmış olur ve ben dizin yeniden oluşturmak için işleme durdurmak gerekir. Sözde veri modeli ve sorgu ...

/**************************************/
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;
/**************************************/

Veri içeriği ...
[DataType] sütunu bir CHAR (1) olarak yazılırken, tüm kayıtların yaklaşık% 35'i 'X'e eşittir, geri kalanı' A'ya eşittir.
Yalnızca [DataType] 'X' değerine eşit olan kayıtlardan yaklaşık% 10'unun NOT NULL [DataStatus] değeri olacaktır.

[ProcessDate] ve [ProcessThreadId] sütunları işlenen her kayıt için güncellenecektir.
[DataType] sütunu yaklaşık% 10 oranında güncellenir ('X', 'A' olarak değiştirilir).
[DataStatus] sütunu zamanın% 1'inden daha az güncellenir.

Şimdilik benim çözümüm tüm kayıtların birincil anahtarını ayrı bir işlem tablosunda işlemek. Anahtarları, dizin parçaları olarak daha az kayıtla uğraştığım şekilde işlerken silerim.

Ancak bu, olmasını istediğim iş akışına uymuyor, böylece bu veriler manuel müdahale ve önemli kesinti süresi olmadan sürekli işleniyor. Temizlik işleri için üç ayda bir kesinti bekliyorum. Ama şimdi, ayrı bir işlem tablosu olmadan, veri kümesinin yarısını bile, parçalanma indeksi durdurmayı ve yeniden oluşturmayı gerektirecek kadar kötü hale getirmeden işleyemiyorum.

İndeksleme veya farklı bir veri modeli için herhangi bir öneriniz var mı? Araştırmam gereken bir model var mı?
Veri modeli ve süreç yazılımı üzerinde tam bir kontrole sahibim, böylece hiçbir şey masadan çıkmıyor.


Bir düşünce de: dizininiz yanlış sırada görünüyor: en seçici ve en az seçici olmalı. Yani ProcessThreadId, ProcessDate, DataStatus, DataType belki?
gbn

Sohbetimizde reklamını yaptık. Çok güzel bir soru. chat.stackexchange.com/rooms/179/the-heap
gbn 17:12

Sorguyu seçimin daha doğru bir temsili olacak şekilde güncelledim. Bunu çalıştıran birden çok eşzamanlı iş parçacığı. Seçici sipariş önerisini not ettim. Teşekkürler.
Chris Gallucci

@ChrisGallucci Yapabiliyorsanız sohbet etmeye gelin ...
JNK

Yanıtlar:


4

Yaptığınız şey tabloyu kuyruk olarak kullanmaktır. Güncellemeniz dequeue yöntemidir. Ancak tablodaki kümelenmiş dizin, bir kuyruk için kötü bir seçimdir. Tabloları Kuyruk olarak kullanmak aslında tablo tasarımına oldukça katı gereksinimler getirir. Kümelenmiş dizininiz , bu durumda büyük olasılıkla ayrılma sırası olmalıdır([DataType], [DataStatus], [ProcessDate]) . Birincil anahtarı kümelenmemiş bir kısıtlama olarak uygulayabilirsiniz . IdxKümelenmiş anahtar rolünü aldığından kümelenmemiş dizini bırakın .

Bulmacanın bir başka önemli parçası, işlem sırasında satır boyutunu sabit tutmaktır. Alan değeri NULL değerinden null değerine değiştiğinden, satırın "işleniyor" olarak büyüdüğünü ve küçüldüğünü gösteren ProcessThreadIdbir olarak bildirdiniz VARCHAR(100). Satırdaki bu büyüme ve küçültme deseni sayfa bölünmelerine ve parçalanmaya neden olur. 'VARCHAR (100)' olan bir iş parçacığı kimliği düşünemiyorum. Sabit uzunluk tipi kullanın, belki bir INT.

Bir yan not olarak, iki adımda ayrıştırmanıza gerek yoktur (GÜNCELLEME ve ardından SEÇ). Yukarıda bağlantılı makalede açıklandığı gibi ÇIKTI yan tümcesini kullanabilirsiniz:

/**************************************/
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 ;
/**************************************/

Buna ek olarak, başarılı bir şekilde işlenmiş öğeleri farklı bir arşiv tablosuna taşımayı düşünürüm. Kuyruk tablolarınızın sıfıra yakın bir boyutta gezinmesini istiyorsunuz, gereksiz eski girdilerden 'geçmişi' koruduklarından büyümelerini istemiyorsunuz. Ayrıca, bölümlemeyi [ProcessDate]alternatif olarak da (örneğin, kuyruk olarak hareket eden ve NULL ProcessDate ile girişleri depolayan bir geçerli etkin bölüm ve null olmayan her şey için başka bir bölüm veya verimli uygulamak istiyorsanız null olmayan için birden çok bölüm olarak düşünebilirsiniz . zorunlu tutma döneminden geçmiş verileri siler (kapatır).[DataType] yeterli seçiciliğe sahipse, ancak bu tasarım, kalıcı hesaplanmış sütun ([DataType] ve [ProcessingDate] 'i birbirine yapıştıran birleşik sütun) ile bölümleme gerektirdiğinden gerçekten karmaşık olacaktır.


3

ProcessDateVe Processthreadidalanlarını başka bir tabloya taşıyarak başlardım.

Şu anda, bu oldukça geniş dizinden seçtiğiniz her satırın da güncellenmesi gerekiyor.

Bu iki alanı başka bir tabloya taşırsanız, ana tablodaki güncelleme hacminiz% 90 oranında kesilir ve bu da parçalanmanın çoğuna dikkat etmelidir.

YENİ tabloda hala parçalanmaya sahip olacaksınız, ancak çok daha az veri içeren daha dar bir tabloda yönetmek daha kolay olacaktır.


Bu ve fiziksel olarak [DataType] dayalı veri bölme olması gereken yerde beni almak gerekir. Şu anda bunun tasarım (aslında yeniden tasarım) aşamasındayım, bu yüzden bu değişikliği test etme şansımın olması biraz zaman alacak.
Chris Gallucci
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.