mysql - 大型SQL数据库-解决效率

标签 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表具有超过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_saudio)包含removedverified列。

(我们知道,如果MySQL没有引发“歧义的列”错误,两者就不可能同时存在。但是MySQL可以访问表定义,而我们没有。)MySQL还知道有关列的基数的信息,特别是哪些列(或列的组合)是UNIQUE,哪些列可以包含NULL值,等等。

最佳实践是使用表名称或(最好是)表别名来限定所有列引用。 (这使人类更容易阅读SQL,并且还避免了在向表中添加新列时查询中断。)



同样,查询作为LIMIT子句,但是没有ORDER BY子句(或暗含的ORDER BY),这会使结果集不确定。我们没有任何保证将返回的“第一”行。



编辑

要仅从播放列表中返回20行(数千个或更多),我可以尝试在SELECT列表中使用相关的子查询;在内联视图中使用LIMIT子句来减少运行子查询所需的行数。鉴于子查询需要运行的次数,因此相关的子查询可以大批量地使用午餐(也可以午餐盒)。

据我所知,您正在尝试从playlists返回20行,从成员那里获取相关行(通过播放列表中的外键),在播放列表中找到“第一首”歌曲;获取过去31天(从任何播放列表中)播放“歌曲”的次数;获取一首歌曲出现在该播放列表中的次数(只要经过验证并且未被删除...如果removedverified列上的谓词否定了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/

相关文章:

python - 如何提高 openpyxl 在单元格中写入值?

sql - PostgreSQL 洗牌列值

javascript - 将数组连接到自身是否比遍历数组以创建更多索引更快?

php - Laravel - 在 where 子句列中使用 mysql 函数

java - 如何将数据从android发送到服务器上的java应用程序

mysql - 尽管有索引,但 sql 查询速度很慢

mysql - 将 MYSQL 转储附加到表

mysql - 对 MySQL 的攻击——以及如何防止它?

javascript - 使用 JavaScript 的事件委托(delegate)而不是 jQuery 有性能优势吗?

javascript - 用工具提示的跨度替换标题属性对 SEO 来说是个坏主意吗?