2017-10-10 6 views
0

J'ai une requête assez volumineuse qui est utilisée pour une recherche d'utilisateur sur 'map_item'.Optimisation d'une requête simple avec beaucoup de jointures à gauche

SELECT map_item_name 
FROM map_item 
LEFT JOIN map_section_item ON map_section_item_item_id = map_item_id 
LEFT JOIN map_section ON map_section_id = map_section_item_section_id 
LEFT JOIN map_item_flag ON map_item_flag_item_id = map_item_id 
LEFT JOIN flag ON flag_id = map_item_flag_flag_id 
LEFT JOIN map ON map_id = map_section_map_id 
LEFT JOIN place_map ON place_map_map_id = map_id 
LEFT JOIN place ON place_id = place_map_place_id 
LEFT JOIN place_category ON place_category_place_id = place_id 
LEFT JOIN category ON category_id = place_category_category_id 
LEFT JOIN review ON review_map_item_id = map_item_id 
LEFT JOIN map_price ON map_price_item_id = map_item_id 
LEFT JOIN county_list ON place_address_county = county_id 

'map_item' a 5399 enregistrements au total et aucune des tables jointes ne contient beaucoup de données.

Si j'exécute cette requête sans les jointures de gauche (SELECT map_item_name FROM map_item), elle renvoie 0.00s comme prévu, mais la requête ci-dessus avec les jointures prend environ 10.00s.

Toutes les jointures de gauche sont requises dans la requête en raison des différents filtres que l'utilisateur peut appliquer à la recherche, mais la requête initiale a pris beaucoup de temps à s'exécuter (20 secondes environ) et après suppression la plupart des parties de la requête que j'ai été laissé avec le ci-dessus (qui est juste les jointures à gauche) et même cela prend 18 secondes pour fonctionner.

Voici la EXPLAIN de la requête:

+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+ 
| id | select_type | table    | type | possible_keys     | key        | key_len | ref              | rows | Extra               | 
+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+ 
| 1 | SIMPLE  | map_item   | ALL | NULL        | NULL        | NULL | NULL              | 5455 | NULL               | 
| 1 | SIMPLE  | map_section_item | index | NULL        | map_section_item_section_id  | 8  | NULL              | 5330 | Using where; Using index; Using join buffer (Block Nested Loop) | 
| 1 | SIMPLE  | map_section  | eq_ref | PRIMARY       | PRIMARY       | 4  | bestmeal.map_section_item.map_section_item_section_id  | 1 | NULL               | 
| 1 | SIMPLE  | map_item_flag  | ALL | NULL        | NULL        | NULL | NULL              | 1509 | Using where; Using join buffer (Block Nested Loop)    | 
| 1 | SIMPLE  | flag    | eq_ref | PRIMARY       | PRIMARY       | 4  | bestmeal.map_item_flag.map_item_flag_flag_id    | 1 | Using index              | 
| 1 | SIMPLE  | map    | eq_ref | PRIMARY       | PRIMARY       | 4  | bestmeal.map_section.map_section_map_id     | 1 | Using index              | 
| 1 | SIMPLE  | place_map   | index | NULL        | branch_map_branch_id    | 8  | NULL              | 1275 | Using where; Using index; Using join buffer (Block Nested Loop) | 
| 1 | SIMPLE  | place    | eq_ref | PRIMARY       | PRIMARY       | 4  | bestmeal.place_map.place_map_place_id      | 1 | NULL               | 
| 1 | SIMPLE  | place_category | ref | place_category_place_id   | place_category_place_id   | 4  | bestmeal.place.place_id         | 1 | Using index              | 
| 1 | SIMPLE  | category   | eq_ref | PRIMARY       | PRIMARY       | 4  | bestmeal.place_category.place_category_category_id  | 1 | Using index              | 
| 1 | SIMPLE  | review   | ref | review_map_item_id    | review_map_item_id    | 4  | bestmeal.map_item.map_item_id        | 1 | Using index              | 
| 1 | SIMPLE  | map_price   | ref | map_price_item_id    | map_price_item_id    | 4  | bestmeal.map_item.map_item_id        | 1 | Using index              | 
| 1 | SIMPLE  | county_list  | eq_ref | PRIMARY       | PRIMARY       | 4  | bestmeal.place.place_address_county      | 1 | Using index              | 
+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+ 

