2017-09-15 1 views
2

ich diese Auswahlabfrage haben, ItemType varchar Typ ist und ItemComments ist int-Typ:MySQL SELECT-Abfrage wird ziemlich langsam mit beiden, wo und absteigender Reihenfolge

select * from ItemInfo where ItemType="item_type" order by ItemComments desc limit 1 

Sie können sehen diese Abfrage hat 3 Bedingungen:

  1. wobei 'ItemType' gleich einem bestimmten Wert ist;
  2. Auftrag von ‚ItemComments‘
  3. mit absteigender Reihenfolge

Die interessante Sache ist, wenn ich Zeilen mit allen drei Bedingungen wählen, ist es sehr langsam wird. Aber wenn ich eine der drei abbringe (außer Bedingung 2), läuft die Abfrage ziemlich schnell. Siehe:

select * from ItemInfo where ItemType="item_type" order by ItemComments desc limit 1; 
/* Affected rows: 0 Found rows: 1 Warnings: 0 Duration for 1 query: 16.318 sec. */ 

select * from ItemInfo where ItemType="item_type" order by ItemComments limit 1; 
/* Affected rows: 0 Found rows: 1 Warnings: 0 Duration for 1 query: 0.140 sec. */ 

select * from ItemInfo order by ItemComments desc limit 1; 
/* Affected rows: 0 Found rows: 1 Warnings: 0 Duration for 1 query: 0.015 sec. */ 

Plus

  1. Ich bin mit MySQL 5.7 mit InnoDB-Engine.
  2. Ich habe Indizes für ItemType und ItemComments erstellt und Tabelle ItemInfo enthält 2 Millionen Zeilen.

Ich habe viele mögliche Erklärungen wie MySQL Unterstützung für absteigende Index, Composite-Index und so weiter gesucht. Aber diese können immer noch nicht erklären, warum die Abfrage # 1 langsam abläuft, während die Abfrage # 2 und # 3 gut läuft.

Es wäre sehr geschätzt, wenn mir jemand helfen könnte.

Updates: Tabelle erstellen und erläutern info

erstellen Code:

CREATE TABLE `ItemInfo` (
`ItemID` VARCHAR(255) NOT NULL, 
`ItemType` VARCHAR(255) NOT NULL, 
`ItemPics` VARCHAR(255) NULL DEFAULT '0', 
`ItemName` VARCHAR(255) NULL DEFAULT '0', 
`ItemComments` INT(50) NULL DEFAULT '0', 
`ItemScore` DECIMAL(10,1) NULL DEFAULT '0.0', 
`ItemPrice` DECIMAL(20,2) NULL DEFAULT '0.00', 
`ItemDate` DATETIME NULL DEFAULT '1971-01-01 00:00:00', 
PRIMARY KEY (`ItemID`, `ItemType`), 
INDEX `ItemDate` (`ItemDate`), 
INDEX `ItemComments` (`ItemComments`), 
INDEX `ItemType` (`ItemType`) 
) 
COLLATE='utf8_general_ci' 
ENGINE=InnoDB; 

Erklären Ergebnis:

mysql> explain select * from ItemInfo where ItemType="item_type" order by ItemComments desc limit 1; 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+ 
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref | rows | filtered | Extra  | 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+ 
| 1 | SIMPLE  | i  | NULL  | index | ItemType  | ItemComments | 5  | NULL | 83 |  1.20 | Using where | 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+ 

mysql> explain select * from ItemInfo where ItemType="item_type" order by ItemComments limit 1; 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+ 
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref | rows | filtered | Extra  | 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+ 
| 1 | SIMPLE  | i  | NULL  | index | ItemType  | ItemComments | 5  | NULL | 83 |  1.20 | Using where | 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+ 

mysql> explain select * from ItemInfo order by ItemComments desc limit 1; 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------+ 
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref | rows | filtered | Extra | 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------+ 
| 1 | SIMPLE  | i  | NULL  | index | NULL   | ItemComments | 5  | NULL | 1 | 100.00 | NULL | 
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------+ 

Abfrage von O. Jones:

mysql> explain 
    -> SELECT a.* 
    ->  FROM ItemInfo a 
    ->  JOIN (
    ->    SELECT MAX(ItemComments) ItemComments, ItemType 
    ->    FROM ItemInfo 
    ->    GROUP BY ItemType 
    ->   ) maxcomm ON a.ItemType = maxcomm.ItemType 
    ->     AND a.ItemComments = maxcomm.ItemComments 
    ->  WHERE a.ItemType = 'item_type'; 
