Używanie MySQL 5.6 z silnikiem pamięci InnoDB dla większości tabel. Wielkość puli buforów InnoDB wynosi 15 GB, a indeksy Innodb DB + wynoszą około 10 GB. Serwer ma 32 GB pamięci RAM i działa w systemie Cent OS 7 x64.
Mam jeden duży stół, który zawiera około 10 milionów + rekordów.
Otrzymuję zaktualizowany plik zrzutu ze zdalnego serwera co 24 godziny. Plik ma format csv. Nie mam kontroli nad tym formatem. Plik ma rozmiar ~ 750 MB. Próbowałem wstawiać dane do tabeli MyISAM wiersz po rzędzie i zajęło to 35 minut.
Muszę pobrać tylko 3 wartości na linię z 10-12 z pliku i zaktualizować go w bazie danych.
Jaki jest najlepszy sposób na osiągnięcie czegoś takiego?
Muszę to robić codziennie.
Obecnie Flow wygląda następująco:
- mysqli_begin_transaction
- Czytaj zrzut pliku linia po linii
- Zaktualizuj każdy rekord linia po linii.
- mysqli_commit
Powyższe operacje trwają około 30-40 minut, a przy tym trwają inne aktualizacje, które dają mi
Przekroczono limit czasu oczekiwania na zablokowanie; spróbuj ponownie uruchomić transakcję
Aktualizacja 1
ładowanie danych w nowej tabeli za pomocą LOAD DATA LOCAL INFILE
. W MyISAM zajęło to 38.93 sec
w InnoDB 7 min. 5,21 sek. Potem zrobiłem:
UPDATE table1 t1, table2 t2
SET
t1.field1 = t2.field1,
t1.field2 = t2.field2,
t1.field3 = t2.field3
WHERE t1.field10 = t2.field10
Query OK, 434914 rows affected (22 hours 14 min 47.55 sec)
Aktualizacja 2
ta sama aktualizacja z zapytaniem dołączenia
UPDATE table1 a JOIN table2 b
ON a.field1 = b.field1
SET
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4
(14 hours 56 min 46.85 sec)
Wyjaśnienia na podstawie pytań w komentarzach:
- Plik zaktualizuje około 6% wierszy w tabeli, ale czasem może to być nawet 25%.
- Są aktualizowane indeksy pól. Tabela zawiera 12 indeksów, a 8 indeksów zawiera pola aktualizacji.
- Aktualizacja nie jest konieczna w ramach jednej transakcji. Może to zająć trochę czasu, ale nie więcej niż 24 godziny. Chcę to zrobić w ciągu 1 godziny bez blokowania całej tabeli, ponieważ później muszę zaktualizować indeks sfinksa, który jest zależny od tej tabeli. Nie ma znaczenia, czy kroki trwają dłużej, o ile baza danych jest dostępna dla innych zadań.
- Mógłbym zmodyfikować format csv w kroku wstępnego przetwarzania. Liczy się tylko szybka aktualizacja i bez blokowania.
- Tabela 2 to MyISAM. Jest to nowo utworzona tabela z pliku csv przy użyciu pliku danych ładowania. Rozmiar pliku MYI wynosi 452 MB. Tabela 2 jest indeksowana w kolumnie field1.
- MYD tabeli MyISAM wynosi 663 MB.
Aktualizacja 3:
oto więcej szczegółów na temat obu tabel.
CREATE TABLE `content` (
`hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`og_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`files_count` smallint(5) unsigned NOT NULL DEFAULT '0',
`more_files` smallint(5) unsigned NOT NULL DEFAULT '0',
`files` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
`category` smallint(3) unsigned NOT NULL DEFAULT '600',
`size` bigint(19) unsigned NOT NULL DEFAULT '0',
`downloaders` int(11) NOT NULL DEFAULT '0',
`completed` int(11) NOT NULL DEFAULT '0',
`uploaders` int(11) NOT NULL DEFAULT '0',
`creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`upload_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`last_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`vote_up` int(11) unsigned NOT NULL DEFAULT '0',
`vote_down` int(11) unsigned NOT NULL DEFAULT '0',
`comments_count` int(11) NOT NULL DEFAULT '0',
`imdb` int(8) unsigned NOT NULL DEFAULT '0',
`video_sample` tinyint(1) NOT NULL DEFAULT '0',
`video_quality` tinyint(2) NOT NULL DEFAULT '0',
`audio_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
`subtitle_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
`verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
`uploader` int(11) unsigned NOT NULL DEFAULT '0',
`anonymous` tinyint(1) NOT NULL DEFAULT '0',
`enabled` tinyint(1) unsigned NOT NULL DEFAULT '0',
`tfile_size` int(11) unsigned NOT NULL DEFAULT '0',
`scrape_source` tinyint(1) unsigned NOT NULL DEFAULT '0',
`record_num` int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`record_num`),
UNIQUE KEY `hash` (`hash`),
KEY `uploaders` (`uploaders`),
KEY `tfile_size` (`tfile_size`),
KEY `enabled_category_upload_date_verified_` (`enabled`,`category`,`upload_date`,`verified`),
KEY `enabled_upload_date_verified_` (`enabled`,`upload_date`,`verified`),
KEY `enabled_category_verified_` (`enabled`,`category`,`verified`),
KEY `enabled_verified_` (`enabled`,`verified`),
KEY `enabled_uploader_` (`enabled`,`uploader`),
KEY `anonymous_uploader_` (`anonymous`,`uploader`),
KEY `enabled_uploaders_upload_date_` (`enabled`,`uploaders`,`upload_date`),
KEY `enabled_verified_category` (`enabled`,`verified`,`category`),
KEY `verified_enabled_category` (`verified`,`enabled`,`category`)
) ENGINE=InnoDB AUTO_INCREMENT=7551163 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=FIXED
CREATE TABLE `content_csv_dump_temp` (
`hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`category_id` int(11) unsigned NOT NULL DEFAULT '0',
`uploaders` int(11) unsigned NOT NULL DEFAULT '0',
`downloaders` int(11) unsigned NOT NULL DEFAULT '0',
`verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
a oto zapytanie o aktualizację, które aktualizuje content
tabelę przy użyciu danych zcontent_csv_dump_temp
UPDATE content a JOIN content_csv_dump_temp b
ON a.hash = b.hash
SET
a.uploaders = b.uploaders,
a.downloaders = b.downloaders,
a.verified = b.verified
aktualizacja 4:
wszystkie powyższe testy przeprowadzono na maszynie testowej., ale teraz zrobiłem te same testy na maszynie produkcyjnej, a zapytania są bardzo szybkie.
mysql> UPDATE content_test a JOIN content_csv_dump_temp b
-> ON a.hash = b.hash
-> SET
-> a.uploaders = b.uploaders,
-> a.downloaders = b.downloaders,
-> a.verified = b.verified;
Query OK, 2673528 rows affected (7 min 50.42 sec)
Rows matched: 7044818 Changed: 2673528 Warnings: 0
Przepraszam za mój błąd. Lepiej używać łączenia zamiast każdej aktualizacji rekordu. teraz próbuję ulepszyć mpre przy użyciu indeksu sugerowanego przez rick_james, zaktualizuje się po zakończeniu benchmarkingu.
INDEX(field2, field3, field4)
(w dowolnej kolejności)? Proszę nam pokazaćSHOW CREATE TABLE
.UPDATEs
. Powiedz nam dokładnie, jak wygląda prosta instrukcja do aktualizacji tabeli z danych csv. W takim razie możemy pomóc Ci w opracowaniu techniki spełniającej Twoje wymagania.update
i sprawdź zaktualizowane pytanie., DziękiOdpowiedzi:
W oparciu o moje doświadczenie wykorzystam LOAD DATA INFILE do zaimportowania twojego pliku CSV.
Przykład znalazłem w internetowym przykładzie ładowania danych . Przetestowałem ten przykład na moim pudełku i działałem dobrze
Przykładowa tabela
Przykładowy plik CSV
Instrukcja importu do uruchomienia z konsoli MySQL
Wynik
IGNORE po prostu ignoruje pierwszy wiersz, który jest nagłówkiem kolumny.
Po IGNORE określamy kolumny (pomijanie kolumny 2), które mają zostać zaimportowane, co odpowiada jednemu z kryteriów w pytaniu.
Oto kolejny przykład bezpośrednio z Oracle: LOAD DATA INFILE przykład
To powinno wystarczyć, aby zacząć.
źródło
W świetle wszystkich wymienionych rzeczy wygląda na to, że wąskim gardłem jest samo połączenie.
ASPEKT nr 1: Dołącz rozmiar bufora
Najprawdopodobniej twój rozmiar join_buffer_size jest prawdopodobnie zbyt niski.
Zgodnie z dokumentacją MySQL na temat tego, jak MySQL używa pamięci podręcznej buforu dołączania
W takim przypadku klucze bufora łączenia pozostaną w pamięci RAM.
Masz 10 milionów wierszy razy 4 bajty na każdy klucz. To około 40 milionów.
Spróbuj podnieść go w sesji do 42 milionów (trochę więcej niż 40 milionów)
Jeśli to załatwi sprawę, przejdź do dodawania tego
my.cnf
Ponowne uruchomienie mysqld nie jest wymagane w przypadku nowych połączeń. Po prostu biegnij
ASPEKT 2: Dołącz do operacji
Możesz manipulować stylem operacji łączenia, modyfikując optymalizator
Zgodnie z dokumentacją MySQL na temat blokowania połączeń w pętli zagnieżdżonej i złączeń dostępu do klucza wsadowego
Ta sama strona zaleca robienie tego:
ASPEKT 3: Zapisywanie aktualizacji na dysku (OPCJONALNIE)
Większość zapomina zwiększyć innodb_write_io_threads, aby szybciej zapisywać brudne strony z puli buforów.
Będziesz musiał ponownie uruchomić MySQL dla tej zmiany
SPRÓBUJ !!!
źródło
CREATE TABLE
który pasuje do CSVLOAD DATA
do tego stołuUPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
DROP TABLE csv_table;
Krok 3 będzie znacznie szybszy niż rząd po rzędzie, ale nadal zablokuje wszystkie wiersze w tabeli na niebanalny czas. Jeśli ten czas blokady jest ważniejszy niż czas trwania całego procesu, ...
Jeśli nic więcej nie pisze do stołu, to ...
CREATE TABLE
który pasuje do CSV; Indeksy nie tylko to, co jest potrzebne wJOIN
wUPDATE
. Jeśli jest wyjątkowy, zrób toPRIMARY KEY
.LOAD DATA
do tego stołureal_table
donew_table
(CREATE ... SELECT
)UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
RENAME TABLE real_table TO old, new_table TO real_table;
DROP TABLE csv_table, old;
Krok 3 jest szybszy niż aktualizacja, zwłaszcza jeśli niepotrzebne indeksy zostaną pominięte.
Krok 5 jest „natychmiastowy”.
źródło
pt-online-schema-digest
; zajmuje się takimi problemami za pośrednictwemTRIGGER
.LOAD DATA
. Dodanie niepotrzebnych indeksów jest kosztowne (na czas).AUTO_INCREMENT
, a następnie dzielenia 1K wierszy na raz na podstawie PK. Ale zanim spróbuję przeliterować szczegóły, muszę zobaczyć wszystkie wymagania i schemat tabeli.PRIMARY index
, ale chociaż dzielenie na 50k za pomocą zapytania o zamówienie zajmuje więcej czasu. czy byłoby lepiej, jeśli utworzę automatyczny przyrost? i ustawić jakoPRIMARY index
?Powiedziałeś:
Wiele z tych stwierdzeń może być sprzecznych. Na przykład duże aktualizacje bez blokowania stołu. Lub unikanie warunków wyścigowych bez użycia jednej wielkiej transakcji.
Ponieważ tabela jest mocno indeksowana, wstawianie i aktualizacje mogą być powolne.
Unikanie warunków wyścigu
Jeśli możesz dodać zaktualizowany znacznik czasu do swojego stołu, możesz rozwiązać warunki wyścigu, unikając jednocześnie rejestrowania pół miliona aktualizacji w jednej transakcji.
Dzięki temu możesz przeprowadzać aktualizacje linia po linii (tak jak obecnie), ale z automatycznym zatwierdzaniem lub bardziej rozsądnymi partiami transakcji.
Unikasz warunków wyścigu (podczas aktualizacji linia po linii), sprawdzając, czy późniejsza aktualizacja jeszcze nie nastąpiła (
UPDATE ... WHERE pk = [pk] AND updated < [batchfile date]
)Co ważne, umożliwia to uruchamianie równoległych aktualizacji.
Działa tak szybko, jak to możliwe - Równoległe
Po sprawdzeniu tego znacznika czasu:
mysql
uruchom każdy plik SQL.(np
bash
spojrzeniesplit
ixargs -P
sposobów, aby łatwo uruchomić polecenie wieloma względami równolegle. Stopień równoległości zależy ile wątków jesteś gotów poświęcić do aktualizacji )źródło
Duże aktualizacje są związane z operacjami we / wy. Sugerowałbym:
źródło
Aby
UPDATE
szybko biegać, potrzebujeszMoże być na dowolnym stole. Trzy pola mogą być w dowolnej kolejności.
Ułatwi to
UPDATE
to szybkie dopasowanie wierszy między dwiema tabelami.I uczyń typy danych takie same w dwóch tabelach (obu
INT SIGNED
lub obuINT UNSIGNED
).źródło
EXPLAIN UPDATE ...;
.