J'ai les deux procédures suivantes sur mon serveur:
CREATE DEFINER=`someone`@`localhost`
PROCEDURE `NewMagnet`(
IN `nick` VARCHAR(32),
IN `tth` CHAR(39),
IN `name` TINYTEXT,
IN `size` BIGINT,
IN `eid` INT,
OUT `maid` INT
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'Automated insertion to magnets and filename tables'
BEGIN
INSERT INTO magnets (eid, tth, size, nick, date)
SELECT
eid,
tth,
size,
m.id,
NOW()
FROM modtable m
WHERE m.nick = nick;
SET maid = LAST_INSERT_ID();
INSERT INTO filenames
VALUES( LAST_INSERT_ID(), name );
END
Cette procédure est appelée à partir de l'intérieur d'une autre procédure nommée NewEntry
:
CREATE DEFINER=`someone`@`localhost`
PROCEDURE `NewEntry`(
IN `ctg` VARCHAR(15),
IN `msg` TINYTEXT,
IN `nick` VARCHAR(32),
IN `tth` CHAR(39),
IN `name` TINYTEXT,
IN `size` BIGINT,
OUT `eid` INT,
OUT `maid` INT
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'Automated procedure to insert a new entry'
BEGIN
INSERT INTO entries (msg, nick, date, ctg)
SELECT
msg,
m.id,
NOW(),
c.id
FROM modtable m, ctgtable c
WHERE c.name = ctg
AND m.nick = nick
LIMIT 1;
SET eid = LAST_INSERT_ID();
CALL NewMagnet( nick, tth, name, size, eid, maid );
END
Je veux la procédure NewEntry
_ procédure elle-même si l'appel à NewMagnet
échoue. Actuellement, ce qui se passe est qu'une nouvelle entrée est ajoutée à la table entries
même si la table magnets
INSERT
La requête a échoué pour une raison quelconque (clé en double, surtout).
J'ai lu ce fil sur le débordement de pile ( https://stackoverflow.com/a/20046066/1190388 ) Pour créer un gestionnaire dans une procédure, mais je ne suis pas au courant sur la manière de la mettre en œuvre pour la procédure mère NewEntry
dans mon cas.
Basé sur le fil lié ci-dessus; J'ai mis à jour mes procédures sur les éléments suivants:
BEGIN
DECLARE exit handler for SQLEXCEPTION
BEGIN
ROLLBACK;
END;
DECLARE exit handler for SQLWARNING
BEGIN
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO entries (msg, nick, date, ctg)
SELECT
msg,
m.id,
NOW(),
c.id
FROM modtable m, ctgtable c
WHERE c.name = ctg
AND m.nick = nick
LIMIT 1;
SET eid = LAST_INSERT_ID();
CALL NewMagnet( nick, tth, name, size, eid, maid );
COMMIT;
END
BEGIN
DECLARE exit handler for sqlexception
BEGIN
ROLLBACK;
END;
DECLARE exit handler for sqlwarning
BEGIN
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO magnets (eid, tth, size, nick, date)
SELECT
eid,
tth,
size,
m.id,
NOW()
FROM modtable m
WHERE m.nick = nick;
SET maid = LAST_INSERT_ID();
INSERT INTO filenames
VALUES( LAST_INSERT_ID(), name );
COMMIT;
END
Mais il insère toujours une nouvelle ligne à la table entries
même lorsque la clé en double existe pour les valeurs magnets
passées.
Expliquer ce qui se passe et éclaircir certains doutes ANUP ont ; J'ai créé deux nouvelles tables:
CREATE TABLE `a` (
`b` TINYINT(3) UNSIGNED NOT NULL,
PRIMARY KEY (`b`)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
CREATE TABLE `b` (
`id` TINYINT(3) UNSIGNED NOT NULL AUTO_INCREMENT,
`f` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
Puis a créé deux nouvelles procédures également:
CREATE PROCEDURE `s`(IN `f` TINYINT, OUT `z` TINYINT)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'dsf'
BEGIN
DECLARE exit HANDLER
FOR SQLEXCEPTION, SQLWARNING
BEGIN
SET z = -1;
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO a
VALUES( f );
SET z = f;
COMMIT;
END
et
CREATE PROCEDURE `t`(IN `r` TINYINT, OUT `l` TINYINT, OUT `z` TINYINT)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE exit HANDLER
FOR SQLEXCEPTION, SQLWARNING
BEGIN
SET l = -1;
SET z = -1;
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO b (f)
VALUES( r );
SET l = LAST_INSERT_ID();
CALL s( r, z );
COMMIT;
END
Ensuite, j'ai exécuté des commandes suivantes:
CALL s(3, @z);
SELECT @z;
/* Affected rows: 0 Found rows: 1 Warnings: 0 Duration for 2 queries: 0.046 sec. */
Cela a inséré la valeur 3
Dans ma table a
. z
a été retourné comme 3
correctement. Ensuite, j'ai exécuté:
CALL s(3, @z);
SELECT @z;
/* Affected rows: 0 Found rows: 1 Warnings: 1 Duration for 2 queries: 0.000 sec. */
Et l'avertissement a conduit à la restauration et j'ai reçu z
comme -1
. Jusqu'ici tout va bien.
Maintenant, j'ai exécuté des déclarations suivantes:
CALL t(3, @l, @z);
SELECT @l, @z;
/* Affected rows: 0 Found rows: 1 Warnings: 1 Duration for 2 queries: 0.015 sec. */
CALL t(3, @l, @z);
SELECT @l, @z;
/* Affected rows: 0 Found rows: 1 Warnings: 1 Duration for 2 queries: 0.016 sec. */
CALL t(3, @l, @z);
SELECT @l, @z;
/* Affected rows: 0 Found rows: 1 Warnings: 1 Duration for 2 queries: 0.000 sec. */
Et la valeur pour z
a toujours été retourné comme -1
Mais la valeur de l
a été incrémentée à chaque fois. Alors, maintenant j'ai la table b
comme dans l'image ci-dessous:
Ce qui est exactement ce que je veux éviter.
CREATE PROCEDURE `t`(IN `r` TINYINT, OUT `l` TINYINT, OUT `z` TINYINT)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE exit HANDLER
FOR SQLEXCEPTION, SQLWARNING
BEGIN
SET l = -1;
SET z = -1;
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO b (f)
VALUES( r );
SET l = LAST_INSERT_ID();
CALL s( r, z );
IF z==-1
THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END
Edit: 1
voici la démo SQL FIDDLE
la procédure intérieure a également une transaction et cela fait tout commettre de DML précédent. Si vous déplacez votre insert après l'appel dans la procédure extérieure, vous n'y trouverez aucun insert. Mais il n'est peut-être pas toujours possible de changer d'ordre. D'une manière ou d'une autre, nous devons éviter la commission de toute procédure imbriquée et ne faites que dans la procédure mère.
mais ces procédures imbriquées peuvent être appelées indépendamment et vous voudrez peut-être conserver des transactions pour ces appels. Donc, fondamentalement, ce que je fais est d'ajouter une transaction/une déclaration de restauration/validation basée sur le paramètre transmis à la procédure.
lorsque vous apportez un appel directement à partir d'un code d'application en procédure, passez toujours 1 et la procédure utilisera une transaction explicite. Mais lorsque vous effectuez un appel imbriqué dans la procédure parent, vous ne souhaitez pas utiliser la transaction pour les appels imbriqués, de manière explicite explicitement 0.
CREATE TABLE `a` (
`b` TINYINT(3) UNSIGNED NOT NULL,
PRIMARY KEY (`b`)
);
CREATE TABLE `b` (
`id` TINYINT(3) UNSIGNED NOT NULL AUTO_INCREMENT,
`f` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
CREATE PROCEDURE `s`
(
IN `f` TINYINT,
IN `NestedTran` TINYINT,
OUT `z` TINYINT
)
BEGIN
DECLARE exit HANDLER
FOR SQLEXCEPTION, SQLWARNING
BEGIN
SET z = -1;
IF NestedTran = 1 THEN
ROLLBACK;
END IF;
END;
IF NestedTran = 1 THEN
START TRANSACTION;
END IF;
INSERT INTO a
VALUES( f );
SET z = f;
IF NestedTran = 1 THEN
COMMIT;
END IF;
END;
CREATE PROCEDURE `t`
(
IN `r` TINYINT,
IN `NestedTran` TINYINT,
OUT `l` TINYINT,
OUT `z` TINYINT
)
BEGIN
DECLARE exit HANDLER
FOR SQLEXCEPTION, SQLWARNING
BEGIN
SET l = -1;
IF NestedTran = 1 THEN
ROLLBACK;
END IF;
END;
IF NestedTran = 1 THEN
START TRANSACTION;
END IF;
INSERT INTO b (f)
VALUES( r );
SET l = LAST_INSERT_ID();
CALL s( r,0, z );
IF z = -1 THEN
IF NestedTran = 1 THEN
ROLLBACK;
END IF;
ELSE
IF NestedTran = 1 THEN
COMMIT;
END IF;
END IF;
END/