+----+-------------+------------+------------+-------+----------------------------------------+-------------+---------+---------------------------+---------+----------+--------------------------+ 
| id | select_type | table  | partitions | type | possible_keys       | key   | key_len | ref      | rows | filtered | Extra     | 
+----+-------------+------------+------------+-------+----------------------------------------+-------------+---------+---------------------------+---------+----------+--------------------------+ 
| 1 | PRIMARY  | a   | NULL  | ref | ItemComments,ItemType     | ItemType | 767  | const      | 27378 | 100.00 | Using where    | 
| 1 | PRIMARY  | <derived2> | NULL  | ref | <auto_key0>       | <auto_key0> | 772  | mydb.a.ItemComments,const |  10 | 100.00 | Using where; Using index | 
| 2 | DERIVED  | ItemInfo | NULL  | index | PRIMARY,ItemDate,ItemComments,ItemType | ItemType | 767  | NULL      | 2289466 | 100.00 | NULL      | 
+----+-------------+------------+------------+-------+----------------------------------------+-------------+---------+---------------------------+---------+----------+--------------------------+ 

Ich bin mir nicht sicher, ob ich diese Abfrage richtig ausführen, aber ich konnte die Datensätze nicht recht lange erhalten.

Anfrage von Vijay. Aber ich hinzufügen ItemType Zustand Ursache kommt mit nur Objekte aus anderen ItemType zurückkehren max_comnt:

SELECT ifo.* FROM ItemInfo ifo 
JOIN (SELECT ItemType, MAX(ItemComments) AS max_comnt FROM ItemInfo WHERE ItemType="item_type") inn_ifo 
ON ifo.ItemComments = inn_ifo.max_comnt and ifo.ItemType = inn_ifo.ItemType 
/* Affected rows: 0 Found rows: 1 Warnings: 0 Duration for 1 query: 7.441 sec. */ 

explain result: 
+----+-------------+------------+------------+-------------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------------------------------------+ 
| id | select_type | table  | partitions | type  | possible_keys   | key     | key_len | ref | rows | filtered | Extra            | 
+----+-------------+------------+------------+-------------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------------------------------------+ 
| 1 | PRIMARY  | <derived2> | NULL  | system  | NULL     | NULL     | NULL | NULL |  1 | 100.00 | NULL            | 
| 1 | PRIMARY  | ifo  | NULL  | index_merge | ItemComments,ItemType | ItemComments,ItemType | 5,767 | NULL | 88 | 100.00 | Using intersect(ItemComments,ItemType); Using where | 
| 2 | DERIVED  | ItemInfo | NULL  | ref   | ItemType    | ItemType    | 767  | const | 27378 | 100.00 | NULL            | 
+----+-------------+------------+------------+-------------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------------------------------------+ 

Und ich mag erklären, warum ich um mit Limit an erster Stelle verwenden: Ich habe geplant Datensatz aus der Tabelle zu holen zufällig mit eine bestimmte Wahrscheinlichkeit. Der Zufallsindex, der aus Python generiert und an MySQL als Variable gesendet wird. Aber dann fand ich, dass es so viel Zeit kostete, also entschied ich mich, einfach die erste Platte zu verwenden, die ich bekam.

Nach der Inspiration von O.Jones und Vijay, ich versuchte, mit Max-Funktion, aber es führt nicht gut:

select max(ItemComments) from ItemInfo where ItemType='item_type' 
/* Affected rows: 0 Found rows: 1 Warnings: 0 Duration for 1 query: 6.225 sec. */ 

explain result: 
+----+-------------+------------+------------+------+---------------+----------+---------+-------+-------+----------+-------+ 
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref | rows | filtered | Extra | 
+----+-------------+------------+------------+------+---------------+----------+---------+-------+-------+----------+-------+ 
| 1 | SIMPLE  | ItemInfo | NULL  | ref | ItemType  | ItemType | 767  | const | 27378 | 100.00 | NULL | 
+----+-------------+------------+------------+------+---------------+----------+---------+-------+-------+----------+-------+ 

Vielen Dank für diese Frage beitragen. Ich hoffe, Sie könnten mehr Lösungen basierend auf den oben genannten Informationen bringen.

+0

Können Sie ein EXPLAIN zeigen? –

Antwort

1

Bitte geben Sie CURRENT SHOW CREATE TABLE ItemInfo an.

Für die meisten dieser Abfragen, müssen Sie den Composite-Index

INDEX(ItemType, ItemComments) 

Für das letzte, was Sie brauchen

INDEX(ItemComments) 

Für die besonders langsame Abfragen, bitte EXPLAIN SELECT ... liefern.

Diskussion - Warum hilft INDEX(ItemType, ItemComments) mit where ItemType="item_type" order by ItemComments desc limit 1?

Ein Index ist in einer BTree (siehe Wikipedia) strukturiert, wodurch die Suche nach einem einzelnen Objekt sehr schnell ist und das Scannen in einer bestimmten Reihenfolge sehr schnell ist.

