【问题标题】:Calling stored procedure sequentially from .sql file从 .sql 文件顺序调用存储过程
【发布时间】:2011-05-16 02:40:34
【问题描述】:

我被困在这里了。

我有一个程序,我想连续运行 X* 次。 (*X 是几千次)
基于输入数据的过程是这样的:
1. 寻找一个actions.id,如果没有找到LEAVEs。
2.查找users.id,如果没有找到,创建一个并使用LAST_INSERT_ID();
3-5。查找 summaries.id(3 种类型,总计、每日和每月),如果未找到,则创建一个并使用它的 ID。
6. 收集完所有必需的 ID 后,INSERTs 新行进入操作,并更新事务中的摘要行,因此如果有任何失败 - 它会执行 ROLLBACK - 不会造成任何伤害。
7. 视结果而定SELECTs 消息。

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;

现在,我已经获得了想要输入此过程的原始数据。目前大约有 3000 行。

我尝试了所有我知道的解决方案:

A. # mysql -uuser -ppass DB < calls.sql - 使用 php 我基本上创建了一个这样的调用列表:

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');  
...

在第 452 行发现两个摘要 ID(第 3 步)时总是失败(尝试了几次)。
我认为这可能是因为之前(第 375-376 行)有针对相同操作的相同用户的调用。
好像 mysql 没有及时更新表,所以当第 376 行执行时,在第 375 行的 CALL 创建的摘要行还不可见 - 因此创建另一个摘要行。
以为我会尝试延迟通话...

B.使用mysql的SLEEP(duration).
这并没有改变什么。执行再次在同一个 CALL 处停止。

我现在没有主意了。
非常感谢您的建议和帮助。

注意:动作名称和用户名重复。

PS。请记住,这是我编写的第一个程序之一。
PS2。运行 mysql 5.1.52-community-log 64bit (Windows 7U)、PHP 5.3.2 和 Apache 2.2.17


编辑

我已将与 PHP 相关的部分问题移至 separate question here


EDIT2

好的,我已经从 .sql 文件中删除了前 200 个调用。出于某种原因,它顺利通过了停止执行的前一行。现在它停在第 1618 行。
这意味着,在某一时刻,新的INSERTed 摘要行暂时不可见,因此,当以下迭代之一想要SELECT 它时,它们尚无法访问它。这是 MySQL 的错误吗?


EDIT3

现在我注意到了另一件有趣的事情。我调查了创建两个 users_summaries 的位置。当有两个 CALL 非常接近地引用相同的 useraction 时,就会发生这种情况(并非总是如此,但如果是的话)。它们可能彼此相邻,也可能被 1 或 2 个不同的调用分开。

如果我将其中一个(在 .sql 文件中)移动 50-100 行(较早执行)就可以了。我什至设法使 .sql 文件作为一个整体工作。但这仍然不能真正解决问题。有 3000 行还不错,但如果我有 100000 行,我就迷路了。我不能依赖手动调整 .sql 文件。

【问题讨论】:

  • @Michal M - 尝试更改“SELECT 'error';”更详细,例如“SELECT 'error - RETREIVING action.id';”,以便您更轻松地识别哪个部分有错误
  • 如果我们谈论的是前两种方法(A,B),那么我确切地知道哪个部分是。事实上,SELECT 'error' 根本不会发生。在第 3 步,我得到 2 行,这意味着之前的 SELECT users_summary.id 确实返回 NULL 而不是导致插入第二行的数字。
  • 有没有机会选择 SET a_id = (SELECT id` FROM actions WHERE name = a_name);` 返回多行?
  • 不。 actions 表是预定义的,不会在过程中进行修改。 name 列有一个唯一键,所以总是返回一个结果。
  • 使用 EDIT2 再次更新问题

标签: mysql stored-procedures


【解决方案1】:

这并不是真正的解决方案,而是一种解决方法。

澄清一下,汇总表的 id 列为 PRIMARY KEY,带有 AUTO_INCREMENT 选项,并且在 user_idaction_id 列上都有索引。

我的调查表明,尽管我的程序在某些情况下使用WHERE user_id = u_id AND action_id = a_id 查找存在的条目,但它没有发现它会导致插入具有相同user_id 和@ 的新行987654328@ 值 - 我不想要的东西。

调试过程显示我正在寻找的摘要行虽然无法通过WHERE user_id = u_id AND action_id = a_id 条件访问,但在调用它时正确返回id - PRIMARY KEY
有了这个发现,我决定将id 列的格式从UNASIGNED INTAUTO_INCEREMENT 更改为CHAR(32),其中包括:

<user_id>|<action_id>

这意味着我甚至在我想要的行的id 存在之前就确切地知道它是什么。这确实解决了问题。它还使我能够使用INSERT ... ON DUPLICATE KEY UPDATE ... 构造。

在我更新的程序下方:

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

无论如何,我仍然认为 MySQL 中存在某种错误,但我认为问题已解决。

【讨论】:

  • 为什么不创建两个具有正确类型的列而不是 CHAR(32) 混乱?
  • 他们已经在那里了。如果我说得不够清楚,请原谅我 - 如果我使用这两列,MySQL 找不到该行。
  • 不,我自己不喜欢这个解决方案,但这是让它发挥作用的唯一方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-10
  • 1970-01-01
  • 2019-05-11
  • 2014-03-30
  • 2015-05-08
相关资源
最近更新 更多