PDOException和DROP TABLE IF EXISTS的问题

3
我正在使用PDO调用以DROP TABLE IF EXISTS开头的存储过程。我随机地遇到PDOException 'SQLSTATE [42S02]:Base table or view not found:1146 Table' historygr.reached '不存在',更令人烦恼的是,它在几秒钟内就会从告诉我表不存在的异常转为抛出异常说该表已经存在,看起来是来自同一个连接。
我无法自己触发错误,但我会收到错误通知。
以下是产生错误的PHP代码段:
$dbh = PDODB::getInstance();
$stmt = $dbh->query("CALL ListReached(".$this->item_id.")"); // <-- ERROR
$items = $stmt->fetchAll();

以下是MySQL存储过程定义:

DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `ListReached`( IN root INT)
BEGIN
    DECLARE rows SMALLINT DEFAULT 0;

    DROP TABLE IF EXISTS reached;
    CREATE TABLE reached(
    node_id INT PRIMARY KEY
    ) ENGINE=HEAP;

    INSERT INTO reached VALUES (root);
    SET rows = ROW_COUNT();

    WHILE rows > 0 DO
    INSERT IGNORE INTO reached
        SELECT DISTINCT child_id 
        FROM related_item AS r
        INNER JOIN reached AS p ON r.parent_id = p.node_id;
    SET rows = ROW_COUNT();

    INSERT IGNORE INTO reached
        SELECT DISTINCT parent_id
        FROM related_item AS r
        INNER JOIN reached AS p ON r.child_id = p.node_id;
    SET rows = rows + ROW_COUNT();
    END WHILE;

    DELETE FROM reached WHERE node_id = root;

    SELECT * FROM reached;
    DROP TABLE reached;

END
1个回答

1

您遇到了竞态条件。

如果两个连接都在执行相同的脚本,则它们将同时创建和删除相同的表,导致冲突。

考虑使用临时表而不是在事务中创建和删除真实表。

http://dev.mysql.com/doc/refman/5.1/en/create-table.html

在创建表时,您可以使用TEMPORARY关键字。 TEMPORARY表仅对当前连接可见,并在关闭连接时自动删除。这意味着两个不同的连接可以使用相同的临时表名称而不会互相冲突或与同名的现有非TEMPORARY表发生冲突。(现有表在临时表被删除之前是隐藏的。)要创建临时表,您必须具有CREATE TEMPORARY TABLES特权。
编辑:
如评论中所述,查询引用了表多次。
另一种方法是在过程中使用锁:http://dev.mysql.com/doc/refman/5.5/en/miscellaneous-functions.html#function_get-lock

很遗憾,无法使用临时表,因为在查询中不能多次引用它们,而这正是该过程的功能所在。 - aron.duby
是的,如果要多次引用临时表,您需要创建该表的副本(每个引用n-1个副本),或更改查询方式,避免多次引用临时表。 - Martin Samson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接