MySQL 8.0.15 大坝慢选查询

Posted

技术标签:

【中文标题】MySQL 8.0.15 大坝慢选查询【英文标题】:MySQL 8.0.15 Dam slow select query 【发布时间】:2019-08-21 08:43:27 【问题描述】:

查询的主旨很简单,找出外键列上的最后一个条目。 我可以说的伪代码是

select vehicleid , last_journey_point , last_journey_time from journeyTable.

这是我的 SQL 语句

-- loconumber 是一个索引列 -- Journeyserla 是一个自动编号的主键 int(11) -- 表 locojourney 包含 400,000 条记录

-- 下面的代码块在 19 秒内执行

with LocomotiveLastRun AS(
     -- this block of code runs in 0.016 sec
    SELECT locojourney.loconumber , MAX(locojourney.journeyserla) as lastrunid
      FROM locojourney GROUP BY loconumber)

  SELECT locojourney.CurrentCombiners , locojourney.JourneySerla , 
         locojourney.From_RunPoint , locojourney.NEXT_RunPoint 
   FROM  LocomotiveLastRun FORCE INDEX(lastrunid)
   JOIN  locojourney FORCE INDEX(PRIMARY) ON x.lastrunid = locojourney.journeyserla 
  WHERE  locojourney.ishoc = 'n'

EXPLAIN 命令显示一个派生表,它不使用索引,使用 where 并键入 ALL

这是表定义:

    -- SHOW CREATE TABLE locojourney
