Bir SQL Server arka ucunu toplayan ve depolayan bir uygulama ve son derece büyük miktarda kayıt yazdım. Zirvede, ortalama kayıt miktarının günde 3-4 milyar (20 saatlik çalışma) caddesinde bir yerde olduğunu hesapladım.
Orijinal çözümüm (verilerin gerçek hesaplamasını yapmadan önce) uygulamamın, müşterilerim tarafından sorgulanan aynı tabloya kayıtları eklemesini sağlamaktı. Bu çok hızlı bir şekilde çöktü ve yakıldı, çünkü o kadar çok kaydın eklendiği bir tabloyu sorgulamak imkansız.
İkinci çözümüm, biri uygulama tarafından alınan ve diğeri istemciye hazır veriler için olmak üzere 2 veritabanı kullanmaktı.
Uygulamam veri alır, ~ 100k kayıtlar halinde yığınlar ve hazırlama tablosuna toplu olarak eklenir. ~ 100k kayıtlardan sonra, uygulama daha önce olduğu gibi aynı şemaya sahip başka bir hazırlama tablosu oluşturur ve bu tabloya eklemeye başlar. 100k kayıt içeren tablonun adıyla bir iş tablosunda bir kayıt oluşturacak ve SQL Server tarafında depolanmış bir yordam verileri hazırlama tablolarından istemci hazır üretim tablosuna taşıyacak ve benim uygulama tarafından oluşturulan tablo geçici tablo.
Her iki veritabanında da işler tablosu olan hazırlama veritabanı dışında aynı şemaya sahip 5 tablo kümesi bulunur. Evreleme veritabanında, kayıtların büyük bir kısmının yer alacağı tabloda bütünlük kısıtlamaları, anahtar, dizinler vb. Yoktur. Aşağıda gösterilen tablo adı SignalValues_staging
. Amaç, uygulamamın verileri mümkün olan en kısa sürede SQL Server'a çarptırmaktı. Kolayca taşınabilmeleri için anında tablo oluşturma iş akışı oldukça iyi çalışır.
Aşağıda, hazırlama veritabanımdaki 5 ilgili tablo ve ayrıca işler tabloum yer almaktadır:
Yazdığım saklı yordam, verilerin tüm hazırlama tablolarından taşınmasını ve üretime sokulmasını sağlar. Aşağıda, saklı yordamın hazırlama tablolarından üretime ekleyen bir parçasıdır:
-- Signalvalues jobs table.
SELECT *
,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM
(
SELECT JobId
,ProcessingComplete
,SignalValueStagingTableName AS 'TableName'
,(DATEDIFF(SECOND, (SELECT last_user_update
FROM sys.dm_db_index_usage_stats
WHERE database_id = DB_ID(DB_NAME())
AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
,GETUTCDATE())) SecondsSinceLastUpdate
FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
OR cte.SecondsSinceLastUpdate >= 120
DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)
DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128)
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)
WHILE @i > 0
BEGIN
SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
FROM #JobsToProcess
WHERE RowIndex = @i
SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'
SET @sqlStatement = N'
--Signal values staging table.
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
INSERT INTO SignalValues SELECT * FROM #sValues
SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
DROP TABLE #sValues
DROP TABLE #uniqueIdentifiers
IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
BEGIN
-- processing is completed so drop the table and remvoe the entry
IF @processingComplete = 1
BEGIN
DELETE FROM SignalValueJobs WHERE JobId = @currentJob
IF '''+@currentTable+''' <> ''SignalValues_staging''
BEGIN
DROP TABLE '+ @qualifiedTableName +'
END
END
END
'
EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;
SET @i = @i - 1
END
DROP TABLE #JobsToProcess
Kullandığım sp_executesql
evreleme tablolar için tablo isimleri işler tablosundaki kayıtlardan metin olarak geldiği için.
Bu saklı yordam, bu dba.stackexchange.com yazıdan öğrendim hile kullanarak her 2 saniyede bir çalışır .
Çözdüğüm yaşam için çözemediğim sorun, ek parçaların üretime getirilme hızıdır. Uygulamam geçici evreleme tabloları oluşturur ve bunları inanılmaz hızlı bir şekilde kayıtlarla doldurur. Üretime eklenen tabloların miktarına ayak uyduramaz ve sonunda binlere fazladan tablolar eklenir. Sadece şimdiye kadar gelen veri yetişmek mümkün oldum yolu üretim üzerinde ... tüm anahtarlar, indeksler, kısıtlamaları vb kaldırmaktır SignalValues
masaya. Sonra karşılaştığım sorun tablo o kadar çok kayıt ile sorgulamak imkansız hale gelir olmasıdır.
Ben [Timestamp]
boşuna bir bölümleme sütun olarak kullanarak tablo bölümleme denedim . Herhangi bir dizine ekleme biçimi, eklentileri o kadar yavaşlatır ki, yetişemezler. Buna ek olarak, binlerce bölüm (her dakikada bir mi? Saat?) Önceden oluşturmam gerekir. Onları anında nasıl oluşturacağımı anlayamadım
Adı verilen tabloya hesaplanan bir sütun ekleyerek bölümleme oluşturma çalıştı TimestampMinute
değer, üzerinde INSERT
, DATEPART(MINUTE, GETUTCDATE())
. Hala çok yavaş.
Bu Microsoft makalesine göre Bellek için Optimize Edilmiş Tablo yapmayı denedim . Belki nasıl yapılacağını anlamıyorum, ancak MOT uçları bir şekilde yavaşlattı.
Saklı yordamın Yürütme Planını kontrol ettim ve (sanırım?) En yoğun operasyonun
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
Benim için bu bir anlam ifade etmiyor: Aksi kanıtlanan saklı yordama duvar saati günlüğü ekledim.
Zaman kaydı açısından, yukarıdaki söz konusu ifade 100 bin kayıtta ~ 300 ms içinde yürütülür.
İfade
INSERT INTO SignalValues SELECT * FROM #sValues
100 bin kayıtta 2500-3000ms'de çalışır. Etkilenen kayıtların tablodan silinmesi,
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
300 ms daha alır.
Bunu nasıl daha hızlı yapabilirim? SQL Server günde milyarlarca kaydı işleyebilir mi?
Alakalıysa, bu SQL Server 2014 Enterprise x64'tür.
Donanım yapılandırması:
Bu sorunun ilk geçişine donanımı dahil etmeyi unuttum. Benim hatam.
Bunu şu ifadelerle önsöz edeceğim: Donanım yapılandırmam nedeniyle bazı performansları kaybettiğimi biliyorum . Birçok kez denedim ama bütçe, C-Level, gezegenlerin hizalanması, vb ... nedeniyle maalesef daha iyi bir kurulum elde etmek için yapabileceğim bir şey yok. Sunucu sanal bir makinede çalışıyor ve hafızayı bile arttıramıyorum çünkü artık yok.
İşte sistem bilgilerim:
Depolama, VM sunucusuna iSCSI arabirimi aracılığıyla bir NAS kutusuna bağlanır (Bu, performansı düşürür). NAS kutusunun RAID 10 yapılandırmasında 4 sürücüsü vardır. 6GB / s SATA arabirimine sahip 4 TB WD WD4000FYYZ dönen disk sürücüleridir. Sunucunun yapılandırılmış yalnızca bir veri deposu vardır, böylece tempdb ve veritabanım aynı veri deposundadır.
Maks DOP sıfırdır. Bunu sabit bir değere değiştirmeli miyim yoksa SQL Server'ın işlemesine izin vermeli miyim? RCSI hakkında okudum: RCSI'nin tek faydasının satır güncellemeleriyle geldiğini varsayarak doğru muyum? Bu belirli kayıtlarda hiçbir zaman güncelleme olmayacak, bunlar INSERT
düzenlenecek ve SELECT
düzenlenecektir. RCSI hala bana fayda sağlayacak mı?
Benim tempdb 8mb. Aşağıdaki cevaba göre jyao, #sValues tempdb tamamen önlemek için normal bir tabloya değiştirdim. Performans yine de aynıydı. Tempdb'nin boyutunu ve büyümesini artırmaya çalışacağım, ancak #sValues büyüklüğünün her zaman aynı boyutta olacağı göz önüne alındığında, çok fazla kazanç beklemiyorum.
Aşağıda eklediğim bir yürütme planı aldım. Bu yürütme planı, 100 k kayıtları olan bir aşama tablosunun tekrarıdır. Sorgunun yürütülmesi oldukça hızlıydı, yaklaşık 2 saniye, ancak bunun SignalValues
tablo üzerinde dizinler olmadığını ve SignalValues
tablonun hedefinin içinde INSERT
hiç kayıt olmadığını unutmayın.