Как ускорить запросы к большой таблице из 220 миллионов строк (данные 9 ГБ)?

31

Проблема:

У нас есть социальный сайт, где участники могут оценивать друг друга для совместимости или соответствия. Эта user_match_ratingsтаблица содержит более 220 миллионов строк (данные по 9 ГБ или почти 20 ГБ по индексам). Запросы к этой таблице обычно отображаются в slow.log (порог> 2 секунды) и являются наиболее часто регистрируемым медленным запросом в системе:

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 1051
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 395357 group by rating;"

Query_time: 4  Lock_time: 0  Rows_sent: 3  Rows_examined: 1294
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 4182969 group by rating;"

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 446
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 630148 group by rating;"

Query_time: 5  Lock_time: 0  Rows_sent: 3  Rows_examined: 3788
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1835698 group by rating;"

Query_time: 17  Lock_time: 0  Rows_sent: 3  Rows_examined: 4311
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1269322 group by rating;"

Версия MySQL:

  • версия протокола: 10
  • версия: 5.0.77-log
  • версия bdb: Программное обеспечение Sleepycat: Беркли DB 4.1.24: (29 января 2009 г.)
  • машина компиляции версии: x86_64 version_compile_os: redhat-linux-gnu

Информация о таблице:

SHOW COLUMNS FROM user_match_ratings;

дает:

╔═══════════════╦════════════╦════╦═════╦════════╦════════════════╗
 id             int(11)     NO  PRI  NULL    auto_increment 
 rater_user_id  int(11)     NO  MUL  NULL                   
 rated_user_id  int(11)     NO  MUL  NULL                   
 rating         varchar(1)  NO       NULL                   
 created_at     datetime    NO       NULL                   
╚═══════════════╩════════════╩════╩═════╩════════╩════════════════╝

Пример запроса:

select * from mutual_match_ratings where id=221673540;

дает:

╔═══════════╦═══════════════╦═══════════════╦════════╦══════════════════════╗
 id         rater_user_id  rated_user_id  rating  created_at           
╠═══════════╬═══════════════╬═══════════════╬════════╬══════════════════════╣
 221673540  5699713        3890950        N       2013-04-09 13:00:38  
╚═══════════╩═══════════════╩═══════════════╩════════╩══════════════════════╝

Индексы

Таблица имеет 3 настроенных индекса:

  1. единый индекс на rated_user_id
  2. составной индекс на rater_user_idиcreated_at
  3. составной индекс на rated_user_idиrater_user_id
показать индекс из user_match_ratings;

дает:

╔════════════════════╦════════════╦═══════════════════════════╦══════════════╦═══════════════╦═══════════╦═════════════╦══════════╦════════╦═════════════════════════╦════════════╦══════════════════╗
 Table               Non_unique  Key_name                   Seq_in_index  Column_name    Collation  Cardinality  Sub_part  Packed  Null                     Index_type  Comment          
╠════════════════════╬════════════╬═══════════════════════════╬══════════════╬═══════════════╬═══════════╬═════════════╬══════════╬════════╬═════════════════════════╬════════════╬══════════════════╣
 user_match_ratings  0           PRIMARY                    1             id             A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  1             rater_user_id  A          11039059     NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  2             created_at     A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  1             rated_user_id  A          4014203      NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  2             rater_user_id  A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index3  1             rated_user_id  A          2480687      NULL      NULL    BTREE                                                 
╚════════════════════╩════════════╩═══════════════════════════╩══════════════╩═══════════════╩═══════════╩═════════════╩══════════╩════════╩═════════════════════════╩════════════╩══════════════════╝

Даже с индексами эти запросы медленные.

Мой вопрос:

Будет ли разделение этой таблицы / данных в другой базе данных на сервере, на котором достаточно оперативной памяти для хранения этих данных в памяти, ускорит ли это эти запросы? Есть ли что-то в таблицах / индексах, которые мы можем улучшить, чтобы сделать эти запросы быстрее?

В настоящее время у нас есть 16 ГБ памяти; однако мы рассматриваем либо модернизацию существующей машины до 32 ГБ, либо добавление новой машины, по крайней мере, с таким большим, может быть, твердотельным накопителем.

