Ich habe eine Anwendung mit einem SQL Server-Backend geschrieben, die sehr viele Datensätze sammelt und speichert. Ich habe berechnet, dass die durchschnittliche Anzahl von Datensätzen zu Spitzenzeiten zwischen 3 und 4 Milliarden pro Tag liegt (20 Betriebsstunden).
Meine ursprüngliche Lösung (bevor ich die eigentliche Berechnung der Daten durchgeführt hatte) bestand darin, dass meine Anwendung Datensätze in dieselbe Tabelle einfügte, die von meinen Kunden abgefragt wurde. Das stürzte ab und brannte ziemlich schnell, offensichtlich, weil es unmöglich ist, eine Tabelle abzufragen, in die so viele Datensätze eingefügt wurden.
Meine zweite Lösung bestand darin, zwei Datenbanken zu verwenden, eine für die von der Anwendung empfangenen Daten und eine für clientfähige Daten.
Meine Anwendung empfing Daten, teilte sie in Gruppen von ~ 100.000 Datensätzen auf und fügte sie als Masseneintrag in die Staging-Tabelle ein. Nach ~ 100.000 Datensätzen erstellt die Anwendung im Handumdrehen eine weitere Staging-Tabelle mit demselben Schema wie zuvor und beginnt mit dem Einfügen in diese Tabelle. Es wird ein Datensatz in einer Auftragstabelle mit dem Namen der Tabelle erstellt, die 100.000 Datensätze enthält, und eine gespeicherte Prozedur auf der SQL Server-Seite verschiebt die Daten von den Staging-Tabellen in die clientfähige Produktionstabelle und löscht dann die Tabelle Tabelle temporäre Tabelle von meiner Anwendung erstellt.
Beide Datenbanken haben den gleichen Satz von 5 Tabellen mit dem gleichen Schema, mit Ausnahme der Staging-Datenbank, die die Auftragstabelle enthält. Die Staging-Datenbank hat keine Integritätsbeschränkungen, Schlüssel, Indizes usw. für die Tabelle, in der sich der Großteil der Datensätze befinden wird. Im Folgenden wird der Tabellenname angezeigt SignalValues_staging
. Das Ziel war, dass meine Anwendung die Daten so schnell wie möglich in SQL Server überträgt. Der Workflow zum Erstellen von Tabellen im laufenden Betrieb, damit sie leicht migriert werden können, funktioniert ziemlich gut.
Im Folgenden sind die 5 relevanten Tabellen aus meiner Staging-Datenbank sowie meine Jobtabelle aufgeführt:
Die gespeicherte Prozedur, die ich geschrieben habe, übernimmt das Verschieben der Daten aus allen Staging-Tabellen und das Einfügen in die Produktion. Unten sehen Sie den Teil meiner gespeicherten Prozedur, der aus den Staging-Tabellen in die Produktion eingefügt wird:
-- 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
Ich verwende, sp_executesql
weil die Tabellennamen für die Staging-Tabellen als Text aus den Datensätzen in der Jobtabelle stammen.
Diese gespeicherte Prozedur wird alle 2 Sekunden mit dem Trick ausgeführt, den ich aus diesem dba.stackexchange.com-Beitrag gelernt habe .
Das Problem, das ich für mein ganzes Leben nicht lösen kann, ist die Geschwindigkeit, mit der die Beilagen in der Produktion ausgeführt werden. Meine Anwendung erstellt temporäre Staging-Tabellen und füllt sie unglaublich schnell mit Datensätzen. Der Einsatz in der Produktion kann nicht mit der Anzahl der Tabellen mithalten, und schließlich gibt es einen Überschuss an Tabellen zu Tausenden. Die einzige Möglichkeit, mit den eingehenden Daten Schritt zu halten, besteht darin, alle Schlüssel, Indizes, Einschränkungen usw. auf dem Produktionstisch zu entfernen SignalValues
. Das Problem, dem ich dann gegenüberstehe, ist, dass die Tabelle so viele Datensätze enthält, dass keine Abfrage mehr möglich ist.
Ich habe versucht, die Tabelle unter Verwendung der [Timestamp]
Spalte als Partitionierung ohne Erfolg zu partitionieren. Jede Form der Indizierung verlangsamt die Einfügungen so sehr, dass sie nicht mithalten können. Außerdem müsste ich Tausende von Partitionen (jede Minute eine? Stunde?) Jahre im Voraus erstellen. Ich konnte nicht herausfinden, wie man sie im laufenden Betrieb erstellt
Ich versuchte , durch die Schaffung von Partitionieren eines berechneten Spalte der Tabelle bezeichnet das Hinzufügen , TimestampMinute
dessen Wert war, auf INSERT
, DATEPART(MINUTE, GETUTCDATE())
. Immer noch zu langsam.
Ich habe versucht, eine speicheroptimierte Tabelle gemäß diesem Microsoft-Artikel zu erstellen . Vielleicht verstehe ich nicht, wie es geht, aber der TÜV hat die Einsätze irgendwie langsamer gemacht.
Ich habe den Ausführungsplan der gespeicherten Prozedur überprüft und festgestellt, dass (glaube ich?) Die intensivste Operation ist
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
Für mich macht das keinen Sinn: Ich habe der gespeicherten Prozedur die Protokollierung der Wanduhr hinzugefügt, die das Gegenteil bewiesen hat.
In Bezug auf die Zeitprotokollierung wird diese bestimmte Anweisung in ~ 300 ms bei 100.000 Datensätzen ausgeführt.
Die Aussage
INSERT INTO SignalValues SELECT * FROM #sValues
Wird in 2500-3000 ms auf 100.000 Datensätzen ausgeführt. Löschen der betroffenen Datensätze aus der Tabelle nach:
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
dauert weitere 300ms.
Wie kann ich das schneller machen? Kann SQL Server Milliarden von Datensätzen pro Tag verarbeiten?
Wenn es relevant ist, ist dies SQL Server 2014 Enterprise x64.
Hardwarekonfiguration:
Ich habe vergessen, Hardware in den ersten Durchgang dieser Frage aufzunehmen. Mein Fehler.
Ich gehe mit den folgenden Aussagen vor: Ich weiß, dass ich aufgrund meiner Hardwarekonfiguration etwas an Leistung verliere. Ich habe es schon oft versucht, aber aufgrund des Budgets, des C-Levels, der Ausrichtung der Planeten usw. kann ich leider nichts tun, um ein besseres Setup zu erzielen. Der Server läuft auf einer virtuellen Maschine und ich kann nicht einmal den Speicher vergrößern, weil wir einfach keine mehr haben.
Hier sind meine Systeminformationen:
Der Speicher wird über die iSCSI-Schnittstelle an eine NAS-Box an den VM-Server angeschlossen (dies beeinträchtigt die Leistung). Die NAS-Box verfügt über 4 Laufwerke in einer RAID 10-Konfiguration. Es handelt sich um 4 TB WD WD4000FYYZ-Laufwerke mit 6 GB / s SATA-Schnittstelle. Auf dem Server ist nur ein Datenspeicher konfiguriert, sodass sich tempdb und meine Datenbank im selben Datenspeicher befinden.
Max DOP ist Null. Sollte ich dies in einen konstanten Wert ändern oder einfach SQL Server damit umgehen lassen? Ich habe über RCSI nachgelesen: Stimmt die Annahme, dass der einzige Vorteil von RCSI mit Zeilenaktualisierungen verbunden ist? Es wird niemals Aktualisierungen für einen dieser bestimmten Datensätze geben, sie werden INSERT
bearbeitet und SELECT
bearbeitet. Kommt mir RCSI trotzdem zugute?
Mein tempdb ist 8mb. Basierend auf der Antwort von jyao unten habe ich die #sValues in eine reguläre Tabelle geändert, um Tempdb insgesamt zu vermeiden. Die Leistung war jedoch in etwa gleich. Ich werde versuchen, die Größe und das Wachstum von Tempdb zu erhöhen, aber da die Größe von #sValues mehr oder weniger immer dieselbe ist, erwarte ich keinen großen Gewinn.
Ich habe einen Ausführungsplan erstellt, den ich unten angefügt habe. Dieser Ausführungsplan ist eine Iteration einer Staging-Tabelle - 100.000 Datensätze. Die Ausführung der Abfrage war mit ca. 2 Sekunden relativ schnell. Beachten Sie jedoch, dass dies keine Indizes für die SignalValues
Tabelle enthält und die SignalValues
Tabelle, deren Ziel die INSERT
ist, keine Datensätze enthält.