TOP (1) DENGAN KELOMPOK tabel yang sangat besar (100.000.000+)

8

Mendirikan

Saya memiliki tabel besar ~ 115.382.254 baris. Tabel ini relatif sederhana dan mencatat operasi proses aplikasi.

CREATE TABLE [data].[OperationData](
    [SourceDeciveID] [bigint] NOT NULL,
    [FileSource] [nvarchar](256) NOT NULL,
    [Size] [bigint] NULL,
    [Begin] [datetime2](7) NULL,
    [End] [datetime2](7) NOT NULL,
    [Date]  AS (isnull(CONVERT([date],[End]),CONVERT([date],'19000101',(112)))) PERSISTED NOT NULL,
    [DataSetCount] [bigint] NULL,
    [Result] [int] NULL,
    [Error] [nvarchar](max) NULL,
    [Status] [int] NULL,
 CONSTRAINT [PK_OperationData] PRIMARY KEY CLUSTERED 
(
    [SourceDeviceID] ASC,
    [FileSource] ASC,
    [End] ASC
))

CREATE TABLE [model].[SourceDevice](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NULL,
 CONSTRAINT [PK_DataLogger] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
))

ALTER TABLE [data].[OperationData]  WITH CHECK ADD  CONSTRAINT [FK_OperationData_SourceDevice] FOREIGN KEY([SourceDeviceID])
REFERENCES [model].[SourceDevice] ([ID])

Tabel ini berkerumun di sekitar 500 cluster dan setiap hari.

partisi

masukkan deskripsi gambar di sini

Selain itu, tabel diindeks dengan baik oleh PK, statistik terkini dan INDEXer dirusak setiap malam.

SELECT berbasis indeks cepat kilat dan kami tidak punya masalah dengan itu.

Masalah

Saya perlu tahu baris terakhir (TOP) [End]dan dipartisi oleh [SourceDeciveID]. Untuk mendapatkan yang terakhir [OperationData]dari setiap perangkat sumber.

Pertanyaan

Saya perlu menemukan cara untuk menyelesaikan ini dengan cara yang baik dan tanpa membawa DB ke batas.


Upaya 1

Percobaan pertama jelas GROUP BYatau SELECT OVER PARTITION BYpermintaan. Masalahnya di sini juga jelas, setiap query harus memindai urutan partisi yang sangat / menemukan baris atas. Jadi permintaannya sangat lambat dan memiliki dampak IO yang sangat tinggi.

Contoh permintaan 1

;WITH cte AS
(
   SELECT *,
         ROW_NUMBER() OVER (PARTITION BY [SourceDeciveID] ORDER BY [End] DESC) AS rn
   FROM [data].[OperationData]
)
SELECT *
FROM cte
WHERE rn = 1

Contoh permintaan 2

SELECT *
FROM [data].[OperationData] AS d 
CROSS APPLY 
(
   SELECT TOP 1 *
   FROM [data].[OperationData] 
   WHERE [SourceDeciveID] = d.[SourceDeciveID]
   ORDER BY [End] DESC
) AS ds

GAGAL!

Usaha 2

Saya membuat tabel bantuan untuk selalu memegang referensi ke baris TOP.

