2010-11-26 7 views
1

Je suis coincé ici.Appel de procédure stockée séquentiellement à partir du fichier .sql

J'ai une procédure que je veux exécuter X * fois dans une rangée. (* X est quelques milliers de fois)
La procédure basée sur les données d'entrée fait ceci:
1. Recherche un actions.id, s'il n'est pas trouvé LEAVE s.
2. Cherche user.id, s'il n'est pas trouvé, en crée un et utilise LAST_INSERT_ID();
3-5. Cherche summaries.id (3 types, total, quotidien et mensuel), s'il n'est pas trouvé, en crée un et utilise son identifiant.
6. Une fois que tous les identifiants requis ont été collectés, INSERT s nouvelle rangée dans les actions et soit met à jour les lignes de résumés dans une transaction, donc si tout échoue - il fait un ROLLBACK - aucun mal fait. 7. En fonction du résultat SELECT s message.

CREATE PROCEDURE NEW_ACTION(
    IN a_date TIMESTAMP, 
    IN u_name VARCHAR(255), 
    IN a_name VARCHAR(255), 
    IN a_chars INT, 
    IN url VARCHAR(255), 
    IN ip VARCHAR(15)) 

    lbl_proc: BEGIN 
    DECLARE a_id, u_id, us_id, usd_id, usm_id, a_day, a_month, error INT; 
    DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET error = 1; 

    SET error = 0; 
    SET a_day = DATE_FORMAT(SUBSTRING(a_date ,1,10), '%Y%m%d'); 
    SET a_month = SUBSTRING(a_day, 1, 6); 

    /* 1. RETREIVING action.id */ 
    SET a_id = (SELECT `id` FROM `actions` WHERE `name` = a_name); 
    IF a_id IS NULL THEN 
     SELECT 'error'; 
     LEAVE lbl_proc; 
    END IF; 

    /* 2. RETREIVING users.id */ 
    SET u_id = (SELECT `id` FROM `users` WHERE `name` = u_name); 
    IF u_id IS NULL THEN 
     INSERT INTO `users` (name) VALUES (u_name); 
     SET u_id = (SELECT LAST_INSERT_ID()); 
    END IF; 

    /* 3. RETREIVING user_summaries.id */ 
    SET us_id = (SELECT `id` FROM `users_summaries` WHERE `user_id` = u_id AND `action_id` = a_id); 
    IF us_id IS NULL THEN 
     INSERT INTO `users_summaries` (user_id, action_id) VALUES (u_id, a_id); 
     SET us_id = (SELECT LAST_INSERT_ID()); 
    END IF; 

    /* 4. RETREIVING user_summaries_days.id */ 
    SET usd_id = (SELECT `id` FROM `users_summaries_days` WHERE `day` = a_day AND `user_id` = u_id AND `action_id` = a_id); 
    IF usd_id IS NULL THEN 
     INSERT INTO `users_summaries_days` (day, user_id, action_id) VALUES (a_day, u_id, a_id); 
     SET usd_id = (SELECT LAST_INSERT_ID()); 
    END IF; 

    /* 5. RETREIVING user_summaries_months.id */ 
    SET usm_id = (SELECT `id` FROM `users_summaries_months` WHERE `month` = a_month AND `user_id` = u_id AND `action_id` = a_id); 
    IF usm_id IS NULL THEN 
     INSERT INTO `users_summaries_months` (month, user_id, action_id) VALUES (a_month, u_id, a_id); 
     SET usm_id = (SELECT LAST_INSERT_ID()); 
    END IF; 

    /* 6. SAVING action AND UPDATING summaries */ 
    SET autocommit = 0; 
    START TRANSACTION; 
     INSERT INTO `users_actions` (`date`, `user_id`, `action_id`, `chars`, `url`, `ip`) VALUES (a_date, u_id, a_id, a_chars, url, ip); 
     UPDATE `users_summaries` SET qty = qty + 1, chars = chars + a_chars WHERE id = us_id; 
     UPDATE `users_summaries_days` SET qty = qty + 1, chars = chars + a_chars WHERE id = usd_id; 
     UPDATE `users_summaries_months` SET qty = qty + 1, chars = chars + a_chars WHERE id = usm_id; 

     IF error = 1 THEN 
     SELECT 'error'; 
     ROLLBACK; 
     LEAVE lbl_proc; 
     ELSE 
     SELECT 'success'; 
     COMMIT; 
     END IF; 
    END; 

