【问题标题】:Efficiently querying a 15,000,000 rows table in MySQL在 MySQL 中高效查询 15,000,000 行表
【发布时间】:2010-11-08 17:38:02
【问题描述】:

考虑以下数据库表:

  • 包含 13,000,000 行(每条消息一行)的“消息”表。
  • 包含 3,000,000 行(每个用户一行)的“用户”表。

以下查询用于获取一堆消息和对应的用户:

SELECT messages.id, messages.message, users.id, users.username
FROM messages
INNER JOIN users ON messages.user_id=users.id 
WHERE messages.id in (?, ?, ?, ? ... a total of 100 "?":s);

在每个查询中获取 100 条消息。

“消息”在 id(主键,BIGINT 不是自动生成)和 user_id 上编入索引。

"users" 以 id 为索引(主键,INT 自动生成)。

数据库是使用 MyISAM 的 MySQL。

目前,查询的执行时间远远超过 3000 毫秒,这让我感到困惑,因为“消息”是在“id”上索引的,因此检索正确的行应该非常快。

我的问题是:考虑到描述场景和设置,3000 毫秒的查询时间是“正常的”还是我遗漏了什么?如果需要更多详细信息,请告诉我。

更新 #1: 以下是表定义:

CREATE TABLE messages (
  id bigint(20) NOT NULL DEFAULT '0',
  user_id int(11) NOT NULL DEFAULT '0',
  message varchar(160) NOT NULL DEFAULT '',
  PRIMARY KEY (id),
  KEY user_id (user_id),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE users (
  id int(11) NOT NULL DEFAULT '0',
  username varchar(32) NOT NULL DEFAULT '',
  PRIMARY KEY (id),
  UNIQUE KEY username (username),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

我在定义中观察到的唯一“非标准”内容是“messages.id”是 BIGINT 而不是 INT。这可能是一个提示吗?

【问题讨论】:

  • 你能发布 EXPLAIN SELECT 的输出吗...
  • 您是否尝试过将 100 个 MessageIds 放入临时表中,然后加入该表或 Exists?
  • 3000ms 看起来很糟糕。在索引中查找 100 条消息中的每条消息应该非常快,然后只需检索这 100 条记录,在索引中查找他们的用户,然后检索这 100 条记录。
  • 你能解释一下查询吗?
  • 当你减少 IN() 中的 id 数量时,所花费的时间是否会按比例减少?

标签: optimization mysql indexing query-optimization


【解决方案1】:

我曾处理过具有数十亿行的 MyISAM 表,在某些行数限制之后我发现的一件事是优化器花了太长时间来决定如何处理查询,并且错误地执行了一些表扫描。我找不到描述它的确切页面,但我开始总是在我知道它应该如何请求对象的每个查询段上使用 FORCE_INDEX

http://dev.mysql.com/doc/refman/5.1/en/index-hints.html

事实上,如果您使用的是这么大的表,则需要设计每个查询以使用您的索引,因此强制索引没有任何问题。如果必须,它仍然会扫描表,但 FORCE_INDEX 告诉它不要这样做,除非它绝对必须这样做。

另外,如果您的表很大,我假设您的索引也很大。你绝对需要确保你有正确的配置设置,并且你的 key_buffer 有足够的大小并且你有足够的 i/o。如果您正在运行 32 位 mysql(您不应该这样做),则将您的 key_buffer 设置为 1GB(假设您有 1GB 可用)并使用“mysqlreport”检查其使用情况

如果您运行的是 64 位 mysql,请选择使其尽可能大,同时仍为操作系统留出空间来缓存文件和您正在运行的任何其他应用程序,因此如果可以的话,可能需要几 GB。

即使您的查询使用索引,如果索引无法在内存中正确缓冲,您仍然会访问磁盘,并且性能损失与索引大小和磁盘/可用 i/o 的速度成正比。

就 int 与 big int 而言,我看到的唯一明显的性能差异是在大 int 上执行计算,例如 SUM。 SUM 在 big int 上比在 int 上要慢得多,以至于我会考虑以不同的数量级存储数字,或者如果您需要对它们执行频繁的计算,则将它们分成两个 int。

【讨论】:

    【解决方案2】:
    1. 我们需要这样解释。
    2. MyISAM 提供较差的并发性。考虑到并发插入可能会让您头疼。拥有如此庞大的数据库,InnoDB 可能是前进的方向。
    3. 如果正在插入和删除消息,如果您的表不偶尔优化,这可能会导致事情发生偏差。此外,MyISAM 主键不是集群的。同样,拥有如此庞大的数据库,InnoDB 可能是前进的方向。

    【讨论】:

      【解决方案3】:
      SELECT  messages.id, messages.message, users.id, users.username
      FROM    messages
      INNER JOIN
              users
      ON      users.id = messages.user_id
      WHERE   messages.id in (?, ?, ?, ? ... a total of 100 "?":s);
      

      您的消息似乎具有数据类型TEXT 并且很长。

      TEXT 列存储在行外,这就是为什么您需要进行一些额外的页面读取来检索它们,这可能需要很长时间。

      请您检查两件事:

      1. 此查询的性能:

        SELECT  messages.id, users.id, users.username
        FROM    messages
        INNER JOIN
                users
        ON      users.id = messages.user_id
        WHERE   messages.id in (?, ?, ?, ? ... a total of 100 "?":s);
        
        • 此查询和您的原始查询生成的执行计划。

      【讨论】:

      • 我已经发布了一个包含精确表定义的更新。请注意,“messages.message”只是 VARCHAR(160)。 “messages.id”是一个 BIGINT - 这可能是一个提示吗?
      • @knorv:能否请您发布两个查询的计划?运行 EXPLAIN SELECT messages.id ... 并在此处发布输出。
      【解决方案4】:

      好吧,查询和表设计本身可能不是原因。尽管查询可以使用一些帮助(例如将“in list”添加到连接谓词中以消除后期过滤器,但我猜优化器无论如何都会返回相同的计划)

      我的猜测是这是其他问题的症状,索引\表碎片或过时的统计信息。这些表是否经常被删除?对表和索引进行碎片整理可能会有所帮助,否则您可能会成为只有 10% 或更少的页面的受害者,这会导致大量磁盘 I/O。

      注意:对于主键使用整数种子,除非您对行进行大量删除和更新,否则您不会经常看到大量碎片。

      【讨论】:

        【解决方案5】:

        目前查询已经接管了 3000 毫秒执行

        每次,还是只是第一次查询?会不会是第一个查询会产生加载索引等的成本?

        作为比较,对特定消息 ID 执行相同查询需要多长时间?

        还取决于您运行它的机器的规格,然后按照其他人的建议查看执行计划,还可能值得查看 mysqld 的内存使用情况,并确保它不是简单的交换。

        【讨论】:

        • 3000+ 毫秒是每次发出查询。请参阅我的更新 wrt 表定义。
        【解决方案6】:

        因为这通常被解析器重写为:

        SELECT messages.id, messages.message, users.id, users.username
        FROM messages
        INNER JOIN users ON messages.user_id=users.id 
        WHERE messages.id = ?
        OR messages.id = ?
        OR messages.id = ? etc.
        

        我有兴趣查看单个案例的执行计划和性能:

        SELECT messages.id, messages.message, users.id, users.username
        FROM messages
        INNER JOIN users ON messages.user_id=users.id 
        WHERE messages.id = ?
        

        在这种情况下,您最好执行 UNION 或创建包含 ID 的表并执行 JOIN

        【讨论】:

          【解决方案7】:

          您在这里查看的是什么硬件?我假设您的服务器具有合理数量的 ram 和 key_buffer 设置非常大(例如,大于两个中等大小表的组合索引大小)。我假设服务器是一个空闲的性能测试服务器。

          你能衡量 IO 的数量吗?

          如果你重复完全相同的查询,它是否很快?

          如果您将整个数据库加载到 ram 磁盘中(只有 15M 行的小表很容易放入 ram 磁盘中)会更快吗?

          另外(正如其他人所指出的),发布 EXPLAIN 计划。

          但是这么小的数据库应该总是很快的,因为它可以在除了最弱的服务器之外的所有服务器上安装到内存中。

          【讨论】:

            猜你喜欢
            • 2011-09-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-11-13
            • 2018-06-08
            • 2013-10-08
            相关资源
            最近更新 更多