【问题标题】:How to improve a MYSQL Query with multiple JOINS如何改进具有多个 JOINS 的 MYSQL 查询
【发布时间】:2015-08-26 23:15:29
【问题描述】:

我有 2 个搜索页面在获得结果方面非常慢。

我没有编写查询,但我知道它们没有以有效的方式编写;我只是没有足够的 MYSQL 实践来弄清楚如何使它们更高效。

我应该如何改进以下查询?

SELECT DISTINCT m.id AS memberID , m.login , m.age , p.gender
 , p.name AS header , p.id AS profileID , p.city , p.state , p.lastlogin
 , o.login AS online , c.name AS country , ph.filename_1 AS pic 
FROM dt_members AS m 
INNER JOIN dt_profile_approved AS p ON m.id=p.member_id 
LEFT JOIN dt_privacy AS pv ON m.id=pv.member_id 
INNER JOIN dt_countries AS c ON c.id=p.country 
LEFT JOIN dt_photos AS ph ON m.id=ph.member_id 
LEFT JOIN dt_usersonline AS o ON m.login=o.login 
WHERE p.status=1 AND (pv.unsearchable IS NULL OR pv.unsearchable='') 
AND p.gender='Female' AND m.age BETWEEN 25 AND 40 
ORDER BY p.lastlogin DESC 
LIMIT 0, 21;   

它非常慢并且经常显示 500 错误。

解释的输出:

显示创建表:

