大型SQL数据库 - 解决效率问题

时间:2014-08-28 01:27:38

标签: mysql performance

我有以下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

1 个答案:

答案 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_saudio)包含removedverified列。

(我们知道它不可能同时存在,如果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