我在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
的结果
我猜红色红色块是危险的。请有人帮我理解这个计划吗?我有这个执行计划后,我应该检查一些提示。
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`)
)
答案 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_CONGCALC
和CRR_INVENTORY
上的每个子查询都会发生全表扫描。然后,当您将子查询连接在一起时,另一个全表扫描,最后,在订购结果集时,再进行一次全表扫描。
提高性能的一些提示
使用作为join语句的一部分编入索引的字段,where子句,group by clause&按条款订购。如果经常使用此查询,请考虑将索引添加到所有相关列。
尽可能避免在连接中使用聚合操作的嵌套子查询。子查询返回的结果集未编入索引,加入它将最终扫描整个表,而不仅仅是索引。 此查询中的联接也可能导致奇怪和困难检测扇出问题,但这不是您正在寻求解决方案的性能问题
尽早过滤结果集(即在最内层的所有子查询中,以最小化数据库服务器随后要处理的行数。
除非最后的订单是必要的,否则请避免使用。
使用临时(或物化)表来解嵌子查询。在这些表上,您可以添加索引,因此进一步加入将是有效的。 这假设您有权创建&删除服务器上的表
那就是说,
以下是我将如何重构您的查询。
在生成内部查询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
datetime
或timestamp
列隐含cast
为char
。最好使用函数DATE_FORMAT
作为:
DATE_FORMAT(<FIELD>, '%Y-%m-01')
即使您使用的where子句有效,也会省略END_DT
和CRR_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_CONGCALC
和CRR_INVENTORY
,那就更快
答案 3 :(得分:1)
全表扫描操作并不总是坏的,或者必然是邪恶的。有时,完整扫描是满足查询的最有效方式。例如,查询SELECT * FROM mytable
要求MySQL返回表中的每个行以及每行中的列。在这种情况下,使用索引只会做更多工作。只进行全面扫描更快。
另一方面,如果您要检索一百万行中的几行,使用合适索引的访问计划很可能比全表扫描快得多。有效使用索引可以消除大量的行,否则需要检查这些行;索引基本上告诉MySQL我们要查找的行不能在表中99%的块中,因此不需要检查这些块。
MySQL以不同于其他数据库的方式处理视图(包括内联视图)。 MySQL使用术语派生表作为内联视图。在您的查询a
中,b
和c
都是派生表。 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_dt
和end_dt
被定义为DATE
,DATETIME
或TIMESTAMP
列,那么我更愿意像这样编写谓词:
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)
的索引能够有效地找到匹配的行。