在MySQL中有效查询15,000,000行表

时间:2009-07-07 14:58:38

标签: optimization mysql indexing query-optimization

考虑以下数据库表:

  • 表“消息”包含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条消息。

“messages”的索引是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。这可能是一个暗示吗?

7 个答案:

答案 0 :(得分: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。< / p>

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

就int vs big int而言,我见过的唯一明显的性能差异在于对大整数执行计算,例如SUM。对于大型int而言,SUM明显慢于int,因此我会考虑以不同的大小存储数字,或者如果需要对它们进行频繁计算,则将它们分成两个整数。

答案 1 :(得分:6)

  1. 我们需要解释。
  2. MyISAM提供较差的并发性。考虑并发插入可能会让您头疼。有了这么大的数据库,InnoDB可能就是方向。
  3. 如果正在插入和删除邮件,如果您的表格不是偶尔优化的话,这可能会导致事情发生偏差。此外,MyISAM主键不是群集。再次,有了这么大的数据库,InnoDB可能就是方向。

答案 2 :(得分: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);
    
    • 此查询和原始查询生成的执行计划。

答案 3 :(得分:1)

嗯,查询和表格设计本身可能不是原因。虽然查询可以使用一些帮助(比如将“in list”添加到连接谓词中以消除后期过滤器,但我猜想优化器会返回相同的计划)

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

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

答案 4 :(得分:0)

  

目前查询已经完成了   执行时间为3000毫秒

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

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

另外,根据您运行此框的规格,然后根据其他人的建议查看执行计划,也可能值得查看mysqld的内存使用情况,并确保它不是简单的交换。

答案 5 :(得分: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 = ?

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

答案 6 :(得分:0)

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

你能衡量一下IO的数量吗?

如果你重复完全相同的查询,它会快吗?

如果你将整个数据库加载到ram光盘(一个只有15M行的小桌子很容易装入ram光盘)它会更快吗?

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

但是这样一个小型数据库应该总是很快,因为除了最卑微的服务器之外,它几乎适合于ram。