Ranknoodle
источник
1
Ваш вопрос невероятен. Мне очень интересно ваше текущее решение, как вам удалось получить результат за <= 2 секунды? Потому что у меня есть одна таблица, в которой всего 20 миллионов записей, и все же это занимает 30 секунд SELECT QUERY. Не могли бы вы предложить? PS Ваш вопрос заставил меня присоединиться к этому сообществу (у);)
NullPointer
2
Посмотрите на индексы в таблице, к которой вы обращаетесь. Часто можно сделать много улучшений в запросах, создав соответствующий индекс. Не всегда, но видел много случаев, когда запросы выполняются быстро, предоставляя индекс по столбцам в предложении where в запросе. Особенно, если стол становится все больше и больше.
Ranknoodle
Конечно @Ranknoodle. Спасибо. Я проверю соответственно.
NullPointer

Ответы:

28

Мысли по этому вопросу, выброшенные в случайном порядке:

  • Очевидный индекс для этого запроса: (rated_user_id, rating). Запрос, который получает данные только для одного из миллиона пользователей и требует 17 секунд, делает что-то не так: читает из (rated_user_id, rater_user_id)индекса, а затем читает из таблицы (от сотен до тысяч) значений для ratingстолбца, чего ratingнет в любом индексе. Таким образом, запрос должен прочитать много строк таблицы, которые расположены в разных местах диска.

  • Прежде чем приступить к добавлению многочисленных индексов в таблицы, попробуйте проанализировать производительность всей базы данных, весь набор медленных запросов, еще раз проверить выбор типов данных, используемый вами механизм и параметры конфигурации.

  • Подумайте о переходе на более новую версию MySQL, 5.1, 5.5 или даже 5.6 (также: версии Percona и MariaDB.) Несколько преимуществ, поскольку исправлены ошибки, улучшен оптимизатор, и вы можете установить низкий порог для медленных запросов менее 1 секунды. (как 10 миллисекунд). Это даст вам гораздо лучшую информацию о медленных запросах.

  • Выбор типа данных ratingстранный. VARCHAR(1)? Почему нет CHAR(1)? Почему нет TINYINT? Это сэкономит вам немного места, как в таблице, так и в индексах, которые (будут) включать этот столбец. Столбцу varchar (1) требуется еще один байт по сравнению с char (1), и если они имеют значение utf8, столбцам (var) char потребуется 3 (или 4) байта вместо 1 (tinyint).

ypercubeᵀᴹ
источник
2
Какое влияние на производительность или потерю памяти в%, если вы используете неправильный тип данных?
FlyingAtom
1
@FlyingAtom Это зависит от случая, но для некоторых индексированных столбцов, которые все еще нужно сканировать (например, если у вас нет предложения where, но вы только извлекаете этот столбец), механизм может решить сканировать индекс вместо таблицу, и если вы оптимизируете свой тип данных в половину размера, тогда сканирование будет в два раза быстрее, а ответ будет вдвое меньше. Если вы по-прежнему сканируете таблицу вместо индекса (например, когда извлекаете больше столбцов, а не только столбцы в индексе), преимущества будут менее значительными.
Себастьян Гриньоли
-1

Я занимался таблицами для правительства Германии, иногда с 60 миллионами записей.

У нас было много этих таблиц.

И нам нужно было много раз узнать общее количество строк в таблице.

Поговорив с программистами Oracle и Microsoft, мы не были так счастливы ...

Таким образом, мы, группа программистов баз данных, решили, что в каждой таблице есть запись, всегда одна запись, в которой хранится общее количество записей. Мы обновили это число, в зависимости от строк INSERT или DELETE.

Мы перепробовали все другие способы. Это, безусловно, самый быстрый способ.

Мы используем этот способ с 1998 года и никогда не имели неправильного количества строк во всех наших многомиллионных таблицах записей.

FrankyBkk
источник
7
Я бы посоветовал взглянуть на некоторые функции, представленные за последние 18 лет. Среди прочего, count(*)есть некоторые улучшения.
Dezso
Откуда ты знаешь, что у тебя никогда не было неправильного числа, если ты не мог их посчитать? хмммм ...
Тонка
-3

Я постараюсь разделить на рейтинговые типы, как:

mutat_match_ratings_N, mutat_match_ratings_S и т. д.

Вы должны выполнить запрос для каждого типа, но, возможно, это быстрее, чем другой способ. Попробуйте.

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

Если это так, вам следует искать другой подход или поддерживать две копии таблицы (исходную таблицу и секционированные), если это доступно с точки зрения пространства и удобства обслуживания (или логики приложения).

appartisan
источник