Utiliser MySQL 5.6 avec le moteur de stockage InnoDB pour la plupart des tables. La taille du pool de mémoire tampon InnoDB est de 15 Go et les index Innodb DB + d'environ 10 Go. Le serveur dispose de 32 Go de RAM et exécute Cent OS 7 x64.
J'ai une grande table qui contient environ 10 millions de disques.
Je reçois un fichier de vidage mis à jour d'un serveur distant toutes les 24 heures. Le fichier est au format csv. Je n'ai pas de contrôle sur ce format. Le fichier est ~ 750 MB. J'ai essayé d'insérer des données dans une table MyISAM ligne par ligne et cela a pris 35 minutes.
Je dois prendre seulement 3 valeurs par ligne sur 10-12 du fichier et le mettre à jour dans la base de données.
Quel est le meilleur moyen de réaliser quelque chose comme ça?
J'ai besoin de faire ça tous les jours.
Actuellement, Flow est comme ça:
- mysqli_begin_transaction
- Lire le fichier de vidage ligne par ligne
- Mettez à jour chaque enregistrement ligne par ligne.
- mysqli_commit
Les opérations ci-dessus prennent environ 30 à 40 minutes et, ce faisant, d’autres mises à jour sont en cours, ce qui me permet
Délai d'attente de verrouillage dépassé; essayez de redémarrer la transaction
Mise à jour 1
Chargement des données dans une nouvelle table avec LOAD DATA LOCAL INFILE
. Dans MyISAM, il a 38.93 sec
fallu 7 minutes à 5,21 secondes pour se rendre à InnoDB. Puis j'ai fait:
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)
Mise à jour 2
même mise à jour avec requête de jointure
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)
Clarifications des questions dans les commentaires:
- Environ 6% des lignes de la table seront mises à jour par le fichier, mais cela peut parfois atteindre 25%.
- Il y a des index sur les champs en cours de mise à jour. La table contient 12 index et 8 index incluent les champs de mise à jour.
- Il n'est pas nécessaire de faire la mise à jour en une seule transaction. Cela peut prendre du temps mais pas plus de 24 heures. Je cherche à le faire en 1 heure sans verrouiller l'ensemble de la table, car je devrai ensuite mettre à jour l'index sphinx, qui dépend de cette table. Peu importe si les étapes durent plus longtemps tant que la base de données est disponible pour d'autres tâches.
- Je pourrais modifier le format CSV dans une étape de prétraitement. La seule chose qui compte est la mise à jour rapide et sans verrouillage.
- Le tableau 2 est MyISAM. C'est la table nouvellement créée à partir d'un fichier csv utilisant load data infile. La taille du fichier MYI est de 452 Mo. Le tableau 2 est indexé sur la colonne champ1.
- MYD de la table MyISAM est de 663 Mo.
Mise à jour 3:
voici plus de détails sur les deux tables.
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
et voici la requête de mise à jour qui met à jour la content
table en utilisant les données decontent_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
mise à jour 4:
Tous les tests ci-dessus ont été effectués sur une machine de test., mais maintenant, j'ai effectué les mêmes tests sur la machine de production et les requêtes sont très rapides.
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
Je m'excuse pour mon erreur. Il est préférable d’utiliser join au lieu de chaque mise à jour d’enregistrement. Maintenant, j'essaye d'améliorer mpre en utilisant l'index suggéré par rick_james, je le mettrai à jour une fois le benchmark terminé.
INDEX(field2, field3, field4)
(dans n'importe quel ordre)? S'il vous plaît nous montrerSHOW CREATE TABLE
.UPDATEs
. Dites-nous exactement à quoi ressemble une déclaration simple pour la mise à jour de la table à partir des données CSV. Ensuite, nous pourrons peut-être vous aider à concevoir une technique qui réponde à vos exigences.update
, et s'il vous plaît vérifier la question mise à jour., MerciRéponses:
D'après mon expérience, j'utiliserais LOAD DATA INFILE pour importer votre fichier CSV.
Exemple trouvé sur Internet Exemple de chargement de données . J'ai testé cet exemple sur ma boîte et j'ai bien travaillé
Exemple de table
Exemple de fichier CSV
Déclaration d'importation à exécuter depuis la console MySQL
Résultat
IGNORE ignore simplement la première ligne qui sont des en-têtes de colonnes.
Après IGNORE, nous spécifions les colonnes (en ignorant la colonne2) à importer, qui correspondent à l'un des critères de votre question.
Voici un autre exemple directement d'Oracle: Exemple LOAD DATA INFILE
Cela devrait être suffisant pour vous aider à démarrer.
la source
À la lumière de toutes les choses mentionnées, il semble que le goulot d'étranglement est le joint lui-même.
ASPECT # 1: Taille du tampon de jointure
Probablement , votre join_buffer_size est probablement trop basse.
Selon la documentation de MySQL sur la manière dont MySQL utilise le cache de tampon de jointure
Ceci étant, faites en sorte que les clés du tampon de jointure restent dans la RAM.
Vous avez 10 millions de lignes fois 4 octets pour chaque clé. C'est à peu près 40M.
Essayez d'augmenter la séance à 42M (un peu plus de 40M)
Si cela vous convient, ajoutez-le à
my.cnf
Le redémarrage de mysqld n'est pas nécessaire pour les nouvelles connexions. Il suffit de courir
ASPECT # 2: Opération de jointure
Vous pouvez manipuler le style de l'opération de jointure en modifiant l'optimiseur.
Selon la documentation MySQL sur les blocages de jointures imbriquées en boucle imbriquée et en lot
Cette même page recommande de faire ceci:
ASPECT # 3: Ecriture des mises à jour sur le disque (FACULTATIF)
La plupart oublient d’augmenter innodb_write_io_threads pour écrire plus rapidement les pages non conformes du pool de mémoire tampon.
Vous devrez redémarrer MySQL pour ce changement
ESSAIE !!!
la source
CREATE TABLE
qui correspond au CSVLOAD DATA
dans cette tableUPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
DROP TABLE csv_table;
L'étape 3 sera beaucoup plus rapide que ligne par ligne, mais toutes les lignes de la table seront verrouillées pour une durée non négligeable. Si ce temps de verrouillage est plus important que la durée totale du processus, ...
Si rien d'autre n'écrit sur la table, alors ...
CREATE TABLE
qui correspond au CSV; pas d'index sauf ce qui est nécessaireJOIN
dans leUPDATE
. Si unique, faites-lePRIMARY KEY
.LOAD DATA
dans cette tablereal_table
à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;
L'étape 3 est plus rapide que la mise à jour, surtout si les index inutiles sont laissés.
L'étape 5 est "instantanée".
la source
pt-online-schema-digest
; il prend en charge de telles questions via unTRIGGER
.LOAD DATA
. L'ajout d'index inutiles est coûteux (à temps).AUTO_INCREMENT
, puis tronquant des rangées de 1K à la fois en fonction du PK. Mais il me faut voir toutes les exigences et le schéma de la table avant d'essayer de préciser les détails.PRIMARY index
, mais bien que découper dans 50k à l'aide d'une requête d'ordre prenne plus de temps, serait-il préférable que je crée une incrémentation automatique? et le définir commePRIMARY index
?Vous avez dit:
Beaucoup de ces déclarations peuvent être contradictoires. Par exemple, les mises à jour volumineuses ne verrouillent pas la table. Ou éviter les conditions de course sans utiliser une transaction géante.
De plus, comme votre table est fortement indexée, les insertions et les mises à jour peuvent être lentes.
Eviter les conditions de course
Si vous pouvez ajouter un horodatage mis à jour à votre table, vous pouvez résoudre le problème des conditions de concurrence tout en évitant de consigner un demi-million de mises à jour en une seule transaction.
Cela vous libère pour effectuer des mises à jour ligne par ligne (comme vous le faites actuellement), mais avec des lots de transactions autocommit ou plus raisonnables.
Vous évitez les situations de concurrence (lors de la mise à jour ligne par ligne) en vérifiant qu’une mise à jour ultérieure n’a pas déjà eu lieu (
UPDATE ... WHERE pk = [pk] AND updated < [batchfile date]
)Et surtout, cela vous permet d'exécuter des mises à jour parallèles .
Courir aussi vite que possible —Parallélisation
Avec cette vérification d'horodatage maintenant en place:
mysql
exécutez chaque fichier SQL.(par exemple
bash
regardersplit
etxargs -P
des façons de facilement exécuter une commande en parallèle de nombreuses façons. Le degré de parallélisme dépend du nombre de threads que vous êtes prêt à consacrer à la mise à jour )la source
Les mises à jour volumineuses sont liées aux entrées / sorties. Je voudrais suggerer:
la source
Pour le
UPDATE
faire vite, il fautCela peut être sur l'une ou l'autre table. Les trois champs peuvent être dans n'importe quel ordre.
Cela facilitera la
UPDATE
possibilité de faire correspondre rapidement les lignes entre les deux tables.Et rendre les types de données identiques dans les deux tables (les deux
INT SIGNED
ou les deuxINT UNSIGNED
).la source
EXPLAIN UPDATE ...;
.