requête concernant la combinaison d'une mise à jour et d'une requête d'insertion dans une seule requête dans mysql

9

je veux suivre l'historique des changements pour un utilisateur, de sorte que chaque fois qu'il change son profil, j'ai besoin de prendre les anciennes données et de les stocker dans l'historique et de les mettre à jour avec de nouvelles données.

Je peux utiliser un selectpour récupérer les anciennes données, un insertpour l'historique et enfin un updatepour modifier les données.

puis-je avoir tout cela dans une seule requête dans mysql sans utiliser de procédures stockées, de déclencheurs, etc. comme utiliser des verrous, etc. si c'est le cas, donnez-moi un petit échantillon.

Saravanan
la source
1
@savaranan: Cette question est digne d'un +1 car elle rappelle aux administrateurs de base de données et aux développeurs d'utiliser les transactions et de tirer pleinement parti des propriétés ACID de la base de données.
RolandoMySQLDBA
2
@savaranan: À toutes fins utiles, Jack a fourni la SEULE réponse plausible. En fait, Jack Douglas a franchi une étape supplémentaire et a forcé un verrouillage intermittent sur chaque ligne avec id = 10 pour une protection MVCC supplémentaire en faisant SELECT ... FOR UPDATE. Sa réponse accentue davantage le point que Jack et moi avons toujours dit: une mise à jour et une insertion ne peuvent pas être, ni ne seront jamais, une seule requête, elles ne peuvent être qu'une seule transaction pour le comportement SQL proposé par votre question.
RolandoMySQLDBA

Réponses:

13

Pour ce faire , sans risque de bloquer un autre utilisateur essayant de mettre à jour le même profil en même temps, vous devez verrouiller la ligne en t1premier, puis utilisez une transaction (comme Rolando fait remarquer dans les commentaires à votre question):

start transaction;
select id from t1 where id=10 for update;
insert into t2 select * from t1 where id=10;
update t1 set id = 11 where id=10;
commit;
Jack dit d'essayer topanswers.xyz
la source
C'est tout simplement génial pour verrouiller davantage chaque ligne avec id = 10. Cela devrait être un +2. Tout ce que je peux donner, c'est un +1 !!!
RolandoMySQLDBA
1

Je ne pense pas qu'il existe un moyen de combiner les trois déclarations. La chose la plus proche de cela ne vous aide pas vraiment, et c'est un SET SELECT. Votre meilleur pari est un déclencheur. Voici un exemple de déclencheur que j'utilise souvent pour maintenir une telle piste d'audit (construite avec PHP):

$trigger = "-- audit trigger --\nDELIMITER $ \n".
    "DROP TRIGGER IF EXISTS `{$prefix}_Audit_Trigger`$\n".
    "CREATE TRIGGER `{$prefix}_Audit_Trigger` AFTER UPDATE ON `$this->_table_name` FOR EACH ROW BEGIN\n";

foreach ($field_defs as $field_name => $field) {
    if ($field_name != $id_name) {
       $trigger .= "IF (NOT OLD.$field_name <=> NEW.$field_name) THEN \n".'INSERT INTO AUDIT_LOG ('.
                    'Table_Name, Row_ID, Field_Name, Old_Value, New_Value, modified_by, DB_User) VALUES'.
                    "\n ('$this->_table_name',OLD.$this->_id_name,'$field_name',OLD.$field_name,NEW.$field_name,".
                    "NEW.modified_by, USER()); END IF;\n";
    }
}
$trigger .= 'END$'."\n".'DELIMITER ;';
Bryan Agee
la source
-3

J'ai trouvé que cette requête fonctionne sur les serveurs SQL et MySQL INSERT INTO t2 SELECT * FROM t1 WHERE id=10; UPDATE t1 SET id=11 WHERE id=10;

J'espère que cela sera utile à d'autres également à l'avenir.

Saravanan
la source
4
Ce n'est pas vraiment une requête. Il s'agit en fait de deux requêtes qui doivent être traitées comme une transaction.
RolandoMySQLDBA
@rolandomysqldba: cela fonctionne bien comme une seule requête lorsque j'envoie à un serveur db à partir du code d'application, où je traite cet ensemble comme une seule requête. pourquoi ne le dites?. pouvez-vous réfuter cela avec de bonnes raisons ..
Saravanan
2
@saravanan: Aux yeux d'InnoDB ou de tout SGBDR compatible ACID (Oracle, SQLServer, PostreSQL, Sybase, etc.), il est impossible d'appeler ces deux instructions SQL une seule requête. Comme une base de données conforme à ACID les traiterait comme deux déclarations. Par défaut, InnoDB a activé la validation automatique. La première instruction, INSERT, s'exécuterait comme une transaction unique. Des données de contrôle de concurrence multivisionnelle (MVCC) seraient générées pour conserver une copie des données d'origine dans la table t2 ligne par ligne. Si MySQL se bloque pendant l'exécution de INSERT, InnoDB utilise les données MVCC pour ramener t2 à son état d'origine.
RolandoMySQLDBA
1
@saravanan: Supposons que l'insertion ait fonctionné avec succès. Les données résultant de l'insertion ont été validées (avec la validation automatique activée) et la table de protection MVCC t2 est supprimée. Lorsque vous effectuez la mise à jour, MVCC est généré par rapport à la table t1 et la mise à jour est effectuée. Si MySQL se bloque pendant la MISE À JOUR, InnoDB utilise les données MVCC sur t1 pour restaurer la MISE À JOUR. Même si la MISE À JOUR ne change qu'une seule ligne, la possibilité d'un sur un million existe de déplacer des enregistrements de t1 vers t2 avec l'id 10 et de ne pas changer l'id 10 en id 11 en t1. Pour éviter ce scénario unique, vous devez faire ce qui suit ...
RolandoMySQLDBA
@savaranan: Traitez les deux instructions SQL comme une seule transaction. Un moyen simple de le faire est: COMMENCER; INSÉRER DANS t2 SELECT * FROM t1 WHERE id = 10; UPDATE t1 SET id = 11 WHERE id = 10; COMMETTRE; La raison la plus importante pour traiter les deux instructions SQL comme une seule transaction est le fait que le MVCC créé pour l'INSERT demeurerait en vigueur pendant la MISE À JOUR. Si un crash MySQL devait se produire lors de la MISE À JOUR dans une transaction (BEGIN; ... COMMIT; block) MVCC annulerait toutes les modifications dans un état cohérent. Si INSERT et UPDATE sont terminés, MVCC est rejeté au dernier moment.
RolandoMySQLDBA