CREATE TABLE `locojourney` (
  `trainID` smallint(5) NOT NULL,
  `LocoNumber` varchar(5) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
  `CurrentLocoBase` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
  `CurrentDuedate` date DEFAULT NULL,
  `LocoConsist` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
  `CurrentLocoDomain` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
  `DomainChange` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
  `FEDR` enum('N','Y') CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT 'N',
  `LADR` enum('N','Y') CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT 'N',
  `ISBANKER` enum('N','Y') CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT 'N',
  `TrainName` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
  `WithOutLoad` enum('N','Y') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'N',
  `runRoute` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
  `From_RunPoint` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
  `From_RunTime` datetime NOT NULL,
  `NEXT_RunPoint` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
  `NEXT_RunTime` datetime NOT NULL,
  `Affects_Outage` enum('N','Y') CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT 'N',
  `Affects_Mileage` enum('N','Y') CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT 'N',
  `GroundDistance` double(5,2) DEFAULT '0.00',
  `SHGallowance` int(11) DEFAULT '0',
  `Outage` double(5,4) DEFAULT '0.0000',
  `UnderServiceType` enum('FHT','CHG','DEP','MIX','DETN') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'FHT',
  `SubServiceHead` varchar(25) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'RUN',
  `IShoc` enum('N','Y') CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT 'N',
  `CurrentCombiners` varchar(28) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
  `RunSetSerla` varchar(25) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
  `JourneySerla` int(11) NOT NULL AUTO_INCREMENT,
  `NominationSerla` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
  `Traction` enum('DSL','AC') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'DSL',
  `Trainload` smallint(4) NOT NULL DEFAULT '0',
  `LeadAssist` enum('Y','N') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'N',
  `DEO` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
  `DEOtime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`JourneySerla`),
  KEY `trainID` (`trainID`) USING BTREE,
  KEY `routesection_idx` (`runRoute`) USING BTREE,
  KEY `loconumber_idx` (`LocoNumber`) USING BTREE,
  KEY `runsetserla_idx` (`RunSetSerla`) USING BTREE,
  KEY `subservicehead_idx` (`SubServiceHead`) USING BTREE,
  CONSTRAINT `locojourney_ibfk_1` FOREIGN KEY (`SubServiceHead`) REFERENCES `ineffective` (`IneffectiveHead`) ON UPDATE CASCADE,
  CONSTRAINT `locojourney_ibfk_3` FOREIGN KEY (`runRoute`) REFERENCES `routesections` (`Sectionname`) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT `loconumber_fk` FOREIGN KEY (`LocoNumber`) REFERENCES `lococontainer` (`LocoNumber`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=345719 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

【问题讨论】:

您没有向我们提供足够的信息来帮助您。请阅读这个。 meta.***.com/a/271056 请特别注意查询性能部分。那么请edit你的问题。 添加适当的数据样本和预期结果 我担心查询的性能不佳,mysql 说 mysql 每秒能够查询 1400 万条记录,但在我的情况下它只有 40 万条记录。 需要正确的索引才能使任何 SQL 系统在超过几千行的表上运行良好。为了建议正确的索引,我们需要更多地了解您的情况。 FORCE INDEX() 几乎从来没有帮助过,当然在 CTE 上也没有帮助,因为 CTE 本身就缺乏索引。 JOIN 在主键列上执行,实际上应该没有任何异常 【参考方案1】:
with LocomotiveLastRun AS(
       -- this block of code runs in 0.016 sec
    SELECT locojourney.loconumber , MAX(locojourney.journeyserla) as lastrunid
      FROM locojourney 
     GROUP BY loconumber)

为什么这个 CTE 子查询很快?因为您的表已经在(loconumber, journeyserla) 上有一个索引。 (InnoDb 自动将主键附加到每个索引。)该查询可以通过对该索引的松散索引扫描来满足,而且速度很快。

现在是您的主要查询:

    摆脱FORCE INDEX()。除非你有至少十年的 SQL 经验或者你已经阅读了 MySQL 中 InnoDB 索引的源代码,否则不要梦想使用它。值得注意的是,它在 CTE 上完全没用,因为 CTE 没有索引。 为清楚起见,将主(详细)表放在首位,将 CTE 放在第二位。 为清楚起见,将 JOIN 重铸为 WHERE...IN...

这三个建议给了我们这个:

   WITH LocomotiveLastRun AS (...)
 SELECT locojourney.CurrentCombiners , locojourney.JourneySerla , 
        locojourney.From_RunPoint , locojourney.NEXT_RunPoint 
   FROM locojourney
  WHERE journeyserla IN (SELECT lastrunid FROM LocomotiveLastRun)
    AND locojourney.ishoc = 'n'

现在,很明显什么索引可以帮助这个查询。

(ishoc) 上的索引会有所帮助。 (它实际上是一个索引,因为 InnoDB 在 (ishoc, journeyserla) 上,所以它对 WHERE 条件都有帮助。)查询计划器使用 BTREE 随机访问来查找具有 ishoc'n' 的第一个索引行,然后扫描主键的值以将它们与IN 子句匹配。

代替该索引,覆盖查询的复合索引将提供更多帮助。这样的覆盖索引特别有用,因为表的每一行都很大,有很多列。该索引提到了WHERE 子句中的列以及您要选择的列,如下所示:

(ishoc, journeyserla, CurrentCombiners, From_RunPoint, NEXT_RunPoint)

查询计划器可以完全从索引中满足您的查询,从而节省磁盘读取时间来满足查询。如果你经常使用你的查询,这个索引是个好主意。但是,它确实会消耗磁盘空间并稍微减慢INSERTUPDATE 的操作速度。

阅读https://use-the-index-luke.com/

【讨论】:

要求不超过 1 秒,因为这是一个几乎实时的应用程序,所以这个查询在 30 秒内运行了几乎 1 次 我可以对这种情况下的桌子设计提出建议吗,我的意思是专业人士在现实世界的应用程序中会做什么 使用IN ( SELECT ...) 通常效率很低。您编写查询的方式意味着遍历所有 400K 行,根据派生表中的 1217 检查每一行。我会预计这是低效的。 "不要梦想使用 FORCE INDEX ..." -- 是的,这是一场噩梦! 如果nishoc 的共同点,则不会使用INDEX(ishoc)。可以使用以 ishoc 开头的复合索引。【参考方案2】:

试试这个:

SELECT  lj.CurrentCombiners , lj.JourneySerla , lj.From_RunPoint , lj.NEXT_RunPoint
    FROM (  SELECT  MAX(journeyserla) as lastrunid
                FROM  locojourney
                GROUP BY  loconumber 
         ) AS llr
    JOIN  locojourney AS lj  ON llr.lastrunid = lj.journeyserla
    WHERE  lj.ishoc = 'n' 

(计时并提供EXPLAIN

【讨论】:

以上是关于MySQL 8.0.15 大坝慢选查询的主要内容,如果未能解决你的问题,请参考以下文章

centos7安装mysql-8.0.15

已解决Mysql中使用group by出错

mysql 8.0.15 的my.ini 配置

如何创建查询以在 Magnolia 的 2 个工作区进行搜索?

MySQL安装 8.0.15版本

JDBC 连接 MySQL 8.0.15+ 常见错误记录