CREATE TABLE [data].[LastOperationData](
    [SourceDeciveID] [bigint] NOT NULL,
    [FileSource] [nvarchar](256) NOT NULL,
    [End] [datetime2](7) NOT NULL,
 CONSTRAINT [PK_LastOperationData] PRIMARY KEY CLUSTERED 
(
    [SourceDeciveID] ASC
)

ALTER TABLE [data].[LastOperationData]  WITH CHECK ADD  CONSTRAINT [FK_LastOperationData_OperationData] FOREIGN KEY([SourceDeciveID], [FileSource], [End])
REFERENCES [data].[OperationData] ([SourceDeciveID], [FileSource], [End])

Untuk mengisi tabel pemicu yang dibuat untuk selalu menambah / memperbarui baris sumber jika [End]kolom yang lebih tinggi dimasukkan.

CREATE TRIGGER [data].[OperationData_Last]
   ON  [data].[OperationData]
   AFTER INSERT
AS 
BEGIN
    SET NOCOUNT ON;

    MERGE [data].[LastOperationData] AS [target]
    USING (SELECT [SourceDeciveID], [FileSource], [End] FROM inserted) AS [source] ([SourceDeciveID], [FileSource], [End])  
    ON ([target].[SourceDeciveID] = [FileSource].[SourceDeciveID])

    WHEN MATCHED AND [target].[End] < [source].[End] THEN
        UPDATE SET [target].[FileSource] = source.[FileSource], [target].[End] = source.[End]

    WHEN NOT MATCHED THEN  
        INSERT ([SourceDeciveID], [FileSource], [End])  
        VALUES (source.[SourceDeciveID], source.[FileSource], source.[End]);

END

Masalahnya di sini adalah, itu juga memiliki dampak IO yang sangat besar dan saya tidak tahu mengapa.

Seperti yang Anda lihat di sini dalam rencana kueri itu juga menjalankan pemindaian seluruh [OperationData]tabel.

Ini memiliki dampak keseluruhan yang sangat besar pada DB saya. statistik

GAGAL!

Steffen Mangold
sumber
2
Di blok kode pertama Anda, saya tidak bisa melihat dari mana kolom pertama dari indeks berkerumun berasal - apakah benar?
George.Palacios
Ya maaf SSMS tidak memasukkannya ke dalam CREATE TABLEskrip tetapi di dalam rencana kueri Anda akan melihat partisi. Saya akan mengedit pertanyaan.
Steffen Mangold
Bukan indeks tambahan karena termasuk di dalam PRIMARY KEY CLUSTEREDAnda pikir itu dapat membantu?
Steffen Mangold
Soryy itu adalah kesalahan, saya memodifikasi nama untuk pertanyaan menjadi lebih jelas, saya memperbaikinya.
Steffen Mangold
@ ypercubeᵀᴹ ya karena SELECT [SourceID], [Source], [End] FROM insertedbeberapa cara melakukan pemindaian tabel pada [OperationData].
Steffen Mangold

Jawaban:

9

Jika Anda memiliki tabel SourceIDnilai, dan indeks di tabel utama Anda aktif (SourceID, End) include (othercolumns), gunakan saja OUTER APPLY.

SELECT d.*
FROM dbo.Sources s
OUTER APPLY (SELECT TOP (1) *
    FROM data.OperationData d
    WHERE d.SourceID = s.SourceID
    ORDER BY d.[End] DESC) d;

Jika Anda tahu Anda hanya setelah partisi terbaru Anda, Anda bisa memasukkan filter di Akhir, seperti AND d.[End] > DATEADD(day, -1, GETDATE())

Sunting: Karena indeks kluster Anda aktif SourceID, Source, End), masukkan juga Source ke dalam tabel Sumber Anda dan gabung juga. Maka Anda tidak perlu indeks baru.

SELECT d.*
FROM dbo.Sources s -- Small table
OUTER APPLY (SELECT TOP (1) *
    FROM data.OperationData d -- Big table quick seeks
    WHERE d.SourceID = s.SourceID
    AND d.Source = s.Source
    AND d.[End] > DATEADD(day, -1, GETDATE()) -- If you’re partitioning on [End], do this for partition elimination
    ORDER BY d.[End] DESC) d;
Rob Farley
sumber
Indeks benar-benar mempercepat kueri. Masalah kedua yang muncul adalah bahwa indeks yang tidak dipartisi pada tabel sebesar itu hampir tidak dapat dipelihara. Pada semua tabel "big-data" kami, kami bekerja dengan pengindeks yang dipartisi. Mereka dapat dipertahankan partisi demi partisi secara online. Begitu pengindeks dipartisi masalah adalah yang lama karena ia harus dijalankan melalui setiap partisi.
Steffen Mangold
1
@SteffenMangold: Semakin sedikit data dalam indeks, semakin baik (asalkan memiliki semua yang Anda butuhkan) dan tidak termasuk tampilan terwujud, indeks berkerumun memiliki jumlah data maksimum yang mungkin. Indeks Clustered hadir karena mendapatkan semua data dengan kunci adalah norma. Dalam hal ini Anda mendapatkan semua data, tetapi Anda tidak benar-benar mendapatkannya dengan kunci, Anda mendapatkannya dengan bagian dari kunci. Anda memerlukan indeks yang dapat ditanyakan dengan bagian kunci.
jmoreno
Saya benar-benar minta maaf tetapi ada SourceTabel yang merujuk sourceIDkolom. Sumber kolom hanya nama file. Itu penamaan yang sedikit membingungkan. Untuk setiap Sourceperangkat (sourceID) mungkin hanya ada satu entri tunggal untuk satu file source(kolom) pada satu stempel waktu. Juga saya tidak bisa melakukan penghapusan partisi karena yang terbaru Endadalah fragmen widly. Itulah mengapa saya datang dengan solusi pemicu. Saya pikir permintaan langsung tidak akan berfungsi di sini.
Steffen Mangold
@Rob Farley Saya mengedit pertanyaan itu menjadi lebih jelas
Steffen Mangold
Dengan mempartisi, Anda akan menemukan semua yang dicari dalam setiap partisi. Dengan predikat ekstra, Anda dapat membuatnya sehingga tidak mengganggu mereka semua, dan hanya melakukan beberapa. Buat satu bulan jika perlu.
Rob Farley