CREATE TABLE `dt_members` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `login` varchar(25) DEFAULT NULL,
 `pswd` varchar(20) DEFAULT NULL,
 `email` varchar(255) DEFAULT NULL,
 `name` varchar(40) DEFAULT NULL,
 `gender` varchar(10) DEFAULT NULL,
 `age` int(11) DEFAULT NULL,
 `country` varchar(255) DEFAULT NULL,
 `looking_for` varchar(255) DEFAULT NULL,
 `ip_addr` varchar(15) DEFAULT NULL,
 `reg_date` int(11) DEFAULT NULL,
 `status` int(11) DEFAULT NULL,
 `system_status` int(11) DEFAULT '0',
 `system_status_end` int(11) DEFAULT NULL,
 `unlimited` int(11) DEFAULT '0',
 `unlimited_end` int(11) DEFAULT NULL,
 `matchfinder` int(1) DEFAULT '0',
 PRIMARY KEY (`id`),
 KEY `login` (`login`),
 KEY `pswd` (`pswd`),
 KEY `email` (`email`),
 KEY `name` (`name`),
 KEY `gender` (`gender`),
 KEY `age` (`age`),
 KEY `country` (`country`),
 KEY `looking_for` (`looking_for`),
 KEY `ip_addr` (`ip_addr`),
 KEY `reg_date` (`reg_date`),
 KEY `status` (`status`),
 KEY `system_status` (`system_status`),
 KEY `system_status_end` (`system_status_end`),
 KEY `unlimited` (`unlimited`),
 KEY `unlimited_end` (`unlimited_end`),
 KEY `matchfinder` (`matchfinder`)
) ENGINE=MyISAM AUTO_INCREMENT=29150 DEFAULT CHARSET=latin1

 CREATE TABLE `dt_profile` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `member_id` int(11) DEFAULT NULL,
 `country` int(11) DEFAULT NULL,
 `state` varchar(255) DEFAULT NULL,
 `city` varchar(255) DEFAULT NULL,
 `email` varchar(255) DEFAULT NULL,
 `name` varchar(255) DEFAULT NULL,
 `gender` varchar(20) DEFAULT NULL,
 `birth_day` int(11) DEFAULT NULL,
 `birth_month` varchar(6) DEFAULT NULL,
 `birth_year` int(11) DEFAULT NULL,
 `marital_status` int(11) DEFAULT NULL,
 `children` int(11) DEFAULT NULL,
 `drinking` int(11) DEFAULT NULL,
 `smoking` int(11) DEFAULT NULL,
 `food` int(11) DEFAULT NULL,
 `eye_color` int(11) DEFAULT NULL,
 `hair_color` int(11) DEFAULT NULL,
 `height` int(11) DEFAULT NULL,
 `body_type` int(11) DEFAULT NULL,
 `race` int(11) DEFAULT NULL,
 `religion` int(11) DEFAULT NULL,
 `occupation` int(11) DEFAULT NULL,
 `education` int(11) DEFAULT NULL,
 `lang_1` int(11) DEFAULT NULL,
 `lang_1_rate` int(11) DEFAULT NULL,
 `lang_2` int(11) DEFAULT NULL,
 `lang_2_rate` int(11) DEFAULT NULL,
 `lang_3` int(11) DEFAULT NULL,
 `lang_3_rate` int(11) DEFAULT NULL,
 `lang_4` int(11) DEFAULT NULL,
 `lang_4_rate` int(11) DEFAULT NULL,
 `looking_for` varchar(10) DEFAULT NULL,
 `age_from` int(11) DEFAULT NULL,
 `age_to` int(11) DEFAULT NULL,
 `general_info` text,
 `appearance_info` text,
 `looking_for_info` text,
 `status` int(11) DEFAULT NULL,
 `finish_status` int(11) DEFAULT NULL,
 `not_newbie` int(11) DEFAULT NULL,
 `lastlogin` int(10) NOT NULL DEFAULT '0',
 `zipcode` varchar(5) NOT NULL DEFAULT '',
 `longitude` double DEFAULT NULL,
 `latitude` double DEFAULT NULL,
 `photo_pass` varchar(25) NOT NULL DEFAULT '',
 `view_count` int(11) DEFAULT '0',
 `wants_kids` int(11) DEFAULT NULL,
 `kids_okay` int(11) DEFAULT NULL,
 `relocate_domestic` int(11) DEFAULT NULL,
 `relocate_international` int(11) DEFAULT NULL,
 `pioneer` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `member_id` (`member_id`),
 KEY `country` (`country`),
 KEY `state` (`state`),
 KEY `city` (`city`),
 KEY `email` (`email`),
 KEY `name` (`name`),
 KEY `gender` (`gender`),
 KEY `birth_day` (`birth_day`),
 KEY `birth_month` (`birth_month`),
 KEY `birth_year` (`birth_year`),
 KEY `marital_status` (`marital_status`),
 KEY `children` (`children`),
 KEY `drinking` (`drinking`),
 KEY `smoking` (`smoking`),
 KEY `food` (`food`),
 KEY `eye_color` (`eye_color`),
 KEY `hair_color` (`hair_color`),
 KEY `height` (`height`),
 KEY `body_type` (`body_type`),
 KEY `race` (`race`),
 KEY `religion` (`religion`),
 KEY `occupation` (`occupation`),
 KEY `education` (`education`),
 KEY `lang_1` (`lang_1`),
 KEY `lang_1_rate` (`lang_1_rate`),
 KEY `lang_2` (`lang_2`),
 KEY `lang_2_rate` (`lang_2_rate`),
 KEY `lang_3` (`lang_3`),
 KEY `lang_3_rate` (`lang_3_rate`),
 KEY `lang_4` (`lang_4`),
 KEY `lang_4_rate` (`lang_4_rate`),
 KEY `looking_for` (`looking_for`),
 KEY `age_from` (`age_from`),
 KEY `age_to` (`age_to`),
 KEY `status` (`status`),
 KEY `finish_status` (`finish_status`),
 KEY `not_newbie` (`not_newbie`),
 KEY `lastlogin` (`lastlogin`),
 KEY `zipcode` (`zipcode`),
 KEY `longitude` (`longitude`),
 KEY `latitude` (`latitude`),
 KEY `photo_pass` (`photo_pass`),
 KEY `view_count` (`view_count`),
 KEY `wants_kids` (`wants_kids`),
 KEY `kids_okay` (`kids_okay`),
 KEY `relocate_domestic` (`relocate_domestic`),
 KEY `relocate_international` (`relocate_international`),
 KEY `pioneer` (`pioneer`)
) ENGINE=MyISAM AUTO_INCREMENT=18389 DEFAULT CHARSET=latin1

