MySQL и NoSQL: помогите выбрать правильный - PullRequest
40 голосов
/ 12 декабря 2010

Существует большая база данных, 1 000 000 000 строк, которые называются потоками (эти потоки действительно существуют, я не усложняю их только потому, что мне это нравится). В Threads есть только несколько вещей, чтобы сделать вещи быстрее: (int id, хэш строки, int replycount, int dateline (timestamp), int forumid, string title)

Запрос:

select * from thread where forumid = 100 and replycount > 1 order by dateline desc limit 10000, 100

Поскольку существует 1G записей, это довольно медленный запрос. Поэтому я подумал, давайте разделим этот 1G записей на столько таблиц, сколько у меня есть форумов (категорий)! Это почти идеально. Имея много таблиц, у меня меньше записей для поиска, и это действительно быстрее. Запрос теперь становится:

select * from thread_{forum_id} where replycount > 1 order by dateline desc limit 10000, 100

Это действительно быстрее с 99% форумов (категорий), так как большинство из них имеют только несколько тем (100k-1M). Однако из-за того, что некоторые записи содержат около 10 миллионов записей, некоторые запросы все еще должны быть медленными (0,1 / 0,2 секунды для моего приложения! Я уже использую индексы! ).

Я не знаю, как улучшить это с помощью MySQL. Есть ли способ?

Для этого проекта я буду использовать 10 серверов (12 ГБ ОЗУ, жесткий диск 4x7200 об / мин на программном рейде 10, четырехъядерный процессор)

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

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

Что мне делать? Продолжать работать с MySQL с распределенной базой данных на нескольких машинах или создать кластер кассандры?

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

mysql> show index in thread;
PRIMARY id
forumid
dateline
replycount

Выберите объяснение:

mysql> explain SELECT * FROM thread WHERE forumid = 655 AND visible = 1 AND open <> 10 ORDER BY dateline ASC LIMIT 268000, 250;
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
| id | select_type | table  | type | possible_keys | key     | key_len | ref         | rows   | Extra                       |
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
|  1 | SIMPLE      | thread | ref  | forumid       | forumid | 4       | const,const | 221575 | Using where; Using filesort | 
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+

Ответы [ 5 ]

79 голосов
/ 12 декабря 2010

Вы должны прочитать следующее и немного узнать о преимуществах хорошо спроектированной таблицы innodb и о том, как лучше всего использовать кластерные индексы - доступно только с innodb!

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

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

затем спроектируйте свою систему в соответствии со следующим упрощенным примером:

Пример схемы (упрощенно)

Важными особенностями являются то, что в таблицах используется механизм innodb, и первичный ключ для таблицы потоков больше не является единственным ключом auto_incrementing, а составным кластеризованным ключом , основанным на комбинации forum_id и thread_id. например,

threads - primary key (forum_id, thread_id)

forum_id    thread_id
========    =========
1                   1
1                   2
1                   3
1                 ...
1             2058300  
2                   1
2                   2
2                   3
2                  ...
2              2352141
...

Каждая строка форума содержит счетчик с именем next_thread_id (unsigned int), который поддерживается триггером и увеличивается каждый раз при добавлении темы в данный форум. Это также означает, что мы можем хранить 4 миллиарда потоков на форуме, а не 4 миллиарда потоков, если использовать один первичный ключ auto_increment для thread_id.

forum_id    title   next_thread_id
========    =====   ==============
1          forum 1        2058300
2          forum 2        2352141
3          forum 3        2482805
4          forum 4        3740957
...
64        forum 64       3243097
65        forum 65      15000000 -- ooh a big one
66        forum 66       5038900
67        forum 67       4449764
...
247      forum 247            0 -- still loading data for half the forums !
248      forum 248            0
249      forum 249            0
250      forum 250            0

Недостатком использования составного ключа является то, что вы больше не можете просто выбрать поток по одному значению ключа следующим образом:

select * from threads where thread_id = y;

вам нужно сделать:

select * from threads where forum_id = x and thread_id = y;

