c0nsp1r4cy
Goto Top

MS SQL - unperformante Query - WITH gejointe Tabelle erhöht Abfragedauer um 50

Guten Morgen allerseits,

MS SQL Server 2008, Microsoft SQL Server Management Studio 10.50.1600.1

ich versuche derzeit eine wirklich schrecklich unperformante Query zu optimieren (ca. 80.000 Datensätze).

Geringfügig gelang mir das auch bereits durch das Spielen und Ersetzen von Joins durch SubQueries und umgekehrt sowie weiterer kleinerer Syntaxspielereien.

Die abgefragten Spalten habe ich sukzessive auskommentiert, Abfrage ausgeführt, Ergebnis notiert und so die strittigen Stellen identifiziert.

Schließlich war ich bis zu einem bestimmten Punkt zufrieden, weil die Ausführungszeit der Query von 18s bei allen Datensätzen auf 4s reduzieren konnte.

Nun wollte ich den letzten Part angehen:

		
WE_Belege = (Select Substring((Select DISTINCT ', ' + ((select WE = (Convert(nvarchar(8),(DocNum)) + '(' + convert(varchar, DocDate, 104) + ')')  from OPDN where B.DocEntry = DocEntry AND B.BaseLine = BaseLine AND CANCELED <> 'Y'))  From PDN1 B Where B.BaseRef=A.BaseRef AND B.BaseLine=A.BaseLine For XML Path('')),2,8000) As WEs  
From PDN1 A where BaseRef = (Select DocNum from OPOR where DocEntry = EKT.DocEntry AND CANCELED <> 'Y') AND BaseEntry = EKT.DocEntry AND BaseLine = EKT.LineNum  
Group By BaseRef, BaseLine),

und stattdessen eine temporäre Tabelle mittels WITH erzeugen:
WITH WE(DocNum,Belege,DocEntry,LineNum) AS
	(Select BSDocNum,WE_Belege,DocEntry,LineNum from(
	Select DISTINCT BSDocNum = OPOR.DocNum, EKT.DocEntry,EKT.LineNum,
				-- Kpl. String --
	WE_Belege = (Select Substring((Select DISTINCT ', ' +   
					-- WE SUBSTRING --
					((select WE = (Convert(nvarchar(8),(DocNum)) + '(' + convert(varchar, DocDate, 104) + ')')    
					from OPDN where B.DocEntry = DocEntry AND B.BaseLine = BaseLine AND CANCELED <> 'Y'))    
					From PDN1 B Where B.BaseRef=A.BaseRef AND B.BaseLine=A.BaseLine For XML Path('')),2,8000) As WEs  
					-- ENDE WE SUBSTRING --
				From PDN1 A 
				where BaseRef = OPOR.DocNum AND BaseEntry = EKT.DocEntry AND BaseLine = EKT.LineNum
				Group By BaseRef, BaseLine)
				-- ENDE Kpl. String --
	from PDN1 EKT
			
	INNER JOIN (Select DocNum,DocEntry,CANCELED from OPOR) OPOR ON OPOR.DocEntry = EKT.DocEntry AND OPOR.CANCELED <> 'Y'  
) AS WEBEL where WE_Belege IS NOT NULL)

Dieser Part führt zu einem Anstieg der Abfragezeit von 4s auf 10s-11s.