CREATE TABLE `dt_privacy` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `member_id` int(11) DEFAULT NULL,
 `online_yn` char(1) DEFAULT NULL,
 `vkiss_yn` char(1) DEFAULT NULL,
 `profiles_yn` char(1) DEFAULT NULL,
 `IM_yn` char(1) DEFAULT NULL,
 `featured_yn` char(1) DEFAULT NULL,
 `HL_messaged_yn` char(1) DEFAULT NULL,
 `HL_im_yn` char(1) DEFAULT NULL,
 `HL_viewed_yn` char(1) DEFAULT NULL,
 `HL_kissed_yn` char(1) DEFAULT NULL,
 `HL_favorite_yn` char(1) DEFAULT NULL,
 `unsearchable` char(1) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `member_id` (`member_id`),
 KEY `online_yn` (`online_yn`),
 KEY `vkiss_yn` (`vkiss_yn`),
 KEY `profiles_yn` (`profiles_yn`),
 KEY `IM_yn` (`IM_yn`),
 KEY `featured_yn` (`featured_yn`),
 KEY `HL_messaged_yn` (`HL_messaged_yn`),
 KEY `HL_im_yn` (`HL_im_yn`),
 KEY `HL_viewed_yn` (`HL_viewed_yn`),
 KEY `HL_kissed_yn` (`HL_kissed_yn`),
 KEY `HL_favorite_yn` (`HL_favorite_yn`),
 KEY `unsearchable` (`unsearchable`)
) ENGINE=MyISAM AUTO_INCREMENT=26305 DEFAULT CHARSET=latin1

CREATE TABLE `dt_countries` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(100) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `name` (`name`)
) ENGINE=MyISAM AUTO_INCREMENT=226 DEFAULT CHARSET=latin1

