我有以下SQL查询,当我最初对其进行编码时,它异常快速,现在需要1秒钟才能完成:
SELECT counted/scount as ratio, [etc]
FROM
playlists
LEFT JOIN (
select AID, PLID FROM (SELECT AID, PLID FROM p_s ORDER BY `order` asc, PLSID desc)as g GROUP BY PLID
) as t USING(PLID)
INNER JOIN (
SELECT PLID, count(PLID) as scount from p_s LEFT JOIN audio USING(AID) WHERE removed='0' and verified='1' GROUP BY PLID
) as g USING(PLID)
LEFT JOIN (
select AID, count(AID) as counted FROM a_p_all WHERE ".time()." - playtime < 2678400 GROUP BY AID
) as r USING(AID)
LEFT JOIN audio USING (AID)
LEFT JOIN members USING (UID)
WHERE scount > 4 ORDER BY ratio desc
LIMIT 0, 20
我已经确定了问题,
a_p_all
表具有超过50万行。这使查询变慢。我想出了一个解决方案:创建一个较小的临时表,该表仅存储必需的数据,并删除任何早于需要的数据。
但是,有没有更好的使用方法?理想情况下,我不需要临时表。 YouTube / Facebook之类的网站对大型表有什么作用,以保持快速查询时间?
编辑
这是@ spencer7593答案中查询的EXPLAIN表
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY <derived3> ALL NULL NULL NULL NULL 20
1 PRIMARY u eq_ref PRIMARY PRIMARY 8 q.AID 1 Using index
1 PRIMARY m eq_ref PRIMARY PRIMARY 8 q.UID 1 Using index
3 DERIVED <derived6> ALL NULL NULL NULL NULL 20
6 DERIVED t ALL NULL NULL NULL NULL 21
5 DEPENDENT SUBQUERY s ALL NULL NULL NULL NULL 49 Using where; Using filesort
4 DEPENDENT SUBQUERY c ALL NULL NULL NULL NULL 49 Using where
4 DEPENDENT SUBQUERY o eq_ref PRIMARY PRIMARY 8 database.c.AID 1 Using where
2 DEPENDENT SUBQUERY a ALL NULL NULL NULL NULL 510594 Using where
最佳答案
我注意到两个“大石头”问题。
首先,这个谓词
WHERE ".time()." - playtime < 2678400
(我假设这不是要提交给数据库的实际SQL,但是要发送给数据库的是这样的……
WHERE 1409192073 - playtime < 2678400
因此我们只希望
playtime
在过去31天内(即在time()
返回的整数值的31 * 24 * 60 * 60秒内)的行。该谓词不能对
playtime
的适当索引使用范围扫描操作。 MySQL对表中的每一行(每一行未被其他谓词排除)评估左侧的表达式,并将该表达式的结果与右侧的文字进行比较。为了提高性能,请重写谓词,以便在裸列上进行比较。将存储在
playtime
列中的值与需要一次求值的表达式进行比较,例如:WHERE playtime > 1409192073 - 2678400
有了合适的索引,MySQL可以执行“范围”扫描操作,并有效消除不需要评估的大量行。
第二个“大石头”是内联视图或MySQL术语中的“派生表”。 MySQL在处理内联视图方面与其他数据库有很大不同。 MySQL实际上运行该最里面的查询,并将结果集存储为临时MyISAM表,然后外部查询针对MyISAM表运行。 (当我们了解MySQL如何处理内联视图时,MySQL使用的名称“派生表”就很有意义。)而且,MySQL不会“推”谓词,从外部查询到视图查询。并且在派生表上,没有创建索引。 (我相信MySQL 5.7会改变这种情况,有时会创建索引来提高性能。)但是,大型“派生表”可能会对性能产生重大影响。
此外,LIMIT子句在语句处理中最后应用;这是在准备好结果集中的所有行并对其进行排序之后。即使您只返回20行,MySQL仍会准备整个结果集。只是不将它们转移给客户。
许多列引用都没有表名或别名的限定,因此,例如,我们不知道哪个表(
p_s
或audio
)包含removed
和verified
列。(我们知道,如果MySQL没有引发“歧义的列”错误,两者就不可能同时存在。但是MySQL可以访问表定义,而我们没有。)MySQL还知道有关列的基数的信息,特别是哪些列(或列的组合)是UNIQUE,哪些列可以包含NULL值,等等。
最佳实践是使用表名称或(最好是)表别名来限定所有列引用。 (这使人类更容易阅读SQL,并且还避免了在向表中添加新列时查询中断。)
同样,查询作为
LIMIT
子句,但是没有ORDER BY
子句(或暗含的ORDER BY),这会使结果集不确定。我们没有任何保证将返回的“第一”行。编辑
要仅从播放列表中返回20行(数千个或更多),我可以尝试在SELECT列表中使用相关的子查询;在内联视图中使用LIMIT子句来减少运行子查询所需的行数。鉴于子查询需要运行的次数,因此相关的子查询可以大批量地使用午餐(也可以午餐盒)。
据我所知,您正在尝试从
playlists
返回20行,从成员那里获取相关行(通过播放列表中的外键),在播放列表中找到“第一首”歌曲;获取过去31天(从任何播放列表中)播放“歌曲”的次数;获取一首歌曲出现在该播放列表中的次数(只要经过验证并且未被删除...如果removed
和verified
列上的谓词否定了LEFT JOIN的外观,如果这些列中的任何一个都来自audio
表...)。我会用这样的照片来比较性能:
SELECT q.*
, ( SELECT COUNT(1)
FROM a_p_all a
WHERE a.playtime < 1409192073 - 2678400
AND a.AID = q.AID
) AS counted
FROM ( SELECT p.PLID
, p.UID
, p.[etc]
, ( SELECT COUNT(1)
FROM p_s c
JOIN audio o
ON o.AID = c.AID
AND o.removed='0'
AND o.verified='1'
WHERE c.PLID = p.PLID
) AS scount
, ( SELECT s.AID
FROM p_s s
WHERE s.PLID = p.PLID
ORDER BY s.order ASC, s.PLSID DESC
LIMIT 1
) AS AID
FROM ( SELECT t.PLID
, t.[etc]
FROM playlists t
ORDER BY NULL
LIMIT 20
) p
) q
LEFT JOIN audio u ON u.AID = q.AID
LEFT JOIN members m ON m.UID = q.UID
LIMIT 0, 20
更新
杜德(Dude),
EXPLAIN
输出显示您没有合适的索引可用。为了获得与相关子查询相关的性能提升机会,您需要添加一些索引,例如... ON a_p_all (AID, playtime)
... ON p_s (PLID, order, PLSID, AID)
关于mysql - 大型SQL数据库-解决效率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25539360/