where ItemType="item_type" sagt, auf ItemType zu filtern, aber es gibt eine Menge solcher im Index. In diesem Index sind sie geordnet nach ItemComments (für eine gegebene ItemType). Die Richtung desc schlägt vor, mit dem höchsten Wert von ItemContents zu beginnen; Das ist das "Ende" der Indexelemente. Schließlich sagt limit 1 zu stoppen, nachdem ein Element gefunden wurde. (. Etwas wie die letzten „S“ in Ihrem Rolodex zu finden)

So ist die Abfrage zu ‚Drill-Down‘ die BTree bis zum Ende der Einträge für ItemType im Verbund INDEX(ItemType, ItemContents) und einen Eintrag greifen - ein sehr effizienten Aufgabe.

Eigentlich bedeutet SELECT *, dass es einen weiteren Schritt gibt, nämlich alle Spalten für diese eine Zeile zu bekommen. Diese Information ist nicht im Index, sondern in der BTree für ItemInfo - die alle Spalten für alle Zeilen enthält, geordnet nach PRIMARY KEY.

Die „Sekundärindex“ (INDEX(ItemType, ItemComments)) enthält implizit eine Kopie der betreffenden PRIMARY KEY Spalten, so haben wir nun die Werte von ItemID und ItemType. Mit diesen können wir diesen anderen BTree aufreißen, um die gewünschte Zeile zu finden und alle Spalten (*) abzurufen.

+0

Ich fügte einen zusammengesetzten Index hinzu, wie Sie angegeben haben, und das hat funktioniert! Die erste Abfrageausführung wird sehr schnell. Aber könnten Sie bitte erklären, wie es funktioniert? –

+0

Lesen Sie http://mysql.rjweb.org/doc.php/index_cookbook_mysql. Es gibt Ihnen vielleicht nicht die Erklärung, die Sie wünschen, aber es sollte Ihnen mehr Anhaltspunkte geben. –

+0

@BarryZhai - hinzugefügt eine ausführliche, ausführliche Diskussion darüber. –

1

Ihre erste Abfrage Reihenfolge aufsteigend kann Ihren Index auf ItemComment nutzen.

SELECT * ... ORDER BY ... LIMIT 1 ist eine notorische Leistung Antipattern. Warum? Der Server muss eine ganze Reihe von Zeilen sortieren, nur um alle außer den ersten zu verwerfen.

Sie könnten dies versuchen (für Ihre absteigende Reihenfolge Variante). Es ist ein wenig ausführlicher, aber viel effizienter.

SELECT a.* 
    FROM ItemInfo a 
    JOIN (
      SELECT MAX(ItemComments) ItemComments, ItemType 
       FROM ItemInfo 
      GROUP BY ItemType 
     ) maxcomm ON a.ItemType = maxcomm.ItemType 
        AND a.ItemComments = maxcomm.ItemComments 
    WHERE a.ItemType = 'item type' 

Warum funktioniert das? Es verwendet GROUP BY/MAX(), um den maximalen Wert statt ORDER BY ... DESC LIMIT 1 zu finden. Die Unterabfrage führt Ihre Suche aus.

Damit dies so effizient wie möglich funktioniert, benötigen Sie einen zusammengesetzten (mehrspaltigen) Index unter (ItemType, ItemComments). Erstellen Sie, dass mit

ALTER TABLE ItemInfo CREATE INDEX ItemTypeCommentIndex (ItemType, ItemComments); 

Wenn Sie den neuen Index zu erstellen, lassen Sie Ihren Index auf ItemType, weil der neue Index mit, dass ein redundant ist.

Der MySQL-Abfrageplaner ist intelligent genug, um die äußere WHERE-Klausel zu sehen, bevor die innere GROUP BY-Abfrage ausgeführt wird, sodass die gesamte Tabelle nicht aggregiert werden muss.

Mit diesem zusammengesetzten Index kann MySQL loose index scan verwenden, um die Unterabfrage zu erfüllen. Diese sind fast wundersam schnell. Sie sollten das Thema lesen.

0

Ihre Abfrage wählt alle Zeilen basierend auf der WHERE-Bedingung aus. Danach werden die Zeilen nach der Anweisung sortiert, danach wird die erste Zeile ausgewählt. Eine bessere Abfrage wäre so etwas wie

SELECT ifo.* FROM ItemInfo ifo 
JOIN (SELECT MAX(ItemComments) AS max_comnt FROM ItemInfo WHERE ItemType="item_type") inn_ifo 
ON ifo.ItemComments = inn_ifo.max_comnt 

Da diese Abfrage findet nur Maximalwert aus der Säule. Das Finden von MAX() ist nur O (n), aber der schnellste Algorithmus zum Sortieren ist O (nlogn). Also, wenn Sie die Reihenfolge von Statemet vermeiden, wird die Abfrage schneller ausführen. Hoffe das half.

Verwandte Themen