Ich suche Beratung bei der Gestaltung von Tabellen / Indizes für die folgende Situation:
Ich habe eine große Tabelle (Aktienkursverlaufsdaten, InnoDB, 35 Millionen Zeilen und wachsend) mit einem zusammengesetzten Primärschlüssel (assetid (int), date (date)). Zusätzlich zu den Preisinformationen habe ich 200 Doppelwerte, die jedem Datensatz entsprechen müssen.
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;
Ich habe die 200 Doppelspalten ursprünglich direkt in dieser Tabelle gespeichert, um das Aktualisieren und Abrufen zu vereinfachen. Dies hat einwandfrei funktioniert, da für diese Tabelle nur die Asset-ID und das Datum abgefragt wurden (diese sind religiös in jeder Abfrage für diese Tabelle enthalten) ), und die 200 Doppelspalten wurden nur gelesen. Meine Datenbankgröße betrug ca. 45 Gig
Jetzt muss ich jedoch in der Lage sein, diese Tabelle mit einer beliebigen Kombination dieser 200 Spalten (mit den Namen f1, f2, ... f200) abzufragen. Beispiel:
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
Ich hatte in der Vergangenheit noch nie mit dieser großen Datenmenge zu tun, und mein erster Instinkt war, dass für jede dieser 200 Spalten Indizes benötigt wurden, oder ich musste mit großen Tabellenscans usw. aufwarten. Für mich bedeutete dies, dass Ich brauchte eine Tabelle für jede der 200 Spalten mit Primärschlüssel, Wert und Index der Werte. Also ging ich mit.
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;
Ich füllte und indizierte alle 200 Tabellen. Ich habe die Haupttabelle mit allen 200 Spalten intakt gelassen, da sie regelmäßig nach Asset-ID und Datumsbereich abgefragt wird und alle 200 Spalten ausgewählt werden. Ich dachte mir, dass es am leistungsstärksten wäre, diese Spalten in der übergeordneten Tabelle (nicht indiziert) für Lesezwecke zu belassen und sie dann zusätzlich in ihren eigenen Tabellen indizieren zu lassen (zum Filtern von Joins). Ich lief erklärt auf die neue Form der Abfrage
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)
In der Tat wurde mein gewünschtes Ergebnis erreicht, erklären Sie mir, dass die gescannten Zeilen für diese Abfrage viel kleiner sind. Allerdings hatte ich einige unerwünschte Nebenwirkungen.
1) Meine Datenbank wurde von 45 Gig auf 110 Gig erweitert. Ich kann die Datenbank nicht mehr im RAM behalten. (Ich habe 256 GB RAM auf dem Weg jedoch)
2) Das nächtliche Einfügen neuer Daten muss jetzt 200-mal statt einmal erfolgen
3) Wartung / Defragmentierung der neuen 200 Tische dauert 200-mal länger als nur die 1-Tabelle. Es kann nicht in einer Nacht abgeschlossen werden.
4) Abfragen gegen die Tabellen f1, etc sind nicht unbedingt performant. beispielsweise:
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'
Die obige Abfrage zeigt zwar, dass sie <1000 Zeilen enthält, kann jedoch mehr als 30 Sekunden dauern. Ich gehe davon aus, dass die Indizes zu groß sind, um in den Speicher zu passen.
Da das viele schlechte Nachrichten waren, habe ich weiter gesucht und Partitionierung gefunden. Ich habe Partitionen auf dem Haupttisch implementiert, die alle 3 Monate nach dem Datum partitioniert wurden. Monthly schien mir Sinn zu machen, aber ich habe gelesen, dass die Leistung leidet, wenn man über 120 Partitionen hat. Die vierteljährliche Partitionierung wird mich für die nächsten 20 Jahre oder so unterkriegen. Jede Partition ist etwas kleiner als 2 Gig. Ich habe EXPLAIN-Partitionen ausgeführt und alles scheint ordnungsgemäß zu bereinigen. Unabhängig davon war die Partitionierung ein guter Schritt, zumindest zu Analyse- / Optimierungs- / Reparaturzwecken.
Ich habe viel Zeit mit diesem Artikel verbracht
http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html
Meine Tabelle ist derzeit mit noch vorhandenem Primärschlüssel partitioniert. In dem Artikel wird erwähnt, dass Primärschlüssel eine partitionierte Tabelle verlangsamen können. Wenn Sie jedoch einen Computer haben, der dies unterstützt, sind Primärschlüssel in der partitionierten Tabelle schneller. Da ich wusste, dass ich eine große Maschine auf dem Weg habe (256 G RAM), ließ ich die Schlüssel an.
so wie ich es sehe, sind hier meine Wahlen
Option 1
1) Entfernen Sie die zusätzlichen 200 Tabellen, und lassen Sie die Abfrage Tabellensuchen durchführen, um die Werte für f1, f2 usw. zu ermitteln. Nicht eindeutige Indizes können die Leistung einer ordnungsgemäß partitionierten Tabelle beeinträchtigen. Führen Sie eine Erklärung aus, bevor der Benutzer die Abfrage ausführt, und lehnen Sie sie ab, wenn die Anzahl der gescannten Zeilen einen von mir festgelegten Schwellenwert überschreitet. Rette mich vor dem Schmerz der riesigen Datenbank. Verdammt, es wird sowieso bald alles in Erinnerung bleiben.
Unterfrage:
klingt es, als hätte ich ein geeignetes Partitionsschema gewählt?
Option 2
Partitionieren Sie alle 200 Tabellen nach demselben 3-Monats-Schema. Genießen Sie die kleineren Zeilenprüfungen und lassen Sie die Benutzer größere Abfragen ausführen. Jetzt, da sie mindestens partitioniert sind, kann ich sie zu Wartungszwecken 1 Partition auf einmal verwalten. Verdammt, es wird sowieso bald alles in Erinnerung bleiben. Entwickeln Sie eine effiziente Methode, um sie jede Nacht zu aktualisieren.
Unterfrage:
Sehen Sie einen Grund, warum ich Primärschlüsselindizes für diese Tabellen f1, f2, f3, f4 ... vermeiden kann, da ich weiß, dass ich bei der Abfrage immer die Asset-ID und das Datum habe? scheint mir kontraintuitiv zu sein, aber ich bin nicht an Datensätze dieser Größe gewöhnt. das würde die Datenbank ein Haufen verkleinern, nehme ich an
Option 3
Löschen Sie die Spalten f1, f2, f3 in der Mastertabelle, um diesen Speicherplatz freizugeben. Mache 200 Joins, wenn ich 200 Features lesen muss, vielleicht wird es nicht so langsam sein, wie es sich anhört.
Option 4
Sie alle haben eine bessere Möglichkeit, dies zu strukturieren, als ich bisher gedacht habe.
* ANMERKUNG: Ich werde bald weitere 50-100 dieser doppelten Werte zu jedem Element hinzufügen, daher muss ich das Design so gestalten, dass ich weiß, dass es kommen wird.
Vielen Dank für jede Hilfe
Update Nr. 1 - 24.03.2013
Ich habe mich an die in den Kommentaren vorgeschlagene Idee gehalten und eine neue Tabelle mit folgendem Aufbau erstellt:
create table 'features'{
assetid int,
date date,
feature varchar(4),
value double
}
Ich habe den Tisch in 3-Monats-Intervallen aufgeteilt.
Ich habe die früheren 200 Tische weggeblasen, so dass meine Datenbank wieder auf 45 Gig war, und habe angefangen, diese neue Tabelle zu füllen. Eineinhalb Tage später war es vollbracht, und meine Datenbank liegt jetzt bei molligen 220 Gigs!
Es erlaubt die Möglichkeit, diese 200 Werte aus der Master-Tabelle zu entfernen, da ich sie von einem Join erhalten kann, aber das würde mir wirklich nur 25 Gigs oder so zurückgeben
Ich bat es, einen Primärschlüssel für AssetID, Datum, Funktion und einen Index für den Wert zu erstellen, und nach 9 Stunden Tuckern hatte es wirklich keine Beule hinterlassen und schien einzufrieren, sodass ich diesen Teil abgeschafft habe.
Ich habe ein paar Partitionen neu erstellt, aber es schien nicht viel Platz zurückzugewinnen.
Diese Lösung scheint also wahrscheinlich nicht ideal zu sein. Nehmen Zeilen deutlich mehr Platz in Anspruch als Spalten, frage ich mich. Könnte dies der Grund sein, warum diese Lösung so viel mehr Platz in Anspruch nimmt?
Ich bin auf diesen Artikel gestoßen:
http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows
Es gab mir eine Idee. Es sagt:
Zuerst dachte ich über eine RANGE-Partitionierung nach Datum nach, und während ich das Datum in meinen Abfragen verwende, ist es sehr häufig, dass eine Abfrage einen sehr großen Datumsbereich hat, was bedeutet, dass sie leicht alle Partitionen abdecken kann.
Jetzt teile ich den Bereich auch nach Datum auf, erlaube aber auch die Suche nach einem großen Datumsbereich, was die Effektivität meiner Partitionierung verringert. Ich werde immer einen Datumsbereich haben, wenn ich suche, aber ich werde auch immer eine Liste von assetids haben. Vielleicht sollte meine Lösung darin bestehen, nach Asset-ID und Datum zu partitionieren, wobei ich die normalerweise gesuchten Asset-ID-Bereiche identifiziere (die ich mir ausdenken kann, es gibt Standardlisten, S & P 500, Russell 2000 usw.). Auf diese Weise würde ich fast nie den gesamten Datensatz betrachten.
Andererseits bin ich sowieso primär auf assetid und date fixiert, also würde das vielleicht nicht viel helfen.
Über weitere Gedanken / Kommentare würde ich mich freuen.
(value_name varchar(20), value double)
Lage wäre , zu speichern alles ( zuvalue_name
seinf1
,f2
...)