2012-11-06 9 views
18

Warum generiert das Entitätsframework verschachtelte SQL-Abfragen?Warum generiert das Entitätsframework verschachtelte SQL-Abfragen?

Ich habe diesen Code

var db = new Context(); 
    var result = db.Network.Where(x => x.ServerID == serverId) 
     .OrderBy(x=> x.StartTime) 
     .Take(limit); 

Welche dies erzeugt! (Beachte die Doppelauswahlanweisung)

SELECT 
`Project1`.`Id`, 
`Project1`.`ServerID`, 
`Project1`.`EventId`, 
`Project1`.`StartTime` 
FROM (SELECT 
`Extent1`.`Id`, 
`Extent1`.`ServerID`, 
`Extent1`.`EventId`, 
`Extent1`.`StartTime` 
FROM `Networkes` AS `Extent1` 
WHERE `Extent1`.`ServerID` = @p__linq__0) AS `Project1` 
ORDER BY 
`Project1`.`StartTime` DESC LIMIT 5 

Was soll ich ändern, damit es eine Auswahlerklärung ergibt? Ich benutze MySQL und Entity Framework mit Code First.

aktualisiert

Ich habe das gleiche Ergebnis unabhängig von der Art des Parameters auf die OrderBy() Methode übergeben.

Update 2: Timed

Total Time (hh:mm:ss.ms) 05:34:13.000 
Average Time (hh:mm:ss.ms) 25:42.000 
Max Time (hh:mm:ss.ms) 51:54.000 
Count 13 
First Seen Nov 6, 12 19:48:19 
Last Seen Nov 6, 12 20:40:22 

Raw query:

SELECT `Project?`.`Id`, `Project?`.`ServerID`, `Project?`.`EventId`, `Project?`.`StartTime` FROM (SELECT `Extent?`.`Id`, `Extent?`.`ServerID`, `Extent?`.`EventId`, `Extent?`.`StartTime`, FROM `Network` AS `Extent?` WHERE `Extent?`.`ServerID` = ?) AS `Project?` ORDER BY `Project?`.`Starttime` DESC LIMIT ? 

verwendete ich ein Programm Schnappschüsse aus dem aktuellen Prozess in MySQL zu übernehmen.

Andere Abfragen wurden zur gleichen Zeit ausgeführt, aber wenn ich sie in nur eine SELECT-Anweisung ändere, geht sie NIEMALS über eine Sekunde hinaus. Vielleicht habe ich noch etwas anderes vor; Ich frage, weil ich bin nicht so in DBs ...

Update 3: Die Anweisung explain

Das Entity Framework erzeugt

'1', 'PRIMARY', '<derived2>', 'ALL', NULL, NULL, NULL, NULL, '46', 'Using filesort' 
'2', 'DERIVED', 'Extent?', 'ref', 'serveridneventid,serverid', 'serveridneventid', '109', '', '45', 'Using where' 

One-Liner

'1', 'SIMPLE', 'network', 'ref', 'serveridneventid,serverid', 'serveridneventid', '109', 'const', '45', 'Using where; Using filesort' 

Dies stammt aus meiner QA-Umgebung, daher bezieht sich das Timing, das ich oben eingefügt habe, nicht auf die rowcount EXPLAIN-Anweisungen. Ich denke, dass es etwa 500.000 Datensätze gibt, die mit einer Server-ID übereinstimmen.

Lösung

Ich wechselte von MySQL zu SQL Server. Ich möchte nicht die Anwendungsschicht komplett neu schreiben.

+3

Zu allererst LINQ To SQL und Entity Framework sind verschiedene Dinge. Zweitens, warum denkst du, diese Abfrage ist schlecht? Hast du Performance-Analysen durchgeführt oder zumindest erklärt? – Stilgar

+1

Ja, ich erstelle sie manuell in reinem SQL. Mit 28 Millionen Datensätzen gibt es Millisekunden vs Minuten –

+0

Sorry für den Tippfehler mit Linq To SQL –

Antwort

7

Es ist der einfachste Weg, die Abfrage logisch aus der Ausdrucksbaumstruktur aufzubauen. Normalerweise ist die Leistung kein Problem. Wenn Sie Performance-Probleme haben können Sie so etwas wie dies versuchen, die Objekte zu erhalten zurück:

var results = db.ExecuteStoreQuery<Network>(
    "SELECT Id, ServerID, EventId, StartTime FROM Network WHERE ServerID = @ID", 
    serverId); 

