it-swarm-eu.dev

Jak aktualizovat 10 milionů + řádků v MySQL jediné tabulce co nejrychleji?

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:

  1. mysqli_begin_transaction
  2. Přečtěte si soubor výpisu řádek po řádku
  3. Aktualizujte každý záznam řádek po řádku.
  4. mysqli_commit

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

Aktualizace 1

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)

Aktualizace 2

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:

  • Soubor bude aktualizován přibližně 6% řádků v tabulce, ale někdy to může být až 25%.
  • Na aktualizovaných polích jsou indexy. V tabulce je 12 indexů a 8 indexů obsahuje aktualizační pole.
  • Není nutné provádět aktualizaci v jedné transakci. Může to chvíli trvat, ale ne déle než 24 hodin. Chci to udělat za 1 hodinu bez uzamčení celé tabulky, protože později musím aktualizovat index sfingy, který je závislý na této tabulce. Nezáleží na tom, zda kroky trvají déle, pokud je databáze k dispozici pro jiné úkoly.
  • Mohl jsem upravit formát CSV v kroku předběžného zpracování. Jediná věc, na které záleží, je rychlá aktualizace a bez uzamčení.
  • Tabulka 2 je MyISAM. Jedná se o nově vytvořenou tabulku ze souboru csv pomocí infile načtení dat. Velikost souboru MYI je 452 MB. Tabulka 2 je indexována ve sloupci field1.
  • MYD tabulky MyISAM je 663 MB.

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.

35
AMB

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.

17
Craig Efrein

Ve světle všech zmíněných věcí to vypadá, že úzkým hrdlem je spojení samotné.

Aspekt č. 1: Připojte se k velikosti vyrovnávací paměti

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;

ASPECT # 2: Připojte se k operaci

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';

ASPECT # 3: Zápis aktualizací na disk (VOLITELNÉ)

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

POKUSIT SE !!!

16
RolandoMySQLDBA

Řekl jste:

  • Aktualizace ovlivňují 6–25% tabulky
  • Chcete to udělat co nejrychleji (<1h)
  • bez zamykání
  • nemusí to být v jedné transakci
  • přesto (v komentáři k odpovědi Ricka Jamese) vyjadřujete obavy ohledně závodních podmínek

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í:

  1. Rozdělte dávkový soubor na kousky rozumné velikosti (řekněme 50 000 řádků/soubor)
  2. Paralelně si přečtěte skript v každém souboru a vydejte soubor s 50 000 příkazy UPDATE.
  3. Paralelně, jakmile (2) skončí, nechte 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 )

3
Peter Dixon-Moses
  1. CREATE TABLE, Který odpovídá CSV
  2. LOAD DATA Do této tabulky
  3. UPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
  4. 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 ...

  1. 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.
  2. LOAD DATA Do této tabulky
  3. zkopírujte real_table do new_table (CREATE ... SELECT)
  4. UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
  5. RENAME TABLE real_table TO old, new_table TO real_table;
  6. 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ý“.

3
Rick James

Velké aktualizace jsou vázány na V/V. Navrhoval bych:

  1. Vytvořte samostatnou tabulku, ve které budou uložena 3 často aktualizovaná pole. Zavolejme jednu tabulku asset_static kde uchováváte, dobře, statická data, a druhou asset_dynamic, která bude ukládat uploadery, downloadery a ověřená.
  2. Pokud je to možné, použijte motor MEMORY pro tabulku asset_dynamic. (záloha na disk po každé aktualizaci).
  3. Aktualizujte svůj odlehčený a svižný asset_dynamic podle vaší aktualizace 4 (tj. ZAČNĚTE INFILE ... DO temp; UPDATE asset_dynamic a JOIN temp b na a.id = b.id SET [co je třeba aktualizovat]. Mělo by to trvat méně než minutu. (V našem systému má asset_dynamic 95 řádků a aktualizace mají dopad na řádky 6 milionů řádků za něco málo přes 40 s.)
  4. Při spuštění Sphinxova indexátoru JOIN asset_static and asset_dynamic (za předpokladu, že chcete použít jedno z těchto polí jako atribut).
1
user3127882

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).

0
Rick James