Тем не менее, код вашего приложения должен знать, какой форум просматривает пользователь, поэтому его не сложно реализовать - сохраните просматриваемый в данный момент forum_id в переменной сеанса или в скрытом поле формы и т. Д. *

Вот упрощенная схема:

drop table if exists forums;
create table forums
(
forum_id smallint unsigned not null auto_increment primary key,
title varchar(255) unique not null,
next_thread_id int unsigned not null default 0 -- count of threads in each forum
)engine=innodb;


drop table if exists threads;
create table threads
(
forum_id smallint unsigned not null,
thread_id int unsigned not null default 0,
reply_count int unsigned not null default 0,
hash char(32) not null,
created_date datetime not null,
primary key (forum_id, thread_id, reply_count) -- composite clustered index
)engine=innodb;

delimiter #

create trigger threads_before_ins_trig before insert on threads
for each row
begin
declare v_id int unsigned default 0;

  select next_thread_id + 1 into v_id from forums where forum_id = new.forum_id;
  set new.thread_id = v_id;
  update forums set next_thread_id = v_id where forum_id = new.forum_id;
end#

delimiter ;

Вы, возможно, заметили, что я включил response_count как часть первичного ключа, что немного странно, так как составная часть (forum_id, thread_id) уникальна сама по себе. Это всего лишь оптимизация индекса, которая сохраняет некоторые операции ввода-вывода при выполнении запросов, использующих response_count. Пожалуйста, обратитесь к 2 ссылкам выше для получения дополнительной информации об этом.

Примеры запросов

Я все еще загружаю данные в мои таблицы примеров, и до сих пор у меня загружено прибл. 500 миллионов строк (вдвое меньше, чем ваша система). Когда процесс загрузки будет завершен, я должен ожидать примерно:

250 forums * 5 million threads = 1250 000 000 (1.2 billion rows)

Я специально сделал, что некоторые форумы содержат более 5 миллионов тем, например, форум 65 содержит 15 миллионов тем:

forum_id    title   next_thread_id
========    =====   ==============
65        forum 65      15000000 -- ooh a big one

Время выполнения запроса

select sum(next_thread_id) from forums;

sum(next_thread_id)
===================
539,155,433 (500 million threads so far and still growing...)

при innodb суммирование next_thread_ids для подсчета общего количества потоков происходит намного быстрее, чем обычно:

select count(*) from threads;

Сколько тем в форуме 65:

select next_thread_id from forums where forum_id = 65

next_thread_id
==============
15,000,000 (15 million)

опять же, это быстрее, чем обычно:

select count(*) from threads where forum_id = 65

Хорошо, теперь мы знаем, что у нас около 500 миллионов потоков, а форум 65 имеет 15 миллионов потоков - давайте посмотрим, как работает схема:)

select forum_id, thread_id from threads where forum_id = 65 and reply_count > 64 order by thread_id desc limit 32;

runtime = 0.022 secs

select forum_id, thread_id from threads where forum_id = 65 and reply_count > 1 order by thread_id desc limit 10000, 100;

runtime = 0.027 secs

Для меня это выглядит довольно производительно - так что это единственная таблица с 500+ миллионами строк (и растущая) с запросом, который покрывает 15 миллионов строк за 0,02 секунды (при загрузке!)

Дальнейшие оптимизации

Это будет включать:

  • разбиение по диапазонам

  • Sharding

  • метание денег и оборудования на это

и т.д ...

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

24 голосов
/ 12 декабря 2010

РЕДАКТИРОВАТЬ : недостаточно одного столбца.Вам нужно, как минимум, охватить три участвующих столбца.

Более продвинутое решение: замените replycount > 1 на hasreplies = 1, создав новое поле hasreplies, равное 1, когда replycount > 1.Как только это будет сделано, создайте индекс по трем столбцам в следующем порядке: INDEX(forumid, hasreplies, dateline).Убедитесь, что это индекс BTREE для поддержки порядка.

Вы выбираете на основе:

  • данное forumid
  • данное hasreplies
  • по заказу dateline

