1

我遇到了性能问题,以下是在大约 2.5 秒内执行,仅返回 40 行:

SELECT DISTINCT song.song_id, song.title, song.length, song.bpm, song.keysig 
FROM song 
INNER JOIN (
    SELECT song_id 
    FROM song_genre 
    WHERE genre_id IN ('25')
) genre1 ON genre1.song_id = song.song_id 
INNER JOIN (
    SELECT song_id 
    FROM song_production 
    WHERE production_id IN ('8')
) production1 ON production1.song_id = song.song_id 
WHERE approved='1' 
ORDER by song.priority DESC, song.song_id DESC 
LIMIT 0, 40

ORDER BY运行查询会在 0.01 秒内放弃执行,等等。

我知道这个问题可能与计算信息的方式有关,因为我正在使用 JOINS,所以可能需要嵌套查询,但我不是 100% 我会怎么做?

id select_type table type possible_keys key key_len ref rows Extra
 1 PRIMARY <derived3> ALL NULL NULL NULL NULL 321 使用临时;使用文件排序
 1 PRIMARY <derived2> ALL NULL NULL NULL NULL 3424 使用连接缓冲区
 1 PRIMARY 歌曲 eq_ref PRIMARY PRIMARY 4 production1.song_id 1 使用 where
 3 DERIVED song_production ref PRIMARY PRIMARY 4 339 使用索引
 2 DERIVED song_genre index NULL PRIMARY 8 NULL 3424 使用where;使用索引

song

CREATE TABLE `song` (
`song_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` tinytext,
`length` varchar(5) DEFAULT NULL,
`Type` varchar(1) DEFAULT NULL,
`Vocals` varchar(10) DEFAULT NULL,
`Themes` varchar(10) DEFAULT NULL,
`Explicit` varchar(10) DEFAULT NULL,
`timesig` varchar(3) DEFAULT NULL,
`keysig` varchar(250) NOT NULL,
`bpm` int(3) DEFAULT NULL,
`speed` varchar(7) DEFAULT NULL,
`Era` varchar(10) DEFAULT NULL,
`Language` varchar(10) DEFAULT NULL,
`Keywords` varchar(10) DEFAULT NULL,
`description` mediumtext,
`search_description` longtext NOT NULL,
`key` varchar(25) NOT NULL,
`priority` int(2) NOT NULL,
`approved` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`song_id`),
FULLTEXT KEY `description` (`description`),
FULLTEXT KEY `search_description` (`search_description`),
FULLTEXT KEY `title` (`title`),
FULLTEXT KEY `head_desc` (`title`,`search_description`)
) ENGINE=MyISAM 
  AUTO_INCREMENT=1388 
  DEFAULT CHARSET=utf8 ;

song_genre

CREATE TABLE `song_genre` (
`genre_id` int(10) NOT NULL,
`song_id` int(10) NOT NULL, 
PRIMARY KEY (`genre_id`,`song_id`)
) ENGINE=MyISAM 
  DEFAULT CHARSET=latin1 ;

song_production

CREATE TABLE `song_production` (
`production_id` int(10) NOT NULL,
`song_id` int(10) NOT NULL,
PRIMARY KEY (`production_id`,`song_id`)
) ENGINE=MyISAM 
  DEFAULT CHARSET=latin1 ;
4

3 回答 3

2

第一步:

我猜genre_id,production_idapprovedinteger列而不是char,所以在数字周围加上引号是没有用的'25'。取消引用它们:25

如果删除 会发生什么DISTINCT?(如果没有表的结构、主键、外键和唯一约束,我们就无法知道)。结果中是否有多个相同的行?如果没有,只需删除DISTINCT. 如果是,请将其删除并添加GROUP BY song.song_id

SELECT song.song_id, song.title, song.length, song.bpm, song.keysig 
FROM song 
  INNER JOIN (
    SELECT song_id 
    FROM song_genre 
    WHERE genre_id IN (25)
  ) genre1 ON genre1.song_id = song.song_id 
  INNER JOIN (
    SELECT song_id 
    FROM song_production 
    WHERE production_id IN (8)
  ) production1 ON production1.song_id = song.song_id 
