【问题标题】:Super-slow MySQL query with WHERE and Index使用 WHERE 和索引的超慢 MySQL 查询
【发布时间】:2021-06-16 14:23:20
【问题描述】:

我有一个超级慢的查询,不知道它为什么很慢,而且还有一些奇怪的行为。

SELECT <fields list> 
FROM scadenze s 
     JOIN cartelle_cliniche cc ON (cc.id = s.id_cartella) 
     JOIN anagrafica a ON (a.id = cc.id_anagrafica) 
     JOIN scadenze_sedute ss ON (ss.id_scadenza = s.id) 
     JOIN pmultiple_prenotazioni pp ON (pp.id = ss.id_fisico OR pp.id = ss.id_virtuale) 
     LEFT JOIN medici_privati mp ON (mp.id = a.id_medico) 
     LEFT JOIN operatorif o ON (o.id = cc.id_operatore_emittente) 
WHERE pp.confermato = '1' AND pp.annullato = '0' 
      AND pp.id_esito != 4 AND s.id_stato = '0' 
      AND (DATE(s.data_scadenza) BETWEEN '2021-06-01' AND '2021-06-16') 
GROUP BY s.id, TIME(ss.data_seduta)

持续5分钟!!

现在,您可以看到有很多 JOIN 并且只有 5 个 WHERE 子句。 如果我从 WHERE 子句中删除这 3 个参数:pp.confermato = '1' AND pp.annullato = '0' AND pp.id_esito != 4,则查询仅持续 0.15 秒。 我在这 3 个参数上有单个索引,并且我还尝试使用 MySQL 的 FORCE INDEX 函数添加多个索引。但什么都没有。

如果我将这些参数放在 HAVING 子句中,例如:

SELECT <fields list> 
FROM scadenze s 
     JOIN cartelle_cliniche cc ON (cc.id = s.id_cartella) 
     JOIN anagrafica a ON (a.id = cc.id_anagrafica) 
     JOIN scadenze_sedute ss ON (ss.id_scadenza = s.id) 
     JOIN pmultiple_prenotazioni pp ON (pp.id = ss.id_fisico OR pp.id = ss.id_virtuale) 
     LEFT JOIN medici_privati mp ON (mp.id = a.id_medico) 
     LEFT JOIN operatorif o ON (o.id = cc.id_operatore_emittente) 
WHERE s.id_stato = '0' AND (DATE(s.data_scadenza) BETWEEN '2021-06-01' AND '2021-06-16') 
GROUP BY s.id, TIME(ss.data_seduta) 
HAVING pp.confermato = '1' AND pp.annullato = '0' AND pp.id_esito != 4

查询持续 0.15 秒。 但是在 WHERE 子句中使用这些参数有什么问题呢? (在 Have 子句中,我得到 308 条记录而不是 309 条)

如果我在该字段的任何一个上都有索引,什么会导致这个缓慢的查询?

提前谢谢你。

编辑:

在这里解释查询:

在此处为 pmultiple_prenotazione (pp) 创建表:

CREATE TABLE `pmultiple_prenotazioni` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `id_utente` INT(11) NOT NULL,
    `id_anagrafica` INT(11) NOT NULL DEFAULT '0',
    `id_cartella` INT(11) NOT NULL DEFAULT '0',
    `id_kit` INT(11) NOT NULL DEFAULT '0',
    `id_fase` INT(11) NOT NULL DEFAULT '0',
    `data_seduta` DATETIME NOT NULL,
    `giorno` VARCHAR(50) NOT NULL COLLATE 'latin1_swedish_ci',
    `id_reparto` INT(11) NOT NULL,
    `importo_prestazione` DECIMAL(6,2) NOT NULL DEFAULT '0.00',
    `peso_prestazione` DECIMAL(5,2) NOT NULL DEFAULT '0.00',
    `durata_prestazione` SMALLINT(6) NOT NULL DEFAULT '0',
    `id_operatore` INT(11) NOT NULL DEFAULT '0',
    `solo` TINYINT(1) NOT NULL DEFAULT '0',
    `confermato` TINYINT(1) NOT NULL DEFAULT '0',
    `annullato` TINYINT(1) NOT NULL DEFAULT '0',
    `id_esito` TINYINT(4) NOT NULL DEFAULT '0',
    `creazione` DATETIME NOT NULL,
    `univoco` VARCHAR(255) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `id_cartella` (`id_cartella`) USING BTREE,
    INDEX `id_kit` (`id_kit`) USING BTREE,
    INDEX `data_seduta` (`data_seduta`) USING BTREE,
    INDEX `confermato` (`confermato`) USING BTREE,
    INDEX `id_utente` (`id_utente`) USING BTREE,
    INDEX `id_reparto` (`id_reparto`) USING BTREE,
    INDEX `id_anagrafica` (`id_anagrafica`) USING BTREE,
    INDEX `id_fase` (`id_fase`) USING BTREE,
    INDEX `peso_prestazione` (`peso_prestazione`) USING BTREE,
    INDEX `durata_prestazione` (`durata_prestazione`) USING BTREE,
    INDEX `solo` (`solo`) USING BTREE,
    INDEX `id_operatore` (`id_operatore`) USING BTREE,
    INDEX `univoco` (`univoco`) USING BTREE,
    INDEX `annullato` (`annullato`) USING BTREE,
    CONSTRAINT `prenkit` FOREIGN KEY (`id_kit`) REFERENCES `starbene_1`.`pmultiple_kit` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=717
