web-dev-qa-db-fra.com

MySQL Expliquer a un nombre de lignes différent du journal de requête lente

J'ai cette entrée dans le journal de requête lente:

# User@Host: user[Host] @  [ip]
# Thread_id: 1514428  Schema: db  Last_errno: 0  Killed: 0
# Query_time: 2.795454  Lock_time: 0.000116  Rows_sent: 15  Rows_examined: 65207  Rows_affected: 0  Rows_read: 65207
# Bytsent: 26618
SET timestamp=1407511874;

select off.*,translated_title,translated_description 
from ephpb2b_products off  USE INDEX(id_viewed)  
  INNER JOIN ephpb2b_members mem 
    ON  off.uid = mem.id 
  Left Join ephpb2b_product_language_new pol 
    ON  off.id = pol.offer_id                                         
    and pol.language='en'
where off.approved=1 
order by off.viewed  
LIMIT 15; 

Quand j'explique cette requête, c'est absolument bien.

mysql> explain select off.*,translated_title,translated_description from ephpb2b_products off  USE INDEX(id_viewed)  INNER JOIN ephpb2b_members mem ON off.uid = mem.id Left Join ephpb2b_product_language_new pol ON off.id = pol.offer_id and pol.language='en' where off.approved=1 order by off.viewed  LIMIT 15;

+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
| id | select_type | table | type   | possible_keys           | key         | key_len | ref                       | rows | Extra       |
+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
|  1 | SIMPLE      | off   | index  | NULL                    | id_viewed   | 4       | NULL                      |    3 | Using where |
|  1 | SIMPLE      | mem   | eq_ref | PRIMARY                 | PRIMARY     | 4       | db.off.uid |    1 | Using index |
|  1 | SIMPLE      | pol   | ref    | offer_id,id_language | offer_id | 5       | db.off.id  |    4 |             |
+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
3 rows in set (0.17 sec)

Comment optimiser cette requête? Pourquoi expliquera-t-il 3 lignes et journal de requête lentement indique qu'il a examiné 65207 rangées.

5
codefreak

Merci à tous pour vos idées. J'ai résolu cette question en divisant cette requête dans deux requêtes différentes. D'abord, j'ai demandé aux identifiants, puis passez ces identifiants à une autre table pour info.

select id from ephpb2b_products off INNER JOIN ephpb2b_members mem 
    ON  off.uid = mem.id 
where off.approved=1 
order by off.viewed  
LIMIT 15;

Puis:

select * from ephpb2b_product_language_new where offer_id IN ({ids from lasts query})

Cela fonctionne beaucoup mieux et n'agit pas étrange.

0
codefreak

Afin de répondre à cette question, vous devez comprendre la colonne des lignes sur expliquer des moyens et la différence entre les calculs basés sur des statistiques et des statistiques post-exécution.

Lorsque vous exécutez Expliquez, la colonne des rangées vous indiquera, pour chaque accès de table, combien de lignes seront examinées en utilisant le filtre souhaité. Il existe deux façons de calculer que: soit une indice de plongée (qui devrait généralement vous donner des résultats exacts) ou en utilisant des statistiques approximatives que chaque moteur stocke indépendamment -UP à 5.6- pour chaque table. Bien que la première méthode est préférée lorsqu'elle peut être utilisée (filtres simples sur une colonne indexée unique), dans de nombreux cas, seule une approximation pourrait être utilisée dans l'ensemble, l'optimisateur de requête prendrait autant de temps que l'exécution de la requête elle-même.

Dans tous les cas, les lignes calculées sont les lignes calculées à lire (à ne pas être retournées) par accès à la table. Même s'il était exact (et plusieurs fois ont des différences avec plusieurs ordres de grossitude, mais il suffit toujours de l'optimiseur), il ne prévoit pas le nombre réel de lignes accessibles dans une jointure. Par exemple, si vous rejoignez la table A (lecture exactement X lignes) et tableau B (lecture exactement Y lignes), dans l'ordre A -> B, le nombre réel de lignes LIRE sera: X + # of rows returned by A (<=X) multiplied by Y, comme standard MySQL prend uniquement en charge les jointures de boucle imbriquées.

Le journal lent, comme les statistiques de gestionnaire, ou d'autres mécanismes de profilage vous indiquent que le nombre réel de lignes traitées et envoyées, car ces statistiques sont rassemblées après l'exécution, donc exacte.

En ce qui concerne votre cas particulier, EXPLAIN est à blâmer car il montre que seules 3 lignes seraient numérisées pour le premier accès, lorsqu'il est en réalité, il peut effectuer une analyse complète de l'index (car elle utilise la clé uniquement pour le tri. ), qui est multiplié plus tard pour chaque join réalisée. Ne faites pas confiance à Expliquer. Vous pouvez utiliser:

FLUSH STATUS;
-- Execute your query here
SHOW STATUS like 'Hand%';

Pour vérifier le nombre réel et le type (accès PK, ref, balayage d'index, balayage de table) des opérations de ligne. Je l'utiliserais pour tester chaque accès de table individuellement.

Pour une aide plus spécifique, nous aurions besoin de la structure de la table de chaque table et de la sélectivité approximative de chaque condition de filtrage.

8
jynus

+1 à @junus pour les explications concernant EXPLAIN, le journal des requêtes lentes et les lignes examinées.

Concernant -(( "Comment optimiser la requête" :=:

En supposant qu'il existe une relation de clé étrangère explicite entre le products et la table members, la jointure entre eux:

ephpb2b_products off  
  INNER JOIN ephpb2b_members mem 
    ON  off.uid = mem.id 

peut être converti en un LEFT JOIN. Le FK assurera que les deux requêtes sont 100% équivalentes. Avoir cela à l'esprit, et que les clauses where et order:

WHERE off.approved=1
ORDER BY off.viewed  
LIMIT 15

utilise uniquement des colonnes à partir de la table de base (products), c'est-à-dire la table dans la partie "gauche" dans la clause FROM, nous pouvons utiliser une sous-requête pour limiter d'abord les lignes et rejoindre l'autre deux tables, une technique que j'appelle
((( "Premier limit, alors join" :=:

SELECT 
    off.*, 
    pol.translated_title, 
    pol.translated_description 
FROM
    ( SELECT p.*                                 -- first limit
      FROM ephpb2b_products AS p
      WHERE p.approved=1
      ORDER BY p.viewed  
      LIMIT 15  
    ) AS off 
  LEFT JOIN                                      -- then join
    ephpb2b_members AS mem 
      ON  off.uid = mem.id 
  LEFT JOIN 
    ephpb2b_product_language_new AS pol 
      ON  pol.language = 'en'                                         
      AND pol.offer_id = off.id   
ORDER BY 
    off.viewed ;                         -- no LIMIT required here 

Avec un index sur ephpb2b_products (approved, viewed), la sous-requête sera assez efficace. Le reste du plan d'exécution n'importera pas que seules 15 lignes seront impliquées (et je suppose que vous avez des index dans les colonnes de jonction).

Un index supplémentaire sur phpb2b_product_language_new (language, offer_id) peut également améliorer la poursuite de l'efficacité (mais non aveuglément, testez-le en premier. L'indice ci-dessus et la réécriture peuvent être suffisants pour améliorer la vitesse de manière drastique.)

2
ypercubeᵀᴹ