Je recherche des conseils sur la conception de table / index pour la situation suivante:
J'ai une grande table (données d'historique des cours boursiers, InnoDB, 35 millions de lignes et en croissance) avec une clé primaire composée (assetid (int), date (date)). en plus des informations de prix, j'ai 200 valeurs doubles qui doivent correspondre à chaque enregistrement.
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;
J'ai initialement stocké les 200 doubles colonnes directement dans cette table pour faciliter la mise à jour et la récupération, et cela fonctionnait bien, car la seule requête effectuée sur cette table était par l'actif et la date (elles sont religieusement incluses dans toute requête contre cette table). ), et les 200 doubles colonnes ont seulement été lues. La taille de ma base de données était d'environ 45 Gig
Cependant, j'ai maintenant l'exigence où je dois pouvoir interroger cette table par n'importe quelle combinaison de ces 200 colonnes (nommées f1, f2, ... f200), par exemple:
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
je n'ai jamais eu à traiter une telle quantité de données auparavant, donc mon premier réflexe était que des index étaient nécessaires sur chacune de ces 200 colonnes, ou je finirais avec de grandes analyses de table, etc. Pour moi, cela signifiait que j'avais besoin d'une table pour chacune des 200 colonnes avec la clé primaire, la valeur et l'indexation des valeurs. Je suis donc allé avec ça.
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;
j'ai rempli et indexé les 200 tables. J'ai laissé le tableau principal intact avec les 200 colonnes, car il est régulièrement interrogé sur la plage d'actifs et de dates et les 200 colonnes sont sélectionnées. J'ai pensé que laisser ces colonnes dans la table parent (non indexées) à des fins de lecture, puis les avoir indexées dans leurs propres tables (pour le filtrage des jointures) serait plus performant. J'ai couru explique la nouvelle forme de la requête
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)
En effet, mon résultat souhaité a été atteint, explique-moi montre que les lignes numérisées sont beaucoup plus petites pour cette requête. Cependant, je me suis retrouvé avec des effets secondaires indésirables.
1) ma base de données est passée de 45 Gig à 110 Gig. Je ne peux plus garder la base de données en RAM. (J'ai cependant 256 Go de RAM en route)
2) Les insertions nocturnes de nouvelles données doivent maintenant être effectuées 200 fois au lieu d'une fois
3) la maintenance / défragmentation des 200 nouvelles tables prend 200 fois plus de temps que la 1 seule table. Il ne peut pas être terminé en une nuit.
4) les requêtes contre les tables f1, etc. ne sont pas nécessairement performantes. par exemple:
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'
la requête ci-dessus, bien qu'expliquer montre qu'elle ressemble à <1000 lignes, peut prendre plus de 30 secondes. Je suppose que c'est parce que les index sont trop grands pour tenir en mémoire.
Comme c'était beaucoup de mauvaises nouvelles, j'ai regardé plus loin et j'ai trouvé le partitionnement. J'ai implémenté des partitions sur la table principale, partitionnées à date tous les 3 mois. Le mensuel semblait avoir du sens pour moi, mais j'ai lu qu'une fois que vous obtenez plus de 120 partitions, les performances en souffrent. le partitionnement trimestriel me laissera en dessous pour les 20 prochaines années. chaque partition est un peu moins de 2 Gig. J'ai couru expliquer les partitions et tout semble se tailler correctement, donc peu importe que je pense que le partitionnement était une bonne étape, à tout le moins à des fins d'analyse / d'optimisation / de réparation.
J'ai passé beaucoup de temps avec cet article
http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html
ma table est actuellement partitionnée avec la clé primaire toujours dessus. L'article mentionne que les clés primaires peuvent ralentir une table partitionnée, mais si vous avez une machine qui peut la gérer, les clés primaires de la table partitionnée seront plus rapides. Sachant que j'ai une grosse machine en route (256 G de RAM), j'ai laissé les clés allumées.
donc comme je le vois, voici mes options
Option 1
1) supprimez les 200 tables supplémentaires et laissez la requête effectuer des analyses de table pour trouver les valeurs f1, f2, etc. les index non uniques peuvent en fait nuire aux performances sur une table correctement partitionnée. exécutez une explication avant que l'utilisateur n'exécute la requête et refusez-les si le nombre de lignes analysées dépasse un certain seuil que je définis. me sauver la douleur de la base de données géante. Heck, tout sera bientôt en mémoire de toute façon.
sous-question:
cela ressemble-t-il à avoir choisi un schéma de partition approprié?
Option 2
Partitionnez les 200 tables en utilisant le même schéma de 3 mois. profiter des analyses de lignes plus petites et permettre aux utilisateurs d'exécuter des requêtes plus volumineuses. maintenant qu'ils sont partitionnés au moins, je peux les gérer 1 partition à la fois à des fins de maintenance. Heck, tout sera bientôt en mémoire de toute façon. Développez un moyen efficace de les mettre à jour tous les soirs.
sous-question:
Voyez-vous une raison pour laquelle je peux éviter les index de clé primaire sur ces tables f1, f2, f3, f4 ..., sachant que j'ai toujours un identifiant et une date lors de la requête? me semble contre-intuitif mais je ne suis pas habitué aux ensembles de données de cette taille. cela réduirait la base de données un tas je suppose
Option 3
Supprimez les colonnes f1, f2, f3 dans la table principale pour récupérer cet espace. faire 200 jointures si j'ai besoin de lire 200 fonctionnalités, peut-être que ce ne sera pas aussi lent que cela puisse paraître.
Option 4
Vous avez tous une meilleure façon de structurer cela que je ne le pensais jusqu'à présent.
* REMARQUE: j'ajouterai bientôt 50 à 100 de ces valeurs doubles à chaque élément, je dois donc concevoir en sachant que cela arrive.
Merci pour toute aide
Mise à jour # 1 - 24/03/2013
Je suis allé avec l'idée suggérée dans les commentaires ci-dessous et j'ai créé un nouveau tableau avec la configuration suivante:
create table 'features'{
assetid int,
date date,
feature varchar(4),
value double
}
J'ai partitionné la table à 3 mois d'intervalle.
J'ai fait sauter les 200 tables précédentes afin que ma base de données soit redescendue à 45 Gig et j'ai commencé à remplir cette nouvelle table. Un jour et demi plus tard, il s'est terminé, et ma base de données se trouve maintenant à 220 concerts joufflus !
Cela permet de supprimer ces 200 valeurs de la table principale, car je peux les obtenir à partir d'une jointure, mais cela ne me redonnerait vraiment que 25 Gigs ou alors peut-être
Je lui ai demandé de créer une clé primaire sur l'actif, la date, la fonctionnalité et un index sur la valeur, et après 9 heures de calage, cela n'avait vraiment pas fait de bosses et semblait geler, alors j'ai tué cette partie.
J'ai reconstruit quelques partitions mais cela ne semble pas récupérer beaucoup / aucun espace.
Cette solution semble donc ne pas être idéale. Les lignes occupent-elles beaucoup plus d'espace que les colonnes, je me demande, cela pourrait-il être la raison pour laquelle cette solution a pris beaucoup plus d'espace?
Je suis tombé sur cet article:
http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows
ça m'a donné une idée. Ça dit:
Au début, j'ai pensé au partitionnement RANGE par date, et pendant que j'utilise la date dans mes requêtes, il est très courant qu'une requête ait une très grande plage de dates, ce qui signifie qu'elle pourrait facilement s'étendre sur toutes les partitions.
Maintenant, je partitionne également la plage par date, mais je vais également autoriser les recherches par plage de dates étendue, ce qui diminuera l'efficacité de mon partitionnement. J'aurai toujours une plage de dates lorsque je recherche, mais j'aurai également toujours une liste d'actifs. Peut-être que ma solution devrait être de partitionner par identifiant d'actif et par date, où j'identifie les plages d'actifs généralement recherchées (que je peux trouver, il existe des listes standard, S&P 500, Russell 2000, etc.). De cette façon, je ne regarderais presque jamais l'ensemble des données.
Là encore, je suis principalement sur Assetid et date de toute façon, alors peut-être que cela n'aiderait pas beaucoup.
Toute autre réflexion / commentaire serait apprécié.
(value_name varchar(20), value double)
serait en mesure de tout magasin (value_name
êtref1
,f2
...)