Я ищу совет по дизайну таблицы / индекса для следующей ситуации:
У меня есть большая таблица (данные истории цен акций, InnoDB, 35 миллионов строк и растет) с составным первичным ключом (сборка (int), дата (дата)). В дополнение к информации о ценах у меня есть 200 двойных значений, которые должны соответствовать каждой записи.
CREATE TABLE `mytable` (
`assetid` int(11) NOT NULL,
`date` date NOT NULL,
`close` double NOT NULL,
`f1` double DEFAULT NULL,
`f2` double DEFAULT NULL,
`f3` double DEFAULT NULL,
`f4` double DEFAULT NULL,
... skip a few …
`f200` double DEFAULT NULL,
PRIMARY KEY (`assetid`, `date`)) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE
latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0
PARTITION BY RANGE COLUMNS(`date`) PARTITIONS 51;
Первоначально я сохранил 200 двойных столбцов непосредственно в этой таблице для простоты обновления и поиска, и это работало нормально, так как единственные запросы к этой таблице выполнялись по ассемблеру и дате (они неукоснительно включены в любой запрос к этой таблице). ), и 200 двойных столбцов были только прочитаны. Размер моей базы данных был около 45 Гиг
Тем не менее, теперь у меня есть требование, где мне нужно иметь возможность запрашивать эту таблицу по любой комбинации из этих 200 столбцов (с именем f1, f2, ... f200), например:
select from mytable
where assetid in (1,2,3,4,5,6,7,....)
and date > '2010-1-1' and date < '2013-4-5'
and f1 > -0.23 and f1 < 0.9
and f117 > 0.012 and f117 < .877
etc,etc
Раньше мне раньше не приходилось иметь дело с таким большим количеством данных, так что мой первый инстинкт был в том, что для каждого из этих 200 столбцов требовались индексы, или я хотел бы получить результаты сканирования больших таблиц и т. д. Для меня это означало, что мне нужна таблица для каждого из 200 столбцов с первичным ключом, значением и индексом значений. Итак, я пошел с этим.
CREATE TABLE `f1` (
`assetid` int(11) NOT NULL DEFAULT '0',
`date` date NOT NULL DEFAULT '0000-00-00',
`value` double NOT NULL DEFAULT '0',
PRIMARY KEY (`assetid`, `date`),
INDEX `val` (`value`)
) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;
я заполнил и проиндексировал все 200 таблиц. Я оставил основную таблицу нетронутой со всеми 200 столбцами, так как она регулярно запрашивается в диапазоне дат и сборок, и все 200 столбцов выбраны. Я подумал, что оставить эти столбцы в родительской таблице (неиндексированные) для целей чтения, а затем дополнительно проиндексировать их в своих собственных таблицах (для фильтрации соединений) будет наиболее эффективным. Я побежал объясняет на новой форме запроса
select count(p.assetid) as total
from mytable p
inner join f1 f1 on f1.assetid = p.assetid and f1.date = p.date
inner join f2 f2 on f2.assetid = p.assetid and f2.date = p.date
where p.assetid in(1,2,3,4,5,6,7)
and p.date >= '2011-01-01' and p.date < '2013-03-14'
and(f1.value >= 0.96 and f1.value <= 0.97 and f2.value >= 0.96 and f2.value <= 0.97)
Действительно, мой желаемый результат был достигнут, объяснение показывает, что отсканированные строки для этого запроса намного меньше. Однако я столкнулся с некоторыми нежелательными побочными эффектами.
1) моя база данных выросла с 45 Гиг до 110 Гиг. Я больше не могу держать БД в оперативной памяти. (У меня есть 256 Гб оперативной памяти на пути, однако)
2) ночные вставки новых данных теперь нужно делать 200 раз, а не один раз
3) обслуживание / дефрагментация новых 200 таблиц занимает в 200 раз больше времени, чем только 1 таблица. Это не может быть завершено за ночь.
4) запросы к таблицам f1 и т. Д. Не обязательно являются производительными. например:
select min(value) from f1
where assetid in (1,2,3,4,5,6,7)
and date >= '2013-3-18' and date < '2013-3-19'
Приведенный выше запрос, хотя объяснение показывает, что он выглядит как <1000 строк, может занять более 30 секунд. Я предполагаю, что это потому, что индексы слишком велики, чтобы поместиться в памяти.
Так как это было много плохих новостей, я посмотрел дальше и нашел разделение. Я реализовал разделы на основной таблице, разделенные по дате каждые 3 месяца. Кажется, что ежемесячные имеют смысл для меня, но я прочитал, что как только вы получите более 120 разделов или около того, производительность страдает. Разделение ежеквартально оставит меня под этим на ближайшие 20 лет или около того. каждый раздел немного под 2 гига. Я выполнил объяснение разделов, и все, кажется, сокращается должным образом, так что независимо от того, что я чувствую, разделение было хорошим шагом, по крайней мере, для анализа / оптимизации / исправления.
Я провел много времени с этой статьей
http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html
моя таблица в настоящее время разделена с первичным ключом все еще на нем. В статье упоминается, что первичные ключи могут сделать многораздельную таблицу медленнее, но если у вас есть машина, которая может обрабатывать ее, первичные ключи в многораздельной таблице будут быстрее. Зная, что у меня в пути большая машина (256 G RAM), я оставил ключи включенными.
так что, как я вижу, вот мои варианты
Опция 1
1) удалите лишние 200 таблиц и позвольте запросу выполнить сканирование таблиц, чтобы найти значения f1, f2 и т. Д. неуникальные индексы могут фактически снизить производительность правильно разделенной таблицы. выполнить объяснение до того, как пользователь выполнит запрос, и отклонить его, если число проверенных строк превысит определенный мной порог. избавь себя от боли гигантской базы данных. Черт возьми, все это скоро останется в памяти.
суб-вопрос:
это звучит так, как будто я выбрал подходящую схему разделов?
Вариант 2
Разделите все 200 таблиц, используя одну и ту же трехмесячную схему. наслаждайтесь небольшими сканированиями строк и позволяйте пользователям выполнять большие запросы. теперь, когда они разделены по крайней мере, я могу управлять ими по 1 разделу за один раз в целях обслуживания. Черт возьми, все это скоро останется в памяти. Разработайте эффективный способ их обновления по ночам.
суб-вопрос:
Вы видите причину, по которой я могу избегать индексов первичного ключа в этих таблицах f1, f2, f3, f4 ..., зная, что у меня всегда есть ассемблер и дата при запросе? мне кажется противоречивым, но я не привык к наборам данных такого размера. что бы сжать базу данных кучу я предполагаю
Вариант 3
Удалите столбцы f1, f2, f3 в основной таблице, чтобы освободить это пространство. сделать 200 соединений, если мне нужно прочитать 200 функций, возможно, это будет не так медленно, как кажется.
Вариант 4
У всех вас есть лучший способ структурировать это, чем я думал до сих пор.
* ПРИМЕЧАНИЕ: я скоро добавлю еще 50-100 из этих двойных значений к каждому элементу, поэтому мне нужно проектировать, зная, что это произойдет.
Спасибо за любую помощь
Обновление № 1 - 24.03.2013
Я пошел с идеей, предложенной в комментариях, которые я получил ниже, и создал одну новую таблицу со следующей настройкой:
create table 'features'{
assetid int,
date date,
feature varchar(4),
value double
}
Я разделил таблицу с интервалом в 3 месяца.
Я отбросил более ранние 200 таблиц, так что моя база данных вернулась к 45 гигабайтам и начала заполнять эту новую таблицу. Полтора дня спустя, это закончено, и моя база данных теперь сидит на пухлых 220 Гигах!
Это позволяет удалить эти 200 значений из мастер-таблицы, так как я могу получить их за одно соединение, но это на самом деле вернет мне только 25 гигабайт или около того.
Я попросил его создать первичный ключ на ассемблете, дате, функции и индексе стоимости, и после 9 часов работы с ним он действительно не оставил следов и, казалось, завис, поэтому я убил эту часть.
Я перестроил пару разделов, но, похоже, они не занимали много места.
Так что это решение выглядит так, как будто оно не будет идеальным. Интересно, занимают ли строки значительно больше места, чем столбцы? Может быть, поэтому это решение заняло гораздо больше места?
Я наткнулся на эту статью:
http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows
это дало мне идею. Это говорит:
Сначала я думал о RANGE-разбиении по дате, и, хотя я использую дату в своих запросах, очень часто запрос имеет очень большой диапазон дат, и это означает, что он может легко охватывать все разделы.
Теперь я также делю диапазоны по датам, но также буду разрешать поиск по большому диапазону дат, что снизит эффективность моего разбиения. У меня всегда будет диапазон дат при поиске, однако у меня также всегда будет список сборок. Возможно, мое решение должно состоять в том, чтобы разделить по ассембле и дате, где я определяю типично искомые диапазоны ассемблера (которые я могу придумать, есть стандартные списки, S & P 500, Рассел 2000 и т. Д.). Таким образом, я почти никогда не смотрю на весь набор данных.
Опять же, я в любом случае пользуюсь первичным ключом на ассемблете и дате, так что, возможно, это не сильно поможет.
Любые другие мысли / комментарии будут оценены.
(value_name varchar(20), value double)
будет иметь возможность хранить все (value_name
будучиf1
,f2
...)