Komplette Abfrage nach Anpassungen:
WITH WE(DocNum,Belege,DocEntry,LineNum) AS
	(Select BSDocNum,WE_Belege,DocEntry,LineNum from(
	Select DISTINCT BSDocNum = OPOR.DocNum, EKT.DocEntry,EKT.LineNum,
				-- Kpl. String --
	WE_Belege = (Select Substring((Select DISTINCT ', ' +   
					-- WE SUBSTRING --
					((select WE = (Convert(nvarchar(8),(DocNum)) + '(' + convert(varchar, DocDate, 104) + ')')    
					from OPDN where B.DocEntry = DocEntry AND B.BaseLine = BaseLine AND CANCELED <> 'Y'))    
					From PDN1 B Where B.BaseRef=A.BaseRef AND B.BaseLine=A.BaseLine For XML Path('')),2,8000) As WEs  
					-- ENDE WE SUBSTRING --
				From PDN1 A 
				where BaseRef = OPOR.DocNum AND BaseEntry = EKT.DocEntry AND BaseLine = EKT.LineNum
				Group By BaseRef, BaseLine)
				-- ENDE Kpl. String --
	from PDN1 EKT
			
	INNER JOIN (Select DocNum,DocEntry,CANCELED from OPOR) OPOR ON OPOR.DocEntry = EKT.DocEntry AND OPOR.CANCELED <> 'Y'  
) AS WEBEL where WE_Belege IS NOT NULL)

		select 
		BANF = EKT.BaseRef,
		Abt = CASE WHEN EKT.SlpCode = '21' Then 'H4' Else OSLP.Memo End,  
		Name = OSLP.SlpName,
		MA = LEFT(RIGHT((OSLP.SlpName), CHARINDEX (' ' ,REVERSE((OSLP.SlpName)))-1),2) + LEFT(LEFT((OSLP.SlpName), charindex(' ', (OSLP.SlpName)) - 1),2),  
		Kunde = CASE WHEN EKT.project LIKE '15-10%' THEN isnull((OPRJ.PrjName),'') ELSE isnull((ORDR.CardName),'') End,  
		PRJ = EKT.project,
		Kom = EKT.U_ROC_Kommission,
		Lieferant = OPOR.CardName,
		Code = OPOR.CardCode,
		FF = OPOR.FF,
		Beleg = OPOR.DocNum,
		Pos = EKT.VisOrder + 1, 
		Datum = Convert(date,OPOR.DocDate,104),
		Lieferwunsch = convert(date, EKT.ShipDate, 104),
		AB = convert(date, EKT.U_AB_Termin, 104),
		Menge = Convert(nvarchar(10),REPLACE(CONVERT(numeric(15),EKT.OpenQty ),'.',',')) + '/' + Convert(nvarchar(10),REPLACE(CONVERT(numeric(15),EKT.Quantity),'.',',')),  
		ME = EKT.unitMsr,
		Artikel = EKT.ItemCode,
		Bezeichnung = EKT.Dscription,
		Typ = (Select U_VKBez from OITM where ItemCode = EKT.ItemCode),
		Artikelinfo = EKT.U_ArtBem,
		Zeichnung = EKT.U_beas_znr,
		Zeilenerw = EKT.[Text],
		-- Folgende Spalte "Stand" ist Mist!!! --  
		Stand = CASE WHEN EKT.TargetType = 20 AND EKT.OpenQty > 0 THEN 'teilw. geliefert'   
		WHEN (EKT.TargetType = 20 AND EKT.OpenQty = 0) AND (Select Top 1 TargetType from PDN1 where BaseEntry = EKT.DocEntry AND BaseLine = EKT.LineNum) = -1 THEN 'geliefert'   
		WHEN (EKT.TargetType = 20 AND EKT.OpenQty = 0) AND (Select Top 1 TargetType from PDN1 where BaseEntry = EKT.DocEntry AND BaseLine = EKT.LineNum) = 18 THEN 'berechnet'   
		WHEN EKT.TargetType = 18 AND EKT.OpenQty > 0 THEN 'teilw. berechnet'   
		WHEN EKT.TargetType = 18 AND EKT.OpenQty = 0 THEN 'berechnet'   
		WHEN EKT.OpenQty = 0 AND EKT.TargetType = -1 Then 'canceled'   
		ELSE 'Offen' END,  
		WEs = WE.Belege,
		Status = OPOR.DocStatus,
		L_Stat = EKT.LineStatus,
		Suche = (Convert(nvarchar(16),EKT.ItemCode) + ' ' + isnull(EKT.Dscription,'') + ' ' + isnull((Select U_VKBez from OITM where ItemCode = EKT.ItemCode),'') + ' ' + isnull(EKT.U_ArtBem,'') + ' ' + isnull(EKT.U_beas_znr,'') + ' ' + isnull(Convert(nvarchar(256),EKT.[Text]),''))  
		from POR1 EKT 
		
		INNER JOIN (Select DocEntry,DocDate,DocNum,DocStatus,CardCode,CardName, FF =
		CASE WHEN ((Select U_FF from OCRD where OCRD.CardCode = OPOR.CardCode) = 'Y' OR (Select U_VED from OCRD where OCRD.CardCode = OPOR.CardCode) = 'Y') THEN 'X' ELSE '' END  
		from OPOR) OPOR ON OPOR.DocEntry = EKT.DocEntry
		
		INNER JOIN (Select SlpCode,SlpName,Memo from OSLP) OSLP ON OSLP.SlpCode = EKT.SlpCode
		
		LEFT JOIN (Select PrjName, PrjCode from OPRJ) OPRJ ON OPRJ.PrjCode = EKT.Project
		LEFT JOIN (Select CardName,DocNum = Convert(nvarchar(8),DocNum) from ORDR) ORDR ON ORDR.DocNum = EKT.U_ROC_Kommission
		LEFT JOIN WE ON WE.DocEntry = EKT.DocEntry AND WE.LineNum = EKT.Linenum

