Sto cercando consigli sulla progettazione di tabelle / indici per la seguente situazione:
Ho una tabella di grandi dimensioni (dati cronologici sui prezzi delle azioni, InnoDB, 35 milioni di righe e in crescita) con una chiave primaria composta (assetid (int), data (data)). oltre alle informazioni sui prezzi, ho 200 valori doppi che devono corrispondere a ciascun record.
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;
inizialmente memorizzavo le 200 doppie colonne direttamente in questa tabella per facilitare l'aggiornamento e il recupero, e questo aveva funzionato bene, poiché l'unica query fatta su questa tabella era dall'assetid e dalla data (questi sono religiosamente inclusi in qualsiasi query contro questa tabella ) e le 200 doppie colonne sono state solo lette. La dimensione del mio database era di circa 45 Gig
Tuttavia, ora ho il requisito in cui devo essere in grado di interrogare questa tabella con qualsiasi combinazione di queste 200 colonne (denominate f1, f2, ... f200), ad esempio:
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
storicamente non ho mai avuto a che fare con una così grande quantità di dati prima, quindi il mio primo istinto era che erano necessari indici su ciascuna di queste 200 colonne, o avrei finito con scansioni di tabelle di grandi dimensioni, ecc. Per me questo significava che avevo bisogno di una tabella per ciascuna delle 200 colonne con chiave primaria, valore e indicizzazione dei valori. Quindi ci sono andato.
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;
ho riempito e indicizzato tutte e 200 le tabelle. Ho lasciato intatta la tabella principale con tutte le 200 colonne, poiché regolarmente viene interrogata su assetid e intervallo di date e tutte le 200 colonne sono selezionate. Ho pensato che lasciare quelle colonne nella tabella genitore (non indicizzato) per scopi di lettura, e poi averle indicizzate nelle loro stesse tabelle (per il filtro join) sarebbe stato più efficace. Ho eseguito spiegazioni sul nuovo modulo della query
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 effetti il mio risultato desiderato è stato raggiunto, spiega mi mostra che le righe scansionate sono molto più piccole per questa query. Tuttavia ho finito con alcuni effetti collaterali indesiderati.
1) il mio database è passato da 45 Gig a 110 Gig. Non riesco più a mantenere il db nella RAM. (Ho 256Gig di RAM sulla strada comunque)
2) gli inserti notturni di nuovi dati ora devono essere eseguiti 200 volte anziché una volta
3) la manutenzione / deframmentazione delle nuove 200 tabelle richiede 200 volte di più rispetto alla sola 1 tabella. Non può essere completato in una notte.
4) le query contro le tabelle f1, ecc. Non sono necessariamente performanti. per esempio:
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 query sopra, mentre spieghi mostra che sembra <1000 righe, il completamento può richiedere più di 30 secondi. Presumo che ciò sia dovuto al fatto che gli indici sono troppo grandi per adattarsi alla memoria.
Dato che erano molte cattive notizie, ho guardato oltre e ho trovato il partizionamento. Ho implementato le partizioni nella tabella principale, partizionata alla data ogni 3 mesi. Il mensile mi è sembrato sensato, ma ho letto che una volta ottenute più di 120 partizioni, la performance ne risente. il partizionamento trimestrale mi lascerà sotto per i prossimi 20 anni circa. ogni partizione è leggermente inferiore a 2 Gig. ho corso a spiegare le partizioni e tutto sembra potare correttamente, quindi indipendentemente dal fatto che ritengo che il partizionamento sia stato un buon passo, almeno per scopi di analisi / ottimizzazione / riparazione.
Ho trascorso molto tempo con questo articolo
http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html
la mia tabella attualmente è partizionata con la chiave primaria ancora su di essa. L'articolo menziona che le chiavi primarie possono rallentare una tabella partizionata, ma se si dispone di una macchina in grado di gestirla, le chiavi primarie sulla tabella partizionata saranno più veloci. Sapendo che ho una grande macchina in arrivo (256 G RAM), ho lasciato le chiavi accese.
così come la vedo io, ecco le mie opzioni
opzione 1
1) rimuovere le 200 tabelle extra e lasciare che la query esegua le scansioni delle tabelle per trovare i valori f1, f2 ecc. indici non univoci possono effettivamente compromettere le prestazioni su una tabella adeguatamente partizionata. eseguire una spiegazione prima che l'utente esegua la query e negarli se il numero di righe analizzate supera una certa soglia definita. salvami il dolore del gigantesco database. Cavolo, presto sarà presto tutto in memoria.
sub-domanda:
sembra che abbia scelto uno schema di partizione appropriato?
opzione 2
Partiziona tutte le 200 tabelle usando lo stesso schema di 3 mesi. goditi le scansioni delle righe più piccole e consenti agli utenti di eseguire query più grandi. ora che sono partizionate almeno posso gestirle 1 partizione alla volta per scopi di manutenzione. Cavolo, presto sarà presto tutto in memoria. Sviluppa un modo efficiente per aggiornarli di notte.
sub-domanda:
Vedi un motivo per cui potrei evitare gli indici delle chiavi primarie su queste tabelle f1, f2, f3, f4 ..., sapendo che ho sempre assetid e data durante le query? mi sembra poco intuitivo ma non sono abituato a set di dati di queste dimensioni. questo ridurrebbe un po 'il database presumo
Opzione 3
Rilasciare le colonne f1, f2, f3 nella tabella principale per recuperare quello spazio. faccio 200 join se ho bisogno di leggere 200 funzionalità, forse non sarà lento come sembra.
Opzione 4
Tutti voi avete un modo migliore di strutturarlo di quanto non abbia pensato finora.
* NOTA: presto aggiungerò altri 50-100 di questi doppi valori a ciascun articolo, quindi devo progettare sapendo che sta arrivando.
Grazie per qualsiasi aiuto
Aggiornamento n. 1 - 24/03/2013
Sono andato con l'idea suggerita nei commenti che ho ricevuto di seguito e ho creato una nuova tabella con la seguente configurazione:
create table 'features'{
assetid int,
date date,
feature varchar(4),
value double
}
Ho partizionato la tabella a intervalli di 3 mesi.
Ho spazzato via le precedenti 200 tabelle in modo che il mio database fosse tornato a 45 Gig e ho iniziato a riempire questa nuova tabella. Un giorno e mezzo dopo, è stato completato e il mio database ora si trova a 220 concerti paffuti !
Permette la possibilità di rimuovere questi 200 valori dalla tabella principale, in quanto posso ottenerli da un join, ma ciò mi restituirebbe solo 25 concerti o forse così
Gli ho chiesto di creare una chiave primaria su assetid, data, funzionalità e un indice sul valore, e dopo 9 ore di chugging non ha davvero fatto un'ammaccatura e sembrava congelarsi, quindi ho ucciso quella parte.
Ho ricostruito un paio di partizioni ma non sembrava recuperare molto / alcuno spazio.
Quindi quella soluzione sembra che probabilmente non sarà l'ideale. Le righe occupano molto più spazio delle colonne, mi chiedo, potrebbe essere questo il motivo per cui questa soluzione ha richiesto molto più spazio?
Mi sono imbattuto in questo articolo:
http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows
mi ha dato un'idea. Dice:
Inizialmente, ho pensato al partizionamento RANGE per data e, mentre sto usando la data nelle mie query, è molto comune che una query abbia un intervallo di date molto ampio, e ciò significa che potrebbe facilmente estendersi su tutte le partizioni.
Ora sto anche partizionando l'intervallo per data, ma consentirò anche ricerche per ampio intervallo di date, il che ridurrà l'efficacia del mio partizionamento. Avrò sempre un intervallo di date quando cerco, ma avrò sempre anche un elenco di assetidi. Forse la mia soluzione dovrebbe essere quella di partizionare per assetid e data, in cui identifico intervalli di assetid tipicamente cercati (che posso trovare, ci sono elenchi standard, S&P 500, Russell 2000, ecc.). In questo modo non guarderei quasi mai l'intero set di dati.
D'altronde, sono principalmente a chiave su assetid e date comunque, quindi forse non sarebbe di grande aiuto.
Altri pensieri / commenti sarebbero apprezzati.
(value_name varchar(20), value double)
sarebbe in grado di memorizzare tutto (value_name
essendof1
,f2
, ...)