2016-10-19 3 views
3

Ich habe ein sehr seltsames Verhalten von MySQL realisiert, für das ich keine Erklärung habe.MySQL Performance - String vs Integer

Dies ist eine nicht allzu komplexe Abfrage:

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = 2 
    OR enr.Enricher4State = 2 
    OR enr.Enricher5State = 2 
    OR enr.Enricher9State = 2 
); 

Die Säulen Enricher3State, Enricher4State, Enricher5State, Enricher9State einen Index zu tun haben und sind von dem Datentyp int (11).

Jetzt habe ich versucht, diesen enricher [x] Staat in einen String zu ändern:

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = '2' 
    OR enr.Enricher4State = '2' 
    OR enr.Enricher5State = '2' 
    OR enr.Enricher9State = '2' 
); 

Jeder gesunder Menschenverstand würde sagen, dass die String-Variante die gleichen durchführen soll, oder langsamer, da der Datentyp der Spalte ist Ganzzahl!

Aber anscheinend ist das nicht der Fall!

Abfrage mit Integer-Notation (erster): 7.23048825s

Abfrage mit String-Notation (letzten): 5.22188450s

Wie Sie sehen können, gibt es einen riesigen Unterschied in der Leistung, auch wenn die Abfrage Die Kosten sind in beiden Fällen gleich.

Ich habe absolut keine Ahnung, wie dieser Unterschied passieren könnte - und wenn dies bedeutet, soll ich alle Anfragen in meinem Projekt ändern, um die Zeichenfolge-Notation ...

Ich bin mit MySQL Version 5.7.10


Laut Ihren Kommentaren habe ich alle Dienste deaktiviert, die in die Datenbank schreiben oder lesen und das Experiment wiederholen.

A) Die Ganzzahl Notation:

SET profiling=0; 
SET profiling=1; 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = 2 
    OR enr.Enricher4State = 2 
    OR enr.Enricher5State = 2 
    OR enr.Enricher9State = 2 
); 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = 2 
    OR enr.Enricher4State = 2 
    OR enr.Enricher5State = 2 
    OR enr.Enricher9State = 2 
); 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = 2 
    OR enr.Enricher4State = 2 
    OR enr.Enricher5State = 2 
    OR enr.Enricher9State = 2 
); 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = 2 
    OR enr.Enricher4State = 2 
    OR enr.Enricher5State = 2 
    OR enr.Enricher9State = 2 
); 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = 2 
    OR enr.Enricher4State = 2 
    OR enr.Enricher5State = 2 
    OR enr.Enricher9State = 2 
); 

    SHOW PROFILES; 

Ausführungszeit jeder Abfrage:

  • 6,42429325
  • 5,95059900
  • 6,34392825
  • 6,53041775
  • 6,69593450

B) Der String Notation:

SET profiling=0; 
SET profiling=1; 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = '2' 
    OR enr.Enricher4State = '2' 
    OR enr.Enricher5State = '2' 
    OR enr.Enricher9State = '2' 
); 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = '2' 
    OR enr.Enricher4State = '2' 
    OR enr.Enricher5State = '2' 
    OR enr.Enricher9State = '2' 
); 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = '2' 
    OR enr.Enricher4State = '2' 
    OR enr.Enricher5State = '2' 
    OR enr.Enricher9State = '2' 
); 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = '2' 
    OR enr.Enricher4State = '2' 
    OR enr.Enricher5State = '2' 
    OR enr.Enricher9State = '2' 
); 

SELECT 
    COUNT(*) 
FROM 
    incidents.incidents AS inc, 
    incidents.enrichment AS enr 
WHERE 
    inc.Id <= 606734 
    AND inc.Id >= 1 
    AND inc.Id = enr.ParentTableId 
    AND (
    enr.Enricher3State = '2' 
    OR enr.Enricher4State = '2' 
    OR enr.Enricher5State = '2' 
    OR enr.Enricher9State = '2' 
); 

    SHOW PROFILES; 

Ausführungszeit:

  • 5,07188875
  • 4,90356250
  • 4,86164300
  • 4,48403375
  • 5,06533725

Wie Sie deutlich sehen können, ist die String-Notation noch schneller!

Das gleiche Verhalten auch von anderen Entwicklern von meinem Team erkannt wurde, so konnte ich vorübergehend Dummheit von mir ausschließen ...

+1

Wahrscheinlich der InnoDB Puffer-Cache bei der Arbeit. Versuchen Sie es erneut, dieses Mal jede Abfrage zweimal hintereinander auszuführen. Verwerfen Sie das erste Ergebnis, melden Sie das zweite Ergebnis jeder Abfrage. Das heißt, Integer-Abfrage ausführen, Integer-Abfrage (dies melden), String-Abfrage, String-Abfrage (dies melden). Wenn die Ergebnisse immer noch variieren, bis zu jeweils 4, verwerfen Sie den ersten Stand und mitteln dann die verbleibenden 3 für jede Abfrage. –

+1

Auch sollte es keine andere Aktivität in der Datenbank geben, um wirklich Äpfel mit Äpfeln zu vergleichen. –

+0

Ich habe den Test entsprechend Ihrer Eingabe modifiziert - aber das Ergebnis ist immer noch das gleiche. – Andreas

Antwort

2

Da die Felder indiziert sind, und Sie haben OR-Bedingung und Abfrage integer als Bedingung konstant haben, MySQL kann Zeit mit der Berechnung von Index-Joins verbringen und dann Tabellen-Scans und mit String-Konstanten durchführen. MySQL führt keine Indexüberlegungen durch und tut nur den Tabellen-Scan.

Es ist der Fall, wenn Indizes für viele Felder, die im OR-Zustand verwendet werden, nicht von Vorteil sind, sondern für MySQL.

