【问题标题】:Mysql query not optimized and very slow, but why?Mysql查询没有优化而且很慢,但是为什么呢?
【发布时间】:2017-04-17 21:32:45
【问题描述】:

在我开发的软件中,一个汽车经销商软件,有一个议程部分,其中包含用户的所有约会。

此部分的加载速度非常快,每天正常使用议程(数千行),但当议程表达到 100 万行时开始变得非常慢。

结构:

1) 主表

CREATE TABLE IF NOT EXISTS `agenda` (
  `id_agenda` int(11) NOT NULL AUTO_INCREMENT,
  `id_user` int(11) NOT NULL DEFAULT '0',
  `id_agency` int(11) NOT NULL DEFAULT '0',
  `id_customer` int(11) DEFAULT NULL,
  `id_car` int(11) DEFAULT NULL,
  `id_owner` int(11) DEFAULT NULL,
  `type` int(11) NOT NULL DEFAULT '8',
  `title` varchar(255) NOT NULL DEFAULT '',
  `text` text NOT NULL,
  `start_day` date NOT NULL DEFAULT '0000-00-00',
  `end_day` date NOT NULL DEFAULT '0000-00-00',
  `start_hour` time NOT NULL DEFAULT '00:00:00',
  `end_hour` time NOT NULL DEFAULT '00:00:00'
  PRIMARY KEY (`id_agenda`),
  KEY `start_day` (`start_day`),
  KEY `id_customer` (`id_customer`),
  KEY `id_car` (`id_car`),
  KEY `id_user` (`id_user`),
  KEY `id_owner` (`id_owner`),
  KEY `type` (`type`),
  KEY `id_agency` (`id_agency`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 ;

2) 辅助表

CREATE TABLE IF NOT EXISTS `agenda_cars` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `id_agenda` int(11) NOT NULL,
  `id_car` int(11) NOT NULL,
  `id_owner` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `id_agenda` (`id_agenda`),
  KEY `id_car` (`id_car`),
  KEY `id_owner` (`id_owner`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1

查询:

SELECT a.id_agenda
FROM agenda as a
LEFT JOIN agenda_cars as agc on agc.id_agenda = a.id_agenda
WHERE 
(a.id_customer = '22'  OR (a.id_owner = '22' OR agc.id_owner = '22' ))
GROUP BY a.id_agenda
ORDER BY a.start_day,  a.start_hour

解释:

id  select_type table   type    possible_keys  key         key_len    ref   rows    Extra   
1   SIMPLE       a      index   PRIMARY        PRIMARY      4          NULL 1051987 Using temporary; Using filesort
1   SIMPLE       agc    ref     id_agenda      id_agenda    4   db.a.id_agenda  1   Using where

查询达到10秒结束,id为22,但其他id也可以达到20秒,这只是为了查询,加载网页中的所有内容当然需要更多时间。

我不明白为什么要花这么长时间才能获取数据,我认为索引配置正确并且查询非常简单,为什么?

数据太多?

我是这样解决的:

SELECT a.id_agenda
  FROM
      (
              SELECT id_agenda
              FROM agenda  
              WHERE (id_customer = '22'  OR  id_owner = '22' )
          UNION
              SELECT id_agenda
              FROM  agenda_cars
              WHERE id_owner = '22'
      )  as at
INNER JOIN agenda as a on a.id_agenda = at.id_agenda
GROUP BY a.id_agenda
ORDER BY a.start_day,  a.start_hour

这个版本的查询比上一个版本快十倍……但是为什么呢?

感谢所有想为解决我的疑惑做出贡献的人!

Rick James 解决方案后更新:

建议查询

SELECT  a.id_agenda
    FROM  
    (
        SELECT  id_agenda  FROM  agenda  WHERE  id_customer = '22'
        UNION DISTINCT
        SELECT  id_agenda  FROM  agenda  WHERE  id_owner = '22'
        UNION DISTINCT
        SELECT  id_agenda  FROM  agenda_cars  WHERE  id_owner = '22'
    ) as at
    INNER JOIN  agenda as a  ON a.id_agenda = at.id_agenda
    ORDER BY  a.start_datetime;

结果:总共 279 个,0.0111 秒

解释:

id      select_type     table           type    possible_keys   key         key_len     ref             rows        Extra
1       PRIMARY         <derived2>      ALL     NULL            NULL        NULL        NULL            366         Using temporary; Using filesort
1       PRIMARY         a               eq_ref  PRIMARY         PRIMARY     4           at.id_agenda    1           NULL
2       DERIVED         agenda          ref     id_customer     id_customer 5           const           1           Using index
3       UNION           agenda          ref     id_owner        id_owner    5           const           114         Using index
4       UNION           agenda_cars     ref     id_owner        id_owner    4           const           250         NULL
NULL    UNION RESULT    <union2,3,4>    ALL     NULL            NULL        NULL        NULL            NULL        Using temporary

【问题讨论】:

  • 您按没有索引的列排序
  • OR 查询往往会混淆 MySQL 优化器,将它们分解为 UNION 是通常的解决方案。
  • 一些观察:您有一个没有聚合函数的 GROUP BY 子句。你 LEFT JOIN 一个你没有选择任何列的表。
  • OR 在查询中是死亡之吻。这通常意味着创建临时表和整理记录,这总是一件麻烦事。
  • @juergend - ORDER BY 使用索引的唯一方法是 WHEREGROUP BY 已经被同一索引完全处理。这里不可能——如果除了GROUP BYORDER BY 列出不同的列之外没有其他原因。

标签: mysql sql performance optimization innodb


【解决方案1】:

在深入探讨可以做什么之前,让我列出我看到的几个 reg 标志。

  • OR 很难优化
  • 对多个表 JOINed 一起过滤 (WHERE) 很难优化。
  • GROUP BY x ORDER BY z 表示两次传递数据,通常是 2 个临时表和文件排序。
  • 你真的是指LEFT吗?它说“可能缺少正确的表 (agc),在这种情况下提供 NULLs”。

(您可能无法摆脱所有危险信号。)

架构中的危险信号:

  • 为每一列建立索引——通常没用
  • 只有单列索引——“复合”索引通常有帮助。
  • DATETIME 作为单独的列 - 通常会导致查询笨拙。

好的,这些都不在我肩上,现在来研究查询...(哦,感谢您提供CREATEsEXPLAIN!)

ON 暗示着议程:议程汽车之间的 1:many 关系。对吗?

id_ownerid_car 在两个表中,但不包含在 ON 中;怎么了?

(这是您最后一个问题的答案。)为什么有GROUP BY?我看不到聚合。我猜 1:many 关系会导致多行,你需要去重复吗?如需重复数据删除,请使用DISTINCT。但是,真正的解决方案是避免“膨胀 (JOIN) - 放气 (@​​987654339@)”综合症。您的子查询是一个好的开始。

将上面的一些 cmets 加入进来,还有更多:

SELECT  a.id_agenda
    FROM  
    (
        SELECT  id_agenda  FROM  agenda  WHERE  id_customer = '22'
        UNION DISTINCT
        SELECT  id_agenda  FROM  agenda  WHERE  id_owner = '22'
        UNION DISTINCT
        SELECT  id_agenda  FROM  agenda_cars  WHERE  id_owner = '22'
    ) as at
    INNER JOIN  agenda as a  ON a.id_agenda = at.id_agenda
    ORDER BY  a.start_datetime;

注意事项:

  • 摆脱了另一个OR
  • 显式 UNION DISTINCT 明确表示预期重复。
  • GROUP BY而不使用SELECT DISTINCTUNION DISTINCT 满足需求。
  • 您有 4 个必要的索引(每个子查询一个):(id_customer)(id_owner)(在两个表上)和 PRIMARY KEY(id_agenda)
  • 索引是“涵盖所有子查询的索引——额外的好处。
  • 对于ORDER BY,会有一个不可避免的 tmp 表和文件排序,但不会在一百万行上。
  • (这次不需要复合索引。)
  • 我改成了DATETIME;如果您有充分的理由拆分它们,请改回来。

我又给了你 10 倍吗?我解释得够清楚了吗?

哦,还有一件事......

这个查询返回一个按它不返回的东西排序的 id 列表(日期+时间)。你会用 id 做什么?如果您将其用作另一个表中的子查询,则优化器有权丢弃ORDER BY。只是警告你。

【讨论】:

  • 首先感谢您的帮助。我试图更好地解释结构: - 议程是主表,在这个表中你会看到 id_car, id_owner 原因在开始(软件的第一个版本,可以只将一辆车和一个所有者链接到约会,之后我添加了将多个汽车/车主链接到议程的可能性,因此议程_汽车表进来了。我必须使用左连接,因为我必须保持与旧版本的兼容性。
  • - 我为“where”子句使用的所有字段添加了索引,如果我使用复合索引,我能做到吗?还是我总是需要使用复合索引最左边的字段?
  • 我需要按组添加,因为我可以将不止一辆汽车链接到一排议程,并且按时间为客户按时间顺序为列表顺序完成订购。我尝试了您在工会中使用“不同”的建议,我还尝试创建一个复合索引(start_day,start_hour)来覆盖订单,但我没有提高速度。再次感谢您的帮助!
  • @Jung - LEFT 允许 0(或更多)辆汽车; JOIN 允许 1 个或多个。
  • 在使用WHERE a=1 OR b=2 时,没有索引是有用的。对于WHERE a=1 AND b=2INDEX(a,b)(任意顺序)是最好的; INDEX(a)INDEX(b) 都有些用处。
猜你喜欢
  • 2020-10-24
  • 1970-01-01
  • 2017-11-06
  • 2021-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-20
  • 2022-12-13
相关资源
最近更新 更多