【问题标题】:How can I optimise this MySQL query?如何优化这个 MySQL 查询?
【发布时间】:2010-11-15 01:18:33
【问题描述】:

我在包含超过 300,000,000(是的,三亿)行的数据库上的 PHP 脚本中使用以下 MySQL 查询。我知道它非常耗费资源,并且运行这个查询需要很长时间。有谁知道我可以如何优化查询或以另一种更快的方式获取信息?

我需要能够使用 1 到 15 之间的任何整数来代替 MID() 中的 14。我还需要能够匹配LIKE 子句中相同范围内的长度字符串。

表信息:

games | longint, unsigned, Primary Key
win   | bit(1)
loss  | bit(1)

示例查询:

SELECT MID(`game`,14,1) AS `move`,
       COUNT(*) AS `games`,
       SUM(`win`) AS `wins`,
       SUM(`loss`) AS `losses`
FROM `games`
WHERE `game` LIKE '1112223334%'
GROUP BY MID(`game`,1,14)

提前感谢您的帮助!

【问题讨论】:

    标签: php sql optimization mysql


    【解决方案1】:

    首先,在游戏领域有一个索引... :)

    查询看起来简单直接,但它隐藏了可能需要更改数据库设计的事实。

    在这种情况下,我总是更喜欢维护一个包含每天、每个用户或任何其他轴的聚合数据的字段。这样您就可以完成一项日常任务,将相关数据汇总并保存在数据库中。

    如果你确实经常调用这个查询,你应该使用降低插入效率的原则来提高检索效率。

    【讨论】:

      【解决方案2】:

      看起来game 列存储了此查询使用的两个(或可能更多)不同的东西:

      1. game(前 10 个字符)的开头过滤
      2. MID(game,1,14) 分组并返回(我假设MID 表达式之一是错字。

      我将其拆分,这样您就不必在 game 列上使用字符串操作,并且还将索引放在新列上,以便您可以正确过滤和分组它们。

      此查询正在执行大量转换(长到字符串)和字符串操作,如果对表进行规范化(如每列一条信息而不是现在的多个信息),则不需要这些转换。

      保留game 列的原样,并基于它创建一个game_filter 字符串列以在您的WHERE 子句中使用。然后设置一个game_group 列并在插入时使用MID 表达式填充它。将这两列设置为您的聚集索引,首先是game_filter,然后是game_group

      【讨论】:

        【解决方案3】:

        查询很简单,除了确保有所有必要的索引(显然是“游戏”字段)之外,可能没有明显的方法可以通过仅重写查询来使其更快。 可能需要对数据结构进行一些修改。

        一种方法:预先计算总和。这些记录中的每一个很可能都有一个 create_date 或一个自动递增的键字段。预先计算所有记录的总和,其中该字段≤某个 X,将结果放在边表中,然后您只需计算所有记录 > X,然后将这些部分结果与预先计算的结果汇总。

        【讨论】:

          【解决方案4】:

          您可以预先计算 MID(game,14,1) 和 MID(game,1,14),并将 game 的前十位数字存储在单独的被索引的 gameid 列中。

          您也可以调查一下您是否可以只存储预先计算值的聚合表,以便在插入时增加计数和赢或输列。

          【讨论】:

            【解决方案5】:
            SELECT  MID(`game`,14,1) AS `move`,
                    COUNT(*) AS `games`,
                    SUM(`win`) AS `wins`,
                    SUM(`loss`) AS `losses`
            FROM    `games`
            WHERE   `game` LIKE '1112223334%'
            

            game上创建索引:

            CREATE INDEX ix_games_game ON games (game)
            

            并将您的查询重写为:

            SELECT  move,
                    (
                    SELECT  COUNT(*)
                    FROM    games
                    WHERE   game >= move
                            AND game < CONCAT(SUBSTRING(move, 1, 13), CHR(ASCII(SUBSTRING(move, 14, 1)) + 1))
                    ),
                    (
                    SELECT  SUM(win)
                    FROM    games
                    WHERE   game >= move
                            AND game < CONCAT(SUBSTRING(move, 1, 13), CHR(ASCII(SUBSTRING(move, 14, 1)) + 1))
                    ),
                    (
                    SELECT  SUM(lose)
                    FROM    games
                    WHERE   game >= move
                            AND game < CONCAT(SUBSTRING(move, 1, 13), CHR(ASCII(SUBSTRING(move, 14, 1)) + 1))
                    )
            FROM    (
                    SELECT  DISTINCT SUBSTRING(q.game, 1, 14) AS move
                    FROM    games
                    WHERE   game LIKE '1112223334%'
                    ) q
            

            这将有助于更有效地使用game 上的索引。

            【讨论】:

            • 为什么要删除 GROUP BY 子句?他希望 COUNT 和 SUM 被游戏列的第 14 位分开。
            • @mlarsen:我没有先得到它并删除了答案。现在全部改写了。
            【解决方案6】:

            你能用Memcache 或类似的东西缓存结果集吗?这将有助于重复命中。即使您只将结果集缓存几秒钟,您也可以避免大量的 DB 读取。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-04-20
              • 1970-01-01
              • 2012-09-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多