【问题标题】:Mysql with big tables: how to optmize this query?带有大表的 Mysql:如何优化此查询?
【发布时间】:2011-05-18 18:38:14
【问题描述】:

我有一个使用 InnoDB 的表来存储我的系统发送的所有消息。目前该表有 4000 万行,并且每月增长 3/4 百万。

我的查询基本上是选择从用户和数据范围内发送的消息。这是一个简单的创建表:

创建表 `log` (
  `id` int(10) NOT NULL DEFAULT '0',
  `type` varchar(10) NOT NULL DEFAULT '',
  `timeLogged` int(11) NOT NULL DEFAULT '0',
  `orig` varchar(128) NOT NULL DEFAULT '',
  `rcpt` varchar(128) NOT NULL DEFAULT '',
  `user` int(10) 默认为空,
  主键(`id`),
  KEY `timeLogged`(`timeLogged`),
  KEY `user` (`user`),
  KEY `user_timeLogged` (`user`,`timeLogged`)
) ENGINE=InnoDB 默认字符集=latin1;

注意:由于其他查询,我也有单独的索引。

查询如下所示:

SELECT COUNT(*) FROM log WHERE timeLogged BETWEEN 1282878000 AND 1382878000 AND user = 20

问题在于,此查询需要 2 分钟到 10 分钟,具体取决于用户和服务器负载,等待页面加载的时间太长。我启用了mysql缓存并在应用程序中缓存,但问题是当用户搜索新范围时,它不会命中缓存。

我的问题是:

  • 更改 user_timeLogged 索引会有什么不同吗?
  • 这是 MySQL 和大型数据库的问题吗?我的意思是,Oracle 或其他数据库是否也会遇到这个问题?

AFAIK,我的索引已正确创建,此查询不应该花这么长时间。

感谢任何帮助的人!

【问题讨论】:

  • 发布来自以下EXPLAIN SELECT COUNT(*) FROM log WHERE timeLogged BETWEEN 1282878000 AND 1382878000 AND user = 20;的输出
  • 我会将此作为评论发布,因为它不涉及查询优化,但您是否考虑过归档策略而不是将所有消息保存在一个表中?以 750k/月的速度记录 4000 万条记录意味着超过四年的数据价值。除非确实以相同频率查询任意年龄的消息,否则您可能需要考虑将旧消息移动到单独的表,并实现将旧消息请求定向到该表的逻辑。
  • 查询中没有太多需要优化的地方。你的 key_buffer 有多大?
  • 你的意思是innodb_buffer_pool_size,因为它是 InnoDB 表。

标签: mysql query-optimization


【解决方案1】:

您正在使用 innodb,但没有充分利用您的 innodb 聚集索引(主键),因为您的典型查询看起来像以下形式:

select <fields> from <table> where user_id = x and <datefield> between y and z

不是

select <fields> from <table> where id = x 

以下文章将帮助您优化查询的表设计。

http://www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/

如果你正确理解了这篇文章,你应该会发现自己有以下类似的内容:

drop table if exists user_log;
create table user_log
(
user_id int unsigned not null,
created_date datetime not null, 
log_type_id tinyint unsigned not null default 0, -- 1 byte vs varchar(10)
...
...
primary key (user_id, created_date, log_type_id)
)
engine=innodb;

以下是上述设计的一些查询性能统计数据:

计数

select count(*) as counter from user_log

counter
=======
37770394

select count(*) as counter from user_log where 
 created_date between '2010-09-01 00:00:00' and '2010-11-30 00:00:00'

counter
=======
35547897

基于用户和日期的查询(所有查询都使用冷缓冲区运行)

select count(*) as counter from user_log where user_id = 4755

counter
=======
7624

runtime = 0.215 secs


select count(*) as counter from user_log where 
 user_id = 4755 and created_date between '2010-09-01 00:00:00' and '2010-11-30 00:00:00'

counter
=======
7404

runtime = 0.015 secs

select 
 user_id,
 created_date,
 count(*) as counter
from 
 user_log 
where 
 user_id = 4755 and created_date between '2010-09-01 00:00:00' and '2010-11-30 00:00:00'
group by
 user_id, created_date
order by
 counter desc
limit 10;

runtime = 0.031 secs

希望这会有所帮助:)

【讨论】:

    【解决方案2】:

    COUNT(*) 没有从表缓存中加载,因为您有一个 WHERE 子句,使用 @jason 提到的 EXPLAIN,尝试将其更改为 COUNT(id) 并查看是否有帮助。

    我可能是错的,但我也认为您的索引必须与 WHERE 子句的顺序相同。由于您的 WHERE 子句在 user 之前使用 timeLogged,因此您的索引应该是 KEYuser_timeLogged(timeLogged,user)`

    同样,EXPLAIN 会告诉您此索引更改是否会产生影响。

    【讨论】:

    • 实际上,在相同的 where 子句中,约束的顺序没有区别。它是处理该问题的查询优化器工作。 timelogged 是一个范围扫描,因此它将用作最后一部分。索引似乎没问题,也许他从表中选择了太多(导致全表扫描)或数据库配置错误。
    猜你喜欢
    • 1970-01-01
    • 2016-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多