【发布时间】: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 非常接近地引用相同的 user 和 action 时,就会发生这种情况(并非总是如此,但如果是的话)。它们可能彼此相邻,也可能被 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 = (SELECTid` FROMactionsWHEREname= a_name);` 返回多行? -
不。
actions表是预定义的,不会在过程中进行修改。name列有一个唯一键,所以总是返回一个结果。 -
使用 EDIT2 再次更新问题