Maintenant, j'ai des données brutes que je veux alimenter dans cette procédure. Il y a actuellement environ 3000 lignes.

J'ai essayé toutes les solutions que je connaissais:

A. # mysql -uuser -ppass DB < calls.sql - En utilisant php J'ai essentiellement créé une liste des appels comme celui-ci:

CALL NEW_ACTION('2010-11-01 13:23:00', 'username1', 'actionname1', '100', 'http://example.com/', '0.0.0.0'); 
CALL NEW_ACTION('2010-11-01 13:23:00', 'username2', 'actionname1', '100', 'http://example.com/', '0.0.0.0'); 
CALL NEW_ACTION('2010-11-01 13:23:00', 'username1', 'actionname2', '100', 'http://example.com/', '0.0.0.0'); 
... 

Cela échoue toujours (essayé quelques fois) à ligne 452 où il a trouvé deux ID de résumé (étape 3).
Je pensais que cela pouvait être dû au fait que plus tôt (lignes 375-376) il y a des appels pour le même utilisateur pour la même action.
Comme si mysql ne mettait pas à jour les tables à temps, la ligne de résumé créée en CALL à partir de la ligne 375 n'est pas encore visible lorsque la ligne 376 est exécutée - créant ainsi une autre ligne récapitulative.
Je pensais essayer de retarder les appels ...

B. En utilisant le SLEEP(duration) de mysql.
Cela n'a rien changé. L'exécution s'arrête à nouveau exactement au même appel.

Je n'ai plus d'idées maintenant.
Suggestions et aide grandement appréciés.

REMARQUE: les noms d'action et les noms d'utilisateur sont répétés.

PS. Gardez à l'esprit que c'est l'une de mes premières procédures écrites.
PS2. Exécuter MySQL 1.5.52-community-log 64bit (Windows 7U), PHP 5.3.2 et Apache 2.2.17


EDIT

J'ai retiré une partie liée à PHP question à un separate question here .


EDIT2

Ok, j'ai supprimé les 200 premiers appels à partir du fichier sql.Pour une raison quelconque, il s'est bien passé après la ligne précédente qui bloquait l'exécution. Maintenant, il a arrêté à la ligne 1618.
Cela signifierait, qu'à un moment donné une nouvelle ligne récapitulative INSERTed n'est pas visible pour un moment, donc quand il arrive que l'une des itérations suivantes veulent SELECT il, il n'est pas encore accessible pour eux . Est-ce un bug MySQL?


EDIT3

