【问题标题】:Can I make this mySQL query faster?我可以让这个 mySQL 查询更快吗?
【发布时间】:2018-05-06 19:24:26
【问题描述】:

我在 mysql-slow.log 中有以下条目:

# Time: 180506 21:57:03
# User@Host: mysqlserver[mysqlserver] @ localhost []
# Query_time: 88.963476  Lock_time: 0.000088 Rows_sent: 50  Rows_examined: 114197

SET timestamp=1525633023;

SELECT n1.full_name AS sender_full_name, s1.email AS sender_email, e.subject, e.body, 
e.attach, e.date, e.id, r.status, n2.full_name AS receiver_full_name,
s2.email AS receiver_email, r.basket 
FROM people_emails p 
JOIN email_routing r ON r.receiver_email_id = 3223 AND r.status = 2 
JOIN email e ON e.id = r.message_id 
JOIN people_emails s1 ON s1.id = r.sender_email_id 
JOIN people n1 ON n1.id = s1.people_id 
JOIN people_emails s2 ON s2.id = r.receiver_email_id 
JOIN people n2 ON n2.id = s2.people_id  
WHERE p.internal_user_id = 314 
ORDER BY e.date desc 
LIMIT 0, 50;

该查询的结果与此类似:

 ----------------------------------------------------------------------------------------------------
 |sender_full_name|sender_email|subject|body| attach | date |  id  |status|receiver_full_name|basket| 
 ----------------------------------------------------------------------------------------------------
 |John Blow       |jb@corp.lan |Aloha  |Text|        |180506|856050|2     |Mary Johns        |1     |
 ----------------------------------------------------------------------------------------------------

这是关于查询和使用的表的所有数据:

EXPLAIN SELECT n1.full_name AS sender_full_name, s1.email AS sender_email, 
e.subject, e.body, e.attach, e.date, e.id, r.status, n2.full_name AS receiver_full_name, 
s2.email AS receiver_email, r.basket, 'user777' FROM people_emails p 
JOIN email_routing r ON r.receiver_email_id = 3233 AND r.status = 2 
JOIN email e ON e.id = r.message_id 
JOIN people_emails s1 ON s1.id = r.sender_email_id 
JOIN people n1 ON n1.id = s1.people_id 
JOIN people_emails s2 ON s2.id = r.receiver_email_id 
JOIN people n2 ON n2.id = s2.people_id 
WHERE p.internal_user_id = 314 ORDER BY e.date desc LIMIT 0, 50; 

id  select_type table   type    possible_keys   key key_len     ref                     rows    Extra
1   SIMPLE      s2      const   PRIMARY         PRIMARY 4       const                   1       Using temporary; Using filesort
1   SIMPLE      n2      const   PRIMARY         PRIMARY 4       const                   1   
1   SIMPLE      p       ALL     NULL            NULL    NULL    NULL                    18631   Using where
1   SIMPLE      r       ALL     NULL            NULL    NULL    NULL                    899567  Using where; Using join buffer
1   SIMPLE      e       eq_ref  PRIMARY         PRIMARY 4       server.r.message_id     1   
1   SIMPLE      s1      eq_ref  PRIMARY         PRIMARY 4       server.r.sender_email_id1   
1   SIMPLE      n1      eq_ref  PRIMARY         PRIMARY 4       server.s1.people_id     1   



SHOW CREATE TABLE people_emails; 
CREATE TABLE `people_emails` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `nick` varchar(255) NOT NULL,
 `email` varchar(255) NOT NULL,
 `key_name` varchar(255) NOT NULL,
 `people_id` int(11) NOT NULL,
 `status` int(11) NOT NULL DEFAULT '0',
 `activity` int(11) NOT NULL,
 `internal_user_id` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 FULLTEXT KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=22114 DEFAULT CHARSET=utf8

SHOW CREATE TABLE email_routing; 
CREATE TABLE `email_routing` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `message_id` int(11) NOT NULL,
 `sender_email_id` int(11) NOT NULL,
 `receiver_email_id` int(11) NOT NULL,
 `basket` int(11) NOT NULL,
 `status` int(11) NOT NULL,
 `popup` int(11) NOT NULL,
 `tm` int(11) NOT NULL,
 KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=987389 DEFAULT CHARSET=utf8

