如何简化/提高这个 MySQL 查询的性能?
Posted
技术标签:
【中文标题】如何简化/提高这个 MySQL 查询的性能?【英文标题】:How can I simplify/improve the performance of this MySQL query? 【发布时间】:2011-11-29 11:56:04 【问题描述】:我是 mysql 的新手,多亏了这里更有经验的人的大力支持,我正在努力奋斗,同时在这个过程中学到了很多东西。
我有一个完全符合我要求的查询。但是,它对我来说看起来非常混乱,我确信必须有一种方法来简化它。
如何改进和优化此查询的性能?
非常感谢
$sQuery = "
SELECT SQL_CALC_FOUND_ROWS ".str_replace(" , ", " ", implode(", ", $aColumns))."
FROM $sTable b
LEFT JOIN (
SELECT COUNT(*) AS projects_count, a.songs_id
FROM $sTable2 a
GROUP BY a.songs_id
) bb ON bb.songs_id = b.songsID
LEFT JOIN (
SELECT AVG(rating) AS rating, COUNT(rating) AS ratings_count, c.songid
FROM $sTable3 c
GROUP BY c.songid
) bbb ON bbb.songid = b.songsID
LEFT JOIN (
SELECT c.songid, c.userid,
CASE WHEN EXISTS
(
SELECT songid
FROM $sTable3
WHERE songid = c.songid
) Then 'User Voted'
else
(
'Not Voted'
)
end
AS voted
FROM $sTable3 c
WHERE c.userid = $userid
GROUP BY c.songid
) bbbb ON bbbb.songid = b.songsID
编辑:这里是查询正在做什么的描述:-
我有三张桌子:
$sTable = 歌曲表(songid、mp3link、artifact、useruploadid 等等)
$sTable2 = 包含链接到它们的歌曲的项目表(projectid, songid、项目名称等)
$sTable3 = 歌曲评分表(songid、userid、评分)
所有这些数据都输出到 JSON 数组并显示在我的应用程序的表格中,以提供歌曲列表,并结合项目和收视率数据。
查询本身按以下顺序执行以下操作:-
-
从 $sTable 中收集所有行
在 songID 上加入 $sTable2 并计算此表中具有相同 songID 的行(项目)的数量
在 songID 上加入 $stable3 并计算此表中具有相同歌曲 ID 的“评分”列的平均值
此时它还会计算 $sTable3 中具有相同 songID 的总行数,以提供总票数。
最后,它对所有这些行执行检查,以查看 $userid(这是一个包含登录用户 ID 的变量)是否与 $sTable3 中每行的“userid”存储相匹配,以检查是否存在用户是否已经对给定的歌曲 ID 进行了投票。如果匹配,则返回“用户投票”,否则返回“未投票”。它将它作为一个单独的列输出到我的 JSON 数组中,然后我在我的应用程序中检查客户端并向其中添加一个类。
如果有人需要更多详细信息,请告诉我。谢谢大家。
编辑:
感谢 Aurimis 出色的第一次尝试,我正在接近一个更简单的解决方案。
这是我根据该建议尝试的代码。
SELECT SQL_CALC_FOUND_ROWS ".str_replace(" , ", " ", implode(", ", $aColumns))."
FROM
(SELECT
$sTable.songsID, COUNT(rating) AS ratings_count,
AVG(rating) AS ratings
FROM $sTable
LEFT JOIN $sTable2 ON $sTable.songsID = $sTable2.songs_id
LEFT JOIN $sTable3 ON $sTable.songsID = $sTable3.songid
GROUP BY $sTable.songsID) AS A
LEFT JOIN $sTable3 AS B ON A.songsID = B.songid AND B.userid = $userid
但是有几个问题。我不得不删除您答案的第一行,因为它导致了 500 内部服务器错误:
IF(B.userid = NULL, "Not voted", "User Voted") AS voted
显然现在“投票检查”功能已丢失。
此外,更重要的是,它不会返回我的数组中定义的所有列,只返回歌曲 ID。我的 JSON 在“字段列表”中返回未知列“歌曲名称”-如果我将其从我的 $aColumns 数组中删除,它当然会移至下一个。
我在脚本开头定义列,因为该数组用于过滤和汇总 JSON 编码的输出。这是$aColumns的定义:-
$aColumns = array( 'songsID', 'song_name', 'artist_band_name', 'author', 'song_artwork', 'song_file', 'genre', 'song_description', 'uploaded_time', 'emotion', 'tempo', 'user', 'happiness', 'instruments', 'similar_artists', 'play_count', 'projects_count', 'rating', 'ratings_count', 'voted');
为了快速测试查询的其余部分,我修改了子查询中的第一行以选择 $sTable.* 而不是 $sTable.songsID(记住 $sTable 是歌曲表)
然后...查询显然有效,但当然性能很差。但仅返回 5000 首歌曲测试数据集中的 24 首歌曲。因此,我将您的第一个“JOIN”更改为“LEFT JOIN”,以便返回所有 5000 首歌曲。为了澄清查询,需要返回歌曲表中的所有行,但要返回每首歌曲的项目和评分表中的各种额外数据。
所以...我们正在到达那里,我确信这是一个更好的方法,它只需要一些修改。到目前为止,感谢您对 Aurimis 的帮助。
【问题讨论】:
看来写查询的人对MySQL并不陌生 我自己通过“谷歌搜索”写的,在这里提出了各种问题,并阅读了很多。但是,我本质上将几个部分拼凑到一个“工作”查询中,以实现我想要的。尽管我了解查询的每个部分的作用及其工作原理,但我没有经验知道我是否以有效的方式执行此操作。我对其他语言的经验告诉我,我不是。因此,我欢迎更有经验的 MySQL 开发人员的建议。 :) 你能描述一下预期的结果应该是什么吗? 我刚刚更新了我的原始问题,以便为您提供更多详细信息。干杯。 您能否发布表的创建表语句。查看索引和数据类型会很有用。 【参考方案1】:SELECT SQL_CALC_FOUND_ROWS
songsID, song_name, artist_band_name, author, song_artwork, song_file,
genre, song_description, uploaded_time, emotion, tempo,
`user`, happiness, instruments, similar_artists, play_count,
projects_count,
rating, ratings_count,
IF(user_ratings_count, 'User Voted', 'Not Voted') as voted
FROM (
SELECT
sp.songsID, projects_count,
AVG(rating) as rating,
COUNT(rating) AS ratings_count,
COUNT(IF(userid=$userid, 1, NULL)) as user_ratings_count
FROM (
SELECT songsID, COUNT(*) as projects_count
FROM $sTable s
LEFT JOIN $sTable2 p ON s.songsID = p.songs_id
GROUP BY songsID) as sp
LEFT JOIN $sTable3 r ON sp.songsID = r.songid
GROUP BY sp.songsID) as spr
JOIN $sTable s USING (songsID);
您将需要以下索引:
(songs_id)在 $sTable2 上 $sTable3 上的组合(歌曲 ID、评分、用户 ID)查询背后的想法:
子查询使用 INT 操作,以便子查询的结果可以轻松放入内存中 左连接单独分组以减少笛卡尔积 用户投票计入与其他评分相同的子查询中,以避免昂贵的相关子查询 所有其他信息都在最终加入时检索【讨论】:
感谢 newtover 的建议。直到周末之后,我才能检查您的解决方案。快速浏览一下,我觉得一切都很好,我会在星期一告诉你。 :-) 太棒了...它运行良好。就其执行而言,它更容易理解并且无限优雅。我还没有进行任何性能测试,但从最初的印象来看,它肯定不会更糟,使用您的查询将使任何未来的调整更容易管理,因为它在结构上也更优越。非常感谢 newtover,一个非常好的答案!【参考方案2】:让我根据您的描述而不是查询来尝试。我将使用Songs
表示Table1
,Projects
表示Table2
和Ratings
表示Table3
- 为了清楚起见。
SELECT
/* [column list again] */,
IF(B.userid = NULL, "Not voted", "Voted") as voted
FROM
(SELECT
Songs.SongID, count(rating) as total_votes,
avg(rating) as average_rating /*[,.. other columns as you need them] */
FROM Songs
JOIN Projects ON Songs.SongID = Projects.SongID
LEFT JOIN Ratings ON Songs.SongID = Ratings.SongID
GROUP BY Songs.SongID) as A
LEFT JOIN Ratings as B ON A.SongID = B.SongID AND B.userid = ? /* your user id */
如您所见,您可以在一个相对简单的查询中获取所有歌曲信息(只需使用 Group by 和 count() / avg() 函数)。要获取歌曲是否被特定用户评分的信息,需要一个子查询——您可以在其中执行 LEFT JOIN,如果用户 ID 为空——您知道他没有投票。
现在,我没有深入研究您的查询,因为它看起来确实很复杂。可能是我错过了什么 - 如果是这种情况,请更新描述,我可以再试一次:)
【讨论】:
这看起来很棒 Aurimas,非常感谢您的建议。再过一个小时左右,我就无法测试这个了。我会告诉你进展如何。 感谢 Aurimas... 自从尝试您的建议以来,我已经用我的发现更新了我的问题。干杯。 :)以上是关于如何简化/提高这个 MySQL 查询的性能?的主要内容,如果未能解决你的问题,请参考以下文章