60 миллионов записей, выберите записи из определенного месяца. Как оптимизировать базу данных?

У меня есть база данных с 60 млн. записей.

каждая запись содержит:

  • ID
  • DataSourceID
  • Данные
  • DateTime

  1. Мне нужно выбрать записи из определенного месяца. Каждый месяц содержит около 2 млн. записей.

     select * 
       from Entries 
      where time between "2010-04-01 00:00:00" and "2010-05-01 00:00:00"
    

    (запрос занимает примерно 1,5 минуты)

  2. Я хочу выбрать данные из определенных месяц от заданного DataSourceID. (занимает около 20 секунд)

существует около 50-100 различных DataSourceIDs.

есть ли способ сделать это быстрее? Какие у меня варианты? Как оптимизировать эту базу данных / запрос?


EDIT: есть ок. 60-100 вставок в секунду!

4 ответов


воспользуйтесь преимуществами кластеризованных индексов первичного ключа innodb.

http://dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html

Это будет чрезвычайно performant:

create table datasources
(
year_id smallint unsigned not null,
month_id tinyint unsigned not null,
datasource_id tinyint unsigned not null,
id int unsigned not null, -- needed for uniqueness
data int unsigned not null default 0,
primary key (year_id, month_id, datasource_id, id)
)
engine=innodb;

select * from datasources where year_id = 2011 and month_id between 1 and 3;

select * from datasources where year_id = 2011 and month_id = 4 and datasouce_id = 100;

-- etc..

правка 2

забыл, что я был запущен первый тестовый скрипт с 3 месяцев. Вот результаты за один месяц : 0.34 и 0,69 секунды.

select d.* from datasources d where d.year_id = 2010 and d.month_id = 3 and datasource_id = 100 order by d.id desc limit 10;
+---------+----------+---------------+---------+-------+
| year_id | month_id | datasource_id | id      | data  |
+---------+----------+---------------+---------+-------+
|    2010 |        3 |           100 | 3290330 | 38434 |
|    2010 |        3 |           100 | 3290329 |  9988 |
|    2010 |        3 |           100 | 3290328 | 25680 |
|    2010 |        3 |           100 | 3290327 | 17627 |
|    2010 |        3 |           100 | 3290326 | 64508 |
|    2010 |        3 |           100 | 3290325 | 14257 |
|    2010 |        3 |           100 | 3290324 | 45950 |
|    2010 |        3 |           100 | 3290323 | 49986 |
|    2010 |        3 |           100 | 3290322 |  2459 |
|    2010 |        3 |           100 | 3290321 | 52971 |
+---------+----------+---------------+---------+-------+
10 rows in set (0.34 sec)

select d.* from datasources d where d.year_id = 2010 and d.month_id = 3 order by d.id desc limit 10;
+---------+----------+---------------+---------+-------+
| year_id | month_id | datasource_id | id      | data  |
+---------+----------+---------------+---------+-------+
|    2010 |        3 |           116 | 3450346 | 42455 |
|    2010 |        3 |           116 | 3450345 | 64039 |
|    2010 |        3 |           116 | 3450344 | 27046 |
|    2010 |        3 |           116 | 3450343 | 23730 |
|    2010 |        3 |           116 | 3450342 | 52380 |
|    2010 |        3 |           116 | 3450341 | 35700 |
|    2010 |        3 |           116 | 3450340 | 20195 |
|    2010 |        3 |           116 | 3450339 | 21758 |
|    2010 |        3 |           116 | 3450338 | 51378 |
|    2010 |        3 |           116 | 3450337 | 34687 |
+---------+----------+---------------+---------+-------+
10 rows in set (0.69 sec)

правка 1

решил проверить приведенная выше схема с ок. 60 миллионов строк растянулись на 3 года. Каждый запрос выполняется холодно, т. е. каждый запуск отдельно, после чего mysql перезапускается, очищая любые буферы и без кэширования запроса.

полный тестовый скрипт можно найти здесь:http://pastie.org/1723506 или ниже...

как вы можете видеть, это довольно работоспособная схема даже на моем скромном рабочем столе:)

select count(*) from datasources;
+----------+
| count(*) |
+----------+
| 60306030 |
+----------+

select count(*) from datasources where year_id = 2010;
+----------+
| count(*) |
+----------+
| 16691669 |
+----------+

select
 year_id, month_id, count(*) as counter
from
 datasources
where 
 year_id = 2010
group by
 year_id, month_id;
+---------+----------+---------+
| year_id | month_id | counter |
+---------+----------+---------+
|    2010 |        1 | 1080108 |
|    2010 |        2 | 1210121 |
|    2010 |        3 | 1160116 |
|    2010 |        4 | 1300130 |
|    2010 |        5 | 1860186 |
|    2010 |        6 | 1220122 |
|    2010 |        7 | 1250125 |
|    2010 |        8 | 1460146 |
|    2010 |        9 | 1730173 |
|    2010 |       10 | 1490149 |
|    2010 |       11 | 1570157 |
|    2010 |       12 | 1360136 |
+---------+----------+---------+
12 rows in set (5.92 sec)


select 
 count(*) as counter
from 
 datasources d
where 
 d.year_id = 2010 and d.month_id between 1 and 3 and datasource_id = 100;

+---------+
| counter |
+---------+
|   30003 |
+---------+
1 row in set (1.04 sec)

explain
select 
 d.* 
