mixmastertobsi: MySQL Abfrage

Hallo,

ich habe eine MySQL Abfrage, welche ich etwas beschleunigen möchte. Ich versuche diese einfach darzustellen und hoffe, dass ihr versteht, was ich möchte.

Ich habe eine Artikel-Datenbank und möchte, dass manche Artikel nicht angezeigt werden, wenn andere Artikel dafür angezeigt werden. Die Abfrage funktioniert, aber bei mehr als 10.000 Artikel ist die Abfrage nicht die schnellste und ich versuche diese etwas zu optimieren.

Die Abfrage aktuell sieht (ähnlich) wie folgt aus

SELECT * FROM artikel
LEFT JOIN artikel_info ON artikel_info.aid=artikel.aid AND artikel_info.value IN (
SELECT artikel.aid FROM artikel
WHERE artikel.kategorie LIKE '10%'
)
WHERE artikel.kategorie LIKE '10%' AND artikel.aid IS NULL
LIMIT 10

Ich lade also in der Datenbank also zwei mal die ganzen Artikelnummern und gleiche dann ab, ob diese in artikel_info.value vorkommen.

Index ist auf artikel_info.aid und artikel_info.value gesetzt

  1. Tach!

    Die Abfrage funktioniert, aber bei mehr als 10.000 Artikel ist die Abfrage nicht die schnellste und ich versuche diese etwas zu optimieren.

    EXPLAIN zeigt mögliche Schwachstellen an, wie beispielsweise, dass kein Index verwendet werden kann sondern ein Full-Table-Scan herhalten muss, was dann langsam wird.

    Vielleicht hilft schon ein Index auf der Kategorie. Zum Optimieren reicht nicht nur ein Blick auf das Statement, man muss auch die Tabellenstruktur und vor allem die Index kennen.

    dedlfix.

    1. Ein Index-Problem habe ich nicht, sondern vielmehr die Frage, ob es sein muss, dass die DB zwei mal in der Tabelle Artikel suchen muss. Einmal im Join und dann im Ergbeniss.

      1. Hallo mixmastertobsi,

        da Du die Artikel-Tabelle nach verschiedenen Kriterien durchsuchst, wird ein doppelter Zugriff unumgänglich sein. Aber ich muss zugeben, dass ich dein SQL Statement und deinen fachlichen Wunsch nicht recht verstehe.

        Du verwendet diese folgenden Tabellen und Spalten. T ist dein Typ für die aid Spalte, den hast Du nicht angegeben, aber das ist ja auch wurscht. Ich schreibe einfach T.

        Tabelle artikel: 
        Spalte aid - nullable, T
        Spalte kategorie - String
        
        Tabelle artikel_info
        Spalte aid - T
        Spalte value - T
        

        Diese beiden Tabellen verknüpfst Du per LEFT JOIN, d.h. du nimmst alle Zeilen aus der Artikel-Tabelle und erweiterst sie bei Zutreffen der Join-Bedingung um die Spalten aus der Artikel_Info Tabelle.

        Die Join-Bedingung trifft zu, wenn Artikel und Artikel_Info die gleiche AID haben, UND wenn die Spalte value die AID eines Artikels mit Kategorie 10% hat. Dieser innere SELECT würde definitiv von einem Index auf artikel.kategorie profitieren. Ebenso der WHERE des äußeren SELECT. Ohne Index auf artikel.kategorie führen diese SELECTs zu Table Scans auf die 10000 Zeilen der Artikel-Tabelle, mit Index ist es ein Index-Scan.

        Das Ganze filterst Du dann und suchst nur die Artikel, deren Kategorie mit 10 beginnt und wo die AID null ist. Das finde ich schon mal merkwürdig - AID klingt nach "Artikel-ID" und die kann null sein? Sind das vorläufige Artikel, die noch keine ID haben?

        Ein "NOT" oder "<>" sehe ich nirgends, insofern ist mir nicht ganz klar, wie das die Anforderung „dass manche Artikel nicht angezeigt werden, wenn andere Artikel...angezeigt werden“ erfüllen kann.

        Die Frage ist auch, ob eine IN Klausel hier die richtige Lösung ist. IN bestimmt alle AID aus Artikel zur Kategorie 10%, ist also ein Mengen-Select. Aber eigentlich hast Du doch in artikel_info.value eine einzelne aid, gelle, und willst wissen, ob diese aid die Kategorie '10%' hat. Da bietet sich eher eine EXISTS-Abfrage an, die fix ist, wenn Du einen Index auf artikel.aid liegen hast.

        SELECT *
        FROM artikel a1
             LEFT JOIN artikel_info inf 
                  ON  a1.aid = inf.aid
                  AND EXISTS (SELECT * FROM artikel a2 WHERE a2.aid=inf.value AND a2.kategorie like '10%')
        WHERE ...
        

        Trotzdem muss er aber zumindest für alle Sätze den Subselect machen, die einen artikel_info Eintrag haben. Ob er so schlau ist, den Subselect wegzulassen wenn eine aid keinen Eintrag in artikel_info hat, weiß ich nicht, würde es aber erwarten.

        Noch ein Gedanke: je nachdem, wie oft Du diese Abfrage am Tag machst und wie oft sich die Kategorie zu einer AID ändert, kann es sich rentieren, in artikel_info eine Zusatzspalte einzuführen, die die Kategorie zur AID redundant gespeichert ist. Das musst Du natürlich synchron halten - entweder in deinem Programm bei jedem Update auf artikel.kategorie, oder in einem Batchjob. Wie gesagt: KANN sich rentieren. Redundanzen sind zwar Schmiere für die Datenbank, aber keiner macht sich gern die Finger damit fettig.

        Rolf

        --
        sumpsi - posui - clusi
        1. Tach!

          AID klingt nach "Artikel-ID" und die kann null sein?

          Vermutlich soll das artikel_info.aid sein, die ist dann NULL, wenn der Left Join keinen Datensatz in artikel_info findet.

          dedlfix.

          1. Hallo dedlfix,

            das wäre ungeschickt, weil er dann mit einem INNER JOIN statt einem LEFT JOIN viel besser bedient wäre.

            Rolf

            --
            sumpsi - posui - clusi
  2. Bei like greift kein Index. Wenn ein Index greifen soll, muß der ganz genau passen. MfG

    1. Tach!

      Bei like greift kein Index. Wenn ein Index greifen soll, muß der ganz genau passen.

      Bei LIKE 'joker_hinten%' kann ein Index verwendet werden, nicht jedoch bei LIKE '%joker_vorn'.

      dedlfix.