;

【问题讨论】:

  • 当您提出查询优化问题时,请为查询中引用的每个表包含SHOW CREATE TABLE 的结果,这样我们就不必猜测您的表中的列、索引或约束.还包括每个查询的EXPLAIN 的结果。
  • 对不起,我按照你的建议添加了信息。
  • 您在scadenze_sedute.id_fisicoscadenze_sedute.id_virtuale 上有索引吗?
  • @slkorolev 是的,当然!没有 WHERE 子句,查询持续 0.15"

标签: mysql performance indexing query-optimization


【解决方案1】:

问题出在这里:

 JOIN pmultiple_prenotazioni pp ON (pp.id = ss.id_fisico OR pp.id = ss.id_virtuale) 

索引无法帮助解决这个问题。如果您在此连接条件中只有一个术语,它可以使用pp.id 的主键索引。但是对于OR 表达式,它不能。

您可以在 EXPLAIN 中看到它必须对 pp 进行表扫描 (type: ALL) 的效果。因此,连接比较的总数是 ss 或 10193 中检查的行数乘以 pp 或 23565 中检查的行数。总计:2.4 亿!

你可以把它分成两个连接:

 LEFT JOIN pmultiple_prenotazioni pp1 ON (pp1.id = ss.id_fisico)
 LEFT JOIN pmultiple_prenotazioni pp2 ON (pp2.id = ss.id_virtuale)
 ...
 WHERE
    pp1.confermato = '1' AND pp1.annullato = '0' AND pp1.id_esito != 4
    AND pp2.confermato = '1' AND pp2.annullato = '0' AND pp2.id_esito != 4
    AND ...

或者你可以使用UNION:

SELECT * FROM (
  SELECT s.id, ss.data_seduta
  FROM scadenze s
     JOIN cartelle_cliniche cc ON (cc.id = s.id_cartella)
     JOIN anagrafica a ON (a.id = cc.id_anagrafica)
     JOIN scadenze_sedute ss ON (ss.id_scadenza = s.id)
     JOIN pmultiple_prenotazioni pp ON (pp.id = ss.id_fisico) -- ONLY fisico
     LEFT JOIN medici_privati mp ON (mp.id = a.id_medico)
     LEFT JOIN operatorif o ON (o.id = cc.id_operatore_emittente)
  WHERE
        pp.confermato = '1' AND pp.annullato = '0' AND pp.id_esito != 4
        AND s.id_stato = '0' AND (DATE(s.data_scadenza) BETWEEN '2021-06-01' AND '2021-06-16')

  UNION

  SELECT s.id, ss.data_seduta
  FROM scadenze s
     JOIN cartelle_cliniche cc ON (cc.id = s.id_cartella)
     JOIN anagrafica a ON (a.id = cc.id_anagrafica)
     JOIN scadenze_sedute ss ON (ss.id_scadenza = s.id)
     JOIN pmultiple_prenotazioni pp ON (pp.id = ss.id_virtuale) -- ONLY virtuale
     LEFT JOIN medici_privati mp ON (mp.id = a.id_medico)
     LEFT JOIN operatorif o ON (o.id = cc.id_operatore_emittente)
  WHERE
        pp.confermato = '1' AND pp.annullato = '0' AND pp.id_esito != 4
        AND s.id_stato = '0' AND (DATE(s.data_scadenza) BETWEEN '2021-06-01' AND '2021-06-16')
) AS t
GROUP BY id, TIME(data_seduta);

【讨论】:

  • 哦,谢谢!那我就这样试试,更新帖子。
  • 性能提升了吗? EXPLAIN 报告怎么样?
【解决方案2】:

在 MySQL Workbench 中,您只需单击“执行计划”(查询执行后页面右下角)即可查看查询的执行计划。那么应该很容易知道哪个步骤需要更多时间。

【讨论】:

  • 我已经得到了这个信息。删除该子句,查询速度很快。所以这些参数会减慢我的查询速度,但我不知道如何解决它。
【解决方案3】:

看起来这可能是“inflate-deflate”的情况。首先它加入表(膨胀),然后它组合(放气)。在中间可能有很多行,需要做很多工作来完成所有的连接。

所以,试着做GROUP BYfirst

SELECT ...
    FROM ( SELECT ... FROM s JOIN ss ...
            GROUP BY s.id, TIME(ss.data_seduta) ) AS aaa
    JOIN ...

你应该不需要在最后做一个GROUP BY

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多