web-dev-qa-db-fra.com

Comment améliorer le comportement de verrouillage INSERT INTO ... SELECT

Dans notre base de données de production, nous avons exécuté la requête SQL de pseudo-code SQL suivante en cours d'exécution toutes les heures:

INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
     WHERE allKindsOfComplexConditions are true)

Maintenant, cette requête elle-même n'a pas besoin d'être rapide, mais j'ai remarqué qu'elle bloquait HighlyContentiousTableInInnoDb, même si elle ne faisait que la lire. Ce qui faisait que d'autres requêtes très simples prenaient environ 25 secondes (c'est le temps que prend cette autre requête).

Ensuite, j'ai découvert que les tables InnoDB dans un tel cas sont en fait verrouillées par un SELECT! https://www.percona.com/blog/2006/07/12/insert-into-select-performance-with-innodb-tables/

Mais je n'aime pas vraiment la solution dans l'article de sélection dans un OUTFILE, cela ressemble à un hack (les fichiers temporaires sur le système de fichiers semblent idiots). D'autres idées? Existe-t-il un moyen de faire une copie complète d'une table InnoDB sans la verrouiller de cette manière pendant la copie. Ensuite, je pouvais simplement copier le HighlyContentiousTable dans une autre table et y faire la requête.

42
Artem

La réponse à cette question est désormais beaucoup plus simple: - Utilisez la réplication basée sur les lignes et le niveau d'isolation Read Committed.

Le verrouillage que vous rencontriez disparaît.

Explication plus longue: http://harrison-fisk.blogspot.com/2009/02/my-favorite-new-feature-of-mysql-51.html

24
Morgan Tocker

Vous pouvez définir le format binlog comme ça:

SET GLOBAL binlog_format = 'ROW';

Modifiez my.cnf si vous voulez le rendre permanent:

[mysqld]
binlog_format=ROW

Définissez le niveau d'isolement pour la session en cours avant d'exécuter votre requête:

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO t1 SELECT ....;

Si cela ne vous aide pas, essayez de définir le niveau d'isolement sur tout le serveur et pas seulement pour la session en cours:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

Modifiez my.cnf si vous voulez le rendre permanent:

[mysqld]
transaction-isolation = READ-UNCOMMITTED

Vous pouvez remplacer READ-UNCOMMITTED par READ-COMMITTED qui est un meilleur niveau d'isolement.

7
diamonddog

Tous ceux qui utilisent des tables Innodb se sont probablement habitués au fait que les tables Innodb effectuent des lectures non verrouillées, ce qui signifie que, sauf si vous utilisez des modificateurs tels que LOCK IN SHARE MODE ou FOR UPDATE, les instructions SELECT ne verrouillent aucune ligne lors de l'exécution.

Ceci est généralement correct, mais il existe une exception notable - INSERT INTO table1 SELECT * FROM table2. Cette instruction effectuera la lecture de verrouillage (verrous partagés) pour la table table2. Il s'applique également aux tables similaires avec la clause where et les jointures. Il est important que les tables en cours de lecture soient Innodb - même si les écritures sont effectuées dans la table MyISAM.

Alors pourquoi cela a-t-il été fait, étant assez mauvais pour les performances MySQL et la concurrence?

La raison en est - la réplication. Dans MySQL avant 5.1, la réplication est basée sur des instructions, ce qui signifie que les instructions répondues sur le maître devraient avoir le même effet que sur l'esclave. Si Innodb ne verrouillait pas les lignes dans la table source, une autre transaction pourrait modifier la ligne et valider avant la transaction qui exécute l'instruction INSERT .. SELECT. Cela rendrait cette transaction à appliquer sur l'esclave avant l'instruction INSERT… SELECT et entraînerait éventuellement des données différentes de celles sur le maître. Le verrouillage des lignes dans la table source pendant leur lecture protège de cet effet car une autre transaction modifie les lignes avant que INSERT… SELECT n'ait eu la chance d'y accéder, elle sera également modifiée dans le même ordre sur l'esclave. Si la transaction essaie de modifier la ligne après son accès et ainsi verrouillée par INSERT… SELECT, la transaction devra attendre la fin de l'instruction pour s'assurer qu'elle sera exécutée sur l'esclave dans le bon ordre. Ça devient assez compliqué? Eh bien, tout ce que vous devez savoir doit être fait avant la réplication pour fonctionner correctement dans MySQL avant 5.1.

Dans MySQL 5.1, cela ainsi que quelques autres problèmes devraient être résolus par la réplication basée sur les lignes. Je dois cependant encore lui donner de vrais tests de résistance pour voir comment il fonctionne :)

Une dernière chose à garder à l'esprit - INSERT… SELECT effectue la lecture en mode de verrouillage et contourne ainsi partiellement le versioning et récupère la dernière ligne validée. Donc, même si vous opérez en mode REPEATABLE-READ, cette opération sera effectuée en mode READ-COMMITTED, ce qui donnera potentiellement un résultat différent par rapport à ce que donnerait SELECT pur. Ceci s'applique d'ailleurs à SELECT .. LOCK IN SHARE MODE et SELECT… FOR UPDATE également.

