简体   繁体   中英

How to optimize MySQL query with large data on left join?

The query below returns a set of User and for each row a number of relations from the user perpective who is searching (id = 4)

SELECT `users`.`firstname` AS firstname,
       `users`.`lastname` AS lastname,
       COUNT(`trusted_users`.`id`) AS number_of_friend_in_common,
       CASE ... AS friend,
       CASE ... AS facebook_invitable,
       CASE ... AS address_book_invitable,
       CASE ... AS virtual_user,
FROM `users`

LEFT OUTER JOIN `trusted_users` 
  ON `trusted_users`.`user_id` = 4 AND `trusted_users`.`trust_user_id` = `users`.`id`

LEFT OUTER JOIN `facebook_friends` 
  ON (`facebook_friends`.`user_id` = 4 AND `facebook_friends`.`friend_user_id` = `users`.`id`
  OR `facebook_friends`.`user_id` = `users`.`id` AND `facebook_friends`.`friend_user_id` = 4)

LEFT OUTER JOIN `address_book_contacts` 
  ON `address_book_contacts`.`owner_id` = 4 AND `address_book_contacts`.`email_digest` = `users`.`email_digest`

LEFT OUTER JOIN `friends` 
  ON (`friends`.`me_id` = `users`.`id` AND `friends`.`him_id` = 4
  OR `friends`.`me_id` = 4 AND `friends`.`him_id` = `users`.`id`)

WHERE `users`.`id` NOT IN
    (SELECT CASE
                WHEN `friends`.`me_id` = 4 THEN `friends`.`him_id`
                ELSE `friends`.`me_id`
            END
     FROM `friends`
     WHERE (`friends`.`status` = 0
            AND `friends`.`him_id` = 4
            AND `friends`.`him_status` = 7
            OR `friends`.`status` = 0
            AND `friends`.`me_id` = 4
            AND `friends`.`me_status` = 7))
  AND (`users`.`firstname` LIKE '%a%' OR `users`.`lastname` LIKE '%a%')
GROUP BY `users`.`id`
ORDER BY friend DESC,
         facebook_invitable DESC,
         address_book_invitable DESC,
         number_of_friend_in_common DESC,
         virtual_user DESC,
         firstname,
         lastname LIMIT 0, 20

Number of row for each table:

trusted_users : 255k

facebook_friends : 1k

address_book_contacts : 1.5M

friends : 70k

users : 32k

All fields of join are indexed. The query takes 1.1s which is not acceptable for the amount of data we have. What am I doing wrong? Should I split in multiple query and paginate my self?

Edit 1: EXPLAIN result

+----+-------------+-----------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------+---------+----------------------------------+-------+----------------------------------------------------------------------------------------------------------------------------+
| id | select_type | table                 | type        | possible_keys                                                                                                                                                               | key                                                       | key_len | ref                              | rows  | Extra                                                                                                                      |
+----+-------------+-----------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------+---------+----------------------------------+-------+----------------------------------------------------------------------------------------------------------------------------+
|  1 | PRIMARY     | users                 | ALL         | PRIMARY,index_users_on_chat_id,index_users_login_facebook_id,index_users_on_login,index_users_on_parent_id,index_users_account_type,index_users_email_digest,index_users_id | NULL                                                      | NULL    | NULL                             | 31847 | Using where; Using temporary; Using filesort                                                                               |
|  1 | PRIMARY     | trusted_users         | ref         | index_trusted_users_user,index_trusted_users_trust_user                                                                                                                     | index_trusted_users_trust_user                            | 5       | messenger_dev.users.id           |     6 | Using where                                                                                                                |
|  1 | PRIMARY     | facebook_friends      | index_merge | index_facebook_friends_user,index_facebook_friends_friend                                                                                                                   | index_facebook_friends_user,index_facebook_friends_friend | 5,5     | NULL                             |     2 | Using union(index_facebook_friends_user,index_facebook_friends_friend); Using where; Using join buffer (Block Nested Loop) |
|  1 | PRIMARY     | address_book_contacts | ref         | index_address_book_contacts_owner_id,index_address_book_contacts_email                                                                                                      | index_address_book_contacts_email                         | 767     | messenger_dev.users.email_digest |     1 | Using where                                                                                                                |
|  1 | PRIMARY     | friends               | index_merge | index_friends_me_him,index_friends_me,index_friends_him                                                                                                                     | index_friends_him,index_friends_me                        | 5,5     | NULL                             |    18 | Using union(index_friends_him,index_friends_me); Using where; Using join buffer (Block Nested Loop)                        |
|  2 | SUBQUERY    | friends               | index_merge | index_friends_me_him,index_friends_me,index_friends_him                                                                                                                     | index_friends_him,index_friends_me                        | 5,5     | NULL                             |    18 | Using union(index_friends_him,index_friends_me); Using where                                                               |
+----+-------------+-----------------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------+---------+----------------------------------+-------+----------------------------------------------------------------------------------------------------------------------------+
6 rows in set (0,00 sec)