Maintenant, il y a une autre chose intéressante, j'ai remarqué. J'ai étudié où deux users_summaries sont créés. Cela arrive (pas toujours, mais si, alors il l'est) quand il y a deux appels faisant référence aux mêmes user et action à proximité. Ils peuvent être côte à côte ou séparés par 1 ou 2 appels différents.

Si je déplace l'un d'entre eux (dans le fichier .sql) comme 50-100 lignes plus bas (exécuté plus tôt) que c'est bon. J'ai même réussi à faire fonctionner le fichier .sql dans son ensemble. Mais cela ne résout pas vraiment le problème. Avec 3000 lignes ce n'est pas si mal, mais si j'avais 100000, je suis perdu. Je ne peux pas compter sur les réglages manuels au fichier .sql.

+0

@Michal M - Essayez de modifier "SELECT 'erreur"; " à plus verbeux, comme "SELECT" erreur - RETREIVING action.id '; ", si plus facile pour vous d'identifier quelle section est d'avoir une erreur – ajreal

+0

Si nous parlons des deux premières approches (A, B), que je sais exactement quelle section est. En fait, l'erreur 'SELECT 'ne se produit pas du tout. À l'étape 3, j'obtiens 2 lignes, ce qui signifie que plus tôt 'SELECT users_summary.id' a retourné NULL à la place d'un nombre provoquant l'insertion de la deuxième ligne. –

+0

N'importe quelle chance la sélection 'SET a_id = (SELECT' id' FROM 'actions' O WH' name' = a_name); 'retourner plusieurs lignes? – ajreal

Répondre

1

Ce n'est pas vraiment une solution, mais une solution de contournement.

Juste pour clarifier, tableaux de synthèse avaient id colonne comme PRIMARY KEY avec AUTO_INCREMENT option et des index sur les deux colonnes user_id et action_id.

Mon enquête a montré que bien que ma procédure cherchait une entrée qui existait en utilisant WHERE user_id = u_id AND action_id = a_id dans certaines situations, il ne trouve pas causer nouvelle ligne étant insérée avec les mêmes user_id et action_id valeurs - quelque chose que je ne voulais pas.

Débogage la procédure a montré que la ligne de synthèse que je cherchais, mais pas accessible avec WHERE user_id = u_id AND action_id = a_id condition, était bien revenu lors de l'appel est id-PRIMARY KEY.
Avec cette découverte j'ai décidé de changer le format de id colonne, de UNASIGNED INT avec AUTO_INCEREMENT à un CHAR(32) qui consistait en:

<user_id>|<action_id> 

Cela signifiait que je savais exactement ce que le id de la ligne que je voulais est avant même existé. Cela a vraiment résolu le problème. Il m'a également permis d'utiliser la construction INSERT ... ON DUPLICATE KEY UPDATE ....

Voici ma procédure mise à jour:

CREATE PROCEDURE `NEW_ACTION`(
    IN a_date TIMESTAMP, 
    IN u_name VARCHAR(255), 
    IN a_name VARCHAR(255), 
    IN a_chars INT, 
    IN url VARCHAR(255), 
    IN ip VARCHAR(15)) 
    SQL SECURITY INVOKER 

lbl_proc: BEGIN 
    DECLARE a_id, u_id, a_day, a_month, error INT; 
    DECLARE us_id, usd_id, usm_id CHAR(48); 
    DECLARE sep CHAR(1); 
    DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET error = 1; 

    SET sep = '|'; 
    SET error = 0; 
    SET a_day = DATE_FORMAT(SUBSTRING(a_date ,1,10), '%Y%m%d'); 
    SET a_month = SUBSTRING(a_day, 1, 6); 

    /* RETREIVING action.id */ 
    SET a_id = (SELECT `id` FROM `game_actions` WHERE `name` = a_name); 
    IF a_id IS NULL THEN 
     SELECT 'error'; 
     LEAVE lbl_proc; 
    END IF; 

    /* RETREIVING users.id */ 
    SET u_id = (SELECT `id` FROM `game_users` WHERE `name` = u_name); 
    IF u_id IS NULL THEN 
     INSERT INTO `game_users` (name) VALUES (u_name); 
     SET u_id = LAST_INSERT_ID(); 
    END IF; 

    /* SETTING summaries ids */ 
    SET us_id = CONCAT(u_id, sep, a_id); 
    SET usd_id = CONCAT(a_day, sep, u_id, sep, a_id); 
    SET usm_id = CONCAT(a_month, sep, u_id, sep, a_id); 

    /* SAVING action AND UPDATING summaries */ 
    SET autocommit = 0; 
    START TRANSACTION; 
     INSERT INTO `game_users_actions` (`date`, `user_id`, `action_id`, `chars`, `url`, `ip`) 
     VALUES (a_date, u_id, a_id, a_chars, url, ip); 
     INSERT INTO `game_users_summaries` (`id`, `user_id`, `action_id`, `qty`, `chars`) 
     VALUES (us_id, u_id, a_id, 1, a_chars) 
     ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars; 
     INSERT INTO `game_users_summaries_days` (`id`, `day`, `user_id`, `action_id`, `qty`, `chars`) 
     VALUES (usd_id, a_day, u_id, a_id, 1, a_chars) 
     ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars; 
     INSERT INTO `game_users_summaries_months` (`id`, `month`, `user_id`, `action_id`, `qty`, `chars`) 
     VALUES (usm_id, a_month, u_id, a_id, 1, a_chars) 
     ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars; 

     IF error = 1 THEN 
     SELECT 'error'; 
     ROLLBACK; 
     LEAVE lbl_proc; 
     ELSE 
     SELECT 'success'; 
     COMMIT; 
     END IF; 
    END 

Quoi qu'il en soit, je pense toujours qu'il ya une sorte d'un bogue dans MySQL, mais je considère que le problème est résolu.

+0

Pourquoi ne pas créer deux colonnes avec les types appropriés au lieu de ce désordre CHAR (32)? – siride

+0

Ils sont déjà là. Pardonnez-moi si je n'ai pas dit cela assez clairement - MySQL n'a pas pu trouver la ligne si j'ai utilisé ces deux colonnes. –

+0

Et non, je n'aime pas cette solution moi-même, mais c'était le seul moyen de la faire fonctionner. –

Questions connexes