Ich habe zwei sehr ähnliche Fragen
Erste Abfrage:
SELECT count(*)
FROM Audits a
JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
and a.TargetTypeId IN
(1,2,3,4,5,6,7,8,9,
11,12,13,14,15,16,17,18,19,
21,22,23,24,25,26,27,28,29,30,
31,32,33,34,35,36,37,38,39,
41,42,43,44,45,46,47,48,49,
51,52,53,54,55,56,57,58,59,
61,62,63,64,65,66,67,68,69,
71,72,73,74,75,76,77,78,79)
Ergebnis: 267479
Plan: https://www.brentozar.com/pastetheplan/?id=BJWTtILyS
Zweite Abfrage:
SELECT count(*)
FROM Audits a
JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
and a.TargetTypeId IN
(1,2,3,4,5,6,7,8,9,
11,12,13,14,15,16,17,18,19,
21,22,23,24,25,26,27,28,29,
31,32,33,34,35,36,37,38,39,
41,42,43,44,45,46,47,48,49,
51,52,53,54,55,56,57,58,59,
61,62,63,64,65,66,67,68,69,
71,72,73,74,75,76,77,78,79)
Ergebnis: 25650
Plan: https://www.brentozar.com/pastetheplan/?id=S1v79U8kS
Die erste Abfrage dauert ungefähr eine Sekunde, während die zweite Abfrage ungefähr 20 Sekunden dauert. Dies ist für mich völlig kontraintuitiv, da die erste Abfrage eine viel höhere Anzahl als die zweite hat. Dies ist auf SQL Server 2012
Warum gibt es so einen großen Unterschied? Wie kann ich die zweite Abfrage so beschleunigen, dass sie so schnell ist wie die erste?
Hier ist das Skript zum Erstellen einer Tabelle für beide Tabellen:
CREATE TABLE [dbo].[AuditRelatedIds](
[AuditId] [bigint] NOT NULL,
[RelatedId] [uniqueidentifier] NOT NULL,
[AuditTargetTypeId] [smallint] NOT NULL,
CONSTRAINT [PK_AuditRelatedIds] PRIMARY KEY CLUSTERED
(
[AuditId] ASC,
[RelatedId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_AuditRelatedIdsRelatedId_INCLUDES] ON [dbo].[AuditRelatedIds]
(
[RelatedId] ASC
)
INCLUDE ( [AuditId]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
ALTER TABLE [dbo].[AuditRelatedIds] WITH CHECK ADD CONSTRAINT [FK_AuditRelatedIds_AuditId_Audits_Id] FOREIGN KEY([AuditId])
REFERENCES [dbo].[Audits] ([Id])
ALTER TABLE [dbo].[AuditRelatedIds] CHECK CONSTRAINT [FK_AuditRelatedIds_AuditId_Audits_Id]
ALTER TABLE [dbo].[AuditRelatedIds] WITH CHECK ADD CONSTRAINT [FK_AuditRelatedIds_AuditTargetTypeId_AuditTargetTypes_Id] FOREIGN KEY([AuditTargetTypeId])
REFERENCES [dbo].[AuditTargetTypes] ([Id])
ALTER TABLE [dbo].[AuditRelatedIds] CHECK CONSTRAINT [FK_AuditRelatedIds_AuditTargetTypeId_AuditTargetTypes_Id]
CREATE TABLE [dbo].[Audits](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[TargetTypeId] [smallint] NOT NULL,
[TargetId] [nvarchar](40) NOT NULL,
[TargetName] [nvarchar](max) NOT NULL,
[Action] [tinyint] NOT NULL,
[ActionOverride] [tinyint] NULL,
[Date] [datetime] NOT NULL,
[UserDisplayName] [nvarchar](max) NOT NULL,
[DescriptionData] [nvarchar](max) NULL,
[IsNotification] [bit] NOT NULL,
CONSTRAINT [PK_Audits] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
SET ANSI_PADDING ON
CREATE NONCLUSTERED INDEX [IX_AuditsTargetId] ON [dbo].[Audits]
(
[TargetId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
SET ANSI_PADDING ON
CREATE NONCLUSTERED INDEX [IX_AuditsTargetTypeIdAction_INCLUDES] ON [dbo].[Audits]
(
[TargetTypeId] ASC,
[Action] ASC
)
INCLUDE ( [TargetId],
[UserDisplayName]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY]
ALTER TABLE [dbo].[Audits] WITH CHECK ADD CONSTRAINT [FK_Audits_TargetTypeId_AuditTargetTypes_Id] FOREIGN KEY([TargetTypeId])
REFERENCES [dbo].[AuditTargetTypes] ([Id])
ALTER TABLE [dbo].[Audits] CHECK CONSTRAINT [FK_Audits_TargetTypeId_AuditTargetTypes_Id]
TargetTypeId = 30
? Die Pläne scheinen unterschiedlich zu sein, da dieser eine Wert die (voraussichtlich) zurückgegebene Datenmenge wirklich verzerrt.Antworten:
Tl; dr unten
Warum wurde der schlechte Plan gewählt?
Der Hauptgrund für die Wahl eines Plans gegenüber dem anderen sind die
Estimated total subtree
Kosten.Diese Kosten waren für den schlechten Plan niedriger als für den Plan mit der besseren Leistung.
Die geschätzten Gesamtkosten des Teilbaums für den schlechten Plan:
Die geschätzten Gesamtkosten für den Teilbaum für Ihren Plan mit besserer Leistung
Der Betreiber schätzte die Kosten
Bestimmte Betreiber können den größten Teil dieser Kosten tragen und könnten ein Grund für den Optimierer sein, einen anderen Pfad / Plan zu wählen.
In unserem Plan mit besserer Leistung wird der Großteil der
Subtreecost
auf demindex seek
& durchgeführtennested loops operator
Join berechnet :Während für unseren schlechten Abfrageplan die
Clustered index seek
Betreiberkosten niedriger sindDas sollte erklären, warum der andere Plan hätte gewählt werden können.
(Und durch Hinzufügen des Parameters
30
, der die Kosten des fehlerhaften Plans erhöht, wenn er über die871.510000
geschätzten Kosten gestiegen ist ). Geschätzte Vermutung ™Der leistungsfähigere Plan
Der schlechte Plan
Wohin führt uns das?
Diese Informationen bringen uns zu einer Möglichkeit, den Plan für fehlerhafte Abfragen in unserem Beispiel zu erzwingen (siehe DML, um das Problem von OP für die Daten, die zum Replizieren des Problems verwendet wurden, fast zu replizieren).
Durch Hinzufügen eines
INNER LOOP JOIN
Join-HinweisesEs ist näher, weist jedoch einige Unterschiede in der Reihenfolge der Verknüpfungen auf:
Umschreiben
Mein erster Umschreibversuch könnte darin bestehen, alle diese Zahlen stattdessen in einer temporären Tabelle zu speichern:
Und dann ein
JOIN
anstelle des großen hinzufügenIN()
Unser Abfrageplan ist anders, aber noch nicht festgelegt:
mit riesigen geschätzten Betreiberkosten auf dem
AuditRelatedIds
TischHier ist mir das aufgefallen
Der Grund, warum ich Ihren Plan nicht direkt neu erstellen kann, ist die optimierte Bitmap-Filterung.
Ich kann Ihren Plan neu erstellen, indem ich optimierte Bitmap-Filter mithilfe von Traceflags
7497
& deaktiviere7498
Weitere Informationen zu optimierten Bitmap-Filtern finden Sie hier .
Dies bedeutet, dass es für den Optimierer ohne die Bitmap-Filter besser ist, zuerst mit der
#number
Tabelle und dann mit derAuditRelatedIds
Tabelle zu verbinden.Wenn
OPTION (QUERYTRACEON 7497, QUERYTRACEON 7498, FORCE ORDER);
wir die Bestellung erzwingen, können wir sehen warum:&
Nicht gut
Entfernen der Fähigkeit, parallel zu maxdop 1 zu gehen
Beim Hinzufügen führt
MAXDOP 1
die Abfrage eine schnellere Single-Thread-Ausführung durch.Und diesen Index hinzufügen
Bei Verwendung eines Merge-Joins.
Das Gleiche gilt, wenn wir den Abfragehinweis für die erzwungene Reihenfolge entfernen oder die Tabelle #Numbers nicht verwenden und
IN()
stattdessen die Tabelle verwenden .Mein Rat wäre, das Hinzufügen zu prüfen
MAXDOP(1)
und zu prüfen, ob dies Ihrer Anfrage hilft, und bei Bedarf eine Neufassung vorzunehmen.Natürlich sollten Sie auch bedenken, dass es meiner Meinung nach aufgrund der optimierten Bitmap-Filterung und der tatsächlichen Verwendung mehrerer Threads noch besser funktioniert:
TL; DR
Die geschätzten Kosten definieren den gewählten Plan. Ich konnte das Verhalten replizieren und sah, dass
optimized bitmap filters
+parallellism
Operatoren an meinem Ende hinzugefügt wurden, um die Abfrage auf performante und schnelle Weise durchzuführen.Sie könnten versuchen
MAXDOP(1)
, Ihre Abfrage zu erweitern, um hoffentlich jedes Mal das gleiche kontrollierte Ergebnis zu erzielen, mit einemmerge join
und ohne "schlecht"parallellism
.Ein Upgrade auf eine neuere Version und die Verwendung einer Version mit einem höheren Kardinalitätsschätzer als dies
CardinalityEstimationModelVersion="70"
möglicherweise ebenfalls hilfreich ist.Eine temporäre Zahlentabelle für die Mehrwertfilterung kann ebenfalls hilfreich sein.
DML, um das Problem von OP fast zu replizieren
Ich habe mehr Zeit damit verbracht, als ich zugeben möchte
quelle
MAXDOP 0
scheint es behoben zu haben. Vielen Dank!Nach allem, was ich sagen kann, ist der Hauptunterschied zwischen den beiden Plänen der Unterschied im "Primärfilter".
Bei der ersten Version wurde der Hauptfilter abgeleitet, der
Audit.ID
sich darauf bezieht,ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
diese Liste auf diejenigen zu filtern,Audit.TargetTypeID
die in der Liste enthalten waren.Mit der zweiten Version wurde der Hauptfilter abgeleitet, der
Audit.ID
sich auf die Liste von beziehtAudit.TargetTypeID
.Seit der Hinzufügung von
Audit.TargetTypeID = 30
schien sich die Rekordzahl dramatisch zu erhöhen (267.479 bzw. 25.650 gemäß der ursprünglichen Frage). Das ist wahrscheinlich der Grund, warum die Ausführungspläne unterschiedlich sind. (So wie ich es verstehe) SQL wird versuchen, zuerst die selektivste Funktion auszuführen und danach den Rest der Regeln anzuwenden. Bei der ersten Version war das Abfragen bisAuditRelatedID.RelatedID
zum FindenAudit.ID
wahrscheinlich selektiver als der Versuch, dasAudit.TargetTypeID
Finden zu verwendenAudit.ID
.Zu Ypercubes Gunsten. Sie können sicher aktualisieren
[AuditRelatedIds].[IX_AuditRelatedIdsRelatedId_INCLUDES]
, um beideRelatedID
undAuditID
als Teil des Index zu haben, anstattAuditID
als Teil einesINCLUDE
. Es sollte keinen zusätzlichen Indexplatz beanspruchen und es Ihnen ermöglichen, beide Spalten inJOIN
Klauseln zu verwenden. Dies kann dazu beitragen, dass das Abfrageoptimierungsprogramm für beide Abfragen denselben Ausführungsplan erstellt.Wenn Sie mit einer ähnlichen Logik arbeiten, kann ein Index,
Audit
derTargetTypeID ASC, ID ASC
die tatsächlich geordneten / Filterknoten enthält (nicht als Teil vonINCLUDE
) , einige Vorteile haben . Dies sollte es dem Abfrageoptimierer ermöglichen, zu filtern undAudit.TargetTypeID
dann schnell zu verbindenAuditReferenceIds.AuditID
. Dies kann dazu führen, dass beide Abfragen den weniger effizienten Plan auswählen, sodass ich ihn erst ausprobieren würde, nachdem ich die Empfehlung von ypercube ausprobiert habe.quelle