SHOW CREATE TABLE email; 
CREATE TABLE `email` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `subject` text NOT NULL,
 `body` text NOT NULL,
 `date` datetime NOT NULL,
 `attach` text NOT NULL,
 `attach_ondisk` text NOT NULL,
 `attach_dir` varchar(255) CHARACTER SET cp1251 DEFAULT NULL,
 `attach_subject` varchar(255) DEFAULT NULL,
 `attach_content` longtext,
 PRIMARY KEY (`id`),
 KEY `Index_2` (`attach_dir`),
 FULLTEXT KEY `path` (`attach_dir`)
) ENGINE=MyISAM AUTO_INCREMENT=856151 DEFAULT CHARSET=utf8

SHOW CREATE TABLE people; 
CREATE TABLE `people` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `fname` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `lname` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `patronymic` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `gender` tinyint(1) NOT NULL,
 `full_name` varchar(255) NOT NULL DEFAULT ' ',
 `category` int(11) NOT NULL,
 `people_type_id` int(255) DEFAULT NULL,
 `tags` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `job` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `post` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `profession` varchar(255) CHARACTER SET cp1251 DEFAULT NULL,
 `zip` varchar(16) CHARACTER SET cp1251 NOT NULL,
 `country` int(11) DEFAULT NULL,
 `region` varchar(10) NOT NULL,
 `city` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `address` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `address_date` date DEFAULT NULL,
 `inner` tinyint(4) NOT NULL,
 `contact_through` varchar(255) DEFAULT '',
 `next_call` date NOT NULL,
 `additional` text CHARACTER SET cp1251 NOT NULL,
 `user_id` int(11) NOT NULL,
 `changed` datetime NOT NULL,
 `status` int(11) DEFAULT NULL,
 `nick` varchar(255) DEFAULT NULL,
 `birthday` date DEFAULT NULL,
 `last_update_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 `area` text NOT NULL,
 `reviewed_` tinyint(4) NOT NULL,
 `phones_old` text NOT NULL,
 `post_sticker` text NOT NULL,
 `permissions` int(120) NOT NULL DEFAULT '0',
 `internal_user_id` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `most_used` (`category`,`status`,`city`,`lname`,`next_call`),
 KEY `registrars` (`category`,`status`,`contact_through`,`next_call`),
 FULLTEXT KEY `lname` (`lname`),
 FULLTEXT KEY `fname` (`fname`),
 FULLTEXT KEY `mname` (`patronymic`),
 FULLTEXT KEY `Full Name` (`full_name`)
) ENGINE=MyISAM AUTO_INCREMENT=415009 DEFAULT CHARSET=utf8

根据评论的要求获得上述输出时,我还注意到我的表都采用不同的格式 - MyISAM 和 InnoDB。这也可能是问题的一部分吗?

我是否让表格结构过于复杂?我想了解查询的哪一部分让这变得如此缓慢,以便我可以重新安排我的表格。

【问题讨论】:

  • 任何关于查询优化的问题都应该包含SHOW CREATE TABLE <tablename> 对查询中每个表的输出,以便我们知道您当前拥有哪些索引。也是您尝试优化的查询的EXPLAIN <query> 的输出。
  • @BillKarwin 谢谢,通过这样做我看到我的表是 MyISAM 和 InnoDB 的混合,我猜这也可能是问题的一部分
  • 因此,在两个最大的数据集 p 和 r) 上缺乏任何可用索引是一个明显的起点

标签: mysql query-optimization mysql-slow-query-log


【解决方案1】:

一般来说,您希望从 EXPLAIN 报告中删除 type=ALL 的条目。这意味着它正在执行表扫描,如果它发生在大表上,这对性能不利。

在您的情况下,您有 两个 正在执行表扫描的表。检查说明的row 列中的数字,18631 和 899567。将它们相乘 = 16,759,832,777。这就是查询可能检查的行组合数!

部分问题是您的查询正在执行Cartesian product。您没有将您的表 p 与其他表相关联的条件。因此,对于p 中检查的每一行,它都会将其与其他表中检查的行结合起来。这成本很高。

不清楚为什么您的查询中甚至包含p,因为它与其他表无关,并且您不会在选择列表中从中获取任何列。即使我从查询中取出p,我也可以生成您描述的结果集:

SELECT n1.full_name AS sender_full_name, s1.email AS sender_email,
e.subject, e.body, e.attach, e.date, e.id, r.status, n2.full_name AS receiver_full_name,
s2.email AS receiver_email, r.basket, 'user777'
FROM email_routing r
JOIN email e ON e.id = r.message_id
JOIN people_emails s1 ON s1.id = r.sender_email_id
JOIN people n1 ON n1.id = s1.people_id
JOIN people_emails s2 ON s2.id = r.receiver_email_id
JOIN people n2 ON n2.id = s2.people_id
WHERE r.receiver_email_id = 3233 AND r.status = 2
ORDER BY e.date desc LIMIT 0, 50;

我还建议添加这个索引:

ALTER TABLE email_routing ADD KEY bk1 (receiver_email_id, status,
    sender_email_id, message_id, basket);

这有助于搜索r.receiver_email_id = 3233 AND r.status = 2

索引中的附加列使其成为覆盖索引。这意味着查询根本不需要读取 email_routing 表,只要它从索引中获取它需要的所有列。

这个查询的解释看起来更好,现在没有一个表在做type=ALL,其中一个显示“使用索引”,这是覆盖索引的指示符。

+----+-------------+-------+--------+---------------+---------+---------+------------------------+------+---------------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                    | rows | Extra                           |
+----+-------------+-------+--------+---------------+---------+---------+------------------------+------+---------------------------------+
|  1 | SIMPLE      | s2    | const  | PRIMARY       | PRIMARY | 4       | const                  |    1 | Using temporary; Using filesort |
|  1 | SIMPLE      | n2    | const  | PRIMARY       | PRIMARY | 4       | const                  |    1 | NULL                            |
|  1 | SIMPLE      | r     | ref    | bk1           | bk1     | 8       | const,const            |    1 | Using index                     |
|  1 | SIMPLE      | s1    | eq_ref | PRIMARY       | PRIMARY | 4       | test.r.sender_email_id |    1 | NULL                            |
|  1 | SIMPLE      | n1    | eq_ref | PRIMARY       | PRIMARY | 4       | test.s1.people_id      |    1 | NULL                            |
|  1 | SIMPLE      | e     | eq_ref | PRIMARY       | PRIMARY | 4       | test.r.message_id      |    1 | NULL                            |
+----+-------------+-------+--------+---------------+---------+---------+------------------------+------+---------------------------------+

P.S.:MyISAM 与 InnoDB 在这种查询优化方面差别不大。该索引将对两个存储引擎都有很大帮助。但我始终建议转换为 InnoDB(请参阅我对 MyISAM versus InnoDB 的回答)。

【讨论】:

  • 谢谢,太好了!我虽然出于安全原因使用 p 表来确保用户只获取属于他的 user_id 的那些行,但你是对的,可以使用 receiver_email_id 而不检查 user_id,谢谢。我的服务器现在已经运行了几个小时,使用这个更正后的查询 mySQL 负载急剧下降,所以我现在将这些查询分成 2-3 个部分,而不是之前的 88 个
  • 我也尝试将表转换为 InnoDB,但我的 SQL 版本低于 5.6(因为它是旧的 Ubuntu 服务器 12.04),我做不到...WHERE r.receiver_email_id = 3233 AND r.status = 2 MATCH (n1.full_name, s1.email, e.subject, e.body, e.attach) AGAINST (? IN BOOLEAN MODE) 即无法进行全文搜索,所以我现在转换回 MyISAM .那些 MATCH 查询非常慢。但我认为这是一个单独的问题。
  • 我觉得是时候升级了!您正在运行的版本至少落后于当前三个主要版本。
【解决方案2】:

这看起来错误

    FROM  people_emails p
    JOIN  email_routing r  ON r.receiver_email_id = 3223
      AND  r.status = 2

p 未在任何ON 子句中使用。也许您错过了将pr 绑定在一起的方式?没有它,你就有一个“交叉连接”。如果每个中有 1K 行,则连接中最终有 1M 行。

另外,请使用ON 来显示表格之间的关系;使用WHERE 进行过滤(3222 & 2)。

【讨论】:

    猜你喜欢
    • 2013-06-23
    • 1970-01-01
    • 2016-04-23
    • 2017-11-06
    • 2011-11-03
    • 1970-01-01
    • 2013-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多