Запрос занимает слишком много времени для завершения - PullRequest
1 голос
/ 16 февраля 2011

У меня есть таблица с 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

Ответы [ 3 ]

2 голосов
/ 16 февраля 2011

Если возможно, измените структуру таблицы отчетов, чтобы использовать преимущества кластерного индекса первичного ключа innodb.

Вот упрощенный пример того, что я имею в виду:

5 миллионов строк 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)

надеюсь, вы найдете это полезным:)

2 голосов
/ 16 февраля 2011

Возможно, вы захотите попробовать составной индекс на

(bound_time, user_id, direction)

Содержит все необходимые поля и может быть очень эффективно сужен по диапазону дат.

1 голос
/ 16 февраля 2011

Как сказал Тило добавить индексы, также вместо 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'

ОБНОВЛЕНИЕ В целях эксперимента вы можете попробовать использовать подобное вместо

t.bound_time LIKE '2011-02%'

...