优化MySQL查询,在选择内选择,多个相同

时间:2014-08-22 19:36:18

标签: mysql sql optimization

我需要帮助优化我掀起的MySQL语句。它完全符合我的要求,但是我感觉它很慢,因为我在语句中做了多个选择,而且我还多次查询achievement_new。这是我第一次做这样的主要陈述,我习惯了简单的SELECT FROM WHERE风格废话。

我可能会做一些解释,这是针对我网站的排行榜风格的事情。

- 第一个变量输出是根据显示的公式计算的排名,(Log + Log +成就的数量)。

- Wepvalue是id具有的武器值的总和。 playerweapons 包含所有武器, weaponprices 将类型转换为价格,然后SUM计算值。

- Achcount只是解锁的成就数量。 也许这可以通过排名输出以某种方式进行优化?

- achievement_new 中的ID和 playerweapons playerdata

中ID的外键
SELECT
    (
        IFNULL(LOG(1.5, cashearned),0) +
        IFNULL(LOG(1.3, roundswon), 0) +
        (
            SELECT COUNT(*)
            FROM achievements_new
            WHERE `value` = -1 AND achievements_new.id = playerdata.id
        )
    ) as rank,
    nationality,
    nick,
    steamid64,
    cash,
    playtime,
    damage,
    destroyed,
    (
        SELECT SUM(price)
        FROM weaponprices
        WHERE weapon IN
        (
            SELECT class
            FROM playerweapons
            WHERE playerweapons.id = playerdata.id
        )
    ) as wepvalue,
    (
        SELECT COUNT(*)
        FROM achievements_new
        WHERE `value` = -1 AND achievements_new.id = playerdata.id
    ) as achcount,
    lastplayed
FROM playerdata
ORDER BY rank DESC

表格结构:

playerdata:

CREATE TABLE IF NOT EXISTS `playerdata` (
    `id` int(11) unsigned NOT NULL,
  `steamid64` char(17) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `nick` varchar(32) NOT NULL DEFAULT '',
  `cash` int(32) unsigned NOT NULL DEFAULT '0',
  `playtime` int(32) unsigned NOT NULL DEFAULT '0',
  `nationality` char(2) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `damage` int(32) unsigned NOT NULL DEFAULT '0',
  `destroyed` int(32) unsigned NOT NULL DEFAULT '0',
  `cashearned` int(10) unsigned NOT NULL,
  `roundswon` smallint(5) unsigned NOT NULL,
  `lastplayed` datetime NOT NULL,
) ENGINE=InnoDB

achievements_new:

CREATE TABLE IF NOT EXISTS `achievements_new` (
  `id` int(10) unsigned NOT NULL,
  `achkey` enum(<snip - lots of values here>) NOT NULL,
  `value` mediumint(8) NOT NULL DEFAULT '0'
) ENGINE=InnoDB

playerweapons:

CREATE TABLE IF NOT EXISTS `playerweapons` (
  `id` int(10) unsigned NOT NULL,
  `class` varchar(30) CHARACTER SET ascii NOT NULL
) ENGINE=InnoDB

weaponprices:

CREATE TABLE IF NOT EXISTS `weaponprices` (
  `weapon` varchar(30) NOT NULL,
  `price` int(10) unsigned NOT NULL
) ENGINE=InnoDB

Query Explain 提前谢谢!

3 个答案:

答案 0 :(得分:1)

尝试类似下面的查询。
我使用LEFT JOIN而不是加入,因为可能有没有成就或武器的玩家。如果您不需要这些播放器,可以使用JOIN

