我有一个SQL查询(见下文),它完全返回我需要的内容但是当通过phpMyAdmin运行时需要从0.0009秒到0.1149秒,偶尔会一直到7.4983秒。
查询:
SELECT
e.id,
e.title,
e.special_flag,
CASE WHEN a.date >= '2013-03-29' THEN a.date ELSE '9999-99-99' END as date
CASE WHEN a.date >= '2013-03-29' THEN a.time ELSE '99-99-99' END as time,
cat.lastname,
FROM e_table as e
LEFT JOIN a_table as a ON (a.e_id=e.id)
LEFT JOIN c_table as c ON (e.c_id=c.id)
LEFT JOIN cat_table as cat ON (cat.id=e.cat_id)
LEFT JOIN m_table as m ON (cat.name=m.name AND cat.lastname=m.lastname)
JOIN (
SELECT DISTINCT innere.id
FROM e_table as innere
LEFT JOIN a_table as innera ON (innera.e_id=innere.id AND
innera.date >= '2013-03-29')
LEFT JOIN c_table as innerc ON (innere.c_id=innerc.id)
WHERE (
(
innera.date >= '2013-03-29' AND
innera.flag_two=1
) OR
innere.special_flag=1
) AND
innere.flag_three=1 AND
innere.flag_four=1
ORDER BY COALESCE(innera.date, '9999-99-99') ASC,
innera.time ASC,
innere.id DESC LIMIT 0, 10
) AS elist ON (e.id=elist.id)
WHERE (a.flag_two=1 OR e.special_flag) AND e.flag_three=1 AND e.flag_four=1
ORDER BY a.date ASC, a.time ASC, e.id DESC
解释计划:
问题是: 该查询的哪一部分可能导致性能差异很大?
答案 0 :(得分:4)
专门回答您的问题:这不是导致广泛性能的查询的特定部分。这是MySQL做它应该做的事情 - 作为一个关系数据库管理系统(RDBMS),而不仅仅是一个围绕逗号分隔文件的愚蠢的SQL包装。
执行查询时,会发生以下情况:
您所看到的是,当查询成本为0.0009秒时,缓存足够新鲜,可以将所有数据一起提供,当它在7.5秒达到峰值时,查询表中的某些内容发生了变化,或者其他查询被“推”了内存缓存数据输出,或DBMS有其他原因怀疑它需要重新编译查询或再次获取所有数据。可能其他一些变体与使用过的索引有关,这些索引仍然在内存中足够新鲜地缓存。
总结一下,查询速度非常慢,你有时候很幸运,缓存让它看起来很快。
要解决这个问题,我建议您研究两件事:
CASE
和COALESCE
等编程元素通常都很有用,但是它们会强制数据库在运行时评估更多内容,而不仅仅是获取原始表数据。尝试消除此类语句,或将其作为使用检索到的数据进行后处理的业务逻辑。最后,永远不要忘记MySQL实际上是一个相当愚蠢的DBMS。它针对大多数网站需要的简单数据提取查询进行了性能优化。因此,对于大多数通用问题,它比SQL Server和Oracle快得多。一旦您开始使用函数,大小写,大型连接或匹配条件等复杂化,竞争对手通常会更好地进行优化,并在查询编译器中进行更好的优化。因此,当MySQL在特定查询中开始变慢时,请考虑将其拆分为2个或更多个较小的查询,以免它变得混乱,并在PHP或您正在调用的任何语言中进行一些后处理。我已经看到很多情况下这种性能提升很多,只是没有混淆MySQL,特别是在涉及子查询的情况下(如你的情况)。特别是你的子查询是一个派生表,而不仅仅是一个子查询这一事实,众所周知,它可以使MySQL的内容复杂化,超出它的应对范围。
答案 1 :(得分:1)
让我们开始你的外部和内部查询都在使用" e" table最低要求为flag_three = 1 AND flag_four = 1(无论你的内部查询是什么((x和y)或z)条件。另外,你的外部WHERE子句明确引用了a.Flag_two,但是没有NULL会强制你的LEFT JOIN实际上成为一个(内部)JOIN。而且,每个" e"记录必须有一个类别,因为你正在寻找" cat.lastname"没有coalesce(),如果没有找到。这是有道理的,它似乎是一个"查找"表引用。至于" m_table"和" c_table",你没有得到或做任何事情,所以他们可以完全删除。
以下查询会得到相同的结果吗?
select
e1.id,
e1.Title,
e1.Special_Flag,
e1.cat_id,
coalesce( a1.date, '9999-99-99' ) ADate,
coalesce( a1.time, '99-99-99' ) ATime
cat.LastName
from
e_table e1
LEFT JOIN a_table as a1
ON e1.id = a1.e_id
AND a1.flag_two = 1
AND a1.date >= '2013-03-29'
JOIN cat_table as cat
ON e1.cat_id = cat.id
where
e1.flag_three = 1
and e1.flag_four = 1
and ( e1.special_flag = 1
OR a1.id IS NOT NULL )
order by
IF( a1.id is null, 2, 1 ),
ADate,
ATime,
e1.ID Desc
limit
0, 10
Main WHERE子句仅限于那些拥有"三和四"标志设置为1加上(特殊标志存在或者有效的" a"记录在给定日期之上/之后)。
从那个,简单的顺序和限制。
至于获取日期和时间,您似乎只想要包含日期/日期后的记录,否则忽略它们(例如它们陈旧且不适用,您不想看到它们)。
顺序依次为,我正在测试FIRST的" a" ID。如果是这样,我们知道他们都将被迫到#9; 9999-99-99'时间和99-99-99'并希望他们被推到底部(因此2),否则,有一个" a"记录,你想要那些(因此1)。然后,分别按日期/时间排序,然后在相同日期/时间内的许多情况下降序ID。
最后,为了帮助索引,我会确保你的" e"表上有一个索引
( id, flag_three, flag_four, special_flag ).
对于" a"表,索引
(e_id, flag_two, date)