web-dev-qa-db-fra.com

Comment puis-je améliorer les performances de DELETE FROM sur les grandes tables InnoDB?

J'ai une table InnoDB assez volumineuse qui contient environ 10 millions de lignes (et cela devrait être 20 fois plus important). Chaque rangée n’est pas très grande (131 B en moyenne), mais de temps en temps je dois en supprimer une partie, ce qui prend des siècles. Voici la structure de la table:

 CREATE TABLE `problematic_table` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    `taxid` int(10) unsigned NOT NULL,
    `blastdb_path` varchar(255) NOT NULL,
    `query` char(32) NOT NULL,
    `target` int(10) unsigned NOT NULL,
    `score` double NOT NULL,
    `evalue` varchar(100) NOT NULL,
    `log_evalue` double NOT NULL DEFAULT '-999',
    `start` int(10) unsigned DEFAULT NULL,
    `end` int(10) unsigned DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `taxid` (`taxid`),
    KEY `query` (`query`),
    KEY `target` (`target`),
    KEY `log_evalue` (`log_evalue`)
) ENGINE=InnoDB AUTO_INCREMENT=7888676 DEFAULT CHARSET=latin1;

Les requêtes qui suppriment de gros morceaux de la table sont simplement comme ceci:

DELETE FROM problematic_table WHERE problematic_table.taxid = '57';

Une requête comme celle-ci a pris presque une heure. Je peux imaginer que la surcharge de la réécriture d'index rend ces requêtes très lentes.

Je développe une application qui fonctionnera sur des bases de données préexistantes. Je n'ai probablement aucun contrôle sur les variables de serveur, sauf si je les modifie de manière obligatoire (ce que je préférerais ne pas), alors je crains que les suggestions de modification de celles-ci n'aient que peu de valeur.

J'ai essayé de INSERT ... SELECT ces lignes que je ne veux pas supprimer dans une table temporaire et que je laisse simplement tomber le reste, mais comme le rapport supprimer/conserver garde, ceci n'est plus utile Solution.

Il s'agit d'une table dans laquelle il est possible que INSERTs et SELECTs soient fréquents à l'avenir, mais pas de UPDATEs. Fondamentalement, il s'agit d'une table de journalisation et de référence qui doit supprimer des parties de son contenu de temps en temps.

Pourrais-je améliorer mes index sur cette table en limitant leur longueur? Souhaitez-vous passer à l'aide de MyISAM, qui prend en charge DISABLE KEYS lors de transactions? Que puis-je essayer d'améliorer les performances de DELETE?

Modifier: Une telle suppression serait de l'ordre d'environ un million de lignes.

14
mpe

Cette solution peut offrir de meilleures performances une fois terminée, mais la mise en œuvre peut prendre un certain temps.

Une nouvelle colonne BIT peut être ajoutée et définie par défaut sur TRUE pour "actif" et FALSE pour "inactif". Si cela ne suffit pas, vous pouvez utiliser TINYINT avec 256 valeurs possibles.

L'ajout de cette nouvelle colonne prendra probablement beaucoup de temps, mais une fois celle-ci terminée, vos mises à jour devraient être beaucoup plus rapides si vous le faites à partir de la variable PRIMARY comme vous le faites avec vos suppressions et n'indexez pas cette nouvelle colonne.

La raison pour laquelle InnoDB met si longtemps à DELETE sur une table aussi volumineuse que la vôtre est à cause de l'index de cluster. Il ordonne physiquement votre table en fonction de votre PRIMARY, première UNIQUE trouvée, ou tout ce qu'elle peut déterminer comme substitut adéquat si elle ne peut pas trouver PRIMARY ou UNIQUE. Ainsi, lorsqu'une ligne est supprimée, elle réorganise physiquement l'intégralité de votre table. disque pour la vitesse et la défragmentation. Ce n’est donc pas la DELETE qui prend si longtemps; c'est la réorganisation physique après la suppression de cette ligne.

Lorsque vous créez une colonne de largeur fixe et la mettez à jour au lieu de la supprimer, il n'est pas nécessaire de réorganiser physiquement votre grande table car l'espace utilisé par une ligne et par une table est constant.

Pendant les heures creuses, une seule DELETE peut être utilisée pour supprimer les lignes inutiles. Cette opération sera toujours lente mais collectivement beaucoup plus rapide que la suppression de lignes individuelles.

12
user1382306

J'ai eu un scénario similaire avec une table avec 2 millions de lignes et une instruction delete, qui devrait supprimer environ 100 000 lignes - cela a pris environ 10 minutes pour le faire.

Après avoir vérifié la configuration, j’ai constaté que le serveur MySQL était en cours d’exécution avec innodb_buffer_pool_size par défaut = 8 Mo (!).

Après le redémarrage avec innodb_buffer_pool_size = 1,5 Go, le même scénario prenait 10 secondes.

Il semble donc y avoir une dépendance si "la réorganisation de la table" peut s’intégrer dans buffer_pool ou non.

23
vdd

J'ai résolu un problème similaire en utilisant une procédure stockée, améliorant ainsi les performances d'un facteur de plusieurs milliers.

Ma table avait 33 millions de lignes et plusieurs index et je voulais supprimer 10 000 lignes. Ma base de données se trouvait dans Azure sans aucun contrôle sur innodb_buffer_pool_size. 

Pour plus de simplicité, j'ai créé une table tmp_id avec uniquement un champ primaire id:

CREATE TABLE `tmp_id` (
    `id` bigint(20) NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`)
)

J'ai sélectionné l'ensemble d'identifiants que je voulais supprimer dans tmp_id et ai exécuté delete from my_table where id in (select id from tmp_id);. Cela n'a pas été terminé en 12 heures. J'ai donc essayé avec un seul identifiant dans tmp_id et cela a pris 25 minutes. Faire delete from my_table where id = 1234 terminé en quelques millisecondes, j'ai donc décidé d'essayer de le faire dans une procédure:

CREATE PROCEDURE `delete_ids_in_tmp`()
BEGIN
    declare finished integer default 0;
    declare v_id bigint(20);
    declare cur1 cursor for select id from tmp_id;
    declare continue handler for not found set finished=1;    
    open cur1;
    igmLoop: loop
        fetch cur1 into v_id;
        if finished = 1 then leave igmLoop; end if;
        delete from problematic_table where id = v_id;
    end loop igmLoop;
    close cur1;
END

Maintenant, call delete_ids_in_tmp(); a supprimé toutes les lignes de 10 000 en moins d’une minute.

0
Jan Larsen