from 
 datasources d
where 
 d.year_id = 2010 and d.month_id between 1 and 3 and datasource_id = 100
order by
 d.id desc limit 10;

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


select 
 d.* 
from 
 datasources d
where 
 d.year_id = 2010 and d.month_id between 1 and 3 and datasource_id = 100
order by
 d.id desc limit 10;

+---------+----------+---------------+---------+-------+
| year_id | month_id | datasource_id | id      | data  |
+---------+----------+---------------+---------+-------+
|    2010 |        3 |           100 | 3290330 | 38434 |
|    2010 |        3 |           100 | 3290329 |  9988 |
|    2010 |        3 |           100 | 3290328 | 25680 |
|    2010 |        3 |           100 | 3290327 | 17627 |
|    2010 |        3 |           100 | 3290326 | 64508 |
|    2010 |        3 |           100 | 3290325 | 14257 |
|    2010 |        3 |           100 | 3290324 | 45950 |
|    2010 |        3 |           100 | 3290323 | 49986 |
|    2010 |        3 |           100 | 3290322 |  2459 |
|    2010 |        3 |           100 | 3290321 | 52971 |
+---------+----------+---------------+---------+-------+
10 rows in set (0.98 sec)


select 
 count(*) as counter
from 
 datasources d
where 
 d.year_id = 2010 and d.month_id between 1 and 3;

+---------+
| counter |
+---------+
| 3450345 |
+---------+
1 row in set (1.64 sec)

explain
select 
 d.* 
from 
 datasources d
where 
 d.year_id = 2010 and d.month_id between 1 and 3
order by
 d.id desc limit 10;

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


select 
 d.* 
from 
 datasources d
where 
 d.year_id = 2010 and d.month_id between 1 and 3
order by
 d.id desc limit 10;

+---------+----------+---------------+---------+-------+
| year_id | month_id | datasource_id | id      | data  |
+---------+----------+---------------+---------+-------+
|    2010 |        3 |           116 | 3450346 | 42455 |
|    2010 |        3 |           116 | 3450345 | 64039 |
|    2010 |        3 |           116 | 3450344 | 27046 |
|    2010 |        3 |           116 | 3450343 | 23730 |
|    2010 |        3 |           116 | 3450342 | 52380 |
|    2010 |        3 |           116 | 3450341 | 35700 |
|    2010 |        3 |           116 | 3450340 | 20195 |
|    2010 |        3 |           116 | 3450339 | 21758 |
|    2010 |        3 |           116 | 3450338 | 51378 |
|    2010 |        3 |           116 | 3450337 | 34687 |
+---------+----------+---------------+---------+-------+
10 rows in set (1.98 sec)

надеюсь, что это помогает :)


чтобы получить записи в определенный месяц, в течение определенного года, быстрее-вы будете нужно индексировать time колонки:

CREATE INDEX idx_time ON ENTRIES(time) USING BTREE;

дополнительно использовать:

SELECT e.* 
  FROM ENTRIES e
 WHERE e.time BETWEEN '2010-04-01' AND DATE_SUB('2010-05-01' INTERVAL 1 SECOND)

...потому что между это включено, так что вам ничего от "2010-05-01 00:00:00" с запросом вас в курсе.

я также хотел бы выбрать данные из определенного месяца из данного DataSourceID

вы можете либо добавить отдельный индекс для datasourceid колонка:

CREATE INDEX idx_time ON ENTRIES(datasourceid) USING BTREE;

...или настройте индекс покрытия, чтобы включить оба столбца:

CREATE INDEX idx_time ON ENTRIES(time, datasourceid) USING BTREE;

индекс покрытия требует, чтобы крайние левые столбцы использовались в запросе для используемого индекса. В этом примере, имея time first будет работать для обеих ситуаций, которые вы упомянули -- datasourceid не должен использоваться для использования индекса. но вы должны проверить свои запросы, просмотрев вывод EXPLAIN, чтобы действительно знать, что лучше всего работает для ваших данных & запросы, выполняемые по этим данным.

тем не менее, индексы замедлят инструкции INSERT, UPDATE и DELETE. И индекс не дает много значения, если данные столбца имеют несколько различных значений, т. е.: логический столбец-плохой выбор для индексирования, потому что мощность низкая.


вы можете использовать индекс для торговли использованием диска для скорости запроса. Индекс, который запускает time столбец может ускорить запросы, которые запрашивают определенный месяц:

create index IX_YourTable_Date on YourTable (time, DataSourceID, ID, SomeData)

потому что индекс начинается с time поле, MySQL может выполнить сканирование диапазона ключей по индексу. Это должно произойти как можно быстрее. Индекс должен включать все столбцы в запросе, или MySQL должен был бы искать от индекса к данным таблицы для каждой строки. Поскольку вы просите 2 миллиона строк, MySQL скорее всего, будет игнорировать индекс, который не покрывает. (Covering index = индекс, который включает все строки в запросе.)

если вы никогда не запрашиваете ID, вы можете переопределить таблицу для использования (time, DataSourceID, ID) в качестве первичного ключа:

alter table YourTable add primary key (time, DataSourceID, ID)

это ускорит поиск на time бесплатно в дисковом пространстве, но ищет на ID будет очень медленным.


Я бы попытался поставить индекс, Если вы еще не в поле времени.

для DataSourceID вы можете попробовать использовать Enum вместо varchar / int.