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:
und stattdessen eine temporäre Tabelle mittels WITH erzeugen:
Dieser Part führt zu einem Anstieg der Abfragezeit von 4s auf 10s-11s.
Komplette Abfrage nach Anpassungen:
- 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!
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!
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 614746
Url: https://administrator.de/forum/ms-sql-unperformante-query-with-gejointe-tabelle-erhoeht-abfragedauer-um-50-614746.html
Ausgedruckt am: 22.12.2024 um 02:12 Uhr
7 Kommentare
Neuester Kommentar
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 ...
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 ...
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?
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.
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
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)
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
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)