- Konvertiererei problematisch? Ich meine hier geringfügig verschleuderte Performance festzustellen.
- Stringmanipulationen ungünstig?

Standardindexe (SAP-Tabellen) sind überall vorhanden.

Ist es möglich der temporären Tabelle, die über WITH gejoint wird, einen INDEX zu verpassen? Würde das überhaupt etwas bringen?

Über Hilfe würde ich mich freuen!

Vielen Dank vorab!

Content-Key: 614746

Url: https://administrator.de/contentid/614746

Printed on: April 25, 2024 at 11:04 o'clock

Member: akretschmer
akretschmer Oct 21, 2020 at 07:05:22 (UTC)
Goto Top
ich hab mir jetzt nicht die komplette query durchgelesen (sie scheint eh nicht komplett zu sein), aber kurze Frage: welche Version?

Ab PG12 kann PG WITH-Abfragen sinnvoll optimieren, vorher wurde immer alles materialisiert, was u.U. massiv auf die Performance geht.

Ach ja, ein EXPLAIN (ANALYSE, BUFFER) wäre sinnvoll ...
Member: c0nsp1r4cy
c0nsp1r4cy Oct 21, 2020 updated at 07:34:16 (UTC)
Goto Top
Sorry, ja - Frage ergänzt.

Die Query ist komplett.

Ich habe keine Ahnung von den Analysetools oder wie sie zu benutzen sind.

Den ExecutionPlan habe ich mal erstellt, aber wirklich schlau werde ich daraus eben nich. Im Grunde weiss ich ja, wo es klemmt?
Member: akretschmer
akretschmer Oct 21, 2020 at 08:00:48 (UTC)
Goto Top
Ah, das ist M$SQL? Ich bin raus ...
Member: mbehrens
mbehrens Oct 21, 2020 at 09:15:53 (UTC)
Goto Top
Zitat von @c0nsp1r4cy:

Ich habe keine Ahnung von den Analysetools oder wie sie zu benutzen sind.

Den ExecutionPlan habe ich mal erstellt, aber wirklich schlau werde ich daraus eben nich. Im Grunde weiss ich ja, wo es klemmt?

So wird das nichts. Die eingesetzten Werkzeuge sollte man schon wenigstens ansatzweise beherrschen.

WHEN, CASE und Stringoperationen sind eben teuer.
Member: c0nsp1r4cy
c0nsp1r4cy Oct 21, 2020 updated at 10:32:41 (UTC)
Goto Top
Geb ich Dir grundsätzlich Recht, aber gemäß Analyse sind das die verschmerzbaren Parts der Query.

Zitat von @mbehrens:
Ich habe keine Ahnung von den Analysetools oder wie sie zu benutzen sind.

Den ExecutionPlan habe ich mal erstellt, aber wirklich schlau werde ich daraus eben nich. Im Grunde weiss ich ja, wo es klemmt?

So wird das nichts. Die eingesetzten Werkzeuge sollte man schon wenigstens ansatzweise beherrschen.

Es spielt wohl keine Rolle, wie ich zum Ziel komme, denn das Hauptproblem wurde identifiziert. Somit geht es nicht mehr um die Analyse, sondern einen Lösungsvorschlag.

