2014-09-05 4 views
5

Die Abfrage:Optimieren lange Abfrage in MySQL in einer enormen Tabellengröße 33M Reihen

SELECT users.id as uid, name, avatar, avatar_date, driver, messages.id AS mid,messages.msg, messages.removed, messages.from_anonym_id, messages.t 
    o_anonym_id, (messages.date DIV 1000) AS date, from_id = 162077 as outbox, !(0 in (SELECT read_state FROM messages as msgs 
WHERE (msgs.from_id = messages.from_id or msgs.from_id = messages.user_id) and msgs.user_id = 162077 and removed = 0)) as read_state 
FROM dialog, messages, users 
WHERE messages.id = dialog.mid and ((uid1 = 162077 and users.id = uid2) or (uid2 = 162077 and users.id = uid1)) 
ORDER BY dialog.mid DESC LIMIT 0, 101; 

Tabellen Struktur:

mysql> desc messages; 
+----------------+------------------+------+-----+---------+----------------+ 
| Field   | Type    | Null | Key | Default | Extra   | 
+----------------+------------------+------+-----+---------+----------------+ 
| id    | int(11)   | NO | PRI | NULL | auto_increment | 
| from_id  | int(11)   | NO | MUL | NULL |    | 
| user_id  | int(11)   | NO | MUL | NULL |    | 
| group_id  | int(11)   | NO |  | NULL |    | 
| to_number  | varchar(30)  | NO | MUL | NULL |    | 
| msg   | text    | NO |  | NULL |    | 
| image   | varchar(20)  | NO |  | NULL |    | 
| date   | bigint(20)  | NO |  | NULL |    | 
| read_state  | tinyint(1)  | NO |  | 0  |    | 
| removed  | tinyint(1)  | NO | MUL | NULL |    | 
| from_anonym_id | int(10) unsigned | NO | MUL | NULL |    | 
| to_anonym_id | int(10) unsigned | NO | MUL | NULL |    | 
+----------------+------------------+------+-----+---------+----------------+ 

mysql> desc dialog; 
+----------------+------------------+------+-----+---------+----------------+ 
| Field   | Type    | Null | Key | Default | Extra   | 
+----------------+------------------+------+-----+---------+----------------+ 
| id    | int(11)   | NO | PRI | NULL | auto_increment | 
| uid1   | int(11)   | NO | MUL | NULL |    | 
| uid2   | int(11)   | NO | MUL | NULL |    | 
| mid   | int(11)   | NO | MUL | NULL |    | 
| from_anonym_id | int(10) unsigned | NO | MUL | NULL |    | 
| to_anonym_id | int(10) unsigned | NO | MUL | NULL |    | 
+----------------+------------------+------+-----+---------+----------------+ 


mysql> show index from messages; 
+----------+------------+----------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| Table | Non_unique | Key_name  | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | 
+----------+------------+----------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| messages |   0 | PRIMARY  |   1 | id    | A   | 42944290 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | user_id_2  |   1 | user_id  | A   |  2147214 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | user_id_2  |   2 | read_state  | A   |  2862952 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | user_id_2  |   3 | removed  | A   |  2862952 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | from_id  |   1 | from_id  | A   |  825851 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | from_id  |   2 | to_number  | A   |  825851 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | to_number  |   1 | to_number  | A   |   29 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | idx_user_id |   1 | user_id  | A   |  2044966 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | idx_from_id |   1 | from_id  | A   |  447336 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | removed  |   1 | removed  | A   |   29 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | from_anonym_id |   1 | from_anonym_id | A   |   29 |  NULL | NULL |  | BTREE  |   |    | 
| messages |   1 | to_anonym_id |   1 | to_anonym_id | A   |   29 |  NULL | NULL |  | BTREE  |   |    | 
+----------+------------+----------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
12 rows in set (0.01 sec) 

mysql> show index from dialog; 
+--------+------------+----------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| Table | Non_unique | Key_name  | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | 
+--------+------------+----------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| dialog |   0 | PRIMARY  |   1 | id    | A   |  6378161 |  NULL | NULL |  | BTREE  |   |    | 
| dialog |   1 | uid1   |   1 | uid1   | A   |  455582 |  NULL | NULL |  | BTREE  |   |    | 
| dialog |   1 | uid1   |   2 | uid2   | A   |  6378161 |  NULL | NULL |  | BTREE  |   |    | 
| dialog |   1 | uid2   |   1 | uid2   | A   |  2126053 |  NULL | NULL |  | BTREE  |   |    | 
| dialog |   1 | idx_mid  |   1 | mid   | A   |  6378161 |  NULL | NULL |  | BTREE  |   |    | 
| dialog |   1 | from_anonym_id |   1 | from_anonym_id | A   |   17 |  NULL | NULL |  | BTREE  |   |    | 
| dialog |   1 | to_anonym_id |   1 | to_anonym_id | A   |   17 |  NULL | NULL |  | BTREE  |   |    | 
+--------+------------+----------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 

