是否可以改善此查询的效果

时间:2015-07-07 01:19:11

标签: mysql sql performance

我在MySql服务器中查询速度很慢。

我添加了查询:

SELECT  CRR_DT, TOU, SRCE, SINK, NAME, SEASON, SRCESUMCONG, SINKSUMCONG,
        SRCEAVGCONG, SINKAVGCONG, SUMSINKMSRCE, AVGSINKMSRCE,
        HOURCOUNT, TERM, START_DT, END_DT, CTYPE, MW AS MW_AWARD,
        Mark, SCID
    FROM  
      ( SELECT  a.CRR_DT, a.TOU, a.SRCE, a.SINK, a.NAME, a.SEASON, a.SRCESUMCONG,
                a.SINKSUMCONG, a.SRCEAVGCONG, a.SINKAVGCONG, a.SUMSINKMSRCE,
                a.AVGSINKMSRCE, a.HOURCOUNT, b.TERM, b.CTYPE, b.START_DT,
                b.END_DT, b.MW, b.SCID, b.Mark
            FROM  
              ( SELECT  CRR_DT, TOU, SRCE, SINK, NAME, SEASON, SRCESUMCONG, SINKSUMCONG,
                        SRCEAVGCONG, SINKAVGCONG, SUMSINKMSRCE, AVGSINKMSRCE,
                        HOURCOUNT
                    FROM  CRR_CONGCALC
                    WHERE  CRR_DT >= '2015-01'
              ) a
            INNER JOIN  
              ( SELECT  MARKET, TERM, TOU, SRCE, SINK, NAME, SCID, CTYPE, START_DT,
                        END_DT, SUM(MW) AS MW, SUBSTR(MARKET, 1, 3) AS MARK
                    FROM  CRR_INVENTORY
                    WHERE  COPTION = 'OBLIGATION'
                      AND  START_DT >= '2015-01-01'
                      AND  SCID IN ('EAGL' , 'LDES')
                    GROUP BY  MARKET , TOU , SRCE , SINK , NAME , SCID , CTYPE ,
                        START_DT , END_DT
              ) b ON a.NAME = b.NAME
              AND  a.TOU = b.TOU
      ) c
    WHERE  c.CRR_DT BETWEEN SUBSTR(c.START_DT, 1, 7) AND SUBSTR(c.END_DT, 1, 7 )
    ORDER BY  NAME , CRR_DT , TOU ASC

此处使用MysQl Workbrench

生成Explain plan的结果

enter image description here

我猜红色红色块是危险的。请有人帮我理解这个计划吗?我有这个执行计划后,我应该检查一些提示。

编辑添加TABLES布局

CREATE TABLE `CRR_CONGCALC` (
  `CRR_DT` varchar(7) NOT NULL,
  `TOU` varchar(50) NOT NULL,
  `SRCE` varchar(50) NOT NULL,
  `SINK` varchar(50) NOT NULL,
  `SRCESUMCONG` decimal(12,6) DEFAULT NULL,
  `SINKSUMCONG` decimal(12,6) DEFAULT NULL,
  `SRCEAVGCONG` decimal(12,6) DEFAULT NULL,
  `SINKAVGCONG` decimal(12,6) DEFAULT NULL,
  `SUMSINKMSRCE` decimal(12,6) DEFAULT NULL,
  `AVGSINKMSRCE` decimal(12,6) DEFAULT NULL,
  `HOURCOUNT` int(11) NOT NULL DEFAULT '0',
  `SEASON` char(1) NOT NULL DEFAULT '0',
  `NAME` varchar(110) NOT NULL,
  PRIMARY KEY (`CRR_DT`,`SRCE`,`SINK`,`TOU`,`HOURCOUNT`),
  KEY `srce_index` (`SRCE`),
  KEY `srcesink` (`SRCE`,`SINK`)
) 

CREATE TABLE `CRR_INVENTORY` (
  `MARKET` varchar(50) NOT NULL,
  `TERM` varchar(50) NOT NULL,
  `TOU` varchar(50) NOT NULL,
  `INVENTORY_DT` date NOT NULL,
  `START_DT` datetime NOT NULL,
  `END_DT` datetime NOT NULL,
  `CRR_ID` varchar(50) NOT NULL,
  `NSR_INDEX` tinyint(1) NOT NULL,
  `SEGMENT` tinyint(1) NOT NULL,
  `CTYPE` varchar(50) NOT NULL,
  `CATEGORY` varchar(50) NOT NULL,
  `COPTION` varchar(50) NOT NULL,
  `SRCE` varchar(50) DEFAULT NULL,
  `SINK` varchar(50) DEFAULT NULL,
  `MW` decimal(8,4) NOT NULL,
  `SCID` varchar(50) NOT NULL,
  `SEASON` char(1) DEFAULT '0',
  `NAME` varchar(110) NOT NULL,
  PRIMARY KEY (`MARKET`,`INVENTORY_DT`,`CRR_ID`),
  KEY `srcesink` (`SRCE`,`SINK`)
) 