CREATE TABLE `dt_photos` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `member_id` varchar(255) DEFAULT NULL,
 `filename_1` varchar(255) DEFAULT NULL,
 `filename_2` varchar(255) DEFAULT NULL,
 `filename_3` varchar(255) NOT NULL DEFAULT '',
 `filename_4` varchar(255) NOT NULL DEFAULT '',
 `filename_5` varchar(255) NOT NULL DEFAULT '',
 `filename_6` varchar(255) NOT NULL DEFAULT '',
 `filename_7` varchar(255) NOT NULL DEFAULT '',
 `filename_8` varchar(255) NOT NULL DEFAULT '',
 `filename_9` varchar(255) NOT NULL DEFAULT '',
 `filename_10` varchar(255) NOT NULL DEFAULT '',
 `filename_11` varchar(255) NOT NULL DEFAULT '',
 `filename_12` varchar(255) NOT NULL DEFAULT '',
 `filename_13` varchar(255) NOT NULL DEFAULT '',
 `filename_14` varchar(255) NOT NULL DEFAULT '',
 `filename_15` varchar(255) NOT NULL DEFAULT '',
 `filename_16` varchar(255) NOT NULL DEFAULT '',
 `filename_17` varchar(255) NOT NULL DEFAULT '',
 `filename_18` varchar(255) NOT NULL DEFAULT '',
 `filename_19` varchar(255) NOT NULL DEFAULT '',
 `filename_20` varchar(255) NOT NULL DEFAULT '',
 `private_1` tinyint(1) NOT NULL DEFAULT '0',
 `private_2` tinyint(1) NOT NULL DEFAULT '0',
 `private_3` tinyint(1) NOT NULL DEFAULT '0',
 `private_4` tinyint(1) NOT NULL DEFAULT '0',
 `private_5` tinyint(1) NOT NULL DEFAULT '0',
 `private_6` tinyint(1) NOT NULL DEFAULT '0',
 `private_7` tinyint(1) NOT NULL DEFAULT '0',
 `private_8` tinyint(1) NOT NULL DEFAULT '0',
 `private_9` tinyint(1) NOT NULL DEFAULT '0',
 `private_10` tinyint(1) NOT NULL DEFAULT '0',
 `private_11` tinyint(1) NOT NULL DEFAULT '0',
 `private_12` tinyint(1) NOT NULL DEFAULT '0',
 `private_13` tinyint(1) NOT NULL DEFAULT '0',
 `private_14` tinyint(1) NOT NULL DEFAULT '0',
 `private_15` tinyint(1) NOT NULL DEFAULT '0',
 `private_16` tinyint(1) NOT NULL DEFAULT '0',
 `private_17` tinyint(1) NOT NULL DEFAULT '0',
 `private_18` tinyint(1) NOT NULL DEFAULT '0',
 `private_19` tinyint(1) NOT NULL DEFAULT '0',
 `private_20` tinyint(1) NOT NULL DEFAULT '0',
 `password` varchar(255) NOT NULL DEFAULT '',
 `description_1` varchar(255) DEFAULT NULL,
 `description_2` varchar(255) DEFAULT NULL,
 `description_3` varchar(255) NOT NULL DEFAULT '',
 `description_4` varchar(255) NOT NULL DEFAULT '',
 `description_5` varchar(255) NOT NULL DEFAULT '',
 `description_6` varchar(255) NOT NULL DEFAULT '',
 `description_7` varchar(255) NOT NULL DEFAULT '',
 `description_8` varchar(255) NOT NULL DEFAULT '',
 `description_9` varchar(255) NOT NULL DEFAULT '',
 `description_10` varchar(255) NOT NULL DEFAULT '',
 `description_11` varchar(255) NOT NULL DEFAULT '',
 `description_12` varchar(255) NOT NULL DEFAULT '',
 `description_13` varchar(255) NOT NULL DEFAULT '',
 `description_14` varchar(255) NOT NULL DEFAULT '',
 `description_15` varchar(255) NOT NULL DEFAULT '',
 `description_16` varchar(255) NOT NULL DEFAULT '',
 `description_17` varchar(255) NOT NULL DEFAULT '',
 `description_18` varchar(255) NOT NULL DEFAULT '',
 `description_19` varchar(255) NOT NULL DEFAULT '',
 `description_20` varchar(255) NOT NULL DEFAULT '',
 PRIMARY KEY (`id`),
 KEY `filename_12` (`filename_12`),
 KEY `filename_13` (`filename_13`),
 KEY `filename_14` (`filename_14`),
 KEY `filename_15` (`filename_15`),
 KEY `filename_16` (`filename_16`),
 KEY `filename_17` (`filename_17`),
 KEY `filename_18` (`filename_18`),
 KEY `filename_19` (`filename_19`),
 KEY `filename_20` (`filename_20`),
 KEY `member_id` (`member_id`),
 KEY `filename_1` (`filename_1`),
 KEY `filename_2` (`filename_2`),
 KEY `filename_3` (`filename_3`),
 KEY `filename_4` (`filename_4`),
 KEY `filename_5` (`filename_5`),
 KEY `filename_6` (`filename_6`),
 KEY `filename_7` (`filename_7`),
 KEY `filename_8` (`filename_8`),
 KEY `filename_9` (`filename_9`),
 KEY `filename_10` (`filename_10`),
 KEY `private_1` (`private_1`),
 KEY `private_2` (`private_2`),
 KEY `private_3` (`private_3`),
 KEY `private_4` (`private_4`),
 KEY `private_5` (`private_5`),
 KEY `private_6` (`private_6`),
 KEY `private_7` (`private_7`),
 KEY `private_8` (`private_8`),
 KEY `private_9` (`private_9`),
 KEY `private_10` (`private_10`),
 KEY `private_11` (`private_11`),
 KEY `private_12` (`private_12`),
 KEY `private_13` (`private_13`),
 KEY `private_14` (`private_14`),
 KEY `private_15` (`private_15`),
 KEY `private_16` (`private_16`),
 KEY `private_17` (`private_17`),
 KEY `private_18` (`private_18`),
 KEY `private_19` (`private_19`),
 KEY `private_20` (`private_20`),
 KEY `password` (`password`),
 KEY `description_1` (`description_1`),
 KEY `description_2` (`description_2`),
 KEY `description_3` (`description_3`),
 KEY `description_4` (`description_4`),
 KEY `description_5` (`description_5`),
 KEY `description_6` (`description_6`),
 KEY `description_7` (`description_7`),
 KEY `description_8` (`description_8`),
 KEY `description_9` (`description_9`),
 KEY `description_10` (`description_10`),
 KEY `description_11` (`description_11`),
 KEY `description_12` (`description_12`),
 KEY `description_13` (`description_13`),
 KEY `description_14` (`description_14`),
 KEY `description_15` (`description_15`),
 KEY `description_16` (`description_16`),
 KEY `description_17` (`description_17`),
 KEY `description_18` (`description_18`),
 KEY `description_19` (`description_19`),
 KEY `description_20` (`description_20`),
 KEY `filename_10_2` (`filename_10`),
 KEY `filename_10_3` (`filename_10`)
) ENGINE=MyISAM AUTO_INCREMENT=11174 DEFAULT CHARSET=latin1

 CREATE TABLE `dt_usersonline` (
 `id` int(8) NOT NULL AUTO_INCREMENT,
 `timestamp` int(15) NOT NULL DEFAULT '0',
 `ip` varchar(40) NOT NULL DEFAULT '',
 `login` varchar(25) NOT NULL DEFAULT '',
 `userid` int(10) NOT NULL DEFAULT '0',
 `session_id` varchar(255) NOT NULL DEFAULT '',
 PRIMARY KEY (`id`),
 KEY `id_2` (`id`),
 KEY `timestamp` (`timestamp`),
 KEY `ip` (`ip`),
 KEY `login` (`login`),
 KEY `userid` (`userid`),
 KEY `session_id` (`session_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4424348 DEFAULT CHARSET=latin1

【问题讨论】:

  • 首先,您应该在它们上运行EXPLAIN(也许在这里发布结果)。
  • 如前所述,通过解释运行它,然后为每个表执行show create table dt_members 等。发布这些发现。我们回来提供帮助。
  • @BK435 抱歉,我回去看看如何获​​取完整的创建表信息
  • @paul 我认为在继续优化查询之前,您应该考虑破坏了多少基本数据库设计规则……您完全忽略了范式 (1NF) 的基本规则。 .en.wikipedia.org/wiki/First_normal_form
  • @BK435 我用完整的数据更新了。我没有写代码,但我肯定会读一遍。我正在努力解决别人的烂摊子

标签: mysql performance join


【解决方案1】:
CREATE TABLE `dt_members` (
 `id` int(11) NOT NULL AUTO_INCREMENT,...

    CREATE TABLE `dt_photos` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `member_id` varchar(255) DEFAULT NULL,..

您将加入您的查询:

LEFT JOIN dt_photos AS ph ON m.id=ph.member_id

请注意,您每隔一段时间加入 member_idm.id,该列看起来就像 member_id int(11) DEFAULT NULL。您的连接条件应该是相同的数据类型,否则它看起来像是在进行全表扫描。它没有使用密钥,因为它认为它必须扫描 varchar 字段,而不是使用 on 子句的索引。

dt_members 尝试Alteringmember_idm.id 的列

有趣的是,如果您真的尝试创建外键约束,由于数据类型不匹配,它不会允许您这样做...

警告:MYISAM 表将在后面的表进程期间被锁定。

【讨论】:

  • 类似地,login 字段虽然都匹配 varchar,但在任一表中都没有唯一约束。
  • 对不起 BK435,但我对你想让我尝试的东西有点迷茫。时间不早了,明天我会阅读所有内容。您要我更改 TABLE dt_photos 吗?还是要我修改查询?
  • 如果有办法修改 QUERY 我更喜欢这种方法,否则写入这些表的代码需要在许多地方更新
  • 尝试将列 member_id 从 dt_members 更改为 m.id 的列
  • 对不起,我不确定你之前的意思。我将表结构修改为 int(11) 而不是 varchar(255),现在搜索结果在不到一秒的时间内返回。疯了,我没想到这样的事情会对查询产生如此大的影响!
【解决方案2】:

尝试将 where 条件移动到相应的连接条件中:

SELECT DISTINCT m.id AS memberID , m.login , m.age , p.gender
 , p.name AS header , p.id AS profileID , p.city , p.state , p.lastlogin
 , o.login AS online , c.name AS country , ph.filename_1 AS pic 
FROM dt_members AS m 
INNER JOIN dt_profile_approved AS p ON m.id=p.member_id 
   AND p.status=1 AND p.gender='Female' -- Moved from WHERE clause
LEFT JOIN dt_privacy AS pv ON m.id=pv.member_id
   AND (pv.unsearchable IS NULL OR pv.unsearchable='') -- Moved from WHERE clause
INNER JOIN dt_countries AS c ON c.id=p.country 
LEFT JOIN dt_photos AS ph ON m.id=ph.member_id 
LEFT JOIN dt_usersonline AS o ON m.login=o.login 
WHERE m.age BETWEEN 25 AND 40 
ORDER BY p.lastlogin DESC 
LIMIT 0, 21;

虽然在所有连接之后创建条件,但连接条件会在连接过程中进行评估,因此可以尽早避免大量不必要的连接。

虽然理论上查询优化器应该为你做这件事,但我发现mysql在这方面可能特别密集。​​

【讨论】:

  • 值得注意的是,将条件从WHERE 移动到LEFT JOINs ON 可以大大改变结果。在这种情况下,即使他们有 pv.unsearchable = 'x',他也会得到结果,他只是看不到 'x'。
【解决方案3】:

您查询的大部分选择性似乎都在您的dt_profile_approved 表上。尝试在(status, gender, lastlogin) 上创建复合索引

这应该允许仅索引选择和排序。

您的查询采用了臭名昭著的SELECT a lot ORDER BY something DESC LIMIT tinynumber 模式。这是昂贵的。尝试延迟加入。首先从dt_profile_approved 表中获取有趣的项目,如下所示:

                 SELECT member_id, lastlogin
                   FROM dt_profile_approved
                  WHERE status=1 
                    AND gender='Female' 
               ORDER BY lastlogin DESC 

可以使用(status, gender, lastlogin, member_id) 上的复合索引非常干净地优化此子查询。这称为覆盖指数。它有一个很大的好处:不需要额外的排序,因为索引已经排序了。

根据您向我们展示的确切查询,genderstatus 的索引中的顺序无关紧要。但我猜你有另一个查询男性,你可能有一个忽略了它。因此,状态可能是所有查询中更具选择性的字段。 (猜测。)

然后,将该子查询加入到查询的其余部分中……看起来像这样。

      SELECT DISTINCT 
             m.id AS memberID , m.login , m.age,
             p.gender. p.name AS header , p.id AS profileID , 
             p.city , p.state , p.lastlogin,
             o.login AS online , 
             c.name AS country , ph.filename_1 AS pic 
        FROM (
                 SELECT member_id, lastlogin
                   FROM dt_profile_approved
                  WHERE status=1 
                    AND gender='Female' 
               ORDER BY lastlogin DESC 
             ) AS sel
 INNER JOIN dt_profile_approved AS p ON sel.member_id = p.member_id
 INNER JOIN dt_members AS m  ON m.id=sel.member_id
  LEFT JOIN dt_privacy AS pv ON m.id=pv.member_id 
 INNER JOIN dt_countries AS c ON c.id=p.country 
  LEFT JOIN dt_photos AS ph ON m.id=ph.member_id 
  LEFT JOIN dt_usersonline AS o ON m.login=o.login 
      WHERE (pv.unsearchable IS NULL OR pv.unsearchable='') 
        AND m.age BETWEEN 25 AND 40 
   ORDER BY sel.lastlogin DESC 
      LIMIT 0, 21;  

如果这样可行,那将是因为它可以限制使用索引进行排序的工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-24
    • 2020-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-25
    • 1970-01-01
    • 2013-07-21
    相关资源
    最近更新 更多