PS Bitte teilen Sie mir kein theoretisches Rezept, nur Beispiele aus der Praxis. Thx im Voraus.

Wenn ich diese Aussage entfernen

!(0 in (SELECT read_state FROM messages as msgs 
WHERE (msgs.from_id = messages.from_id or msgs.from_id = messages.user_id) and msgs.user_id = 162077 and removed = 0)) as read_state 

Abfrage funktioniert sehr gut im Vergleich zu original: 101 rows in set (0.04 sec)

Ich nehme an, dies ist das Hauptproblem, aber ich brauche diese Feld da draußen. Kann jemand diese Runde drehen und es schneller machen, wäre sehr erfreut.

+0

Wie schnell wird die Abfrage selbst ausgeführt, ohne die entfernte Anweisung? – kevin628

+0

Es kann variieren je nach Auslastung avg-Faktor, aber jetzt '101 Zeilen im Satz (5,59 Sekunden)' Wenn das System ruhig ist - 2-3 Sekunden, was definitiv ein Problem. –

+1

Wenn die Tabelle "messages" die Tabelle mit 33 Millionen Datensätzen enthält, führt die innere Abfrage, die Sie zur Leistungsverbesserung entfernt haben, einen Tabellenscan von 33 Millionen Datensätzen für jedes Element in der äußeren Abfrage aus ist auf 102 Datensätze beschränkt). So 102 * 33 Millionen ist eine Menge von Datensätzen zu scannen. Erwägen Sie möglichst die Verwendung von zusammengesetzten Schlüsselindizes. Wenn nicht, sollten Sie darüber nachdenken, wie die Daten zusammenhängen, damit Sie zusammengesetzte Schlüsselindizes verwenden können. – kevin628

Antwort

3

Ich würde mit einem Index auf der Nachrichtentabelle beginnen. Ein zusammengesetzter Index, um einen Join abzudecken, wie ich in der folgenden Beispielabfrage habe ... Index on (user_id, removed, read_state, from_id).

Nächste, Erklärung meines Prozesses. Ich mache eine vorläufige Abfrage aus der Dialogtabelle als eine UNION, aber jeweils die entgegengesetzte ID für den "LinkToUser" für den nächsten Zyklus Verknüpfung mit Benutzertabelle vs ein "OR" Join-Ergebnis wie in der Where-Klausel. Es kann Ihnen helfen, vorab qualifizierte und qualifizierte Unterlagen zu erhalten.

Im nächsten Teil wird der Index für Ihre Nachrichten ankommen. Ich mache eine Links-Join basierend auf dem bestimmten Benutzer, entfernt = 0 und speziell die read_state = 0. Durch die Verwendung des Index, wird es entweder eine Übereinstimmung finden oder es wird nicht. So wird Ihre ausgewählte Feldklausel von (! 0 in ...) einfach zu einer IS NULL-Prüfung vereinfacht.

SELECT 
     u.id as uid, 
     u.name, 
     avatar, 
     avatar_date, 
     driver, 
     m.id AS mid, 
     m.msg, 
     m.removed, 
     m.from_anonym_id, 
     m.to_anonym_id, 
     (m.date DIV 1000) AS date, 
     from_id = 162077 as outbox, 
     msgFrom.from_id IS NULL as read_state 
    FROM 
     (select distinct d1.*, d1.uid2 as LinkToUser 
      from dialog d1 
      where d1.uid1 = 162077 
     union select d2.*, d2.uid1 as LinkToUser 
      from dialog d2 
      where d2.uid2 = 162077) Qualified 

     JOIN Users u 
      ON Qualified.LinkToUser = u.id 

     JOIN Messages m 
      ON Qualified.mid = m.id 

      LEFT JOIN Messages msgFrom 
       ON msgFrom.user_id = 160277 
       AND msgFrom.Removed = 0 
       AND msgFrom.Read_State = 0 
       AND ( m.from_id = msgFrom.from_id 
        OR m.user_id = msgFrom.from_id) 

    ORDER BY 
     Qualified.mid DESC 
    LIMIT 
     0, 101; 

Sie mit ihm ein wenig, müssen vielleicht so etwas wie ..

für die read_state
if(msgFrom.from_id IS NULL, 0, msgFrom.read_state) as Read_State 

KLARSTELLUNG

Zeusakm, Ihr individuelles Feld als eine Rückkehr wird nur in schriftlicher Form ändern spielen 1 oder 0, da es eine logische Bedingung von NOT ein Wert von Null in einer ausgewählten Liste von Nachrichten ist. Es wird niemals eine -1 zurückgeben, wie Sie in Ihrem Kommentar angegeben haben. Meine Version macht das Gleiche. Wenn es eine Null findet, gebe Null zurück. Wenn es keine Null finden kann, gibt es 1 zurück, da der Vergleichswert NULL wäre und somit ein "IsThisValue IS NULL" Wahr zurückgibt, was dasselbe ist wie ein Flag von 1.

