Auf unserem SQL Server 2008 R2 müssen wir jede Nacht einige Berichte erstellen. Die Berechnung der Berichte dauert mehrere Stunden. Um die Zeit zu verkürzen, berechnen wir eine Tabelle vor. Diese Tabelle basiert auf der Verknüpfung von 12 recht großen (zig Millionen Zeilen) Tabellen.
Die Berechnung dieser Aggregationstabelle dauerte bis vor wenigen Tagen ca. 4 Stunden. Unser Datenbankadministrator hat diesen großen Join in 3 kleinere Joins aufgeteilt (wobei jeder 4 Tabellen verknüpft). Das temporäre Ergebnis wird jedes Mal in einer temporären Tabelle gespeichert, die beim nächsten Join verwendet wird.
Das Ergebnis der DBA-Erweiterung ist, dass die Aggregationstabelle in 15 Minuten berechnet wird. Ich habe mich gefragt, wie das möglich ist. Der DBA hat mir mitgeteilt, dass die Anzahl der Daten, die der Server verarbeiten muss, geringer ist. Mit anderen Worten, dass der Server im großen Original-Join mit mehr Daten arbeiten muss als in summierten kleineren Joins. Ich würde jedoch davon ausgehen, dass das Optimierungsprogramm für eine effiziente Ausführung mit dem ursprünglichen großen Join sorgen würde, indem die Joins selbst aufgeteilt und nur die Anzahl der für die nächsten Joins erforderlichen Spalten gesendet würden.
Das andere, was er getan hat, ist, dass er einen Index für eine der temporären Tabellen erstellt hat. Ich würde jedoch noch einmal davon ausgehen, dass der Optimierer bei Bedarf die entsprechenden Hash-Tabellen erstellt und die Berechnung insgesamt besser optimiert.
Ich habe mit unserem DBA darüber gesprochen, aber er selbst war sich nicht sicher, was die Verbesserung der Verarbeitungszeit zur Folge hatte. Er hat gerade erwähnt, dass er dem Server keine Vorwürfe machen würde, da die Berechnung von so großen Datenmengen sehr aufwändig sein kann und es für den Optimierer möglicherweise schwierig ist, den besten Ausführungsplan vorherzusagen. Das verstehe ich, aber ich hätte gerne eine klarere Antwort darauf, warum.
Die Fragen sind also:
Was könnte möglicherweise die große Verbesserung verursachen?
Ist es eine Standardprozedur, große Joins in kleinere zu teilen?
Ist die Datenmenge, die der Server verarbeiten muss, bei mehreren kleineren Joins wirklich kleiner?
Hier ist die ursprüngliche Abfrage:
Insert Into FinalResult_Base
SELECT
TC.TestCampaignContainerId,
TC.CategoryId As TestCampaignCategoryId,
TC.Grade,
TC.TestCampaignId,
T.TestSetId
,TL.TestId
,TSK.CategoryId
,TT.[TestletId]
,TL.SectionNo
,TL.Difficulty
,TestletName = Char(65+TL.SectionNo) + CONVERT(varchar(4),6 - TL.Difficulty)
,TQ.[QuestionId]
,TS.StudentId
,TS.ClassId
,RA.SubjectId
,TQ.[QuestionPoints]
,GoodAnswer = Case When TQ.[QuestionPoints] Is null Then 0
When TQ.[QuestionPoints] > 0 Then 1
Else 0 End
,WrongAnswer = Case When TQ.[QuestionPoints] = 0 Then 1
When TQ.[QuestionPoints] Is null Then 1
Else 0 End
,NoAnswer = Case When TQ.[QuestionPoints] Is null Then 1 Else 0 End
,TS.Redizo
,TT.ViewCount
,TT.SpentTime
,TQ.[Position]
,RA.SpecialNeeds
,[Version] = 1
,TestAdaptationId = TA.Id
,TaskId = TSK.TaskId
,TaskPosition = TT.Position
,QuestionRate = Q.Rate
,TestQuestionId = TQ.Guid
,AnswerType = TT.TestletAnswerTypeId
FROM
[TestQuestion] TQ WITH (NOLOCK)
Join [TestTask] TT WITH (NOLOCK) On TT.Guid = TQ.TestTaskId
Join [Question] Q WITH (NOLOCK) On TQ.QuestionId = Q.QuestionId
Join [Testlet] TL WITH (NOLOCK) On TT.TestletId = TL.Guid
Join [Test] T WITH (NOLOCK) On TL.TestId = T.Guid
Join [TestSet] TS WITH (NOLOCK) On T.TestSetId = TS.Guid
Join [RoleAssignment] RA WITH (NOLOCK) On TS.StudentId = RA.PersonId And RA.RoleId = 1
Join [Task] TSK WITH (NOLOCK) On TSK.TaskId = TT.TaskId
Join [Category] C WITH (NOLOCK) On C.CategoryId = TSK.CategoryId
Join [TimeWindow] TW WITH (NOLOCK) On TW.Id = TS.TimeWindowId
Join [TestAdaptation] TA WITH (NOLOCK) On TA.Id = TW.TestAdaptationId
Join [TestCampaign] TC WITH (NOLOCK) On TC.TestCampaignId = TA.TestCampaignId
WHERE
T.TestTypeId = 1 -- eliminuji ankety
And t.ProcessedOn is not null -- ne vsechny, jen dokoncene
And TL.ShownOn is not null
And TS.Redizo not in (999999999, 111111119)
END;
Der neue Splitt kommt nach der tollen Arbeit von DBA:
SELECT
TC.TestCampaignContainerId,
TC.CategoryId As TestCampaignCategoryId,
TC.Grade,
TC.TestCampaignId,
T.TestSetId
,TL.TestId
,TL.SectionNo
,TL.Difficulty
,TestletName = Char(65+TL.SectionNo) + CONVERT(varchar(4),6 - TL.Difficulty) -- prevod na A5, B4, B5 ...
,TS.StudentId
,TS.ClassId
,TS.Redizo
,[Version] = 1 -- ?
,TestAdaptationId = TA.Id
,TL.Guid AS TLGuid
,TS.TimeWindowId
INTO
[#FinalResult_Base_1]
FROM
[TestSet] [TS] WITH (NOLOCK)
JOIN [Test] [T] WITH (NOLOCK)
ON [T].[TestSetId] = [TS].[Guid] AND [TS].[Redizo] NOT IN (999999999, 111111119) AND [T].[TestTypeId] = 1 AND [T].[ProcessedOn] IS NOT NULL
JOIN [Testlet] [TL] WITH (NOLOCK)
ON [TL].[TestId] = [T].[Guid] AND [TL].[ShownOn] IS NOT NULL
JOIN [TimeWindow] [TW] WITH (NOLOCK)
ON [TW].[Id] = [TS].[TimeWindowId] AND [TW].[IsActive] = 1
JOIN [TestAdaptation] [TA] WITH (NOLOCK)
ON [TA].[Id] = [TW].[TestAdaptationId] AND [TA].[IsActive] = 1
JOIN [TestCampaign] [TC] WITH (NOLOCK)
ON [TC].[TestCampaignId] = [TA].[TestCampaignId] AND [TC].[IsActive] = 1
JOIN [TestCampaignContainer] [TCC] WITH (NOLOCK)
ON [TCC].[TestCampaignContainerId] = [TC].[TestCampaignContainerId] AND [TCC].[IsActive] = 1
;
SELECT
FR1.TestCampaignContainerId,
FR1.TestCampaignCategoryId,
FR1.Grade,
FR1.TestCampaignId,
FR1.TestSetId
,FR1.TestId
,TSK.CategoryId AS [TaskCategoryId]
,TT.[TestletId]
,FR1.SectionNo
,FR1.Difficulty
,TestletName = Char(65+FR1.SectionNo) + CONVERT(varchar(4),6 - FR1.Difficulty) -- prevod na A5, B4, B5 ...
,FR1.StudentId
,FR1.ClassId
,FR1.Redizo
,TT.ViewCount
,TT.SpentTime
,[Version] = 1 -- ?
,FR1.TestAdaptationId
,TaskId = TSK.TaskId
,TaskPosition = TT.Position
,AnswerType = TT.TestletAnswerTypeId
,TT.Guid AS TTGuid
INTO
[#FinalResult_Base_2]
FROM
#FinalResult_Base_1 FR1
JOIN [TestTask] [TT] WITH (NOLOCK)
ON [TT].[TestletId] = [FR1].[TLGuid]
JOIN [Task] [TSK] WITH (NOLOCK)
ON [TSK].[TaskId] = [TT].[TaskId] AND [TSK].[IsActive] = 1
JOIN [Category] [C] WITH (NOLOCK)
ON [C].[CategoryId] = [TSK].[CategoryId]AND [C].[IsActive] = 1
;
DROP TABLE [#FinalResult_Base_1]
CREATE NONCLUSTERED INDEX [#IX_FR_Student_Class]
ON [dbo].[#FinalResult_Base_2] ([StudentId],[ClassId])
INCLUDE ([TTGuid])
SELECT
FR2.TestCampaignContainerId,
FR2.TestCampaignCategoryId,
FR2.Grade,
FR2.TestCampaignId,
FR2.TestSetId
,FR2.TestId
,FR2.[TaskCategoryId]
,FR2.[TestletId]
,FR2.SectionNo
,FR2.Difficulty
,FR2.TestletName
,TQ.[QuestionId]
,FR2.StudentId
,FR2.ClassId
,RA.SubjectId
,TQ.[QuestionPoints] -- 1+ good, 0 wrong, null no answer
,GoodAnswer = Case When TQ.[QuestionPoints] Is null Then 0
When TQ.[QuestionPoints] > 0 Then 1 -- cookie
Else 0 End
,WrongAnswer = Case When TQ.[QuestionPoints] = 0 Then 1
When TQ.[QuestionPoints] Is null Then 1
Else 0 End
,NoAnswer = Case When TQ.[QuestionPoints] Is null Then 1 Else 0 End
,FR2.Redizo
,FR2.ViewCount
,FR2.SpentTime
,TQ.[Position] AS [QuestionPosition]
,RA.SpecialNeeds -- identifikace SVP
,[Version] = 1 -- ?
,FR2.TestAdaptationId
,FR2.TaskId
,FR2.TaskPosition
,QuestionRate = Q.Rate
,TestQuestionId = TQ.Guid
,FR2.AnswerType
INTO
[#FinalResult_Base]
FROM
[#FinalResult_Base_2] FR2
JOIN [TestQuestion] [TQ] WITH (NOLOCK)
ON [TQ].[TestTaskId] = [FR2].[TTGuid]
JOIN [Question] [Q] WITH (NOLOCK)
ON [Q].[QuestionId] = [TQ].[QuestionId] AND [Q].[IsActive] = 1
JOIN [RoleAssignment] [RA] WITH (NOLOCK)
ON [RA].[PersonId] = [FR2].[StudentId]
AND [RA].[ClassId] = [FR2].[ClassId] AND [RA].[IsActive] = 1 AND [RA].[RoleId] = 1
drop table #FinalResult_Base_2;
truncate table [dbo].[FinalResult_Base];
insert into [dbo].[FinalResult_Base] select * from #FinalResult_Base;
drop table #FinalResult_Base;
quelle
READCOMMITTED
? Ich habe ROWCOMMITTED noch nie gesehen.Antworten:
1 Reduzierung des 'Suchraums', gepaart mit einer besseren Statistik für die Intermediate / Late Joins.
Ich musste mich mit Joins mit 90 Tabellen (Mickey-Mouse-Design) befassen, bei denen der Abfrageprozessor sich weigerte, überhaupt einen Plan zu erstellen. Durch das Aufteilen eines solchen Joins in 10 Unter-Joins mit jeweils 9 Tabellen wurde die Komplexität jedes Joins drastisch verringert, die mit jeder weiteren Tabelle exponentiell zunimmt. Außerdem behandelt das Abfrageoptimierungsprogramm sie jetzt als 10 Pläne, wodurch insgesamt (möglicherweise) mehr Zeit aufgewendet wird (Paul White verfügt möglicherweise sogar über Messdaten!).
Die Zwischenergebnistabellen verfügen nun über eigene neue Statistiken, sodass sie sich viel besser mit den Statistiken eines tiefen Baums verbinden, der früh verzerrt wird und bald darauf als Science-Fiction-Tabelle endet.
Außerdem können Sie die selektivsten Verknüpfungen zuerst erzwingen, indem Sie die Datenvolumina verringern, die sich in der Baumstruktur nach oben bewegen. Wenn Sie die Selektivität Ihrer Prädikate besser einschätzen können als mit dem Optimierer, erzwingen Sie die Verknüpfungsreihenfolge. Vielleicht lohnt es sich, nach "Bushy Plans" zu suchen.
2 Es ist meines Erachtens zu prüfen, ob Effizienz und Leistung wichtig sind
3 Nicht unbedingt, aber es könnte sein, dass die selektivsten Verknüpfungen früh ausgeführt werden
quelle
quelle
Lassen Sie mich zunächst sagen, dass Sie an kleinen Daten arbeiten - 10 ns von Millionen sind nicht groß. Das letzte DWH-Projekt, für das ich 400 Millionen Zeilen in die Faktentabelle aufgenommen hatte. PRO TAG. Lagerung für 5 Jahre.
Das Problem ist teilweise die Hardware. Da große Joins möglicherweise eine Menge temporären Speicherplatz belegen und nur so viel RAM zur Verfügung steht, werden die Dinge in dem Moment, in dem Sie überlaufen, viel langsamer. Daher kann es sinnvoll sein, die Arbeit in kleinere Teile zu unterteilen, da SQL zwar in einer Welt von Mengen lebt und sich nicht um die Größe kümmert, der Server, auf dem Sie ausgeführt werden, jedoch nicht unendlich ist. Ich bin es gewohnt, bei manchen Vorgängen Speicherfehler in einer 64-GB-Tempdb zu vermeiden.
Andernfalls ist der Abfrageoptimierer nicht überfordert, solange die Zustände in Ordnung sind. Es ist egal, wie groß die Tabelle ist - es funktioniert mit Statistiken, die nicht wirklich wachsen. DAS GESAGT: Wenn Sie wirklich eine GROSSE Tabelle (zweistellige Milliarden-Zeilenanzahl) haben, können sie etwas grob sein.
Es gibt auch eine Frage des Sperrens - es sei denn, Sie programmieren, dass der große Join die Tabelle für Stunden sperren kann. Momentan führe ich 200-GB-Kopiervorgänge durch und teile sie durch einen Geschäftsschlüssel (der sich tatsächlich in einer Schleife befindet) in kleine Gruppen auf, wodurch die Sperren viel kürzer bleiben.
Am Ende arbeiten wir mit begrenzter Hardware.
quelle