WHERE approved = 1 

----- GROUP BY song.song_id           --- not needed at all (with these tables)
                                                        --- (and structure)
ORDER BY song.priority DESC, song.song_id DESC 
LIMIT 0, 40 ;

第二步:

添加有用的索引。上的索引(approved, priority, song_id)可能有助于查询。

您还将song.song_id列定义为UNSIGNED INTwhilesong_genre.song_idsong_production.song_id定义为SIGNED INT。如果您也将它们转换为,那就太好了UNSIGNED INT

我还会在(song_id, genre_id)和上添加(唯一)索引(song_id, production_id)。它们可能对这个查询没有用,但在其他情况下你肯定会需要这样的索引。


第三步:

尝试以其他方式重写查询。例如,没有派生表:

SELECT song.song_id, song.title, song.length, song.bpm, song.keysig 
FROM song 
  INNER JOIN
    song_genre AS genre1 
        ON genre1.song_id = song.song_id 
  INNER JOIN 
    song_production AS production1
        ON production1.song_id = song.song_id 
WHERE song.approved = 1
  AND genre1.genre_id IN (25)
  AND production1.production_id IN (8)
ORDER BY song.priority DESC
       , song.song_id DESC 
LIMIT 0, 40 ;

或与EXISTS

SELECT song.song_id, song.title, song.length, song.bpm, song.keysig 
FROM song 
WHERE song.approved = 1
  AND EXISTS
      ( SELECT *
        FROM song_genre AS genre1 
        WHERE genre1.song_id = song.song_id 
          AND genre1.genre_id IN (25)
      ) 
  AND EXISTS
      ( SELECT *
        FROM song_production AS production1
        WHERE production1.song_id = song.song_id
          AND production1.production_id IN (8)
      )
ORDER BY song.priority DESC
       , song.song_id DESC 
LIMIT 0, 40 ;

并测试哪一个执行得更快。

于 2012-05-28T18:22:15.140 回答
1

MySQL 将ORDER BY在应用限制之前对所有行运行,所以如果你的song表很大,并且没有正确索引,那么它仍然会很慢。MySQL 性能博客上有一篇文章,介绍了一些可以用来加快ORDER BY ... LIMIT查询速度的方法。

我会注意到子查询有点多余,join 会解决这个问题。您可以像这样重写查询:

SELECT DISTINCT song.song_id, song.title, song.length, song.bpm, song.keysig 
FROM song 
JOIN song_genre g
  ON g.song_id = song.song_id 
JOIN song_production p
  ON p.song_id = song.song_id 
WHERE approved='1' 
  AND g.genre_id IN ('25')
  AND p.production_id IN ('8')
ORDER by priority DESC, song_id DESC 
LIMIT 0, 40

而且我什至不确定您是否需要DISTINCTSELECT,除非您可以拥有多首具有完全相同 id/title/length/bpm/keysig 值的歌曲。

于 2012-05-28T18:22:38.063 回答
-1

好的,我只是重写查询:

    SELECT DISTINCT song.song_id, song.title, song.length, song.bpm, song.keysig
FROM song
    INNER JOIN (
        SELECT song_id
        FROM song_genre
        WHERE genre_id LIKE '%'
    ) genre1
        ON genre1.song_id = song.song_id
    INNER JOIN (
        SELECT song_id
        FROM song_production
        WHERE production_id IN ('5')
    ) production1
        ON production1.song_id = song.song_id
WHERE approved='1'
ORDER by song.priority DESC, song.song_id DESC
LIMIT 0, 40

首先,我在表 song 上应用 order by,他们在 index 中有 song_id。我也希望优先。如果没有,请在此添加一个索引,并且 order by 将不会通过临时表和文件排序。

如果您没有为 order by 指定一个表,MySQL 可能会选择错误的表来执行 order by。

于 2012-05-28T17:45:26.680 回答