Edit 2: Table structure

CREATE TABLE `address_book_contacts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email_digest` varchar(191) DEFAULT NULL,
  `code` varchar(191) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `owner_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_address_book_contacts_owner_id` (`owner_id`),
  KEY `index_address_book_contacts_email` (`email_digest`)
) ENGINE=InnoDB AUTO_INCREMENT=1598109 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `trusted_users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `friend_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `trust_user_id` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_trusted_users_on_friend_id` (`friend_id`),
  KEY `index_trusted_users_user` (`user_id`),
  KEY `index_trusted_users_trust_user` (`trust_user_id`),
  CONSTRAINT `fk_rails_007c31c802` FOREIGN KEY (`trust_user_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_rails_ca24cb4e23` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=275576 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `facebook_friends` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `friend_user_id` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `code` varchar(191) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_facebook_friends_user` (`user_id`),
  KEY `index_facebook_friends_friend` (`friend_user_id`),
  KEY `index_facebook_friends_code` (`code`(5)),
  CONSTRAINT `fk_rails_78285a074e` FOREIGN KEY (`friend_user_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_rails_aa3ac53a81` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1149 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `friends` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `me_id` int(11) DEFAULT NULL,
  `him_id` int(11) DEFAULT NULL,
  `owner_id` int(11) DEFAULT NULL,
  `status` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `me_status` int(11) DEFAULT '0',
  `him_status` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_friends_me_him` (`me_id`,`him_id`),
  KEY `index_friends_me` (`me_id`),
  KEY `index_friends_him` (`him_id`),
  KEY `index_friends_owner` (`owner_id`),
  CONSTRAINT `fk_rails_9fa3474d31` FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_rails_d3ebb6657f` FOREIGN KEY (`him_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_rails_fccfd1b821` FOREIGN KEY (`me_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=95724 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `account_type` varchar(191) NOT NULL,
  `firstname` varchar(191) DEFAULT NULL,
  `lastname` varchar(191) DEFAULT NULL,
  `login` varchar(191) DEFAULT NULL,
  `avatar` varchar(191) DEFAULT NULL,
  `gender` varchar(1) DEFAULT NULL,
  `locale` varchar(191) DEFAULT NULL,
  `birthdate` date DEFAULT NULL,
  `password_digest` varchar(191) DEFAULT NULL,
  `email_digest` varchar(191) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_users_on_login` (`login`),
  KEY `index_users_account_type` (`account_type`),
  KEY `index_users_email_digest` (`email_digest`),
  KEY `index_uses_firstname` (`firstname`),
  KEY `index_users_lastname` (`lastname`)
) ENGINE=InnoDB AUTO_INCREMENT=32516 DEFAULT CHARSET=utf8mb4;

It looks like MySQL is not choosing any of the available indicies for the users table.

First, run ANALYZE TABLE users; , then re-run the EXPLAIN command. Is the value of the first rows cell now substantially lower than 31847? If so, your problem should be solved!

If not, run OPTIMIZE TABLE users; , then re-run the EXPLAIN command. Is the value of the first rows cell now substantially lower than 31847? If so, your problem should be solved!

If neither of those steps help, try adding USE INDEX (PRIMARY) or USE INDEX (users_id) immediately after the FROM users portion of your query.

Hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM