【问题标题】:Doctrine Data Import - Common way to do a fallback?Doctrine Data Import - 进行后备的常用方法?
【发布时间】:2013-06-03 11:44:23
【问题描述】:

我编写了一个 symfony 命令作为日常 cronjob 执行。 任务是将数据从不同的 CSV 文件导入数据库(干净的表,不更新)。

到目前为止,这很容易,但如果导入失败,我还需要回退旧数据。以非教条的方式,我会做以下事情:

  1. 在后缀为 _tmp 的表中导入数据。
  2. 如果一切正常:重命名原始表并添加后缀 _backup, 从新表中删除 _tmp 后缀。

    如果出现问题:删除 _tmp-Tables 并且原始表保持原样。

对于学说,我找不到任何好的文档来完成这项任务——而且我目前的知识还不够好)。我唯一发现的是这个帖子:Doctrine Backuptables 但是我不太了解它,不知道这是否是常见的方式。

我实施的解决方法是这样的:

// Backup original Table, Truncate Original Table, Import new Data
$dbpw = $this->getContainer()->getParameter('database_password');
$dbuser = $this->getContainer()->getParameter('database_user');
$dbname = $this->getContainer()->getParameter('database_name');
exec('mysqldump -u '.$dbuser.' -p'.$dbpw.' '.$dbname.' customer > '.$dbBackupPath.'customer.sql');

$connection = $em->getConnection();
$plattform = $connection->getDatabasePlatform();
$connection->executeQuery('SET FOREIGN_KEY_CHECKS = 0;');
$truncateSQL = $plattform->getTruncateTableSQL('customer');
$connection->executeUpdate($truncateSQL);
$connection->executeQuery('SET FOREIGN_KEY_CHECKS = 1;');

try
{
   $em->flush();
}
catch(\Exception $e)
{
   exec('mysql -u '.$dbuser.' -p'.$dbpw.' '.$dbname.' < '.$dbBackupPath.'customer.sql');
}

但我不认为这是一个好的解决方案,即使它有效?

我想问你有经验的人我该怎么办?

非常感谢

【问题讨论】:

    标签: mysql symfony import doctrine-orm fallback


    【解决方案1】:

    使用 Doctrine2 或 SQL 的显式事务机制,而不是备份数据库、修改其中的数据以及可能在出现任何问题时恢复您的数据库。

    本质上,在显式事务中,您可以执行多次插入、更新、删除等操作。在显式事务中,开发人员可以完全控制将哪些数据提交到数据库以及何时提交。而且,如果开发人员选择这样做,您甚至可以回滚显式事务,使数据库处于事务开始之前的状态。


    概念和更多信息

    MySQL 显式事务引用:https://dev.mysql.com/doc/refman/5.0/en/commit.html

    Doctrine2 显式事务引用:http://docs.doctrine-project.org/en/2.0.x/reference/transactions-and-concurrency.html


    示例代码

    我整理了一些从 CSV 文件 customers.csv 读取的示例代码,并将该数据导入数据库。只有正确插入数据,才会将其提交到数据库。如果出现问题,数据库将回滚到之前的状态。

    我已经使用了两个beginTransaction()s,但是,这个代码示例不需要最里面的一个才能正常工作。最里面的那个是作为提醒的。在您的实际实现中,您可能会调用一个函数来导入 csv 数据(而不是将其复制/粘贴到此块中),最里面的 beginTransaction()commit()rollback() 应该环绕任何函数用于导入 CSV 数据。

    $className = 'Customer';
    $con = $em->getConnection();
    $tableName = $em->getClassMetadata($className)->getTableName();
    
    // start an explicit transaction
    $con->beginTransaction();
    try {
        // remove the old data from the customers table
        $plat = $con->getDatabasePlatform();
        $con->executeQuery('SET FOREIGN_KEY_CHECKS = 0;');
        $con->executeQuery('DELETE FROM '.$tableName);
        $con->executeQuery('SET FOREIGN_KEY_CHECKS = 1;');
    
        // start another explicit transaction
        $con->beginTransaction();
        try {
                // import new customer data from `customers.csv`
                if (false !== $handle = fopen("customers.csv", "r")) {
                        // read in a row of CSV data
                        while (false !== $data = fgetcsv($handle)) {
                                // $data[0] is the first column in the csv file
                                // and it holds the customer's name.
                                $customer = new Customer();
                                $customer->setName(trim($data[0]));
                                $em->persist($customer);
                        }
                }
                // commit the imported data to the database
                $em->flush();
                $con->commit();
        } catch(\Exception $e) {
                $con->rollback();
                throw $e; // rethrow to restore database to its original state.
        }
    
        // If we made it here, then all rows in the customer data table were
        // removed and all new customer data was imported properly. So, save
        // everything to the database.
        $em->flush();
        $con->commit();
    } catch (Exception $e) {
        // If we made it here, then something went wrong and the database is in
        // an unstable state. So, rollback to the previously stable state.
        $con->rollback();
    }
    

    注意事项

    在显式事务(beginTransaction()commit()rollback() 之间的所有内容)中,您不应该执行数据定义语言 (DDL) 语句 - 例如,ALTER TABLETRUNCATE TABLE等等,因为所有 DDL 都会对数据库执行隐式提交,这意味着 rollback() 不会按预期执行。我在这里写了更多关于它的内容:How to truncate a table using Doctrine 2?

    如果你仔细阅读上面的代码,你会注意到我使用了DELETE FROM而不是TRUNCATE,因为TRUNCATE是一个DDL语句。重要的是要知道DELETE FROM 不等同于TRUNCATEDELETE FROM 不会重置 AUTO_INCREMENT。因此,如果您的旧客户表中有 1032 个条目,并且您运行上述代码,那么您新插入的客户表将从自动增量值 1033 开始。

    您如何解决这个问题?好吧,首先——您不必重置自动增量值。对数据库而言,重要的是id 字段是唯一的。但是,如果您真的必须重置自动增量值,那么我会说您有两个选择:在导入数据时显式设置customer.id,或执行ALTER TABLE 语句。 ALTER TABLE 语句听起来很简单,但它很容易出现问题,因为它是另一个 DDL 语句,它将执行对数据库的隐式提交——这与上述代码使用 DELETE FROM 而不是 TRUNCATE 的原因相同。根据我对您的特殊情况的了解,我会在从 CSV 文件导入数据时显式设置 id 属性。

    记得彻底测试你的实现!交易可能会变得混乱。

    【讨论】:

    • Afaik Doctrine 已经在 $em->flush() 上进行了交易。因此,如果出现错误,数据库不会发生任何事情。但无论如何,最好有一个额外的备份。
    • 问题是,我必须在调用 $em->flush() 之前截断表。 (干净导入)但是当 flush() 失败时,我有一个空数据库。
    • 在#doctrine (IRC) 上,我得到一个提示,通过使用显式事务将截断添加到事务中:docs.doctrine-project.org/en/2.0.x/reference/… 不幸的是,我没有时间直接尝试,但我希望我可以本周做,看看它是否有效。
    猜你喜欢
    • 2013-06-11
    • 2017-01-19
    • 1970-01-01
    • 2012-02-04
    • 2014-04-29
    • 1970-01-01
    • 2011-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多