Comment améliorer les performances d'InnoDB DELETE?

9

J'ai donc cette table d'audit (suit les actions sur n'importe quelle table de ma base de données):

CREATE TABLE `track_table` (
  `id` int(16) unsigned NOT NULL,
  `userID` smallint(16) unsigned NOT NULL,
  `tableName` varchar(255) NOT NULL DEFAULT '',
  `tupleID` int(16) unsigned NOT NULL,
  `date_insert` datetime NOT NULL,
  `action` char(12) NOT NULL DEFAULT '',
  `className` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userID` (`userID`),
  KEY `tableID` (`tableName`,`tupleID`,`date_insert`),
  KEY `actionDate` (`action`,`date_insert`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

et je dois commencer à archiver les éléments obsolètes. La table est passée à environ 50 millions de lignes, donc la façon la plus rapide de supprimer les lignes était de la supprimer une table à la fois (en fonction de tableName).

Cela fonctionne assez bien, mais sur certains tableaux qui sont lourds en écriture, il ne se terminera pas. Ma requête supprime tous les éléments qui ont une deleteaction associée sur une combinaison tupleID / tableName:

DELETE FROM track_table WHERE tableName='someTable' AND tupleID IN (
  SELECT DISTINCT tupleID FROM track_table
  WHERE tableName='someTable' AND action='DELETE' AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
)

J'ai laissé cela fonctionner sur mon serveur pendant 3 jours et cela ne s'est jamais terminé pour la plus grande table. La sortie d'explication (si je commute la suppression pour sélectionner:

| id | select_type        | table       | type | possible_keys      | key     | key_len | ref        | rows    | Extra                        |
|  1 | PRIMARY            | track_table | ref  | tableID            | tableID | 257     | const      | 3941832 | Using where                  |
|  2 | DEPENDENT SUBQUERY | track_table | ref  | tableID,actionDate | tableID | 261     | const,func |       1 | Using where; Using temporary |

Donc, 4 millions de lignes ne devraient pas prendre 3 jours pour être supprimées, je pense. J'ai mon innodb_buffer_pool_size défini sur 3 Go, et le serveur n'est pas configuré pour utiliser one_file_per_table. De quelles autres manières puis-je améliorer les performances de suppression d'InnoDB? (Exécution de MySQL 5.1.43 sur Mac OSX)

Derek Downey
la source

Réponses:

11

Vous pouvez supprimer des données par lots.

Dans SQL Server, la syntaxe correspond aux delete top Xlignes d'une table. Vous le faites ensuite en boucle, avec une transaction pour chaque lot (si vous avez plus d'une instruction, bien sûr), afin de garder les transactions courtes et de maintenir les verrous uniquement pendant de courtes périodes.

Dans la syntaxe MySQL: DELETE FROM userTable LIMIT 1000

Il y a des restrictions à cela (ne peut pas être utilisé LIMITdans les suppressions avec des jointures, par exemple) mais dans ce cas, vous pourriez être en mesure de le faire de cette façon.

Il y a un danger supplémentaire à utiliser LIMITavec DELETElorsqu'il s'agit de réplication; les lignes supprimées ne sont parfois pas supprimées dans le même ordre sur l'esclave que sur le maître.

Marian
la source
6

Essayez d'utiliser une approche de table temporaire. Essayez quelque chose comme ceci:

Étape 1) CREATE TABLE track_table_new LIKE track_table;

Étape 2) INSERT INTO track_table_new SELECT * FROM track_table WHERE action='DELETE' AND date_insert >= DATE_SUB(CURDATE(), INTERVAL 30 day);

Étape 3) ALTER TABLE track_table RENAME track_table_old;

Étape 4) ALTER TABLE track_table_new RENAME track_table;

Étape 5) DROP TABLE track_table_old;

Je n'ai pas inclus le champ de tuple à l'étape 2. Veuillez voir si cela produit l'effet souhaité. Si c'est ce que vous voulez, vous pouvez abandonner complètement le champ de tuple, sauf si vous utilisez le champ de tuple pour d'autres raisons.

RolandoMySQLDBA
la source
Voilà une solution intéressante. J'ai besoin du champ tuple dans la table. tableName / tupleID est une clé étrangère non définie de la table en cours de journalisation. Indéfini car jusqu'à récemment, cette table était MyISAM, qui ne prend pas en charge les clés étrangères.
Derek Downey
1

La suppression de lignes indésirables dans le lot devrait permettre à d'autres opérations de fonctionner. Mais votre suppression d'opération a des conditions, assurez-vous donc qu'il existe un index approprié sur les colonnes par rapport aux conditions.

Parce que MySQL ne supporte pas la fonction complète de balayage libre d'index, vous pouvez essayer d'ajuster la séquence pour KEY actionDate (action, date_insert)la KEY actionDate (date_insert, action). Avec le préfixe 'date_insert', MySQL devrait utiliser cet index pour analyser les lignes qui sont antérieures à votre condition datetime.

Avec un tel index, vous pouvez écrire SQL comme:

DELETE
FROM track_table
WHERE tableName='someTable'
    AND action='DELETE'
    AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
LIMIT 1000 -- Your size of batch
Mike Lue
la source
1
| id | select_type        | table       | type | possible_keys      | key     | key_len | ref        | rows    | Extra                        |
|  1 | PRIMARY            | track_table | ref  | tableID            | tableID | 257     | const      | 3941832 | Using where                  |
|  2 | DEPENDENT SUBQUERY | track_table | ref  | tableID,actionDate | tableID | 261     | const,func |       1 | Using where; Using temporary |

-Pistez, à partir de votre explication du key_len si grand => vous devez rétrograder la taille aussi petite que possible. Pour votre requête, je pense que le meilleur moyen est de changer le type de données du champ d'action de char (12) en tinyint, de sorte que le mappage des données ressemble à:

1: -> DELETE
2: -> UPDATE
3: -> INSERT
...

et vous pouvez également changer table_id à la place nom_table. le DDL pour les meilleures performances peut:

CREATE TABLE `track_table` (
  `id` int(11) unsigned NOT NULL,
  `userID` smallint(6) unsigned NOT NULL,
  `tableid` smallint(6) UNSIGNED NOT NULL DEFAULT 0,
  `tupleID` int(11) unsigned NOT NULL,
  `date_insert` datetime NOT NULL,
  `actionid` tinyin(4) UNSIGNED NOT NULL DEFAULT 0,
  `className` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userID` (`userID`),
  KEY `tableID` (`tableid`,`tupleID`,`date_insert`),
  KEY `actionDate` (`actionid`,`date_insert`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `actions` (
  `id` tinyint(4) unsigned NOT NULL 
  `actionname` varchar(255) NOT NULL,
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `table_name` (
  `id` tinyint(4) unsigned NOT NULL 
  `tablename` varchar(255) NOT NULL,
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

de sorte que la requête puisse fonctionner comme:

DELETE FROM track_table WHERE tableid=@tblid AND tupleID IN (
  SELECT DISTINCT tupleID FROM track_table
  WHERE tableid=@tblid AND actionid=@actionid AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
).

Mais le moyen le plus rapide était d'utiliser la partition. afin que vous puissiez supprimer la partition. Actuellement, ma table compte plus de 40 mil de lignes. et mettre à jour toutes les heures (400k lignes de mise à jour pour chaque fois), et je peux déposer la partition curr_date et recharger les données dans la table. la commande drop très rapide (<100 ms). J'espère que cette aide.

Thanh Nguyen
la source