Aber danke fürs Gespräch.
Member: GrueneSosseMitSpeck
GrueneSosseMitSpeck Oct 21, 2020 updated at 21:02:42 (UTC)
Goto Top
also zuallererst... guck mal wo die TempDB ist. Die sollte auf dem schnellsten verfügbaren Speicher liegen, NVME ist da eigentlich Pflicht oder zumindestens SSD bevor man irgendwas anders betrachtet.

Dann - da sind unendlich viele Stringoperationen drin. Sowas kostet Zeit, da sind Datumskonvertinerungen vorhanden, obwohl der SQL Server eigentlich einen eigenen Datetime Datentyp hat. Wer den nicht verwendet, den bestraft die Konvertierungsperformance... aber oft ist man ja nicht Herr des Schemans sondern muß nur irgendwas draus machen was irgendwie krank aussieht und SAP ist sogar hammerkrank. Ich möcht schreiend weglaufen wenn ich diese drei Buchstaben in der Reihenfolge höhre face-sad

Und mal ehrlich 11 Sekunden für so eine Query mit 80.000 Datensätzen Resultset, damit würde ich mich zufriedengeben. Auch eine Minut wär noch ok. Ich hab ähnliche Abfragen aus Sage Officeline Datenbanken gemacht, die liefen damals (1999-2002, SQL Server 6.5 auf Dual Pentium 3, 256 MB RAM) 2-4 Stunden. Einen richtigen Performanceschub auf 30 Minuten gabs erst nachdem wir auf eine 8-CPU-Maschine mit 3 GB Speicher aufgerüstet hatte. Ich weiß, 3 GB... 8 CPUs... aber in jenen Zeiten war Datensparsamkeit den schwächlichen Datenbankservern der damaligen Zeit geschuldet, nicht gesetzlichen Vorgaben.

Was vielleicht was bringt ist mal auf einen aktuellen SQL SErver zu aktualisieren, falls irgendwie machbar. Der 2008 ist schon lang "end of life", da kann man der Geschäftsführung ohnehin damit argumentieren daß die Version voller Bugs ist (Joins werden oft nicht richtig optimiert) die Microsoft nie mehr fixen wird... ab SQL 2014 gibts den Query store, davor muß man mit dem Abfrageoptimierer vorlieb nehmen. Gehört zum SQL Trace dazu, das sind Tools die man unbedingt installieren sollte und die man verstehen lernen sollte. Denn so manch Abfrage performt schlecht weil es keine unterstütztenden Indizes dazu gibt.

Letztenendlich muß man die Abfrage so passend machen daß der SQL Server sie korrekt optimiert und nicht so daß der SQL Code "schön" aussieht. Ach noch was... wenn der Server in einer VM ist. Wir hatten das neulich daß ein SQL Server schlecht performte... weil 10 Cores einem Socket im ESX zugewiesen waren. Mit 1 core pro Socket stieg die Abfrageleistung um satte 20%, dann machts Sinn am MaxDOP und CTP herumzuschrauben. Steht beides standardmäßig auf unlimited, aber bei komplexen Abfragen ist das oft kontraproduktiv.

Cost Threshold for parallelism sollte auf Werte zwischen 50 und 150 eingestellt sein und "max degree of parallelism" sollte nicht höher sein als die Anzahl an phyischen Cores. Auch das tunt Abfragen ein wenig. Das sind Optionen in der SQL Server Instanz (rechte Maustaste im Management Studio auf den serverknoten ganz links oben, Eigenschaften, Optionen)
Member: c0nsp1r4cy
c0nsp1r4cy Oct 22, 2020 at 05:46:13 (UTC)
Goto Top
Ahoi GrueneSoße,

ein großes Dank für diese ausführlichen Gedanken!

Da ist nun einiges dabei, was ich konkreter untersuchen werde.

Jaaaaa der 2008er Server :/ - klar der sollte mal aktualisiert werden und ist auch geplant.

Ursprünglich war meine Hoffnung allerdings, es gebe eine entscheidende Idee um die With-Tabelle zu ersetzen oder umzubauen.

Diese Tabelle braucht 1-2s, wenn ich sie alleine ausführe und ich verstehe nicht wieso, sobald ich die Tabelle joine, dann plötzlich um die 11s brauche für alle Datensätze.

Gruß