Also, hoffentlich, das klärt, was ich mit dem links-Join für Sie tat. Suchen Sie explizit nach der Benutzer-ID, dem Status "Ausgelöscht" und dem Status "Ausgelesen" und (aus der Übereinstimmung mit oder der Benutzer-ID).

+0

+1 für Versuch, aber Entschuldigung - eine kleine sperrige Lösung, zB: Ich kann nur die Kesselplatte aus und wählen sie auf Ergebnis, und dann durch Werte in Array definieren gibt es ein Element mit 1 Wert - es wird mehr sein anmutig. Danke. –

+0

Nein, das Hauptproblem wurde nicht gelöst - '! (0 in (SELECT read_state FROM Nachrichten als Nachrichten WHERE (msgs.from_id = messages.from_id oder msgs.from_id = messages.user_id) und msgs.user_id = 162077 und entfernt = 0)) wie read_state', obwohl thx für try. –

+0

@zeusakm, ohne Daten zu sehen, und wirklich Ihre! (0 in ...) zu verstehen, kann ich nicht viel mehr tun – DRapp

3

Das ist Ihre Abfrage mit dem join Syntax festgelegt und Tabellen-Aliases für die Tabellen in der äußeren Abfrage hinzugefügt:

SELECT u.id as uid, name, avatar, avatar_date, driver, m.id AS mid, m.msg, 
     m.removed, m.from_anonym_id, m.t 
     o_anonym_id, (m.date DIV 1000) AS date, from_id = 162077 as outbox, 
     !(0 in (SELECT read_state 
       FROM messages m2 
       WHERE (m2.from_id = m.from_id or m2.from_id = m.user_id) and 
        m2.user_id = 162077 and removed = 0 
      ) 
     ) as read_state 
FROM dialog d join 
    messages m 
    on m.id = d.mid join 
    users u 
    on (uid1 = 162077 and users.id = uid2) or 
     (uid2 = 162077 and users.id = uid1) 
ORDER BY d.mid DESC 
LIMIT 0, 101; 

Wenn die Abfrage funktioniert gut, ohne die Unterabfrage in der select Klausel, würde ich empfehlen, zu ersetzen, dass . in kann ein teurer Betreiber sein, insbesondere mit or unter den Bedingungen.Deshalb würde ich empfehlen, mit dem Ersetzen:

(case when exists (select 1 
        from messages m2 
        where m2.user_id = 162077 and m2.removed = 0 and 
          m2.from_id = m.from_id and m2.read_state = 0 
        ) 
     then 0 
     when exists (select 1 
        from messages m2 
        where m2.user_id = 162077 and m2.removed = 0 and 
          m2.from_id = m.user_id and m2.read_state = 0 
        ) 
     then 0 
     else 1 
    end) 

Und Sie auf messages(from_id, user_id, removed, read_state) einen Index mögen.

+0

Leider führt die obere Abfrage von Ihrem Post 4,5 Sekunden aus, mit der unteren Korrektur (verschachtelte Abfrage) 5,6 Sekunden. Scheint schlimmer als original, sorry. Danke für den Versuch. –

+1

@zeusakm. . . Haben Sie den richtigen Index für Nachrichten? –

+0

Definitiv habe ich den 'messages (from_id, user_id, removed, read_state)' Index vor der Abfrage gesetzt. –

2

Erstellen Sie eine temporäre Tabelle und fügen Sie alle Spalten außer readstate mit dem Standardwert -1 ein und speichern Sie form_id aktualisieren Sie die Readstate-Spalte ähnlich wie Gordons Post.

CREATE TEMPORARY TABLE userTable 
SELECT u.id as uid, name, avatar, avatar_date, driver, m.id AS mid, m.msg, 
     m.removed, m.from_anonym_id, m.t 
     o_anonym_id, (m.date DIV 1000) AS date, from_id = 162077 as outbox, 
     m.form_id, 
     -1 as read_state 
FROM dialog d join 
    messages m 
    on m.id = d.mid join 
    users u 
    on (uid1 = 162077 and users.id = uid2) or 
     (uid2 = 162077 and users.id = uid1) 
ORDER BY d.mid DESC 
LIMIT 0, 101; 

update userTable set readstate = 
(case when exists (select 1 
        from messages m2 
        where m2.user_id = 162077 and m2.removed = 0 and 
          m2.from_id = userTable.from_id and m2.read_state = 0 
        ) 
     then 0 
     when exists (select 1 
        from messages m2 
        where m2.user_id = 162077 and m2.removed = 0 and 
          m2.from_id = userTable.uid and m2.read_state = 0 
        ) 
     then 0 
     else 1 
    end)