Toutes ces jointures sont faites contre les champs indexés, et aucune des tables qui sont jointes ont des index inutiles en eux qui pourraient être utilisés au lieu de l'indice prévu.

Je ne suis pas un expert en optimisation de requêtes, mais j'ai du mal à trouver ce que je peux faire pour accélérer cette requête tout en gardant les jointures à gauche. Je ne peux pas non plus vraiment penser à d'autres solutions qui retourneront les mêmes résultats sans utiliser les jointures.

Quelqu'un a-t-il des idées qui m'aideront à améliorer les performances de cette requête ou à effectuer une recherche utilisateur en utilisant une méthode différente et plus rapide?

Modifier structures de table comme l'a demandé:

CREATE TABLE `map_item` (
    `map_item_id` int(11) NOT NULL AUTO_INCREMENT, 
    `map_item_account_id` int(11) NOT NULL DEFAULT '0', 
    `map_item_category_id` int(11) NOT NULL, 
    `map_item_name` varchar(255) DEFAULT NULL, 
    `map_item_description` text, 
    `map_item_tags` varchar(255) DEFAULT NULL, 
    `map_item_type` set('d','f') DEFAULT NULL, 
    PRIMARY KEY (`map_item_id`), 
    KEY `map_item_account_id` (`map_item_account_id`), 
    KEY `map_item_tags` (`map_item_tags`), 
    KEY `map_item_category_id` (`map_item_category_id`), 
    FULLTEXT KEY `map_item_keyword_search` (`map_item_name`,`map_item_description`,`map_item_tags`), 
    FULLTEXT KEY `map_item_name` (`map_item_name`), 
    FULLTEXT KEY `map_item_description` (`map_item_description`), 
    FULLTEXT KEY `map_item_tags_2` (`map_item_tags`) 
) ENGINE=InnoDB AUTO_INCREMENT=5420 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `map_section_item` (
    `map_section_item_id` int(11) NOT NULL AUTO_INCREMENT, 
    `map_section_item_section_id` int(11) NOT NULL DEFAULT '0', 
    `map_section_item_item_id` int(11) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`map_section_item_id`), 
    KEY `map_section_item_section_id` (`map_section_item_section_id`,`map_section_item_item_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=24410 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `map_section` (
    `map_section_id` int(11) NOT NULL AUTO_INCREMENT, 
    `map_section_map_id` int(11) NOT NULL DEFAULT '0', 
    `map_section_map_draft_id` int(11) NOT NULL DEFAULT '0', 
    `map_section_column` tinyint(1) NOT NULL DEFAULT '1', 
    `map_section_name` varchar(255) DEFAULT NULL, 
    `map_section_description` text, 
    PRIMARY KEY (`map_section_id`), 
    KEY `map_section_map_draft_id` (`map_section_map_draft_id`), 
    KEY `map_section_map_id` (`map_section_map_id`), 
    FULLTEXT KEY `index_name` (`map_section_name`) 
) ENGINE=InnoDB AUTO_INCREMENT=4254 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `map_item_flag` (
    `map_item_flag_id` int(11) NOT NULL AUTO_INCREMENT, 
    `map_item_flag_item_id` int(11) NOT NULL DEFAULT '0', 
    `map_item_flag_flag_id` int(11) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`map_item_flag_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=1547 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `flag` (
    `flag_id` int(11) NOT NULL AUTO_INCREMENT, 
    `flag_category_id` int(11) NOT NULL DEFAULT '0', 
    `flag_name` varchar(255) DEFAULT NULL, 
    `flag_description` varchar(255) DEFAULT NULL, 
    `flag_img` varchar(255) DEFAULT NULL, 
    `flag_order` tinyint(2) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`flag_id`), 
    KEY `flag_category_id` (`flag_category_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `map` (
    `map_id` int(11) NOT NULL AUTO_INCREMENT, 
    `map_account_id` int(11) NOT NULL DEFAULT '0', 
    `map_name` varchar(255) DEFAULT NULL, 
    `map_description` text, 
    `map_type` set('d','f') DEFAULT NULL, 
    `map_layout` set('columns','tabs','collapsed') DEFAULT NULL, 
    PRIMARY KEY (`map_id`), 
    KEY `map_account_id` (`map_account_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `place_map` (
    `place_map_id` int(11) NOT NULL AUTO_INCREMENT, 
    `place_map_place_id` int(11) NOT NULL DEFAULT '0', 
    `place_map_map_id` int(11) NOT NULL DEFAULT '0', 
    `place_map_active` tinyint(1) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`place_map_id`), 
    KEY `branch_map_branch_id` (`place_map_place_id`,`place_map_map_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=2176 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `place` (
    `place_id` int(11) NOT NULL AUTO_INCREMENT, 
    `place_account_id` int(11) NOT NULL DEFAULT '0', 
    `place_name` varchar(120) DEFAULT NULL, 
    `place_alias` varchar(255) DEFAULT NULL, 
    `place_description` text, 
    `place_address_line_one` varchar(100) DEFAULT NULL, 
    `place_address_line_two` varchar(100) DEFAULT NULL, 
    `place_address_line_three` varchar(100) DEFAULT NULL, 
    `place_address_town` varchar(100) DEFAULT NULL, 
    `place_address_county` int(11) NOT NULL DEFAULT '0', 
    `place_address_postcode` varchar(10) DEFAULT NULL, 
    `place_address_latitude` decimal(11,8) DEFAULT NULL, 
    `place_address_longitude` decimal(11,8) DEFAULT NULL, 
    `place_phone` varchar(20) DEFAULT NULL, 
    `place_email` varchar(255) DEFAULT NULL, 
    `place_website` varchar(120) DEFAULT NULL, 
    `place_flag_initial_email` tinyint(1) NOT NULL DEFAULT '0', 
    `place_audit_admin_id` int(11) NOT NULL DEFAULT '0', 
    `place_last_audit_datetime` datetime DEFAULT NULL, 
    `place_created_by_admin_id` int(11) NOT NULL DEFAULT '0', 
    `place_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    `place_tried_google` int(1) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`place_id`), 
    KEY `place_account_id` (`place_account_id`), 
    KEY `place_address_county` (`place_address_county`), 
    KEY `place_alias` (`place_alias`), 
    KEY `place_audit_admin_id` (`place_audit_admin_id`), 
    KEY `place_created_by_admin_id` (`place_created_by_admin_id`), 
    FULLTEXT KEY `place_name` (`place_name`), 
    FULLTEXT KEY `place_keyword_search` (`place_name`,`place_address_town`), 
    FULLTEXT KEY `place_address_town` (`place_address_town`) 
) ENGINE=InnoDB AUTO_INCREMENT=135167 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `place_category` (
    `place_category_id` int(11) NOT NULL AUTO_INCREMENT, 
    `place_category_place_id` int(11) NOT NULL DEFAULT '0', 
    `place_category_category_id` int(11) NOT NULL DEFAULT '0', 
    PRIMARY KEY (`place_category_id`), 
    UNIQUE KEY `place_category_place_id` (`place_category_place_id`,`place_category_category_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=208987 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `category` (
    `category_id` int(11) NOT NULL AUTO_INCREMENT, 
    `category_name` varchar(255) DEFAULT NULL, 
    PRIMARY KEY (`category_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=168 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `review` (
    `review_id` int(11) NOT NULL AUTO_INCREMENT, 
    `review_user_id` int(11) NOT NULL DEFAULT '0', 
    `review_place_id` int(11) NOT NULL DEFAULT '0', 
    `review_map_item_id` int(11) NOT NULL DEFAULT '0', 
    `review_otm_item_name` varchar(156) DEFAULT NULL, 
    `review_headline` varchar(255) DEFAULT NULL, 
    `review_message` text, 
    `review_rating` tinyint(1) NOT NULL DEFAULT '0', 
    `review_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    `review_edited_datetime` datetime DEFAULT NULL, 
    `review_hidden` tinyint(1) NOT NULL DEFAULT '0', 
    `review_deleted` tinyint(1) NOT NULL DEFAULT '0', 
    `review_status` set('pending','published','hidden','deleted') NOT NULL, 
    PRIMARY KEY (`review_id`), 
    KEY `review_map_item_id` (`review_map_item_id`), 
    KEY `review_place_id` (`review_place_id`), 
    KEY `review_user_id` (`review_user_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `map_price` (
    `map_price_id` int(11) NOT NULL AUTO_INCREMENT, 
    `map_price_item_id` int(11) NOT NULL DEFAULT '0', 
    `map_price_label` varchar(50) DEFAULT NULL, 
    `map_price_value` decimal(10,2) DEFAULT NULL, 
    PRIMARY KEY (`map_price_id`), 
    KEY `map_price_item_id` (`map_price_item_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=5872 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `county_list` (
    `county_id` int(11) NOT NULL AUTO_INCREMENT, 
    `county_country_id` int(11) NOT NULL DEFAULT '0', 
    `county_name` varchar(120) DEFAULT NULL, 
    `county_alias` varchar(120) DEFAULT NULL, 
    PRIMARY KEY (`county_id`), 
    KEY `county_alias` (`county_alias`), 
    KEY `county_country_id` (`county_country_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=142 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 
+0

Avez-vous des index? –

+0

Assurez-vous d'utiliser INT comme type de données de champs ID et créez des index sur ces clés étrangères. – Lamar

+0

Désolé, je devrais avoir mentionné dans le message original. Toutes les jointures sont jointes dans des champs indexés. Aucune des tables utilisées dans cette requête n'a d'index inutiles. – Ryan

Répondre

2

Regardez ces lignes:

LEFT JOIN map_section_item SUR map_section_item_item_id = map_item_id

| 1 | SIMPLE | map_section_item | index | NULL | map_section_item_section_id | 8 | NULL | 5330 | En utilisant où; En utilisant l'index Utilisation du tampon de jointure (Bloquer la boucle imbriquée) | |

Remarquez "5330". Cela signifie qu'il a dû rechercher environ 5330 éléments pour trouver la rangée dont il avait besoin.

Avec un INDEX(map_section_item_item_id) simple, il irait directement à l'une (ou quelques) rangée dont il avait besoin. Cela rendrait la requête beaucoup plus rapide.

Répétez l'opération pour l'autre JOIN, au moins pour ceux qui ont un "Lignes"> 1.

Pourquoi LEFT? Est-ce que chaque table "correcte" manque éventuellement des données?

Un problème secondaire: ne préfixez pas tout avec le nom de la table; c'est trop de fouillis.

+0

Je ne sais pas pourquoi je ne me suis pas rendu compte index, mais cela a bien fonctionné. J'ai ajouté le premier index 'map_section_item_item_id' qui a pris la requête de 9.85s à 5.71s. Le deuxième index 'map_item_flag_item_id' l'a pris à 3.52s. Le troisième index 'place_map_map_id' l'a ramené à 1.40s ce qui est une grande amélioration. Je vais encore chercher à réduire encore plus et devra travailler cela avant que la base de données ne se développe car il est encore dans les premières étapes à la minute. Ce que vous avez dit avec les jointures est correct, les tables ne contiennent pas nécessairement de données. – Ryan

+0

En outre, je viens d'exécuter la requête originale complète et il est passé d'environ 18 secondes à 0.10s. – Ryan

0

Pour MySQL, essayez d'utiliser la clause STRAIGHT_JOIN ...STRAIGHT_JOIN demande à MySQL de faire la requête dans l'ordre que j'ai indiqué. De cette façon, il force map_item en tant que table primaire et tout le reste en tant que tables secondaires de recherche ...

+0

L'utilisation d'un STRAIGHT_JOIN exécute la requête en 9.95s, sans qu'elle ne s'exécute en 9.87s – Ryan

+0

Comme avec 'FORCE INDEX',' STRAIGHT_JOIN' peut aider aujourd'hui, mais empirer les choses demain. –