2011-02-16 47 views
1

我有900K +的记录查询时间过长完成

需要一分钟或更多来运行此查询的表:

SELECT 
    t.user_id, 
    SUM(t.direction = "i") AS 'num_in', 
    SUM(t.direction = "o") AS 'num_out' 
FROM tbl_user_reports t 
WHERE t.bound_time BETWEEN '2011-02-01' AND '2011-02-28' 
GROUP BY t.user_id 
HAVING t.user_id IS NOT NULL 
ORDER BY num_in DESC 
LIMIT 10; 

你能告诉我如何查询结果快?

- 更多信息 - 结构:

id int(11) unsigned NOT NULL 
subscriber varchar(255) NULL 
user_id int(11) unsigned NULL 
carrier_id int(11) unsigned NOT NULL 
pool_id int(11) unsigned NOT NULL 
service_id int(11) unsigned NOT NULL 
persona_id int(11) unsigned NULL 
inbound_id int(11) unsigned NULL 
outbound_id int(11) unsigned NULL 
bound_time datetime NOT NULL 
direction varchar(1) NOT NULL 

指标:

bound_timebound_time 
FK_tbl_user_reportspersona_id 
FK_tbl_user_reports_messageinbound_id 
FK_tbl_user_reports_serviceservice_id 
FK_tbl_user_reports_poolpool_id 
FK_tbl_user_reports_useruser_id 
FK_tbl_user_reports_carriercarrier_id 
FK_tbl_user_reports_subscribersubscriber 
FK_tbl_user_reports_outboundoutbound_id 
directiondirection 
+0

你使用的索引? – Nazariy 2011-02-16 02:54:27

+0

是的,以下列被编入索引:user_id,bound_time,direction – 2011-02-16 02:59:31

+0

您可以共享查询的EXPLAIN输出吗? http://dev.mysql.com/doc/refman/5.0/en/explain.html – payne 2011-02-16 03:01:23

回答

2

你可能想尝试

(bound_time, user_id, direction) 

一个复合索引包含了所有的领域,你需要并可以非常有效地缩小日期范围。

+0

(bound_time,user_id,direction)已经编入索引,查询似乎加快了,如果我删除了条款 – 2011-02-16 03:00:09

1

正如Thilo所说的添加索引,而不是tbl_user_reports t使用tbl_user_reports AS t,我会将HAVING语句移到WHERE以减少操作量。

WHERE t.user_id IS NOT NULL AND t.bound_time BETWEEN '2011-02-01' AND '2011-02-28'

UPDATE 对于实验目的,你可以尝试使用像,而不是

之间

t.bound_time LIKE '2011-02%'

2

如果有可能重新设计你的报告表,把你的InnoDB更有优势聚集主键指数。

这里是什么,我的意思是一个简单的例子:

500万行 32K用户 126K记录日期范围

冷运行时(的mysqld重启后)=0.13秒

create table user_reports 
(
bound_time datetime not null, 
user_id int unsigned not null, 
id int unsigned not null, 
direction tinyint unsigned not null default 0, 
primary key (bound_time, user_id, id) -- clustered composite PK 
) 
engine=innodb; 


select count(*) as counter from user_reports; 

+---------+ 
| counter | 
+---------+ 
| 5000000 | 
+---------+ 

select count(distinct(user_id)) as counter from user_reports; 

+---------+ 
| counter | 
+---------+ 
| 32000 | 
+---------+ 

select count(*) as counter from user_reports 
where bound_time between '2011-02-01 00:00:00' and '2011-04-30 00:00:00'; 

+---------+ 
| counter | 
+---------+ 
| 126721 | 
+---------+ 

select 
t.user_id, 
sum(t.direction = 1) AS num_in, 
sum(t.direction = 0) AS num_out 
from 
user_reports t 
where 
t.bound_time between '2011-02-01 00:00:00' and '2011-04-30 00:00:00' and 
t.user_id is not null 
group by 
t.user_id 
order by 
direction desc 
limit 10; 

+---------+--------+---------+ 
| user_id | num_in | num_out | 
+---------+--------+---------+ 
| 17397 |  1 |  1 | 
| 14729 |  2 |  1 | 
| 20094 |  4 |  1 | 
| 19343 |  7 |  1 | 
| 24804 |  1 |  2 | 
| 14714 |  3 |  2 | 
| 2662 |  4 |  3 | 
| 16360 |  2 |  3 | 
| 21288 |  2 |  3 | 
| 12800 |  6 |  2 | 
+---------+--------+---------+ 
10 rows in set (0.13 sec) 

explain 
select 
t.user_id, 
sum(t.direction = 1) AS num_in, 
sum(t.direction = 0) AS num_out 
from 
user_reports t 
where 
t.bound_time between '2011-02-01 00:00:00' and '2011-04-30 00:00:00' and 
t.user_id is not null 
group by 
t.user_id 
order by 
direction desc 
limit 10; 

+----+-------------+-------+-------+---------------+---------+---------+------+--------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref |rows | Extra          | 
+----+-------------+-------+-------+---------------+---------+---------+------+--------+----------------------------------------------+ 
| 1 | SIMPLE  | t  | range | PRIMARY  | PRIMARY | 8  | NULL |255270 | Using where; Using temporary; Using filesort | 
+----+-------------+-------+-------+---------------+---------+---------+------+--------+----------------------------------------------+ 
1 row in set (0.00 sec) 

希望你发现这有帮助:)