Un mon demander ce qui se passe si je n'utilise pas la réplication et que mon journal binaire est désactivé? Si la réplication n'est pas utilisée, vous pouvez activer l'option innodb_locks_unsafe_for_binlog, qui assouplira les verrous qu'Innodb définit lors de l'exécution des instructions, ce qui donne généralement une meilleure simultanéité. Cependant, comme son nom l'indique, les verrous ne sont pas sécurisés avant la réplication et la récupération ponctuelle, utilisez donc l'option innodb_locks_unsafe_for_binlog avec prudence.

Notez que la désactivation des journaux binaires n'est pas suffisante pour déclencher des verrous détendus. Vous devez également définir innodb_locks_unsafe_for_binlog = 1. Cela est fait pour que l'activation du journal binaire n'entraîne pas de changements inattendus dans le comportement de verrouillage et les problèmes de performances. Vous pouvez également utiliser cette option avec la réplication parfois, si vous savez vraiment ce que vous faites. Je ne le recommanderais pas à moins qu'il ne soit vraiment nécessaire car vous ne savez peut-être pas quels autres verrous seront assouplis dans les futures versions et comment cela affecterait votre réplication.

5
Vipin Jain

Vous pourriez probablement utiliser la commande Créer une vue (voir Créer une syntaxe de vue ). Par exemple,

Create View temp as SELECT FROM HighlyContentiousTableInInnoDb WHERE allKindsOfComplexConditions are true

Après cela, vous pouvez utiliser votre instruction d'insertion avec cette vue. Quelque chose comme ça

INSERT INTO TemporaryTable (SELECT * FROM temp)

Ce n'est que ma proposition.

1
smg

Avertissement: je ne suis pas très expérimenté avec les bases de données, et je ne sais pas si cette idée est réalisable. Veuillez me corriger si ce n'est pas le cas.

Que diriez-vous de configurer une table équivalente secondaire HighlyContentiousTableInInnoDb2, et en créant AFTER INSERT etc. déclencheurs dans la première table qui maintiennent la nouvelle table à jour avec les mêmes données. Vous devriez maintenant pouvoir verrouiller HighlyContentiousTableInInnoDb2, et ne ralentit que les déclencheurs de la table principale, au lieu de toutes les requêtes.

Problèmes potentiels:

  • 2 x données stockées
  • Travail supplémentaire pour toutes les insertions, mises à jour et suppressions
  • Peut ne pas être solide sur le plan des transactions
1
Internet Friend

Si vous pouvez autoriser certaines anomalies, vous pouvez changer le NIVEAU D'ISOLEMENT pour le moins strict - LIRE NON ENGAGÉ. Mais pendant ce temps, quelqu'un est autorisé à lire depuis votre table de destination. Ou vous pouvez verrouiller la table de destination manuellement (je suppose que mysql donne cette fonctionnalité?).

Ou vous pouvez également utiliser READ COMMITTED, qui ne doit pas également verrouiller la table source. Mais il verrouille également les lignes insérées dans la table de destination jusqu'à la validation.

Je choisirais le deuxième.

1
Azho KG

J'étais confronté au même problème en utilisant CREATE TEMPORARY TABLE ... SELECT ... avec SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction.

Sur la base de votre requête initiale, mon problème a été résolu en verrouillant le HighlyContentiousTableInInnoDb avant de démarrer la requête.

LOCK TABLES HighlyContentiousTableInInnoDb READ;
INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
    WHERE allKindsOfComplexConditions are true)
UNLOCK TABLES;
0

Je ne connais pas MySQL, mais j'espère qu'il existe un équivalent aux niveaux d'isolement des transactions Snapshot et Read committed snapshot dans SQL Server. L'utilisation de l'un de ces éléments devrait résoudre votre problème.

0
MEMark

La raison du verrouillage (readlock) est de sécuriser votre transaction de lecture pour ne pas lire les données "sales" qu'une transaction parallèle pourrait être en train d'écrire. La plupart des SGBD offrent le paramètre que les utilisateurs peuvent définir et révoquer manuellement les verrous en lecture et en écriture. Cela peut être intéressant pour vous si la lecture de données sales n'est pas un problème dans votre cas.

Je pense qu'il n'y a aucun moyen sécurisé de lire à partir d'une table sans verrou dans un DBS avec plusieurs transactions.

Mais ce qui suit est un remue-méninges: si l'espace n'est pas un problème, vous pouvez penser à exécuter deux instances de la même table. HighlyContentiousTableInInnoDb2 pour votre transaction de lecture/écriture constante et un HighlyContentiousTableInInnoDb2_shadow pour votre accès par lots. Peut-être que vous pouvez remplir la table fantôme automatisée via des déclencheurs/routines à l'intérieur de votre SGBD, ce qui est plus rapide et plus intelligent qu'une transaction d'écriture supplémentaire partout.

Une autre idée est la question: toutes les transactions doivent-elles accéder à la table entière? Sinon, vous pouvez utiliser des vues pour verrouiller uniquement les colonnes nécessaires. Si l'accès continu et votre accès groupé sont disjoints en ce qui concerne les colonnes, il est possible qu'elles ne se verrouillent pas!

0
Philipp Andre