4 个答案:

答案 0 :(得分:2)

带回记忆。使用数据库,"全表扫描"意味着数据库没有任何东西可以用来加速查询,它会读取整个表。 行以非排序顺序存储,因此没有更好的方式来搜索"对于您正在寻找的员工ID。

这很糟糕。为什么?

如果您的表有一堆列: public function self($type = null) { // your code } 并进行搜索first_name, last_name, employee_id, ..., column50,如果您在employee_id列上没有索引,则表示您正在进行顺序扫描。更糟糕的是,如果您正在执行where employee_id = 1234,因为它必须将employee_id与连接表中的每条记录相匹配。

如果您创建索引,则会大大缩短扫描时间以查找匹配项(或丢弃不匹配项),因为您可以搜索已排序的字段,而不是执行顺序扫描。快得多。

当您在employee_id字段上创建索引时,您正在创建一种方法来搜索更快,更快,更快的员工编号。 当您创建索引时,您说"我将基于此字段加入,或者根据此字段添加where子句" 。这会以一点磁盘空间为代价来加速查询。

有各种各样的索引技巧,你可以创建它们,使它们是唯一的,不是唯一的,复合的(包含多个列)和各种各样的东西。发布您的查询,我们可以告诉您索引的内容,以加快速度。

一个好的经验法则是,您应该在表中使用where子句,连接条件或order by中使用的字段创建索引。挑选该领域取决于一些超出本讨论范围的内容,但这应该是一个开始。

答案 1 :(得分:2)

  • 模式FROM ( SELECT... ) JOIN ( SELECT... ) ON ...无法很好地优化。看看你是否可以从其中一个表中直接进入,而不是将其隐藏在子查询中。

  • CRR_CONGCALC需要INDEX(CRR_DT)。 (请提供SHOW CREATE TABLE。)

  • CRR_INVENTORY需要INDEX(COPTION, START_DT)

如果需要,请进行更改,然后再回来寻求更多建议。

答案 2 :(得分:2)

根据您的解释图,CRR_CONGCALCCRR_INVENTORY上的每个子查询都会发生全表扫描。然后,当您将子查询连接在一起时,另一个全表扫描,最后,在订购结果集时,再进行一次全表扫描。

提高性能的一些提示

  1. 使用作为join语句的一部分编入索引的字段,where子句,group by clause&按条款订购。如果经常使用此查询,请考虑将索引添加到所有相关列。

  2. 尽可能避免在连接中使用聚合操作的嵌套子查询。子查询返回的结果集未编入索引,加入它将最终扫描整个表,而不仅仅是索引。 此查询中的联接也可能导致奇怪和困难检测扇出问题,但这不是您正在寻求解决方案的性能问题

  3. 尽早过滤结果集(即在最内层的所有子查询中,以最小化数据库服务器随后要处理的行数。

  4. 除非最后的订单是必要的,否则请避免使用。

  5. 使用临时(或物化)表来解嵌子查询。在这些表上,您可以添加索引,因此进一步加入将是有效的。 这假设您有权创建&删除服务器上的表

  6. 那就是说,

    以下是我将如何重构您的查询。

    • 在生成内部查询b时,group by子句不包含非聚合列的所有字段。这是非标准的sql,导致数据格式错误。 Mysql允许它,而对于上帝的爱,我不知道为什么。最好避免使用陷阱

    • 最后的包装查询是不必要的,因为where子句和group by子句可以应用于解包的查询。

    • 这个where子句对我来说似乎很可疑:

      c.CRR_DT BETWEEN SUBSTR(c.START_DT, 1, 7) AND SUBSTR(c.END_DT, 1, 7)
      

      START_DT& END_DT datetimetimestamp列隐含castchar。最好使用函数DATE_FORMAT作为:

      提取年月
      DATE_FORMAT(<FIELD>, '%Y-%m-01') 
      

      即使您使用的where子句有效,也会省略END_DTCRR_DT在同一个月内出现的记录。我不确定这是否是所期望的行为,但这是一个查询来说明你的布尔表达式将评估的内容:

      SELECT CAST('2015-07-05' AS DATETIME) between '2015-07' and '2015-07';
      -- This query returns 0 == False.
      

    使用CREATE TABLE AS SELECT Syntax,首先取消嵌套子查询。 注意:由于我不知道数据,我不确定哪些索引必须是唯一的。您可以在消费结果后删除表。

    表1:

    CREATE TABLE sub_a (KEY(CRR_DT), KEY(NAME), KEY(TOU), KEY(NAME, TOU)) AS
    SELECT CRR_DT,
           TOU,
           SRCE,
           SINK,
           NAME,
           SEASON,
           SRCESUMCONG,
           SINKSUMCONG,
           SRCEAVGCONG,
           SINKAVGCONG,
           SUMSINKMSRCE,
           AVGSINKMSRCE,
           HOURCOUNT
    FROM CRR_CONGCALC
    WHERE CRR_DT >= '2015-01-01';
    

    表2:

    CREATE TABLE sub_b (KEY(NAME), KEY(TOU), KEY(NAME, TOU)) AS
    SELECT MARKET,
           TERM,
           TOU,
           SRCE,
           SINK,
           NAME,
           SCID,
           CTYPE,
           START_DT,
           END_DT,
           SUM(MW) AS MW_AWARD,
           SUBSTR(MARKET,1,3) AS MARK
     FROM CRR_INVENTORY
     WHERE COPTION = 'OBLIGATION'
       AND START_DT >= '2015-01-01'
       AND SCID IN ('EAGL','LDES')
     GROUP BY MARKET, TERM, TOU, 
              SRCE, SINK, NAME, SCID, 
              CTYPE, START_DT, END_DT, MARK
     -- note the two added columns in the groupby clause.
    

    在此之后,最终的查询将是:

    SELECT a.CRR_DT, 
           a.TOU, 
           a.SRCE,
           a.SINK, 
           a.NAME, 
           a.SEASON, 
           a.SRCESUMCONG,
           a.SINKSUMCONG, 
           a.SRCEAVGCONG, 
           a.SINKAVGCONG, 
           a.SUMSINKMSRCE,
           a.AVGSINKMSRCE, 
           a.HOURCOUNT, 
           b.TERM, 
           b.CTYPE, 
           b.START_DT,
           b.END_DT,
           b.MW_AWARD,
           b.SCID, 
           b.Mark
    FROM sub_a a 
    JOIN sub_b b ON a.NAME = b.NAME AND a.TOU = b.TOU
    WHERE a.CRR_DT BETWEEN DATE_FORMAT(b.START_DT,'%Y-%m-01') 
      AND DATE_FORMAT(b.END_DT,'%Y-%m-01')
    ORDER BY NAME,
             CRR_DT,
             TOU;
    

    上面的where子句遵循查询中使用的相同逻辑,但它不会尝试强制转换为字符串。但是,这个WHERE子句可能更合适,

    WHERE sub_a.CRR_DT BETWEEN DATE_FORMAT(sub_b.START_DT,'%Y-%m-01') 
          AND DATE_FORMAT(DATE_ADD(sub_b.END_DT, INTERVAL 1 MONTH),'%Y-%m-01')
    

    最后sub_a&amp; sub_b似乎有字段SRCE&amp; SINK。如果将它们添加到连接中,结果是否会更改。这可以进一步优化查询(此时,可以说是查询)处理时间。

    通过以上操作,我们希望避免两次全表扫描,但我没有你的数据集,所以我只是在这里做了一个有根据的猜测。

    如果可以在不使用中间表的情况下表达此逻辑,并直接通过连接到实际的基础表CRR_CONGCALCCRR_INVENTORY,那就更快

答案 3 :(得分:1)

全表扫描操作并不总是坏的,或者必然是邪恶的。有时,完整扫描是满足查询的最有效方式。例如,查询SELECT * FROM mytable要求MySQL返回表中的每个行以及每行中的列。在这种情况下,使用索引只会做更多工作。只进行全面扫描更快。

另一方面,如果您要检索一百万行中的几行,使用合适索引的访问计划很可能比全表扫描快得多。有效使用索引可以消除大量的行,否则需要检查这些行;索引基本上告诉MySQL我们要查找的行不能在表中99%的块中,因此不需要检查这些块。

MySQL以不同于其他数据库的方式处理视图(包括内联视图)。 MySQL使用术语派生表作为内联视图。在您的查询a中,bc都是派生表。 MySQL运行查询以返回行,然后将视图具体化到表中。完成后,外部查询可以针对派生表运行。但是从MySQL 5.5(我认为5.6)开始,内联视图总是实现为派生表。而且这是大型套装的性能杀手。 (一些性能改进在MySQL的新版本中出现,一些自动索引。)

此外,外部查询中的谓词 not 被推送到视图查询中。也就是说,如果我们运行这样的查询:

  SELECT t.foo 
    FROM mytable t
   WHERE t.foo = 'bar'

MySQL可以使用带有foo前导列的索引来有效地定位行,即使mytable包含数百万行。但是如果我们写这样的查询:

  SELECT t.foo
    FROM (SELECT * FROM mytable) t
   WHERE t.foo = 'bar'

我们基本上强迫MySQL制作mytable的副本,运行内联视图查询,以填充派生表,包含mytable中的所有行。一旦该操作完成,外部查询就可以运行。但是现在,派生表中的foo列没有索引。所以我们强迫MySQL对派生表进行全面扫描,以查看每一行。

如果我们需要内联视图,那么将谓词重定位到内联视图查询将导致更小的派生表。

  SELECT t.foo
    FROM (SELECT * FROM mytable WHERE foo = 'bar') t

通过这种方式,MySQL可以利用foo上的索引快速定位行,只有这些行具体化为派生表。对派生表的完全扫描现在并不那么痛苦,因为外部查询需要返回每个行。在这个例子中,用我们需要返回的列替换*(代表每一列)也会好得多。

您指定的结果集可以在没有不必要的内联视图的情况下返回。像这样的查询:

        SELECT c.crr_dt
             , c.tou
             , c.srce
             , c.sink
             , c.name
             , c.season
             , c.srcesumcong
             , c.sinksumcong
             , c.srceavgcong
             , c.sinkavgcong
             , c.sumsinkmsrce
             , c.avgsinkmsrce
             , c.hourcount
             , b.term
             , b.start_dt
             , b.end_dt
             , b.ctype
             , b.mw AS mw_award
             , b.scid
             , b.mark
          FROM CRR_CONGCALC c
          JOIN ( SELECT i.market
                      , i.term
                      , i.tou
                      , i.srce
                      , i.sink
                      , i.name
                      , i.scid
                      , i.ctype
                      , i.start_dt
                      , i.end_dt
                      , SUM(i.mw) AS mw
                      , SUBSTR(i.market, 1, 3) AS mark
                   FROM CRR_INVENTORY i
                  WHERE i.coption = 'OBLIGATION'
                    AND i.start_dt >= '2015-01-01'
                    AND i.scid IN ('EAGL','LDES')
                  GROUP
                     BY i.market
                      , i.tou
                      , i.srce
                      , i.sink
                      , i.name
                      , i.scid
                      , i.ctype
                      , i.start_dt
                      , i.end_dt
               ) b
            ON c.name = b.name
           AND c.tou  = b.tou
           AND c.crr_dt >= '2015-01'
           AND c.crr_dt BETWEEN SUBSTR(b.start_dt,1,7) 
                            AND SUBSTR(b.end_dt,1,7)
         ORDER
            BY c.name
             , c.crr_dt
             , c.tou

注意:如果start_dtend_dt被定义为DATEDATETIMETIMESTAMP列,那么我更愿意像这样编写谓词:

 AND c.crr_dt BETWEEN DATE_FORMAT(b.start_dt,'%Y-%m') AND DATE_FORMAT(b.end_dt,'%Y-%m')

(我认为在那里没有任何表现;这只是让我们更清楚我们正在做什么。)

在提高该查询的效果方面......

如果我们从CRR_INVENTORY返回一小部分行,则基于谓词:                       在哪里i.coption ='义务'                         AND i.start_dt&gt; ='2015-01-01'                         并且i.scid IN('EAGL','LDES')

然后,MySQL可能能够有效地使用带有(coption,scid,start_dt)前导列的索引。这是假设这表示来自表的行的相对较小的子集。如果这些谓词不是很有选择性,如果我们真的得到表中50%或90%的行,那么索引的效果可能会低得多。

我们或许可以让MySQL使用索引来满足GROUP BY子句,而不需要排序操作。为此,我们需要一个索引,其前导列与GROUP BY子句中列出的列匹配。

派生表不会有索引,所以为了实现连接操作的最佳性能,我们希望另一个表上的索引具体化,那么我们将需要另一个合适的索引表格1}}。我们希望该索引的前导列用于查找匹配的行,即谓词:

CRR_CONGCALC

因此,我们希望具有前导列 ON c.name = b.name AND c.tou = b.tou AND c.crr_dt >= '2015-01' AND c.crr_dt BETWEEN SUBSTR(b.start_dt,1,7) AND SUBSTR(b.end_dt,1,7) 的索引能够有效地找到匹配的行。