none
Sql Abfrage nicht immer extrem langsam! RRS feed

  • Frage

  • Hallo

    Ich habe ein kleines (großes Problem) mit linq mit sql Compact Server 3.5

    Ich habe eine datebank mit Beziehungen erstellt und habe
    mit den Linq-Vb code durch SqlMetal erstellen lassen.
    (wenn ich diesen vb code im Vs einbinde erhalte ich viele Fehlermeldungen von falschen deklarationen usw. also habe ich es wieder im vs ausgeblendet weiß nicht ob das eine Rolle spielt??!!!, Aber Linq funktioniert grundsätzlich!)

    Dann habe ich folgenden code probiert und da habe ich das Problem

    Das ich beim ersten Durchlauf circa 7 Sekunden brauche,
    und beim 2 durchlauf keine spürbare verzögerung habe.

    Meine Datenbank besteht circa aus 30 AlarmDatensätzen und circa 2 Alarme haben circa 2 SendItems,
    also kann mann nicht wirklich von einem Datenvolumen sprechen.
    Was kann ich hier Falsch machen.
    Die zweite Schleife habe ich auch mal durch eine 2 Linq abfrage erstellen lassen da dauert aber
    jeder Testversuch circa 7 sec.


    Ich habe auch mal kurz ein Tool zum zeit messen verwendet und bin glaube zum schluss gekommen
    dass die zeit in der ersten Schleife draufgeht als sie den enumerator holt (bin nicht sicher).
    (Und nur die erste Schleife alleine läuft super schnell nur in Verbindung mit der zweiten gibs probleme)

    Dim AlarmsList = From c In _db.Alarms Select c



    For Each MyAlarm In AlarmsList

    ' Dim SendItems = From v In _db.SendItems Select v
    For Each MySendItem In MyAlarm.SendItems
    MsgBox("1")
    Next


    Next


    Windows 7
    Visual Studio 2008

    danke für Hilfe
    Freitag, 2. Oktober 2009 23:32

