我有以下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
表有超过500k行。这会降低查询速度。我想出了一个解决方案:
但是,有更好的方法吗?最理想的是我不需要临时桌子; 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
答案 0 :(得分:3)
两个“大摇滚”问题对我很突出。
首先,这个谓词
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天(从任何播放列表)播放“歌曲”的次数;得到一首歌曲出现在该播放列表中的次数(只要它已被验证且尚未被删除......该LEFT JOIN的外部性被removed
和{{1}上的谓词否定}列,如果这些列中的任何一列来自verified
表...)。
我会用这样的东西拍摄,以比较性能:
audio
<强>更新强>
Dude,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
输出显示您没有合适的索引。为了获得相关子查询的性能,你需要添加一些索引,例如
EXPLAIN