none
SQL Count Abfrage beschleunigen RRS feed

  • Allgemeine Diskussion

  • Hallo,

    ich speichere Messdaten in einer SQL Tabelle ab und überprüfe anschließen mit "select count(*) from myTable where Messungsnummer like 'xyz' " ob 7500 Datensätze zur Messung abgespeichert wurden. 

    Ist die Tabelle noch ziemlich klein, dauert die ganze Überprüfung ca. 70 ms. Im Moment sind ca 2500 Messungen a 7500 Datensätzen darin gespeichert. Jetzt dauert die Abfrage ca 8 s, was zu lange ist. Ich habe mir die Dauer der Überprüfungsvorgang bei jedem Speichervorgang in einer Textdatei ausgeben lassen und je voller die Tabelle ist, desto länger dauert die Überprüfung. Es werden dieses Jahr noch ca. 10000 Messungen dazukommen.

    Wie kann ich sie beschleunigen? Ziel wären so 1-2 Sekunden Dauer. Ich habe einen Index auf die Spalte "Messungsnummer" gelegt.

    Danke schon mal im voraus.

    VG 

    Matthias

    Donnerstag, 28. Januar 2016 10:31

Alle Antworten

  • Hallo Matthias,

    Wie ist denn das Zeitverhalten wenn du

    Select Count(Messungsnummer) from mytable
    where Messungsnummer like 'xyZ'

    Zudem ist ein like als Vergleichsoperator nicht gerade sehr schnell.

    Gruß Benjamin


    Benjamin Hoch
    MCSE: Data Platform,
    MCSA: Windows Server 2012,

    Donnerstag, 28. Januar 2016 10:42
  • select count(*) from myTable where Messungsnummer like 'xyz'

    Hallo Matthias,

    wieso verwendest Du hier ein LIKE und kein =? Ist im Suchstring ein Wildcard enthalten? Wie sieht der Ausführungsplan der Abfrage aus? Wird während der Abfrage weiter Daten in die Tabelle geschrieben, was die Abfrage blockieren könnte?


    Olaf Helper

    [ Blog] [ Xing] [ MVP]

    Donnerstag, 28. Januar 2016 10:43
  • Hallo Benjamin

    danke für die schnelle Antwort.

    Ich kann es leider nicht sofort ausprobieren.

    Was wäre besser als like?

    Donnerstag, 28. Januar 2016 10:48
  • Hallo Olaf

    Danke für die schnelle Antwort.

    Nein es werden während der Abfrage keine Schreibvorgänge getätigt. Über like vs"=" habe ich mir noch keine Gedanken gemacht.

    Donnerstag, 28. Januar 2016 10:58
  • Hallo

    hier könnte man sich mal Clustered Columnstore Indexes ansehen. (SQL 2014)

    https://msdn.microsoft.com/de-de/library/gg492153.aspx

    Ansonsten müsste man sich das Schema und die darauf ablaufenden Prozesse ansehen, um zu sehen, was man da optimieren kann.


    Andreas Wolter (Blog | Twitter)
    MCSM: Microsoft Certified Solutions Master Data Platform, MCM, MVP
    www.SarpedonQualityLab.com | www.SQL-Server-Master-Class.com


    Donnerstag, 28. Januar 2016 11:28
  • Hast Du Dir denn mal den Ausführungsplan der Abfrage angesehen?

    Olaf Helper

    [ Blog] [ Xing] [ MVP]

    Donnerstag, 28. Januar 2016 12:19
  • Auch ein

    SET STATISTICS IO ON;
    SET STATISTICS TIME ON;

    vor der Abfrage kann Dir etwas mehr über IO und Zeiten verraten.

    Kann es u. a. sein, dass die Statistik des Index in dem Moment des Selects veraltet ist und die Messungsnummer fortlaufend vergeben wurde? Die letzte ist also die höchste Nummer.
    Dann könnte es sein, dass der Optimizer aus dem Index erschließt, dass es wahrscheinlich 0 Sätze zu der Messungsnummer gibt und geht direkt zu einem schlechteren Weg.

    Einen schönen Tag noch,
    Christoph
    --
    Data Platform MVP - http://www.insidesql.org/blogs/cmu

    Donnerstag, 28. Januar 2016 13:55
  • Nein, wie kann ich den ansehen? Im SQL Server Management Studio?

    Freitag, 29. Januar 2016 10:25
  • Ja, man kann sich den Ausführungsplan in SSMS Query Window anzeigen lassen: Anzeigen eines tatsächlichen Ausführungsplans

    Nur man braucht schon etwas Erfahrung, um den lesen zu können. Du kannst ein Screenshot posten oder Du kannst ihn uns auch als Datei bereit stellen: Im Plan Rechte Maus => Speichern als, da kommt dann eine .sqlplan Datei, was aber nur XML ist; die könntest Du uns z.B. auf Deinem OneDrive bereit stellen.


    Olaf Helper

    [ Blog] [ Xing] [ MVP]


    Freitag, 29. Januar 2016 10:42
  • Hallo Matthias,

    Dein Problem ist, dass Du keinen geeigneten Index für eine SEEK-Operation hast. Dein LIKE ist nicht problematisch, da Du keine Platzhalter verwendest. Der "Nachteil", wie von Benjamin Hoch angedeutet, ist hier zu vernachlässigen, da beide Varianten einen performanten INDEX SEEK verwenden. Das gilt aber nur so lange, wie die Verteilung der Daten möglichst gleichmässig ist (würde zu Deinem Problem aber deutlich zu weit führen).

    Andreas hat Dich schon auf den Weg von Columnstore gewiesen; Nachteil dieser Lösung ist, dass es ein "ENTERPRISE" Feature ist und erst ab SQL 2014 updateable ist. Das halte ich für einen wichtigen Punkt, da Du ja schreibst, dass Du noch Daten eintragen willst

    Die Lösung Deines Problems liegt in der richtigen Indexierung. Wenn Du ein COUNT(*) anwendest, sucht Microsoft SQL Server IMMER nach dem kleinsten Index. Der Query Optimizer ist cost based und berechnet die Optimierung unter anderem aus dem zu generierenden IO und den Kosten für die einzusetzenden Operatoren.

    Für ein besseres Verständnis des Vorgehens habe ich eine Testtabelle mit insgesamt 50 Messungen á 7500 Datensätze erstellt. Diese Tabelle hat einen - für das Beispiel - trivialen Aufbau:

    /* Tabelle mit Clustered Index erstellen */
    CREATE TABLE dbo.Messungen
    (
    	Id		int		NOT NULL	IDENTITY (1, 1),
    	MessNr		char(5)	NOT NULL,
    	MessWert	int,
    	Col1		char(300)	NOT NULL	DEFAULT ('Nur ein Lückenfüller')
    );
    GO
    
    CREATE UNIQUE CLUSTERED INDEX cix_Messungen_Id ON dbo.Messungen (Id) WITH DROP_EXISTING;
    GO

    Die Tabelle besitzt einen Clustered Index auf ID; sie ist also nach der ID sortiert. Insgesamt gibt es nur diesen EINEN Index, der die Tabelle selbst ist. Wird also ein Datensatz gelesen, so muss immer die komplette Datenzeile gelesen warden (daher der Hinweis von Andreas auf Columnstore). Wie Du unschwer aus der obigen Struktur entnehmen kannst, hat ein Datensatz eine "Daten"-Lange von 313 Bytes +5 Bytes als Overhead = 318 Bytes.

    Auf eine Datenseite können 8.060 Bytes gespeichert warden; somit gehen 25 Datensätze auf eine Datenseite. Die Daten warden also auf ca. 15.000 Datenseiten gespeichert!

    Mit der nachfolgenden Abfrage suche ich nach der Messung '00001'

    SET STATISTICS IO ON;
    GO
    
    SELECT Id, MessNr
    FROM dbo.Messungen
    WHERE	MessNr = '00001';

    Wenn Du dir das Ergebnis anschaust, werden die grob berechneten 15.000 Datenseiten vollständig durchsucht. Gleiches gilt natürlich, wenn Du statt Einzelwerte einen COUNT auf die Datenmenge ausführst!

    SET STATISTICS IO ON; GO SELECT MessNr, COUNT_BIG(*) AS Messungen FROM dbo.Messungen WHERE MessNr = '00001'
    GROUP BY MessNr
    OPTION (QUERYTRACEON 9130);

    Die Ursache ist relative einfach zu erklären. In beiden Fallen muss Microsoft SQL Server die vollständige Tabelle durchsuchen, da der EINZIGE Index auf der ID liegt und nicht der MessNr. (Ist so, als ob Du in einem Telefonbuch nach dem Vornamen suchst)

    Microsoft SQL Server muss für die Gruppierung erneut die vollständige Tabelle durchsuchen. Fügst Du also weitere Messungen hinzu, vergrößert sich das Volumen der zu durchsuchenden Datenmenge (Es müssen mehr Datenseiten durchsucht werden). Je "breiter" Deine Tabelle ist, umso weniger Datensätze passen auf eine Datenseite und NOCH MEHR Datenseiten müssen gelesen werden.

    Also, was macht man in diesem Fall? Man verwendet einen Index, der "schmaler" ist und somit weniger Datenseiten lessen muss. Idealerweise legt man diesen Index auf ein Attribut, nach dem man regelmäßig sucht (in diesem Beispiel also die MEssNr). Ein möglicher Index könnte wie folgt aussehen:

    CREATE NONCLUSTERED INDEX ix_Messungen_MessNr ON dbo.Messungen(MessNr);
    GO

    Durch die Erstellung des obigen Index habe ich zwei Fliegen mit einer Klappe geschlagen:

    • Auf einer Datenseite können nun MEHR Indexsätze gespeichert werden: 11 Bytes / Datensatz ~ 730 Datensätze
    • Das Feld, nach dem ich suche, ist indiziert und ein teurer SCAN kann durch einen SEEK ersetzt werden

    Führst Du die obige Abfrage nach der Anzahl der Messungen erneut aus, erhältst Du folgenden Ausführungsplan:

    Mit Hilfe eines geeigneten Index hast Du sowohl den Suchalgorithmus als auch das Datenvolumen eingeschränkt. Wäre in dem obigen Beispiel nicht nur ein Clustered Index vorhanden sondern noch ein Index auf dem Feld Col1, so hätte Microsoft SQL Server sich für den Index auf COL1 im ersten Beispiel entschieden, da ja 300 Bytes kleiner sind als 318 Bytes :)

    Ich habe zu diesem Thema mal geblogged. Vielleicht hilft Dir das Beispiel im Artikel etwas weiter:

    http://db-berater.blogspot.de/2013/06/aggregationsfunktionen-beschleunigen.html

    Zumindest zeigt er die Zusammenhänge etwas detaillierter als es diese Antwort vermag!


    MCM - SQL Server 2008
    MCSE - SQL Server 2012
    db Berater GmbH
    SQL Server Blog (german only)

    Freitag, 29. Januar 2016 13:35
  • Die letzte ist also die höchste Nummer.
    Dann könnte es sein, dass der Optimizer aus dem Index erschließt, dass es wahrscheinlich 0 Sätze zu der Messungsnummer gibt und geht direkt zu einem schlechteren Weg.

    Hallo Christoph,

    das ist aus meiner Sicht eher unwahrscheinlich und ist nur dann problematisch, wenn:

    • im SEEK ein Keylookup / RID Lookup generiert wird
    • ein JOIN (je nach Datenvolumen) zwischen einemm HASH/MERGE oder NESTED LOOP unterscheidet
    • Die Wahl zwischen n Indexe getroffen werden muss

    Ansonsten ist das "Ascending Key Problem" hier vollkommen irrelevant!

    Liebe Grüße aus Frankfurt, Uwe


    MCM - SQL Server 2008
    MCSE - SQL Server 2012
    db Berater GmbH
    SQL Server Blog (german only)


    Freitag, 29. Januar 2016 13:41
  • Hallo Uwe,

    Matthias hatte doch geschrieben: "Ich habe einen Index auf die Spalte "Messungsnummer" gelegt." Damit wäre doch schon Dein Vorschlag abgedeckt, oder?

    Ich befürchte, dass wir ohne genauere Kenntnis der Tabellenstruktur und der Indizes wirklich weit kommen.

    Einen schönen Tag noch,
    Christoph
    --
    Data Platform MVP - http://www.insidesql.org/blogs/cmu


    Einen schönen Tag noch, Christoph -- Data Platform MVP - http://www.insidesql.org/blogs/cmu

    Montag, 1. Februar 2016 09:41
  • Hallo Uwe,

    Matthias hatte doch geschrieben: "Ich habe einen Index auf die Spalte "Messungsnummer" gelegt." Damit wäre doch schon Dein Vorschlag abgedeckt, oder?

    Das habe ich doch glatt überlesen! :)

    Stimmt, wenn er einen Index auf Messungsnummer hat, dann MUSS dieser Index auch für einen SEEK verwendet werden. Es hilft nichts - wir brauchen den Execution Plan.

    Dennoch wird das von Dir erwähnte "Ascending Key" Problem nicht auftreten, da - egal ob 1 oder 1.000.000 DS immer ein Index SEEK verwendet wird. Problematisch wird das Verhalten erst, wenn:

    • Speicher für die Abfrage zugewiesen werden muss
    • Key Lookups verwendet werden müssen
    • JOIN Operatoren (HASH / MERGE) verwendet werden müssen

    Da es sich ja nur um ein schlichtes COUNT_BIG() handelt, fällt Option 3 raus. Da der Index auf Messungsnummer ebenfalls gesetzt ist, fällt Option 2 ebenfalls raus.

    Bleibt - aktuell - nur Option 1. Da aber ein STREAM AGGREGATE für die Gruppierung verwendet wird (meine Vermutung) scheidet auch Option 1 als Problem aus.

    However - warten wir mal ab, was der TE noch an Informationen für uns hat.


    MCM - SQL Server 2008
    MCSE - SQL Server 2012
    db Berater GmbH
    SQL Server Blog (german only)

    Montag, 1. Februar 2016 09:49