26

考虑以下数据库表:

  • 包含 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 上建立索引。

“用户”在 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。这可能是一个暗示吗?

4

7 回答 7

11

我处理过具有数十亿行的 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。

于 2009-07-28T09:24:36.797 回答
6
  1. 我们需要那个解释。
  2. MyISAM 提供较差的并发性。考虑到并发插入可能会让您头疼。拥有如此庞大的数据库,InnoDB 可能是前进的方向。
  3. 如果正在插入和删除消息,如果您的表没有偶尔优化,这可能会导致事情出现偏差。此外,MyISAM 主键不是集群的。同样,拥有如此庞大的数据库,InnoDB 可能是前进的方向。
于 2009-07-07T17:56:08.007 回答
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);
    
    • 此查询和您的原始查询生成的执行计划。
于 2009-07-07T15:04:57.450 回答
1

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

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

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

于 2009-07-07T17:03:53.340 回答
0

目前,查询需要 3000 多毫秒才能执行

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

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

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

于 2009-07-07T16:35:48.030 回答
0

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

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 = ?

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

于 2009-07-07T17:09:42.547 回答
0

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

你能测量 IO 的数量吗?

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

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

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

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

于 2009-07-07T21:02:07.553 回答