Pro většinu tabulek používáme MySQL 5.6 s úložným modulem InnoDB. Velikost fondu vyrovnávacích pamětí InnoDB je 15 GB a indexy Innodb DB + jsou kolem 10 GB. Server má 32 GB RAM a běží na operačním systému Cent OS 7 x64).
Mám jednu velkou tabulku, která obsahuje asi 10 milionů + záznamů.
Aktualizovaný soubor výpisu dostávám ze vzdáleného serveru každých 24 hodin. Soubor je ve formátu CSV. Nemám kontrolu nad tímto formátem. Soubor je ~ 750 MB. Pokusil jsem se vložit data do tabulky MyISAM řádek po řádku a trvalo to 35 minut.
Potřebuji vzít pouze 3 hodnoty na řádek z 10-12 ze souboru a aktualizovat je v databázi.
Jaký je nejlepší způsob, jak dosáhnout něčeho takového?
Musím to dělat každý den.
V současné době je Flow takto:
Dokončení výše uvedených operací trvá -40 minut a během toho probíhají další aktualizace, které mi dávají
Překročení časového limitu zámku; zkuste restartovat transakci
načítání dat do nové tabulky pomocí LOAD DATA LOCAL INFILE
. V MyISAM to trvalo 38.93 sec
v InnoDB to trvalo 7 min 5,21 s. Pak jsem udělal:
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)
stejná aktualizace s dotazem na připojení
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)
Vysvětlení z otázek v komentářích:
Aktualizace 3:
zde jsou další podrobnosti o obou tabulkách.
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 zde je aktualizační dotaz, který aktualizuje tabulku content
pomocí dat z content_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
aktualizace 4:
všechno výše uvedené testování bylo provedeno na zkušebním stroji., ale teď jsem provedl stejné testy na výrobním stroji a dotazy jsou velmi rychlé.
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
omlouvám se za svou chybu. Je lepší použít připojit místo každé aktualizace záznamu. Nyní se snažím zlepšit mpre pomocí indexu navrhl rick_james, bude aktualizovat, jakmile bench-značení je hotovo.
Na základě mých zkušeností bych k importu vašeho souboru CSV použil LOAD DATA INFILE .
Příkaz LOAD DATA INFILE čte řádky z textového souboru do tabulky velmi vysokou rychlostí.
Příklad, který jsem našel na internetu Příklad načtení dat . Testoval jsem tento příklad na mé krabici a pracoval dobře
Příklad tabulky
CREATE TABLE example (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Column2` varchar(14) NOT NULL,
`Column3` varchar(14) NOT NULL,
`Column4` varchar(14) NOT NULL,
`Column5` DATE NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB
Příklad souboru CSV
# more /tmp/example.csv
Column1,Column2,Column3,Column4,Column5
1,A,Foo,sdsdsd,4/13/2013
2,B,Bar,sdsa,4/12/2013
3,C,Foo,wewqe,3/12/2013
4,D,Bar,asdsad,2/1/2013
5,E,FOObar,wewqe,5/1/2013
Importní příkaz, který má být spuštěn z konzole MySQL
LOAD DATA LOCAL INFILE '/tmp/example.csv'
-> INTO TABLE example
-> FIELDS TERMINATED BY ','
-> LINES TERMINATED BY '\n'
-> IGNORE 1 LINES
-> (id, Column3,Column4, @Column5)
-> set
-> Column5 = str_to_date(@Column5, '%m/%d/%Y');
Výsledek
MySQL [testcsv]> select * from example;
+----+---------+---------+---------+------------+
| Id | Column2 | Column3 | Column4 | Column5 |
+----+---------+---------+---------+------------+
| 1 | | Column2 | Column3 | 0000-00-00 |
| 2 | | B | Bar | 0000-00-00 |
| 3 | | C | Foo | 0000-00-00 |
| 4 | | D | Bar | 0000-00-00 |
| 5 | | E | FOObar | 0000-00-00 |
+----+---------+---------+---------+------------+
IGNORE jednoduše ignoruje první řádek, který je záhlavím sloupců.
Po IGNORE určíme sloupce (přeskočení sloupce2), které se mají importovat, což odpovídá jednomu z kritérií ve vaší otázce.
Zde je další příklad přímo od společnosti Oracle: Příklad LOAD DATA INFILE
To by mělo stačit, abyste mohli začít.
Ve světle všech zmíněných věcí to vypadá, že úzkým hrdlem je spojení samotné.
S největší pravděpodobností je vaše join_buffer_size pravděpodobně příliš nízká.
Podle dokumentace MySQL o , jak MySQL používá mezipaměť pro připojení do vyrovnávací paměti
Použité sloupce ukládáme pouze do vyrovnávací paměti pro spojení, ne celé řádky.
V tomto případě nechte klíče vyrovnávací paměti join v RAM.
Pro každý klíč máte 10 milionů řádků krát 4 bajty. To je asi 40 milionů.
Zkuste ji v relaci naráčet na 42 mil. (Trochu větší než 40 mil.)
SET join_buffer_size = 1024 * 1024 * 42;
UPDATE table1 a JOIN table2 b
ON a.field1 = b.field1
SET
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4;
Pokud to udělá trik, pokračujte v přidání tohoto do my.cnf
[mysqld]
join_buffer_size = 42M
Restartování mysqld není vyžadováno pro nová připojení. Prostě běž
mysql> SET GLOBAL join_buffer_size = 1024 * 1024 * 42;
Styl operace operace by jste mohli manipulovat tweeking optimalizátoru
Podle dokumentace MySQL o Připojování blokovaných vnořených smyček a dávkových klíčů
Když se použije BKA, hodnota join_buffer_size definuje, jak velká je dávka klíčů v každém požadavku na úložný stroj. Čím větší je vyrovnávací paměť, tím více bude postupný přístup k pravé tabulce operace spojení, což může výrazně zlepšit výkon.
Aby bylo možné použít BKA, musí být nastaven příznak batched_key_access systémové proměnné optimizer_switch. BKA používá MRR, takže musí být také zapnutý příznak mrr. V současné době je odhad nákladů na MRR příliš pesimistický. Proto je také nutné, aby byl mrr_cost_based vypnutý, aby bylo možné použít BKA.
Stejná stránka doporučuje provést toto:
mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
Nejvíce zapomínáte zvýšit innodb_write_io_threads , aby rychleji zapisoval špinavé stránky z fondu vyrovnávacích pamětí.
[mysqld]
innodb_write_io_threads = 16
Pro tuto změnu budete muset restartovat MySQL
Řekl jste:
Mnoho z těchto tvrzení může být protichůdných. Například velké aktualizace bez uzamčení stolu. Nebo se vyhnete závodním podmínkám bez použití jedné obří transakce.
Vzhledem k tomu, že tabulka je silně indexována, mohou být vložení i aktualizace pomalé.
Vyhýbání se závodním podmínkám
Pokud jste schopni přidat aktualizované časové razítko do tabulky, můžete vyřešit závodní podmínky a zároveň se vyhnout protokolování půl milionu aktualizací v jediná transakce.
To vám umožní provádět aktualizace po jednotlivých řádcích (jak je tomu v současné době), ale s automatickými nebo vhodnějšími dávkami transakcí.
Vyhnete se podmínkám závodu (při aktualizaci řádek po řádku) provedením kontroly, zda k pozdější aktualizaci nedošlo (UPDATE ... WHERE pk = [pk] AND updated < [batchfile date]
)
A co je důležité, to vám umožní spouštět paralelní aktualizace.
běží co nejrychleji - paralelizace
S tímto časovým razítkem zkontrolujte nyní:
mysql
spustit každý soubor sql.(např. v bash
podívejte se na split
a xargs -P
pro způsoby, jak snadno spustit příkaz mnoha způsoby paralelně. Stupeň rovnoběžnosti závisí na tom, kolik vláken jste ochotni věnovat aktualizaci )
CREATE TABLE
, Který odpovídá CSVLOAD DATA
Do této tabulkyUPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
DROP TABLE csv_table;
Krok 3 bude mnohem rychlejší než řádek po řádku, ale stále uzamkne všechny řádky v tabulce na netriviální dobu. Pokud je tato doba zámku důležitější než doba, po kterou celý proces trvá, ...
Pokud nic jiného nepíše na stůl, pak ...
CREATE TABLE
, Který odpovídá CSV; žádné indexy kromě toho, co je potřeba v JOIN
v UPDATE
. Pokud je jedinečný, vytvořte jej PRIMARY KEY
.LOAD DATA
Do této tabulkyreal_table
do new_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 je rychlejší než aktualizace, zejména pokud jsou zbytečné indexy vynechány.
Krok 5 je „okamžitý“.
Velké aktualizace jsou vázány na V/V. Navrhoval bych:
K tomu, aby UPDATE
fungoval rychle, potřebujete
INDEX(uploaders, downloaders, verified)
Může to být na každém stole. Tato tři pole mohou být v libovolném pořadí.
To usnadní, aby UPDATE
byl schopen rychle porovnávat řádky mezi dvěma tabulkami.
And učiní datové typy stejné ve dvou tabulkách (obě INT SIGNED
nebo oboje INT UNSIGNED
).