Alle Antworten

  • Hallo,

    was die Fehlermeldungen angeht:
    Welche sind das und um sie beurteilen zu können müsste man den Code (ausschnittsweise) sehen.
    Oder noch besser, die Datenbankdefinition haben und die Anweisung mit der Du den Code generiert hast.

    Was die Ausführung angeht:
    Das mit dem Enumerator kommt schon hin.
    Linq führt die Abfrage erst wenn das Ergebnis erzeugt wird, und das ist in der Regel der Enumerator.

    Und die erste Abfrage ist schon deswegen langsamer, weil damit zum ersten Male die Datenbank
    angesprochen wird, denn die Erzeugung der SQL Anweisungen erfolgt noch aus den Metadaten.

    Bei geschachtelten Abfragen kann die LoadWith Option die Leistung verbessern.

    Wobei mir die 7 Sekunden (auch für Compact) schon ziemlich happig vorkommen.
    (Beim Testen solltest Du allerdings MsgBox durch ein Console.WriteLine oder ähnliches ersetzen)

    Gruß Elmar
    Samstag, 3. Oktober 2009 07:37
    Beantworter
  • Hallo

    Habe hier Screenshoots und Code mit Datenbank uploaded
    http://www.gigasize.com/get.php?d=vwwfbpoyhpf

    Das mit LoadWith werde ich noch testen kann aber fast nicht der grund sein.

    Achja habe das erste mal eine SQL Compact Datenbank erstellt, vieleicht habe ich irgent einen Typische fehler gemacht
    Samstag, 3. Oktober 2009 10:05
  • Hallo,

    günstiger wäre ein vollständiges Projekt gewesen.
    So bin ich erst heute dazu kommen, weil ich es erst einiges zusammenflicken mußte.

    Zunächst zum Problem mit den Fehlermeldungen:
    Das dürfte daraus resultieren das Du Dir sowohl eine Dbml wie eine Klasse generieren läßt.
    Visual Studio hilft dabei unbeabsichtigt aus und stellt für die Dbml-Datei eine Codegenerierung ein.
    Dadurch erhälst Du die Klassen zweimal.

    Es reicht in Deinem Falle die eine Zeile mit:
    SqlMetal.exe DBAlerts.sdf /language:vb /code:DBAlerts.vb /namespace:SQLCompact /pluralize
    dadurch verwendet Linq2Sql die Attribute für das Mapping.
    Oder entferne den Codegenerator in den Dateieigenschaften.

    Wenn Du ein externes Mapping verwenden willst, müsstest Du etwas verwenden wie:
    SqlMetal.exe DBAlerts.sdf /language:vb /code:DBAlerts.vb /map:DBAlerts.map /namespace:SQLCompact /pluralize
    Die Mapping Datei sollte dann auf "Eingebettete Ressource" eingegestellt werden.
    Siehe auch http://davidhayden.com/blog/dave/archive/2006/05/18/2947.aspx
    (unten im Code habe ich eine VB-Fassung eingebaut).

    Zur Geschwindigkeit:
    Ich konnte da nichts ungewöhnliches feststellen. Mit unten stehendem Code
    ergab sich als Zeitverlauf (auf einem Q9300) bei drei Durcläufen:
    • Loop 1: 1123 ms
    • Loop 2: 1351 ms
    • Loop 1: 8 ms
    • Loop 2: 1370 ms
    • Loop 1: 7 ms
    • Loop 2: 1370 ms
    Die längere Laufzeit beim ersten Male ergibt sich daraus, dass zunächst
    das Mapping (Attribute oder Xml) angewendet werden muß.
    Die zweite Schleife ist erwartungsgemäß langsamer, da sie jedesmal
    eine neue Abfrage auslöst.

    Gruß Elmar

    Der Testcode:
    Imports SQLCompact
    Imports System.IO
    Imports System.Data.Linq.Mapping
    
    Public Class Form1
    
        Private _db As SQLCompact.DBAlerts
    
        Public Sub New()
            InitializeComponent()
    
            ' DataContext während Laufzeit erhalten
            IntializeDb()
        End Sub
    
        Private Sub IntializeDb()
            _db = New SQLCompact.DBAlerts(My.Settings.DBAlertsConnectionString)
        End Sub
    
        ' Für eine Variante mit XmlMappingSource
        Private Sub InitializeDbWithMapping()
            Dim mappingSource As XmlMappingSource
            Const mappingSourceName As String = "DBAlerts.map"
            Using mappingStream As Stream = Me.GetType().Assembly.GetManifestResourceStream(mappingSourceName)
                mappingSource = XmlMappingSource.FromStream(mappingStream)
            End Using
            _db = New SQLCompact.DBAlerts(My.Settings.DBAlertsConnectionString, mappingSource)
        End Sub
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim AlarmsList = From c In _db.Alarms Select c
    
            Dim sw = Stopwatch.StartNew
            For Each MyAlarm In AlarmsList
                For Each MySendItem In MyAlarm.SendItems
                    Console.WriteLine("Item 1: {0} - {1}", MySendItem.IDSendItem, MySendItem.IDAlert)
                Next
            Next
            sw.Stop()
            Console.WriteLine("Loop 1: {0} ms", sw.ElapsedMilliseconds)
    
            sw = Stopwatch.StartNew
            For Each MyAlarm In AlarmsList
                Dim idAlert = MyAlarm.IDAlert
                Dim SendItems = From v In _db.SendItems Where v.IDAlert = idAlert Select v
                For Each MySendItem In SendItems
                    Console.WriteLine("Item 2: {0} - {1}", MySendItem.IDSendItem, MySendItem.IDAlert)
                Next
            Next
            sw.Stop()
            Console.WriteLine("Loop 2: {0} ms", sw.ElapsedMilliseconds)
    
            Console.WriteLine()
        End Sub
    End Class
    





    Montag, 5. Oktober 2009 09:16
    Beantworter
  • Hallo

    1000 Dank für deine Hilfe, habe das mit der doppelten Class gelösst.
    Mit der ersten Möglichkeit.

    Habe aber das mit der lese Zeit Problem immer noch nicht gelöst genau mit diesen
    Code

        Private _db As SQLCompact.DBAlerts
    
        Public Sub New()
            InitializeComponent()
    
            ' DataContext während Laufzeit erhalten
            IntializeDb()
        End Sub
    
        Private Sub IntializeDb()
            _db = New SQLCompact.DBAlerts(My.Settings.DBAlertsConnectionString)
        End Sub
    
        '' Für eine Variante mit XmlMappingSource
        'Private Sub InitializeDbWithMapping()
        '    Dim mappingSource As XmlMappingSource
        '    Const mappingSourceName As String = "DBAlerts.map"
        '    Using mappingStream As Stream = Me.GetType().Assembly.GetManifestResourceStream(mappingSourceName)
        '        mappingSource = XmlMappingSource.FromStream(mappingStream)
        '    End Using
        '    _db = New SQLCompact.DBAlerts(My.Settings.DBAlertsConnectionString, mappingSource)
        'End Sub
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim AlarmsList = From c In _db.Alarms Select c
    
            Dim sw = Stopwatch.StartNew
            For Each MyAlarm In AlarmsList
                For Each MySendItem In MyAlarm.SendItems
                    Console.WriteLine("Item 1: {0} - {1}", MySendItem.IDSendItem, MySendItem.IDAlert)
                Next
            Next
            sw.Stop()
            Console.WriteLine("Loop 1: {0} ms", sw.ElapsedMilliseconds)
    
            sw = Stopwatch.StartNew
            For Each MyAlarm In AlarmsList
                Dim idAlert = MyAlarm.IDAlert
                Dim SendItems = From v In _db.SendItems Where v.IDAlert = idAlert Select v
                For Each MySendItem In SendItems
                    Console.WriteLine("Item 2: {0} - {1}", MySendItem.IDSendItem, MySendItem.IDAlert)
                Next
            Next
            sw.Stop()
            Console.WriteLine("Loop 2: {0} ms", sw.ElapsedMilliseconds)
    
            Console.WriteLine()
        End Sub

    und habe diese Werte damit ermittelt P4 3GHz HT

    • Item 1: 40 - 57
    • Item 1: 39 - 58
    • Item 1: 47 - 152
    • Item 1: 48 - 153
    • Item 1: 46 - 154
    • Loop 1: 7044 ms
    • Item 2: 40 - 57
    • Item 2: 39 - 58
    • Item 2: 47 - 152
    • Item 2: 48 - 153
    • Item 2: 46 - 154
    • Loop 2: 7692 ms
    • Item 1: 40 - 57
    • Item 1: 39 - 58
    • Item 1: 47 - 152
    • Item 1: 48 - 153
    • Item 1: 46 - 154
    • Loop 1: 33 ms
    • Item 2: 40 - 57
    • Item 2: 39 - 58
    • Item 2: 47 - 152
    • Item 2: 48 - 153
    • Item 2: 46 - 154
    • Loop 2: 7760 ms
    • Item 1: 40 - 57
    • Item 1: 39 - 58
    • Item 1: 47 - 152
    • Item 1: 48 - 153
    • Item 1: 46 - 154
    • Loop 1: 33 ms
    • Item 2: 40 - 57
    • Item 2: 39 - 58
    • Item 2: 47 - 152
    • Item 2: 48 - 153
    • Item 2: 46 - 154
    • Loop 2: 7723 ms

    • Jetzt weiß ich nicht mehr weiter, ich habe aber ein frisch installiertes Windows 7.

      Irgenteine Idea?

    Dienstag, 6. Oktober 2009 17:15
  • Habe noch etwas gefunden http://www.singingeels.com/Articles/Improving_Performance_With_LINQ.aspx Mit dem hat es glaube ich auch nichts zu tun.
    Dienstag, 6. Oktober 2009 17:31
  • Ich habe nochmals etwas brobiert mit dem Code unten
    und habe alle Beziehungen von Tabellen zu Alarm gelöscht
    und habe mit Kopieren 168 Datenbunkte erstellt und habe diese Werte daraus bekommen

    • Item int: 274ms - 57
    • Item int: 330ms - 58
    • Loop 2: 7344 ms


    und mit keiner internen Schleife-Abfrage habe ich diese Werte

    • Loop 2: 292 ms
    • Loop 2: 52 ms
    • Loop 2: 36 ms

    jetzt stellt sich mir die Frage ob Sql compact so langsam ist?
    Denn eine Abfrage ca 52ms dauert * 168 einträge sind 8,4sec.


     

    sw = Stopwatch.StartNew
    
    For Each MyAlarm In AlarmsList
    
    Dim idAlert = MyAlarm.IDAlert
    
    Dim SendItems = From v In _db.SendItems Where v.IDAlert = idAlert Select v
    
    For Each MySendItem In SendItems
    
    Console.WriteLine("Item int: {0}ms - {1}", sw.ElapsedMilliseconds, MySendItem.IDAlert)
    
    Next
    
    Next
    
    sw.Stop()
    
    Console.WriteLine("Loop 2: {0} ms", sw.ElapsedMilliseconds)
    
    Console.WriteLine()
    

     

     

    Dienstag, 6. Oktober 2009 18:48
  • Hallo Gerry,

    SQL Compact ist im Durchsatz grob vergleichbar mit einer Access Datenbank.

    Zunächst wäre die erste Schleife immer vorzuziehen, denn setorientierte
    Befehle, die alle benötigten Daten abrufen, sind auch bei Standard-SQL vorzuziehen.

    Die zweite (Loop 2) hatte ich quasi als Worst Case eingebaut, denn mir waren 8ms einfach zu schnell ;-)
    Generell sollte man das eher vermeiden, denn dabei werden etliche Befehle
    mehr an die Datenbank gesendet. Und im Falle von Linq müssen die jedesmal
    erstellt werden.

    Mein Prozessor ist nun einige Tage neuer (Quad Core  - von dem hier einer genutzt wird, dafür weniger Taktrate),
    und vermutlich auch die Platte schneller (spielt bei dem Volumen aber kaum eine Rolle).
    Woraus sich die besseren Zeiten ergeben dürften.

    Das Äquivalent mit DataTable/ADO.NET für die zweite Variante liegt hier etwas unter 1000ms,
    Was also gut ein drittel Aufschlag für Linq2Sql (1370ms) bedeutet.
    Um Linq ebenfalls dort hinzubringen, kann man eine CompiledQuery verwwenden -
    damit ist Linq2SQL bei mir etwa 20ms langsamer.

    Unten Code zum Vergleichen und Testen auf Deiner Seite.

    Gruß Elmar

        ' Kompilierte Abfrage für Loop2
        Private Shared SendItemById As Func(Of SQLCompact.DBAlerts, Integer, IQueryable(Of SQLCompact.SendItem)) = _
            CompiledQuery.Compile(Of SQLCompact.DBAlerts, Integer, IQueryable(Of SQLCompact.SendItem))( _
                Function(context As SQLCompact.DBAlerts, idAlert As Integer) _
                    context.SendItems.Where(Function(i) i.IDAlert = idAlert))
        
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim AlarmsList = From c In _db.Alarms Select c
    
            Dim sw = Stopwatch.StartNew
            For Each MyAlarm In AlarmsList
                Dim idAlert = MyAlarm.IDAlert
                For Each MySendItem In SendItemById(_db, idAlert)
                    Console.WriteLine("Item 2: {0} - {1}", MySendItem.IDSendItem, MySendItem.IDAlert)
                Next
            Next
            sw.Stop()
            Console.WriteLine("Loop 2: {0} ms", sw.ElapsedMilliseconds)
    
            Console.WriteLine()
        End Sub
    
        Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
            ' Grobes Äquivalent für die zweite Schleife
            Using connection As New SqlCeConnection(My.Settings.DBAlertsConnectionString)
                ' Entspricht From c In _db.Alarms Select c
                Dim alarmTable As New DataTable("Alarm")
                Using alarmAdapter = New SqlCeDataAdapter("SELECT * FROM Alarm", connection)
                    alarmAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
                    alarmAdapter.MissingMappingAction = MissingMappingAction.Passthrough
    
                    alarmAdapter.Fill(alarmTable)
                End Using
    
                Dim sw = Stopwatch.StartNew
    
                ' Entspricht From v In _db.SendItems Where v.IDAlert = idAlert Select v
                Dim sendItemTable As New DataTable("SendItem")
                Using sendItemAdapter = New SqlCeDataAdapter("SELECT * FROM SendItem WHERE IDAlert=@IDAlert", connection)
                    sendItemAdapter.SelectCommand.Parameters.Add("@IDAlert", SqlDbType.Int)
    
                    sendItemAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
                    sendItemAdapter.MissingMappingAction = MissingMappingAction.Passthrough
                    sendItemAdapter.FillSchema(sendItemTable, SchemaType.Source)
    
                    For Each alarmRow As DataRow In alarmTable.Rows
                        sendItemAdapter.SelectCommand.Parameters("@IDAlert").Value = alarmRow("IDAlert")
                        sendItemTable.Clear()
                        sendItemAdapter.Fill(sendItemTable)
    
                        For Each sendItemRow As DataRow In sendItemTable.Rows
                            Console.WriteLine("Item 2: {0} - {1}", sendItemRow("IDSendItem"), sendItemRow("IDAlert"))
                        Next
                    Next
                End Using
    
                sw.Stop()
                Console.WriteLine("Loop 2: {0} ms", sw.ElapsedMilliseconds)
            End Using
            Console.WriteLine()
        End Sub
    




    Mittwoch, 7. Oktober 2009 09:48
    Beantworter
  • Hallo Elmar

    Meine Versuche

    • Erster Versuch Linq
    • Item 2: 40 - 57
    • Item 2: 39 - 58
    • Loop 2: 5862 ms
    • Zweiter versuch Linq
    • Item 2: 40 - 57
    • Item 2: 39 - 58
    • Loop 2: 5476 ms

     

    • Erster Versuch ADO.NET
    • Item 2: 40 - 57
    • Item 2: 39 - 58
    • Loop 2: 5341 ms
    • Zweiter Versuch ADO.NET
    • Item 2: 40 - 57
    • Item 2: 39 - 58
    • Loop 2: 5292 ms

    Beide Versuche mit
    168 Eintrage in alarmTable ohne Beziehungen zu weiter Tabellen. (habe geändert wie oben geschrieben)

    Wieso bin ich 5mal so langsam, so schlecht ist meine Cpu ja auch nicht?(cpu zu 70%  ausgelasten auf beiden (unechten HT) Cores)
    Aber das kann ja nicht normal sein das das so langsam ist.
    Denn ich glaube eine Access Datenbank ist hier ja noch um Welten besser.
    Da ist ja fast schon aus einer Textdatei auslesen schneller.

    Mittwoch, 7. Oktober 2009 16:14
  • Hallo Elmar
    Hast du noch eine Idea?
    Sonntag, 11. Oktober 2009 18:24
  • Hallo Gerry,

    ich werde morgen einen meiner älteren Rechner darauf ansetzen -
    am Wochenende war es geplant, hat aber nicht geklappt.

    Nur die Zeiten erscheinen mir generell zu hoch,
    um ausschließlich von unterschiedlicher Hardware herzurühren.

    Hast Du das .NET 3.5/VS 2008 Service Pack 1
    und vor allem hier auch SQL Server Compact 3.5 SP1 installiert?
    http://www.microsoft.com/Downloads/details.aspx?displaylang=de&FamilyID=dc614aee-7e1c-4881-9c32-3a6ce53384d9

    Welche Software läuft (Virenscanner, sonstige Hintergrund-Software)?
    Beende ggf. diese, insbesondere wenn der Speicher knapp ist.

    Erstelle aus dem Code auch mal ein Konsolenprogramm,
    und lasse es direkt ohne Debugging laufen (STRG+F5)
    bzw. beende VS, vor allem wenn wenig Speicher verfügbar ist.

    Gruß Elmar
    Sonntag, 11. Oktober 2009 20:48
    Beantworter
  • Hallo Elmar

    Hallo hab nochmal mit Console ohne Debugging getestet

    linq 5,3 ado.net 5,0 also circa alles gleich.

    Speicher nur zu 50% ausgelastet 1,5gb

    SQL Server Compact 3.5 SP1 ist installiert.
    .NET 3.5/VS 2008 Service Pack 1 ist installiert.

    Virenscanner ausgeschaltet bringt circa 0,1
    Habe noch eine Spezialsoftware installiert die eine SPS auf dem Pc darstellt (Beckhoff Twincat)
    das die Runtime vor Windows Startet aber die habe ich extra auf Stop gesetzt bringt auch nichts.
    Sonst keine großeren Programme installiert auser VS und Office.


    Montag, 12. Oktober 2009 19:36
  • Hallo Elmar

    Habe es jetzt auch auf einen Pentium D 3Ghz getestet
    dort habe ich 4 Sekunden

    Mittwoch, 14. Oktober 2009 09:05
  • Ich habe noch eine Frage zu diesen Tema.

    Eigentlich wollte ich mit dieser Funktion einen Treeview laden.
    Kann ich das irgentwie anders auch machen so das ich diesem Problem austellen kann.
    (Die Verschachtelten SQL Abfragen waren jeweils für die SubNodes from Tree)

    Donnerstag, 22. Oktober 2009 19:36
  • Hallo Elmar

    Hast du schon etwas testen können?
    Freitag, 13. November 2009 13:20