Проблемы производительности MySQL с большими объемами данных - PullRequest
8 голосов
/ 16 сентября 2011

У меня есть программный проект, над которым я работаю на работе, который сводит меня с ума. Вот наша проблема: у нас есть ряд данных контактов, которые должны регистрироваться каждую секунду. Он должен включать время, пеленг (массив из 360-1080 байт), диапазон и несколько других полей. Нашей системе также требуется возможность хранить эти данные до 30 дней. На практике может быть до 100 различных контактов, поэтому максимум может быть от 150 000 000 до примерно 1 000 000 000 различных точек за 30 дней.

Я пытаюсь придумать лучший способ хранения всех этих данных и последующего извлечения. Моей первой мыслью было использовать некоторые СУБД, такие как MySQL. Будучи программистом на C / C ++, у меня очень мало опыта работы с MySQL с такими большими наборами данных. Я баловался этим с небольшими наборами данных, но не настолько большими. Я создал схему ниже для двух таблиц, в которых будут храниться некоторые данные:

CREATE TABLE IF NOT EXISTS `HEADER_TABLE` (
  `header_id` tinyint(3) unsigned NOT NULL auto_increment,
  `sensor` varchar(10) NOT NULL,
  `bytes` smallint(5) unsigned NOT NULL,
  PRIMARY KEY  (`header_id`),
  UNIQUE KEY `header_id_UNIQUE` (`header_id`),
  UNIQUE KEY `sensor_UNIQUE` (`sensor`)
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `RAW_DATA_TABLE` (
  `internal_id` bigint(20) NOT NULL auto_increment,
  `time_sec` bigint(20) unsigned NOT NULL,
  `time_nsec` bigint(20) unsigned NOT NULL,
  `transverse` bit(1) NOT NULL default b'0',
  `data` varbinary(1080) NOT NULL,
  PRIMARY KEY  (`internal_id`,`time_sec`,`time_nsec`),
  UNIQUE KEY `internal_id_UNIQUE` (`internal_id`),
  KEY `time` (`time_sec`)
  KEY `internal_id` (`internal_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `rel_RASTER_TABLE` (
  `internal_id` bigint(20) NOT NULL auto_increment,
  `raster_id` int(10) unsigned NOT NULL,
  `time_sec` bigint(20) unsigned NOT NULL,
  `time_nsec` bigint(20) unsigned NOT NULL,
  `header_id` tinyint(3) unsigned NOT NULL,
  `data_id` bigint(20) unsigned NOT NULL,
  PRIMARY KEY  (`internal_id`, `raster_id`,`time_sec`,`time_nsec`),
  KEY `raster_id` (`raster_id`),
  KEY `time` (`time_sec`),
  KEY `data` (`data_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

Таблица заголовков содержит только 10 строк и является статической. Он просто сообщает, с какого датчика поступили необработанные данные, и количество байтов, выводимых датчиком этого типа. RAW_DATA_TABLE, по сути, хранит необработанные несущие данные (массив из 360-1080 байтов, он представляет до трех выборок на градус). Rel_RASTER_TABLE содержит метаданные для RAW_DATA_TABLE, может быть несколько контактов, которые ссылаются на одну и ту же строку необработанных данных. data_id, найденное в rel_RASTER_TABLE, указывает на internal_id некоторой строки в RAW_DATA_TABLE, я сделал это, чтобы уменьшить количество необходимых записей.

Очевидно, как вы, вероятно, можете сказать, у меня проблемы с производительностью при чтении и удалении из этой базы данных. Оператор нашего программного обеспечения может видеть данные в реальном времени по мере их поступления, а также переходить в режим реконструкции и перекрывать диапазон данных за прошлую, например, прошедшую неделю. Наш внутренний сервер журналирования захватывает строки истории и отправляет их на дисплей через интерфейс CORBA. Пока все это происходит, у меня есть рабочий поток, который одновременно удаляет 1000 строк для данных, превышающих 30 дней. Это происходит в случае, если сеанс длится более 30 дней, что может произойти.

Система, которую мы внедрили в настоящее время, хорошо работает для небольших наборов данных, но не для больших наборов. Наши операторы выбора и удаления могут занять до 2 минут, чтобы вернуть результаты. Это полностью убивает производительность нашего потока в реальном времени. Я подозреваю, что мы не проектируем наши схемы правильно, выбираем неправильные ключи, неправильно оптимизируем наши запросы SQL, или некоторые подмножества каждого. Наши записи не будут затронуты, если другие операции не займут слишком много времени.

Вот пример SQL-запроса, который мы используем для получения данных истории:

SELECT 
  rel_RASTER_TABLE.time_sec, 
  rel_RASTER_TABLE.time_nsec, 
  RAW_DATA_TABLE.transverse, 
  HEADER_TABLE.bytes, 
  RAW_DATA_TABLE.data 
FROM 
  RASTER_DB.HEADER_TABLE, 
  RASTER_DB.RAW_DATA_TABLE, 
  RASTER_DB.rel_RASTER_TABLE 
WHERE 
  rel_RASTER_TABLE.raster_id = 2952704 AND 
  rel_RASTER_TABLE.time_sec >= 1315849228 AND 
  rel_RASTER_TABLE.time_sec <= 1315935628 AND 
  rel_RASTER_TABLE.data_id = RAW_DATA_TABLE.internal_id AND 
  rel_RASTER_TABLE.header_id = HEADER_TABLE.header_id;

Я заранее прошу прощения за этот длинный вопрос, но я использовал другие ресурсы, и это мое последнее средство. Я полагаю, что постараюсь быть настолько описательным, насколько это возможно. Ребята, вы видите, каким образом я могу улучшить наш дизайн с первого взгляда? Или, в любом случае, мы можем оптимизировать наши операторы выбора и удаления для таких больших наборов данных? В настоящее время мы используем RHEL в качестве ОС и, к сожалению, не можем изменить конфигурацию нашего оборудования на сервере (4 ГБ ОЗУ, Quad Core). Мы используем C / C ++ и MySQL API. ЛЮБЫЕ улучшения скорости были бы ОЧЕНЬ выгодны. Если вам нужно, чтобы я что-то прояснил, пожалуйста, дайте мне знать. Спасибо!

РЕДАКТИРОВАТЬ: Кстати, если вы не можете предоставить конкретную помощь, может быть, вы можете связать меня с некоторыми отличными учебниками, с которыми вы столкнулись, для оптимизации запросов SQL, проектирования схем или настройки MySQL?

Ответы [ 5 ]

4 голосов
/ 16 сентября 2011

Первое, что вы можете попробовать, - это нормализовать данные. Для набора данных такого размера выполнение объединения, даже если у вас есть индексы, потребует очень интенсивных вычислений. Превратите эти три стола в 1 стол. Конечно, будут дубликаты данных, но без объединений будет гораздо проще работать. Во-вторых, посмотрите, сможете ли вы получить машину с достаточным объемом памяти для размещения всей таблицы в памяти. Это не стоит много (1000 долларов или меньше) для машины с 24 ГБ ОЗУ. Я не уверен, будет ли это содержать весь ваш набор данных, но это также поможет получить SSD. Для всего, что не хранится в памяти, SSD должен помочь вам получить к нему доступ с высокой скоростью. И в-третьих, обратите внимание на другие технологии хранения данных, такие как BigTable , которые предназначены для работы с очень большими наборами данных.

2 голосов
/ 16 сентября 2011

Я бы сказал, что разбиение является абсолютной необходимостью в таком случае:

  • большой объем данных
  • новые данные, поступающие непрерывно
  • неявные: старыеданные удаляются непрерывно.

Проверьте это для mySQL .

Глядя на ваш выбор STMT (который фильтрует по времени), я скажу, что раздел настолбец времени.

Конечно, вы можете добавить несколько индексов, основанных на частых запросах, которые вы хотите использовать.

- edit -

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

- редактировать конец -

Быстрое создание разделов, если вы новичок в этом.

  • Обычно одна таблица преобразуется в один файл данных.Секционированная таблица преобразуется в один файл на раздел.
  • Преимущества
    • вставки выполняются быстрее, поскольку физически они вставляются в меньший файл (раздел).
    • удаление большого количества строкобычно переводится как удаление раздела (гораздо намного дешевле, чем 'delete from xxx, где time> 100 и time <200'); </li>
    • запросов с предложением where для ключа, по которому таблица секционируется,намного намного быстрее.
    • Построение индекса происходит быстрее.
1 голос
/ 16 сентября 2011

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

На самом деле структура немного грязная - если internal_id - это значение с автоинкрементом, то оно уникально - зачем добавлять другие вещи в первичный ключ? Похоже, что более разумная структура для rel_RASTER_TABLE была бы:

PRIMARY KEY  (`internal_id`),
KEY (`raster_id`,`time_sec`,`time_nsec`),

А что касается RAW_DATA_TABLE, то должно быть ослепительно очевидно, что его индексы далеки от оптимальных. И, вероятно, должно быть:

PRIMARY KEY  (`internal_id`,`time_sec`,`time_nsec`),
KEY `time` (`time_sec`, `time_nsec`)

Обратите внимание, что удаление избыточных индексов ускорит вставку / обновление. Захват медленных запросов должен помочь - и научиться использовать объяснение, чтобы увидеть, какие индексы являются избыточными / необходимыми.

Вы также можете повысить производительность, настроив экземпляр mysql - особенно увеличивая буферы сортировки и объединения - попробуйте запустить mysqltuner

1 голос
/ 16 сентября 2011

У меня нет большого опыта работы с MySQL, но вот некоторые априорные мысли, которые приходят на ум.

Ваш выбор в хранимой процедуре?

Предикат выбора обычновыполняется поиск в том порядке, в котором они запрашиваются. Если данные на диске переупорядочены в соответствии с первичным ключом, тогда сначала нужно выполнить идентификатор растра.Вы будете платить стоимость переупорядочения на каждой вкладке, хотя.Если данные хранятся в временном порядке на диске, вы, вероятно, захотите выполнить поиск по time_sec до raster_id.

WHERE 
  rel_RASTER_TABLE.raster_id = 2952704 AND 
  rel_RASTER_TABLE.time_sec >= 1315849228 AND 
  rel_RASTER_TABLE.time_sec <= 1315935628 AND 
  rel_RASTER_TABLE.data_id = RAW_DATA_TABLE.internal_id AND 
  rel_RASTER_TABLE.header_id = HEADER_TABLE.header_id;

Ваши индексы не соответствуют предикатам поиска.

Как правило, он создает индексы на основе ключей.

  PRIMARY KEY  (`internal_id`, `raster_id`,`time_sec`,`time_nsec`),
  KEY `raster_id` (`raster_id`),
  KEY `time` (`time_sec`),
  KEY `data` (`data_id`)

Возможно, он не использует первичный индекс, поскольку вы не используете internal_id.Возможно, вы захотите установить internal_id в качестве первичного ключа и создать отдельный индекс на основе ваших параметров поиска.По крайней мере, в raster_id и time_sec.

Соединения слишком свободные?

Это может быть моей неопытностью в MySQL, но я ожидаю увидеть условия для объединений.Использует ли FROM здесь естественное соединение?Я не вижу никаких указанных внешних ключей, поэтому я не знаю, как они будут рационально объединять эти таблицы.

FROM 
  RASTER_DB.HEADER_TABLE, 
  RASTER_DB.RAW_DATA_TABLE, 
  RASTER_DB.rel_RASTER_TABLE 

Обычно при разработке чего-то подобного я работаю с меньшим набором и удаляю предикаты дляудостоверяется, что каждый шаг соответствует тому, что я ожидаю.Если вы случайно закинули широкую сеть заранее, а затем сузите ее, вы можете замаскировать некоторые недостатки.

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

0 голосов
/ 16 сентября 2011

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

Кстати, MySQL не обязательно является самой оптимизированной системой баз данных для того, что вы пытаетесь достичь ... Посмотрите на другие решения, такие как Oracle, Microsoft SQL, PostgreSQL и т. Д. Кроме того, производительность будет варьироваться в зависимости от сервера. б.

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