Как только вы сделаете это, ваш запрос будет включать:

  • перемещение вниз по BTREE, чтобы найти поддерево, которое соответствует forumid = X.Это логарифмическая операция (длительность: журнал (количество форумов)).
  • двигаясь дальше вниз по BTREE, чтобы найти поддерево, которое соответствует hasreplies = 1 (при этом все еще соответствует forumid = X).Это операция с постоянным временем, потому что hasreplies это только 0 или 1.
  • , перемещающийся по поддереву, отсортированному по дате, чтобы получить требуемые результаты, без необходимости читать и пересортировать весь списокэлементов в форуме.

Мое предыдущее предложение индексировать replycount было неверным, поскольку это был бы запрос диапазона и, следовательно, было запрещено использование dateline для сортировки результатов (поэтому вы бы очень быстро выбрали темы с ответами, но итоговый список из миллионов строк пришлось бы полностью отсортировать, прежде чем искать нужные вам 100 элементов.

ВАЖНО :в то время как это повышает производительность во всех случаях, ваше огромное значение OFFSET (10000!) снизит производительность, потому что MySQL, похоже, не сможет пропустить вперед, несмотря на чтение прямо через BTREE.Таким образом, чем больше ваш OFFSET, тем медленнее будет запрос.

Боюсь, что проблема OFFSET не может быть автоматически решена путем распределения вычислений по нескольким вычислениям (как вы пропускаете смещение параллельно, в любом случае?) Или переходом на NoSQL.Все решения (включая NoSQL) сводятся к моделированию OFFSET на основе dateline (в основном говорят dateline > Y LIMIT 100 вместо LIMIT Z, 100, где Y - дата элемента со смещением Z).Это работает и устраняет любые проблемы с производительностью, связанные со смещением, но предотвращает прямой переход на страницу 100 из 200.

4 голосов
/ 25 июня 2012

Есть часть вопроса, которая связана с опцией NoSQL или MySQL.На самом деле это одна фундаментальная вещь, скрытая здесь.Язык SQL легко написать для человека и немного сложнее для компьютера.В больших базах данных я бы порекомендовал избегать SQL-бэкэнда, так как для этого требуется дополнительный синтаксический анализ команд.Я провел обширный бенчмаркинг, и есть случаи, когда парсер SQL - самая медленная точка.Вы ничего не можете с этим поделать.Хорошо, вы можете использовать предварительно проанализированные операторы и получить к ним доступ.

Кстати, это не широко известно, но MySQL выросла из базы данных NoSQL.Компанией, в которой работали авторы MySQL Дэвид и Монти, была компания по хранению данных, и им часто приходилось писать нестандартные решения для необычных задач.Это привело к большому стеку библиотек homebrew C, которые использовались для ручного написания функций базы данных, когда Oracle и другие работали плохо.SQL был добавлен в этот почти 20-летний зоопарк в 1996 году для забавы.Что произошло после того, как вы узнали.

На самом деле вы можете избежать накладных расходов SQL с MySQL.Но обычно синтаксический анализ SQL - не самая медленная часть, а просто полезно знать.Чтобы проверить издержки парсера, вы можете просто сделать бенчмарк для "SELECT 1", например;).

2 голосов
/ 12 декабря 2010

Вам не следует пытаться приспособить архитектуру базы данных к оборудованию, которое вы планируете купить, а вместо этого планируйте приобретать оборудование, соответствующее вашей архитектуре базы данных.

Как только у вас будет достаточно ОЗУ для хранения рабочего набора индексов в памяти, все ваши запросы, которые могут использовать индексы, будут быстрыми.Убедитесь, что ваш ключевой буфер установлен достаточно большим для хранения индексов.

Поэтому, если 12 ГБ недостаточно, не используйте 10 серверов с 12 ГБ ОЗУ, используйте меньше с 32 ГБ или 64 ГБ ОЗУ.

0 голосов
/ 12 декабря 2010

Индексы являются обязательными, но не забудьте выбрать правильный тип индекса: BTREE больше подходит при использовании запросов с "<" или ">" в ваших предложениях WHERE, тогда как HASH больше подходит, когда у вас много различных значений в один столбец, и вы используете "=" или "<=>" в предложении WHERE.

Дальнейшее чтение http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html

...