未找到基表或视图 - 尽管它应该

时间:2017-09-28 14:39:04

标签: php mysql mariadb

我有一个每天自动更新一次的mysql表。执行此更新的脚本与下面的PHP代码类似。它基本上做了什么,它使一个具有相同结构的临时表,首先插入所有数据,然后截断实际表并从临时表中插入数据。最后临时表被销毁了。使用此方法是因为它显着降低了实际表的停机时间。

很长一段时间以来一切正常。直到最近,随着Debian的升级(从Debian 8到Debian 9,这也意味着从MySQL切换到MariaDB)。现在我经常在日志文件中找到此错误消息:

[Wed Sep 27 06:03:04.903652 2017] [:error] [pid 27393] [client 127.0.0.1:36794] PHP Fatal error:  Uncaught PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'database.table_temp' doesn't exist in /someFile.php:50\nStack trace:\n#0 /someFile.php(50): PDOStatement->execute()\n#1 {main}\n  thrown in /someFile.php on line 50

此错误最终会导致某些数据丢失。如果我理解正确,它说临时表在插入完成之前就被销毁了,对吧?但是如果发生这种情况,考虑到这不是真正的TEMPORARY TABLE,我只使用一个真实的表并将其命名为temp。 MariaDB是否使INSERT延迟,以便php脚本可以在INSERT完成之前执行DROP?

这是cron.d条目,因此该脚本实际上每天只执行一次:

1   6   *   *   *    root      /usr/bin/wget -q http://www.example.com/someFile.php -O /dev/null

同样值得注意的是,脚本在一两个小时内完成执行,因此前一天的执行时间很长,并且不会干扰。

这基本上就是代码:

$dataArray = []; // much data

$db->query("DROP TABLE IF EXISTS `table_temp`;");
$db->query("CREATE TABLE `table_temp` LIKE `table`;");

$tres = $db->prepare("
    INSERT INTO `table_temp`
    (`field1`, `field2`)
    VALUES
    (:field1, :field2)
");
foreach($dataArray as $data){
    $db->beginTransaction();
    $res->execute();
    foreach($data as $item){
        $tres->bindParam('field1', $item['field1'], PDO::PARAM_INT);
        $tres->bindParam('field2', $item['field2']);
        $tres->execute(); // line 50
    }
    $db->commit();
}

$db->query("RENAME TABLE `table` TO `table_old`, `table_temp` TO `table`");
$db->query("DROP TABLE `table_old`");

2 个答案:

答案 0 :(得分:0)

降;创建; (可能是一把锁)

通过锁定延迟的命令被添加到"队列"按照他们被称为的顺序。

mySql命令DROPCREATE都会在受影响的表上应用TABLE_LOCK。这意味着在CREATE释放DELETE之前,您的TABLE_LOCK不会执行。

可能发生的事情是,要运行的命令的顺序可能如下所示:(因为准备和排队CREATE命令需要的时间比前几个INSERTS

  1. DELETE TABLE(锁定表)
  2. INSERT ...
  3. INSERT ...
  4. INSERT ...
  5. CREATE TABLE(锁定表)
  6. INSERT ...
  7. PHP不等待mysql命令完成...

    ...它只是继续发送命令的快乐方式。 那个是您问题的根源。

    innoDB交易

    无法使用交易来解决问题。 CREATE and DROP will commit a transaction

    仔细查看您的$db对象。

    使用一个会根据查询的成功执行返回结果,例如PDO::exec。然后你应该能够将你的INSERT语句包装在一个条件块中(PHP必须等待响应才能检查条件)。

    $result = $db->exec('CREATE ...');
    
    if (false !== $result && $result >= 0) {
        foreach($dataArray as $data){
            // INSERT
        }
    }
    

答案 1 :(得分:0)

打嗝的可能性为零:

CREATE TABLE new LIKE real;
...load the data into `new`...
RENAME TABLE real TO old, new TO real;  -- Atomic and 'instantaneous'
DROP TABLE old;

不需要交易等等。

一个潜在的问题是“负载”。可能是从一些移动目标中获取数据。

另一项优化

使用'批次' INSERT - 这是一个包含多行的INSERT语句。它的速度是1行INSERTs的等效数的10倍。我建议每批100-1000行。