Die OR-Bedingung gewährt keine erforderliche Indexierung der teilnehmenden Felder, oft mit Indizes auf die "1,2,3,4" Felder ist schlecht für die Tabelle. Diese Felder sollten in eine separate Tabelle ausgelagert werden.

Hinzugefügt: Führen Sie EXPLAIN und wenn Sie sehen, Indizes "1,2,3,4" -Felder für die betrachteten Schlüssel aufgelistet, das ist, was MySQL die Zeit verbringt.

2

In Anbetracht der Antwort von Sergiy Tytarenko, habe ich die Indizes auf den Spalten Enricher [x] State gelöscht.

Ausführungszeit für ganzzahlige Notation:

  • 4,93739900
  • 5,01461550
  • 5,05932075
  • 5,02891175
  • 5,02525075

Ausführungszeit für Streich Notation:

  • 5,04365650
  • 5,07545950
  • 5,12358825
  • 5,14665200
  • 5,15426525

Die Ausführungszeit ist über die jetzt gleich.

In der Tat, wenn mehrere Indizes auf Spalten mit OR verbunden sind, sollte darauf geachtet werden.

Es scheint, ich habe versehentlich eine schöne Abhilfe (außer dem Löschen des Index) entdeckt, durch eine Zeichenfolge aus einer ganzen Zahl zu machen ...

+0

Ja, es ist eine nette Problemumgehung, aber Sie müssen sich darauf verlassen, dass Entwickler es befolgen und Ihr Framework, um Anführungszeichen bei der Vorbereitung der Abfragen zu verwenden. In anderen Fällen (wie eine einzelne Bedingung oder Bedingungen mit AND) ist die Verwendung von Anführungszeichen für eine Ganzzahlkonstante ein großer Schmerz. Wenn man die Gründe für "1,2,3,4" -Felder auf dem Tisch betrachtet, sollten diese nicht indiziert werden (und oft kann man die Tabelle nicht einfach neu gestalten, um sie zu vermeiden). –

+0

Persönlich würde ich versuchen, die Abfrage anders zu schreiben, indem ich 'OR''ing der Prädikate vermeiden würde, weil der MySQL-Optimierer dazu neigt, keine optimalen Ausführungspläne für Abfragen zu erzeugen, die ODER-Prädikate haben. – spencer7593

0

Vorausgesetzt, dass Sie jede der Abfragen mehrfach ausgeführt haben, und geworfen aus den Ergebnissen der ersten Ausführung sehen wir einen signifikanten Unterschied in durchschnittliche Ausführungszeit.

Der Leistungsunterschied ist wahrscheinlich auf einen Unterschied im Ausführungsplan zurückzuführen.

Ich würde die Ausgabe von EXPLAIN EXTENDED für beide Abfragen genau unter die Lupe nehmen. Sehr wahrscheinlich, dass die Ausführungspläne in irgendeiner Weise unterschiedlich sind (welche Indizes verwendet werden, die Reihenfolge der Operationen usw.)

Meine Beobachtungen ... MySQL Query Optimizer und Abfragen mit OR Bedingungen ...Die Abfragepläne sind nicht optimal. Um eine bessere Leistung zu erzielen, muss ich normalerweise die Abfrage aufbrechen und UNION ALL set-Operationen verwenden.

Für eine „count“ bekommen, würde ich geneigt sein, die Abfrage wie folgt zu schreiben:

SELECT SUM(2 IN (enr.enricher3state, 
        enr.enricher4state, 
        enr.enricher5state, 
        enr.enricher9state)) 
    FROM incidents.incidents inc 
    JOIN incidents.enrichment enr 
     ON enr.parenttableid = inc.id 
    WHERE inc.id <= 606734 
    AND inc.id >= 1 

Ich würde sicher sein, einen abdeckenden Index zur Verfügung zu haben, z.B.

ON enrichment (parenttableid, enricher3state, enricher4state, 
           enricher5state, enricher9state) 

(oder jeder Index mit parenttableid als führende Säule, die auch die anderen vier Spalten enthält)

Dann würde ich überprüfen die EXTENDED Ausgang und Leistung erklären.

0

Vergleich zu Zahlen

char = 123 -- slow because it converts the char to numeric; can't use index 
char = '123' -- fine 
int = 123 -- fine 
int = '123' -- fine - because '123' is converted to numeric up front 

Fazit: Es ist immer sicherer Konstanten zu zitieren.

OR

OR ist im Wesentlichen un-optimierbar. Folgendes kann jedoch den gleichen Effekt haben, aber schneller ...

Eine allgemeine Regel in schema design: "Spreizen Sie nicht ein Array von Dingen über Spalten." Erstellen Sie stattdessen eine andere Tabelle und richten Sie eine 1: Viele-Beziehung zwischen ihnen ein. Diese kann die beste Lösung für die Leistung sein.

Verwenden Sie bitte JOIN ... ON ... Syntax, nicht die 'Kommajoin'.

Profil

5.6.7 sagt: "Die Show PROFILE und SHOW-Anweisungen PROFILE anstelle des Performance-Schema verwenden,.. Siehe MySQL Performance-Schema"

Indexing

Es ist selten, dass ein Index für eine Low-Mächtigkeit Säule, nützlich zu sein, so wie das, was ich Enricher3State erwarten ist.

IN vs ODER

2 IN (...) gegen ..=2 OR ..=2 OR... - das wahrscheinlich nicht viel Unterschied machen. Kein Index kann verwendet werden; beide beinhalten eine gewisse Komplexität.

Mehr Infos

Need SHOW CREATE TABLE für beide Tabellen zu sehen.
Notwendigkeit zu sehen EXPLAIN SELECT ....