results = results.OrderBy(x=> x.StartTime).Take(limit); 
+1

Ja, ich tat es und es machte enorme Auswirkungen auf die Leistung –

+2

Ich wette, das würde nicht in SQL Server passieren;) Ich frage mich, wie ihre Ausführung Plan Gebäude Routinen unterscheiden. Ich bin unterwegs um zu versuchen und zu reproduzieren. – PeteGO

+2

Ich bin auch neugierig, aber zu faul, um selbst zu forschen :) – Stilgar

1

Wenn Sie möchten, dass die EF die Abfrage ohne den Subselect generiert, verwenden Sie eine Konstante in der Abfrage, keine Variable.

Ich habe zuvor meine eigene .Where und alle anderen LINQ-Methoden erstellt, die zuerst den Ausdrucksbaum durchlaufen und alle Variablen, Methodenaufrufe usw. in Expression.Constant konvertieren. Es wurde nur wegen dieses Problems in Entity Framework ...

+0

Sehr interessant. Wo ist dein Code? –

+0

Wenn EF eine Abfrage ausführen muss, kompiliert es den Ausdrucksbaum zu einem DB-Befehl. Dies ist bei komplexen Abfragen sehr teuer. Wenn Sie Ihre Variablen in Konstanten ändern, kann EF die zwischengespeicherten kompilierten Daten nicht erneut verwenden und muss neu kompiliert werden. Bei komplexen Abfragen ist das ein extremer Performance-Killer. – MBoros

-2

Eigentlich sind die Abfragen von Entity Framework nur wenige hässlich, weniger als LINQ 2 SQL, aber immer noch hässlich.

Sehr wahrscheinlich jedoch Datenbank-Engine wird den gewünschten Ausführungsplan, und die Abfrage wird reibungslos ausgeführt.

3

Mein erster Eindruck war, dass es effizienter wäre, es zu tun, obwohl ich beim Testen gegen einen MSSQL-Server < 1 Sekunde Antworten erhielt.

Mit einer einzigen select-Anweisung, sortiert sie alle Datensätze (Order By) und filtert sie dann auf den Satz, den Sie sehen möchten (Where) und nimmt dann die Top-5 (Limit 5 oder für mich Top 5). Auf einem großen Tisch dauert die Sortierung einen erheblichen Teil der Zeit. Bei einer verschachtelten Anweisung werden die Datensätze zunächst auf eine Teilmenge gefiltert, und erst dann wird die teure Sortieroperation ausgeführt.

Bearbeiten: Ich habe dies getestet, aber ich erkannte, dass ich einen Fehler in meinem Test hatte, die es ungültig gemacht. Testergebnisse entfernt.

+0

Nebengedanke: Ich wünschte wirklich, dass die Person, die nach allen Suchanfragen verlangte, * tatsächlich * diese Daten anschaute. Im Moment ist es nur gut, Hacking-Skripte und Spambots zu erkennen, weil sie nach unsinnigen Abfragen suchen. – Bobson

+1

In Ihrer ersten Abfrage gibt es keine where-Klausel. Ich bin kein MySQL-Experte, aber ich würde erwarten, dass jede anständige Datenbank-Engine einen Abfrageplan erstellt, der in beiden Fällen zuerst die where-Klausel ausführt. –

+0

@JamesGaunt - Ups, du hast Recht. Beim Testen hatte ich einen Fehler beim Kopieren und Einfügen. Beweis ungültig. – Bobson

2

Warum generiert Entity Framework eine verschachtelte Abfrage? Die einfache Antwort besteht darin, dass Entity Framework den Abfrageausdruck in eine Ausdrucksstruktur aufteilt und diese Ausdrucksstruktur dann zum Erstellen der Abfrage verwendet. Ein Baum erzeugt natürlich geschachtelte Abfrageausdrücke (d. H. Ein Kindknoten erzeugt eine Abfrage und ein Elternknoten erzeugt eine Abfrage für diese Abfrage).

Warum vereinfacht Entity Framework die Abfrage nicht und schreibt sie so, wie Sie es tun würden? Die einfache Antwort ist, weil es eine begrenzte Menge an Arbeit gibt, die in die Abfragegenerierungs-Engine gehen kann, und obwohl sie jetzt besser ist als in früheren Versionen, ist sie nicht perfekt und wird es wahrscheinlich auch nie sein.

