web-dev-qa-db-fra.com

Procédure de rollback parent sur l'échec de MySQL

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 magnetsINSERT 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.


ÉDITER

Basé sur le fil lié ci-dessus; J'ai mis à jour mes procédures sur les éléments suivants:

Nouvelle entrée

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

NewMagnet

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.


ÉDITER

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:

my table <code>b</code>

Ce qui est exactement ce que je veux éviter.

2
hjpotter92
    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/
1
Anup Shah