SELECT
    IFNULL(LOG(1.5, p.cashearned),0) +
        IFNULL(LOG(1.3, p.roundswon), 0) +
        SUM(CASE WHEN ac.id IS NOT NULL THEN 1 ELSE 0 END)/COUNT(pw.id) as rank
    p.nationality,
    p.nick,
    p.steamid64,
    p.cash,
    p.playtime,
    p.damage,
    p.destroyed,
    --SUM(CASE WHEN pw.id IS NOT NULL THEN pw.price ELSE 0 END) as wepvalue,
    --wpn.price as wepvalue,
    SUM(CASE WHEN pw.id IS NOT NULL THEN wp.price ELSE 0 END)/COUNT(ac.id) as wepvalue,
    SUM(CASE WHEN ac.id IS NOT NULL THEN 1 ELSE 0 END)/COUNT(pw.id) as achcount,
    lastplayed
FROM playerdata as p
    JOIN playerweapons as pw ON pw.id = p.id
    JOIN weaponprices as wp ON pw.class = wp.weapon
    LEFT JOIN achievements_new as ac ON ac.id = p.id AND ac.value = -1
    --LEFT JOIN playerweapons as pw ON pw.id = p.id
    --LEFT JOIN weaponprices as wp ON pw.class = wp.weapon
    --LEFT JOIN (   SELECT 
                      --pw.id as player,
                      --SUM(wp.price) as price
                  --FROM weaponprices as wp
                      --JOIN playerweapons as pw ON pw.class = wp.weapon 
                  --GROUP BY pw.id
              --) as wpn ON wpn.player = p.id
GROUP BY 
    p.nationality,
    p.nick,
    p.steamid64,
    p.cash,
    p.playtime,
    p.damage,
    p.destroyed,
    p.lastplayed

答案 1 :(得分:1)

你的查询是相当合理的,虽然我会重写子查询以使用显式连接而不是in并将成就子查询分解出来:

SELECT (IFNULL(LOG(1.5, cashearned),0) + IFNULL(LOG(1.3, roundswon), 0) +
        coalesce(an.cnt, 0)
       ) as rank,
       nationality, nick, steamid64, cash, playtime, damage, destroyed,
       (SELECT SUM(wp.price)
        FROM weaponprices wp JOIN
             playerweapons pw
             on pw.class = wp.weapons
        WHERE pw.id = pd.id
       ) as wepvalue,
       coalesce(an.cnt, 0) as achcount,
       lastplayed
FROM playerdata pd left outer join
     (SELECT id, count(*) as cnt
      FROM achievements_new an
      WHERE an.`value` = -1 
      GROUP BY an.id
     ) an
     on an.id = pd.id
ORDER BY rank DESC;

对于此查询,请创建以下索引:

playerweapons(id, weapon);
weaponprices(class, price);
achievements_new(value, id);

这可以做到以下几点:

  • 它消除了achievements_new上的两个冗余子查询。
  • 应优化price子查询以仅使用索引。
  • 它将in替换为明确的join,有时会更好地进行优化。
  • 它不需要外部group by

答案 2 :(得分:0)

我会尝试删除所有相关的子查询

SELECT
      ( COALESCE(LOG(1.5, pd.cashearned), 0)
      + COALESCE(LOG(1.3, pd.roundswon), 0)
      + COALESCE(an.cnt, 0))   AS rank
    , pd.nationality
    , pd.nick
    , pd.steamid64
    , pd.cash
    , pd.playtime
    , pd.damage
    , pd.destroyed
    , COALESCE(pw.wepvalue, 0) AS wepvalue
    , COALESCE(an.cnt, 0)      AS achcount
    , pd.lastplayed
FROM playerdata pd
      LEFT JOIN (
                  SELECT
                        id
                      , COUNT(*) AS cnt
                  FROM achievements_new
                  WHERE value = -1
                  GROUP BY
                        id
            ) an
                  ON pd.id = an.id
      LEFT JOIN (
                  SELECT
                        playerweapons.id
                      , SUM(price) AS wepvalue
                  FROM weaponprices
                        INNER JOIN playerweapons
                                    ON weaponprices.weapon = playerweapons.class
                  GROUP BY
                        playerweapons.id
            ) pw
                  ON pd.id = pw.id
ORDER BY
      rank DESC;