Szukam porady na temat projektowania tabeli / indeksu w następującej sytuacji:
Mam dużą tabelę (dane historii cen akcji, InnoDB, 35 milionów wierszy i rośnie) ze złożonym kluczem podstawowym (assetid (int), date (date)). oprócz informacji o cenach mam 200 podwójnych wartości, które muszą odpowiadać każdemu rekordowi.
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;
początkowo zapisałem 200 podwójnych kolumn bezpośrednio w tej tabeli, aby ułatwić aktualizację i wyszukiwanie, i działało to dobrze, ponieważ jedyne zapytania wykonane w tej tabeli dotyczyły atrybutu i daty (są one religijnie uwzględnione w każdym zapytaniu dotyczącym tej tabeli ), a 200 podwójnych kolumn zostało odczytanych. Rozmiar mojej bazy danych wynosił około 45 Gig
Jednak teraz mam wymaganie, w którym muszę mieć możliwość zapytania do tej tabeli za pomocą dowolnej kombinacji tych 200 kolumn (o nazwach f1, f2, ... f200), na przykład:
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
nigdy wcześniej nie miałem do czynienia z tak dużą ilością danych, więc moim pierwszym instynktem było to, że indeksy były potrzebne w każdej z tych 200 kolumn, albo skończyłbym się skanowaniem dużych tabel itp. Dla mnie oznaczało to, że Potrzebowałem tabeli dla każdej z 200 kolumn z kluczem podstawowym, wartością i indeksem wartości. Więc poszedłem z tym.
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;
wypełniłem i zindeksowałem wszystkie 200 tabel. Pozostawiłem nietkniętą główną tabelę ze wszystkimi 200 kolumnami, ponieważ regularnie jest ona sprawdzana w zakresie assetid i zakresu dat oraz wybierane są wszystkie 200 kolumn. Uznałem, że pozostawienie tych kolumn w tabeli nadrzędnej (nieindeksowane) do celów odczytu, a następnie dodatkowo indeksowanie ich we własnych tabelach (do filtrowania złączeń) byłoby najbardziej wydajne. Uruchomiłem wyjaśnienia na temat nowej formy zapytania
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)
Rzeczywiście mój pożądany wynik został osiągnięty, wyjaśnienie pokazuje mi, że zeskanowane wiersze są znacznie mniejsze dla tego zapytania. Jednak skończyło się z pewnymi niepożądanymi skutkami ubocznymi.
1) moja baza danych zmieniła się z 45 Gig na 110 Gig. Nie mogę dłużej przechowywać bazy danych w pamięci RAM. (Po drodze mam jednak 256 GB pamięci RAM)
2) wstawianie co noc nowych danych należy teraz wykonać 200 razy zamiast raz
3) konserwacja / defragmentacja nowych 200 stołów zajmuje 200 razy więcej niż tylko 1 stół. Nie można go ukończyć w ciągu nocy.
4) zapytania dotyczące tabel f1 itp. Niekoniecznie są wydajne. na przykład:
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'
powyższe zapytanie, chociaż wyjaśnia, że wygląda na <1000 wierszy, może potrwać ponad 30 sekund. Zakładam, że dzieje się tak, ponieważ indeksy są zbyt duże, aby zmieścić się w pamięci.
Ponieważ było wiele złych wiadomości, szukałem dalej i znalazłem podział. Zaimplementowałem partycje na głównym stole, partycjonowane co 3 miesiące. Wydawało mi się, że miesięcznik ma sens, ale przeczytałem, że po uzyskaniu ponad 120 partycji wydajność spada. dzielenie kwartalne pozostawi mnie pod tym przez następne 20 lat. każda partycja ma nieco mniej niż 2 gig. Uruchomiłem wyjaśnianie partycji i wszystko wydaje się prawidłowo przycinane, więc niezależnie od tego, uważam, że partycjonowanie było dobrym krokiem, przynajmniej do analizy / optymalizacji / naprawy.
Spędziłem dużo czasu z tym artykułem
http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html
moja tabela jest obecnie podzielona na partycje z kluczem podstawowym. W artykule wspomniano, że klucze podstawowe mogą spowolnić partycjonowaną tabelę, ale jeśli masz komputer, który może to obsłużyć, klucze podstawowe na partycjonowanej tabeli będą szybsze. Wiedząc, że mam po drodze dużą maszynę (256 G RAM), zostawiłem klucze włączone.
tak, jak widzę, oto moje opcje
opcja 1
1) usuń dodatkowe 200 tabel i pozwól zapytaniu wykonać skanowanie tabeli w celu znalezienia wartości f1, f2 itp. nieunikalne indeksy mogą w rzeczywistości obniżyć wydajność prawidłowo podzielonej na partycje tabeli. uruchom wyjaśnienie, zanim użytkownik uruchomi kwerendę i odrzuć je, jeśli liczba zeskanowanych wierszy przekroczy określony przeze mnie próg. ocalę sobie ból gigantycznej bazy danych. Cholera, i tak wszystko wkrótce zostanie w pamięci.
podpytanie:
czy to brzmi jak wybrałem odpowiedni schemat partycji?
Opcja 2
Podziel wszystkie 200 tabel według tego samego 3-miesięcznego schematu. ciesz się skanowaniem mniejszych wierszy i pozwól użytkownikom uruchamiać większe zapytania. teraz, gdy są one podzielone na partycje, mogę nimi zarządzać 1 partycją na raz w celach konserwacyjnych. Cholera, i tak wszystko wkrótce zostanie w pamięci. Opracuj skuteczny sposób aktualizowania ich co noc.
podpytanie:
Czy widzisz powód, dla którego mogę uniknąć indeksowania kluczy głównych w tabelach f1, f2, f3, f4 ..., wiedząc, że zawsze mam identyfikator zasobu i datę podczas zapytania? wydaje mi się to sprzeczne z intuicją, ale nie jestem przyzwyczajony do zestawów danych o tym rozmiarze. to zmniejszyłoby bazę danych o kilka, które zakładam
Opcja 3
Upuść kolumny f1, f2, f3 w tabeli głównej, aby odzyskać to miejsce. wykonaj 200 dołączeń, jeśli muszę przeczytać 200 funkcji, być może nie będzie to tak wolne, jak się wydaje.
Opcja 4
Wszyscy macie lepszy sposób na ustrukturyzowanie tego, niż do tej pory myślałem.
* UWAGA: Wkrótce dodam kolejne 50-100 tych podwójnych wartości do każdego elementu, więc muszę zaprojektować wiedząc, że nadchodzi.
Dzięki za wszelką pomoc
Aktualizacja nr 1 - 24.03.2013
Poszedłem z pomysłem zasugerowanym w komentarzach, które otrzymałem poniżej i stworzyłem jedną nową tabelę z następującą konfiguracją:
create table 'features'{
assetid int,
date date,
feature varchar(4),
value double
}
Podzieliłem tabelę na 3 miesiące.
Zdmuchnąłem wcześniejsze 200 tabel, więc moja baza danych została przywrócona do 45 Gig i zaczęłam wypełniać ten nowy stół. Półtora dnia później zakończyło się, a moja baza danych znajduje się teraz na pulchnym 220 koncertach!
Daje to możliwość usunięcia tych 200 wartości z tabeli głównej, ponieważ mogę je pobrać z jednego połączenia, ale tak naprawdę dałoby mi to tylko 25 koncertów
Poprosiłem go, aby utworzył główny klucz na assetid, date, feature i indeks wartości, a po 9 godzinach chasing naprawdę nie zrobił to żadnego wrażenia i wydawało się, że zamarł, więc zabiłem tę część.
Odbudowałem kilka partycji, ale nie wydawało się, aby zajmowały dużo miejsca.
Tak więc rozwiązanie wygląda na to, że prawdopodobnie nie będzie idealne. Zastanawiam się, czy wiersze zajmują znacznie więcej miejsca niż kolumny, czy właśnie dlatego to rozwiązanie zajęło o wiele więcej miejsca?
Natknąłem się na ten artykuł:
http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows
dało mi to pomysł. To mówi:
Na początku myślałem o partycjonowaniu RANGE według daty i chociaż używam daty w moich zapytaniach, bardzo często zapytanie ma bardzo duży zakres dat, co oznacza, że może z łatwością obejmować wszystkie partycje.
Teraz dzielę też zakresy według dat, ale pozwolę też na wyszukiwanie według dużego zakresu dat, co zmniejszy skuteczność mojego podziału. Zawsze będę mieć zakres dat podczas wyszukiwania, ale zawsze będę też mieć listę aktywów. Być może moim rozwiązaniem powinno być podzielenie według atrybutu i daty, gdzie identyfikuję najczęściej wyszukiwane zakresy identyfikatorów zasobów (które mogę wymyślić, istnieją standardowe listy, S&P 500, Russell 2000 itp.). W ten sposób prawie nigdy nie spojrzałbym na cały zestaw danych.
Z drugiej strony, i tak jestem przede wszystkim nastawiony na assetid i date, więc może to nie pomoże.
Wszelkie dodatkowe uwagi / komentarze będą mile widziane.
(value_name varchar(20), value double)
byłby w stanie przechowywać wszystkiego (value_name
istotaf1
,f2
...)