Alles, was gesagt wird, sollte keine signifikante Geschwindigkeitsdifferenz zwischen der Abfrage, die Sie von Hand schreiben und der Abfrage EF generiert in diesem Fall sein sollte. Die Datenbank ist clever genug, um einen Ausführungsplan zu generieren, der in jedem Fall die WHERE-Klausel anwendet.

+0

Werfen Sie einen Blick auf den Ausführungsplan. Es ist ein großer Leistungsunterschied zwischen den verschiedenen Aussagen. –

+0

Ich sehe das nicht. Beide Abfragepläne implementieren das WHERE bei der ersten Gelegenheit. Wenn Sie sehr unterschiedliche Zeiten sehen, deutet das darauf hin, dass etwas anderes vor sich geht. Entweder das oder das ist ein MySQL-Feature, aber MySQL ist eine weit verbreitete und angesehene DB-Engine, und ich kann nicht glauben, dass es so einen fundamentalen Fehler macht. Rufen Sie beide Tests mit identischen Parametern an? –

+0

Ah! Lies einfach den Kommentar von @Slauma in Frage! Beeindruckend. Stoppen Sie die Verwendung von MySQL, bis sie es aussortieren, wenn dies der Fall ist. Eine Unterabfrage ist keine temporäre Tabelle! –

1

ich gerade auf diesen Beitrag gestoßen, weil ich aus dem gleichen Problem leiden. Ich verbringe schon Tage damit, dies nachzuverfolgen und es ist nur eine schlechte Abfragegenerierung in MySQL.

Ich legte bereits einen Fehler bei mysql.com http://bugs.mysql.com/bug.php?id=75272

das Problem Fassen wir zusammen:

Diese einfache Abfrage

context.products 
    .Include(x => x.category) 
    .Take(10) 
    .ToList(); 

in

übersetzt wird
SELECT 
`Limit1`.`C1`, 
`Limit1`.`id`, 
`Limit1`.`name`, 
`Limit1`.`category_id`, 
`Limit1`.`id1`, 
`Limit1`.`name1` 
FROM (SELECT 
`Extent1`.`id`, 
`Extent1`.`name`, 
`Extent1`.`category_id`, 
`Extent2`.`id` AS `id1`, 
`Extent2`.`name` AS `name1`, 
1 AS `C1` 
FROM `products` AS `Extent1` INNER JOIN `categories` AS `Extent2` ON `Extent1`.`category_id` = `Extent2`.`id` LIMIT 10) AS `Limit1` 

und führt ziemlich Gut. Wie auch immer, die äußere Abfrage ist ziemlich nutzlos. Nun, wenn ich hinzufügen, eine OrderBy

context.products 
    .Include(x => x.category) 
    .OrderBy(x => x.id) 
    .Take(10) 
    .ToList(); 

die Abfrage Änderungen

SELECT 
`Project1`.`C1`, 
`Project1`.`id`, 
`Project1`.`name`, 
`Project1`.`category_id`, 
`Project1`.`id1`, 
`Project1`.`name1` 
FROM (SELECT 
`Extent1`.`id`, 
`Extent1`.`name`, 
`Extent1`.`category_id`, 
`Extent2`.`id` AS `id1`, 
`Extent2`.`name` AS `name1`, 
1 AS `C1` 
FROM `products` AS `Extent1` INNER JOIN `categories` AS `Extent2` ON `Extent1`.`category_id` = `Extent2`.`id`) AS `Project1` 
ORDER BY 
`Project1`.`id` ASC LIMIT 10 

was schlecht ist, weil die order by in der äußeren Abfrage ist.Theat bedeutet MySQL jeden Datensatz zu ziehen, um hat eine orderby auszuführen, die using filesort

in I führt, dass SQL Server überprüft (Comapact mindestens) erzeugt keine verschachtelte Abfragen für den gleichen Code

SELECT TOP (10) 
[Extent1].[id] AS [id], 
[Extent1].[name] AS [name], 
[Extent1].[category_id] AS [category_id], 
[Extent2].[id] AS [id1], 
[Extent2].[name] AS [name1], 
FROM [products] AS [Extent1] 
LEFT OUTER JOIN [categories] AS [Extent2] ON [Extent1].[category_id] = [Extent2].[id] 
ORDER BY [Extent1].[id] ASC 
Verwandte Themen