简体   繁体   English

PDO MySQL 备份功能

[英]PDO MySQL backups function

This function here http://davidwalsh.name/backup-mysql-database-php这个函数在这里http://davidwalsh.name/backup-mysql-database-php

Has been floating around the internets for a while and is pretty famous, but it's using old MySQL API.已经在互联网上流传了一段时间并且非常有名,但它使用的是旧的 MySQL API。 Does anyone have the same but in PDO?有没有人有相同的但在 PDO 中? If not does anyone want to make one?如果没有,有人想做一个吗?

Is it even possible, I read somewhere that PDO doesn't do SHOW CREATE TABLE - is that right?甚至有可能吗,我在某处读到 PDO 不做 SHOW CREATE TABLE - 是吗?

To anyone looking for the function which acts like the mysqldump, here is the latest draft, with the imperfections discussed in the comments above/below ironed out.对于任何寻找类似 mysqldump 的功能的人来说,这里是最新的草案,上面/下面的评论中讨论的不完善之处已经解决。

require 'login.php';
$DBH = new PDO("mysql:host=$db_hostname;dbname=$db_database; charset=utf8", $db_username, $db_password);

//put table names you want backed up in this array.
//leave empty to do all
$tables = array();

backup_tables($DBH, $tables);

function backup_tables($DBH, $tables) {
    $DBH->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL);

    //Script Variables
    $compression = false;
    $BACKUP_PATH = "";
    $nowtimename = time();

    //create/open files
    if ($compression) {
        $zp = gzopen($BACKUP_PATH . $nowtimename . '.sql.gz', "a9");
    } else {
        $handle = fopen($BACKUP_PATH . $nowtimename . '.sql', 'a+');
    }

    //array of all database field types which just take numbers
    $numtypes = array('tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'float', 'double', 'decimal', 'real');

    //get all of the tables
    if (empty($tables)) {
        $pstm1 = $DBH->query('SHOW TABLES');
        while ($row = $pstm1->fetch(PDO::FETCH_NUM)) {
            $tables[] = $row[0];
        }
    } else {
        $tables = is_array($tables) ? $tables : explode(',', $tables);
    }

    //cycle through the table(s)

    foreach ($tables as $table) {
        $result = $DBH->query("SELECT * FROM $table");
        $num_fields = $result->columnCount();
        $num_rows = $result->rowCount();

        $return = "";
        //uncomment below if you want 'DROP TABLE IF EXISTS' displayed
        //$return.= 'DROP TABLE IF EXISTS `'.$table.'`;';

        //table structure
        $pstm2 = $DBH->query("SHOW CREATE TABLE $table");
        $row2 = $pstm2->fetch(PDO::FETCH_NUM);
        $ifnotexists = str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $row2[1]);
        $return .= "\n\n" . $ifnotexists . ";\n\n";

        if ($compression) {
            gzwrite($zp, $return);
        } else {
            fwrite($handle, $return);
        }
        $return = "";

        //insert values
        if ($num_rows) {
            $return = 'INSERT INTO `' . $table . '` (';
            $pstm3 = $DBH->query("SHOW COLUMNS FROM $table");
            $count = 0;
            $type = array();

            while ($rows = $pstm3->fetch(PDO::FETCH_NUM)) {
                if (stripos($rows[1], '(')) {
                    $type[$table][] = stristr($rows[1], '(', true);
                } else {
                    $type[$table][] = $rows[1];
                }

                $return .= "`" . $rows[0] . "`";
                $count++;
                if ($count < ($pstm3->rowCount())) {
                    $return .= ", ";
                }
            }

            $return .= ")" . ' VALUES';

            if ($compression) {
                gzwrite($zp, $return);
            } else {
                fwrite($handle, $return);
            }
            $return = "";
        }
        $count = 0;
        while ($row = $result->fetch(PDO::FETCH_NUM)) {
            $return = "\n\t(";

            for ($j = 0; $j < $num_fields; $j++) {

                //$row[$j] = preg_replace("\n","\\n",$row[$j]);

                if (isset($row[$j])) {

                    //if number, take away "". else leave as string
                    if ((in_array($type[$table][$j], $numtypes)) && (!empty($row[$j]))) {
                        $return .= $row[$j];
                    } else {
                        $return .= $DBH->quote($row[$j]);
                    }
                } else {
                    $return .= 'NULL';
                }
                if ($j < ($num_fields - 1)) {
                    $return .= ',';
                }
            }
            $count++;
            if ($count < ($result->rowCount())) {
                $return .= "),";
            } else {
                $return .= ");";
            }
            if ($compression) {
                gzwrite($zp, $return);
            } else {
                fwrite($handle, $return);
            }
            $return = "";
        }
        $return = "\n\n-- ------------------------------------------------ \n\n";
        if ($compression) {
            gzwrite($zp, $return);
        } else {
            fwrite($handle, $return);
        }
        $return = "";
    }

    $error1 = $pstm2->errorInfo();
    $error2 = $pstm3->errorInfo();
    $error3 = $result->errorInfo();
    echo $error1[2];
    echo $error2[2];
    echo $error3[2];

    if ($compression) {
        gzclose($zp);
    } else {
        fclose($handle);
    }
}

That backup script is ridiculous and no one should make another version of it.那个备份脚本很荒谬,没有人应该制作它的另一个版本。 I've seen that script before, as well as similar attempts, and they have a lot of problems:之前看过那个脚本,也有类似的尝试,问题很多:

  • Doesn't delimit table names in back-ticks不在反引号中分隔表名
  • Doesn't handle NULLs不处理 NULL
  • Doesn't handle character sets不处理字符集
  • Doesn't handle binary data不处理二进制数据
  • Doesn't back up VIEWs不备份视图
  • Doesn't back up TRIGGERs or STORED PROCEDUREs or STORED FUNCTIONs or EVENTs不备份触发器或存储过程或存储函数或事件
  • Uses obsolete mysql extension (but this is why you want a PDO version, isn't it?)使用过时的 mysql 扩展(但这就是您想要 PDO 版本的原因,不是吗?)
  • Uses addslashes() instead of a proper MySQL escaping function.使用 addlashes() 而不是适当的 MySQL 转义函数。
  • Appends all data for all tables into one really long string, before outputting the whole content.在输出整个内容之前,将所有表的所有数据附加到一个非常长的字符串中。 This means you have to be able to store your entire database in one string, which will almost certainly blow out your PHP max memory limit.这意味着您必须能够将整个数据库存储在一个字符串中,这几乎肯定会超出您的 PHP 最大内存限制。

See also my past answer about the unfortunate David Walsh backup script:另请参阅我过去关于不幸的 David Walsh 备份脚本的回答:


Re your comment:回复您的评论:

Read the comments on the page you linked to.阅读您链接到的页面上的评论。 A lot of folks have identified problems, and some have fixes or at least suggestions.很多人已经确定了问题,有些人有修复或至少建议。

The fact that this script appends everything into one string is a deal-breaker, I think, but it shouldn't be difficult to change the script to open the output file first , then output each row's data during the loop, then close the file after the loop.这个脚本附加万事成一个字符串其实是个致命弱点,我想,但它不应该是很难改变的脚本在循环过程首先打开输出文件,然后输出每行的数据,然后关闭该文件循环后。 That's kind of a no-brainer, I'm not sure why the script doesn't do that.这很简单,我不确定为什么脚本不这样做。 But it's pretty clear that the script was not tested very well.但很明显,该脚本没有经过很好的测试。

But anyway, I would not try to reinvent this wheel.但无论如何,我不会尝试重新发明这个轮子。 Mysqldump or mydumper do this job fine. Mysqldump 或 mydumper 可以很好地完成这项工作。 FWIW, you don't have to run mysqldump on the same server where the database resides. FWIW,您不必在数据库所在的同一台服务器上运行 mysqldump。 Mysqldump supports an option for --host so you can run mysqldump anywhere to back up a remote database, as long as firewalls don't block your client from connecting. Mysqldump 支持--host选项,因此您可以在任何地方运行 mysqldump 来备份远程数据库,只要防火墙不阻止您的客户端连接。 Basically, if you can connect a PHP app to the database from some client host, you can connect mysqldump.基本上,如果您可以将 PHP 应用程序从某个客户端主机连接到数据库,则可以连接 mysqldump。

If that's really not an option, then I would use the database dump feature of phpmyadmin.如果这真的不是一个选项,那么我将使用 phpmyadmin 的数据库转储功能。 These are mature and well-tested and they dump everything correctly.这些都是成熟且经过良好测试的,并且可以正确转储所有内容。 Here's an article that describes how to use the dump feature:这是一篇介绍如何使用转储功能的文章:

http://www.techrepublic.com/blog/smb-technologist/import-and-export-databases-using-phpmyadmin/ http://www.techrepublic.com/blog/smb-technologist/import-and-export-databases-using-phpmyadmin/


[Copying my comments from your answer:] [从你的回答中复制我的评论:]

This is getting into code review, which is not the purpose of StackOverflow.这是进入代码审查,这不是 StackOverflow 的目的。 But briefly:但简单地说:

  • no proper support for NULL (you convert them to '');没有对 NULL 的适当支持(您将它们转换为 '');
  • not consistently delimiting table names;不始终如一地分隔表名;
  • using non-ANSI double-quotes as string delimiters;使用非 ANSI 双引号作为字符串分隔符;
  • using buffered queries on huge tables will break PHP max memory limit;在大表上使用缓冲查询将打破 PHP 最大内存限制;
  • appending all rows for a huge table will break PHP max memory limit;为大表追加所有行将打破 PHP 最大内存限制;
  • using addslashes() instead of PDO::quote();使用 addlashes() 而不是 PDO::quote();
  • checking for query errors only at the end of the function;仅在函数结束时检查查询错误;
  • not checking for failed file creation;不检查失败的文件创建;
  • gzip extension may not be loaded可能未加载 gzip 扩展
  • Also, probably still doesn't support UTF8 data.此外,可能仍然不支持 UTF8 数据。

but it is getting there, no?但它正在到达那里,不是吗?

Yes, this is better than the original David Walsh script.是的,这比原始的大卫沃尔什剧本要好。 :-) :-)

whats wrong with NULLs to ''? NULL 到 '' 有什么问题?

NULL is not the same as '' in SQL (except in Oracle, but they are not complying with the SQL standard in this case). NULL 与 SQL 中的 '' 不同(Oracle 中除外,但在这种情况下它们不符合 SQL 标准)。 See MySQL, better to insert NULL or empty string?MySQL,最好插入 NULL 还是空字符串?

table structure has to be very very big to max memory.表结构必须非常大才能达到最大内存。 each insert row is written to file individually so again, row has to be very very big to max memory.每个插入行都单独写入文件,因此再次,行必须非常大才能达到最大内存。

I misread the code on the memory limit issue.我误读了有关内存限制问题的代码。 You are writing output for each row, so that's okay (unless the row contains a 1GB blob or something).您正在为每一行写入输出,所以没关系(除非该行包含 1GB 的 blob 或其他内容)。

But you shouldn't just output a single INSERT statement with a comma-separated set of rows.但是您不应该只输出带有逗号分隔的一组行的单个 INSERT 语句。 Even mysqldump --extended-insert outputs a finite length of data, then starts a new INSERT statement.甚至mysqldump --extended-insert输出有限长度的数据,然后开始一个新的 INSERT 语句。 The criteria is whether the length of the INSERT statement fits within the option argument for --net-buffer-length .标准是 INSERT 语句的长度是否适合--net-buffer-length的选项参数。

whats with wrong with "" string delimiters? "" 字符串分隔符有什么问题? how do i get the ANSI one?我如何获得ANSI?

In ANSI SQL, single-quotes '' are used to delimit string literals or date literals.在 ANSI SQL 中,单引号 '' 用于分隔字符串文字或日期文字。 Double-quotes "" are used to delimit identifiers like table name or column names.双引号 "" 用于分隔标识符,如表名或列名。 By default, MySQL treats them the same, but this is non-standard.默认情况下,MySQL 将它们视为相同,但这是非标准的。 See Do different databases use different name quote?请参阅不同的数据库是否使用不同的名称引用? . . If you try to import your backup data on a MySQL server where you have SET SQL_MODE=ANSI_QUOTES , the import will fail.如果您尝试在具有SET SQL_MODE=ANSI_QUOTES的 MySQL 服务器上导入备份数据,则导入将失败。

and what tables aren't delimited?哪些表没有分隔?

Example: query('SELECT * FROM '.$table);示例: query('SELECT * FROM '.$table); and in fact each of the other cases where you use $table in a query.实际上,您在查询中使用 $table 的其他每种情况。 You only delimited the table once, in the INSERT statement your script outputs.您只在脚本输出的 INSERT 语句中对表进行了一次分隔。

all $tables aren't delimited, do they all need to be with " "?所有 $tables 都没有分隔,它们是否都需要带有“”?

MySQL always recognizes back-ticks as identifier delimiters, and single-quotes for strings/dates. MySQL 总是将反引号识别为标识符分隔符,以及字符串/日期的单引号。 But double-quotes change meaning depending on the SQL_MODE I mentioned.但是双引号的含义取决于我提到的 SQL_MODE。 You can't assume which SQL_MODE is in effect on the MySQL instance you restore on, so it's best if you use the back-ticks for identifiers, and single-quotes for strings.您不能假设哪个 SQL_MODE 对您还原的 MySQL 实例有效,因此最好对标识符使用反引号,对字符串使用单引号。 The reason you'd delimit them as you query your table is that you might have table names that are SQL reserved words, or which contain special characters, etc.您在查询表时将它们分隔的原因是您的表名可能是 SQL 保留字,或者包含特殊字符等。

can you insert floats without delimiters into mysql, or do the need the ''?你能在mysql中插入没有分隔符的浮点数,还是需要''? thanks谢谢

You can insert all numeric types without delimiters.您可以插入所有没有分隔符的数字类型。 Only strings and dates need delimiters.只有字符串和日期需要分隔符。 See dev.mysql.com/doc/refman/5.6/en/literals.html见 dev.mysql.com/doc/refman/5.6/en/literals.html

All PDO and ext/mysql do are wrap commands to the underlying database (MySQL in this case).所有PDOext/mysql所做的都是将命令包装到底层数据库(在本例中为 MySQL)。 That is to say that there is nothing stopping PDO from running SHOW CREATE TABLE or the other commands.也就是说,没有什么可以阻止PDO运行SHOW CREATE TABLE或其他命令。

For all intents and purposes you can pretty much just replace:对于所有意图和目的,您几乎可以替换:

- $link = mysql_connect($host,$user,$pass);
- mysql_select_db($name,$link);
+ $link = new PDO("mysql:host=$host;dbname=$name", $user, $pass);

And instead of而不是

$result = mysql_query($query);
mysql_fetch_assoc($result);

Use采用

$result = $link->query($query);
$result->fetch();

As recommended by https://stackoverflow.com/a/18281687/2259391 use mysqldump with exec .正如https://stackoverflow.com/a/18281687/2259391推荐的那样,将mysqldumpexec一起使用。 It boils down to this:归结为:

<?php

function importDatabase($host, $user, $password, $database, $backupFilePath)
{
    //returns true iff successfull
    return exec('mysqlimport --host '. $host .' --user '. $user .' --password '. $password .' '. $database .' '.targetFilePath) === 0;
}

function exportDatabase($host, $user, $password, $database, $targetFilePath)
{
    //returns true iff successfull
    return exec('mysqldump --host '. $host .' --user '. $user .' --password '. $password .' '. $database .' --result-file='.targetFilePath) === 0;
}

i've just finished making the PDO version of david walsh's original backup function.我刚刚完成了 david walsh 原始备份功能的 PDO 版本。
i've also improved it to address the issues mentioned in Bill Karwin's answer ;我还对其进行了改进以解决Bill Karwin 的回答中提到的问题 handles NULL, writes indvidual lines so no memory issues, with backticks etc.处理 NULL,写入单独的行,因此没有内存问题,带有反引号等。
Gives out pretty much exactly what mysqldump does.给出了几乎 mysqldump 所做的事情。
Could do with a little tidying but here it is, please advise on any improvements可以稍微整理一下,但在这里,请就任何改进提出建议

require 'login.php';
$DBH = new PDO("mysql:host=$db_hostname;dbname=$db_database; charset=utf8", $db_username, $db_password);




//put table names you want backed up in this array.
//leave empty to do all
$tables = array();

backup_tables($DBH, $tables);



function backup_tables($DBH, $tables) {

$DBH->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING );

//Script Variables
$compression = false;
$BACKUP_PATH = "";
$nowtimename = time();


//create/open files
if ($compression) {
$zp = gzopen($BACKUP_PATH.$nowtimename.'.sql.gz', "w9");
} else {
$handle = fopen($BACKUP_PATH.$nowtimename.'.sql','a+');
}


//array of all database field types which just take numbers 
$numtypes=array('tinyint','smallint','mediumint','int','bigint','float','double','decimal','real');

//get all of the tables
if(empty($tables)) {
$pstm1 = $DBH->query('SHOW TABLES');
while ($row = $pstm1->fetch(PDO::FETCH_NUM)) {
$tables[] = $row[0];
}
} else {
$tables = is_array($tables) ? $tables : explode(',',$tables);
}

//cycle through the table(s)

foreach($tables as $table) {
$result = $DBH->query('SELECT * FROM '.$table);
$num_fields = $result->columnCount();
$num_rows = $result->rowCount();

$return="";
//uncomment below if you want 'DROP TABLE IF EXISTS' displayed
//$return.= 'DROP TABLE IF EXISTS `'.$table.'`;'; 


//table structure
$pstm2 = $DBH->query('SHOW CREATE TABLE '.$table);
$row2 = $pstm2->fetch(PDO::FETCH_NUM);
$ifnotexists = str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $row2[1]);
$return.= "\n\n".$ifnotexists.";\n\n";


if ($compression) {
gzwrite($zp, $return);
} else {
fwrite($handle,$return);
}
$return = "";

//insert values
if ($num_rows){
$return= 'INSERT INTO `'.$table."` (";
$pstm3 = $DBH->query('SHOW COLUMNS FROM '.$table);
$count = 0;
$type = array();

while ($rows = $pstm3->fetch(PDO::FETCH_NUM)) {

if (stripos($rows[1], '(')) {$type[$table][] = stristr($rows[1], '(', true);
} else $type[$table][] = $rows[1];

$return.= $rows[0];
$count++;
if ($count < ($pstm3->rowCount())) {
$return.= ", ";
}
}

$return.= ")".' VALUES';

if ($compression) {
gzwrite($zp, $return);
} else {
fwrite($handle,$return);
}
$return = "";
}

while($row = $result->fetch(PDO::FETCH_NUM)) {
$return= "\n\t(";
for($j=0; $j<$num_fields; $j++) {
$row[$j] = addslashes($row[$j]);
//$row[$j] = preg_replace("\n","\\n",$row[$j]);


if (isset($row[$j])) {
//if number, take away "". else leave as string
if (in_array($type[$table][$j], $numtypes)) $return.= $row[$j] ; else $return.= '"'.$row[$j].'"' ;
} else {
$return.= '""';
}
if ($j<($num_fields-1)) {
$return.= ',';
}
}
$count++;
if ($count < ($result->rowCount())) {
$return.= "),";
} else {
$return.= ");";

}
if ($compression) {
gzwrite($zp, $return);
} else {
fwrite($handle,$return);
}
$return = "";
}
$return="\n\n-- ------------------------------------------------ \n\n";
if ($compression) {
gzwrite($zp, $return);
} else {
fwrite($handle,$return);
}
$return = "";
}



$error1= $pstm2->errorInfo();
$error2= $pstm3->errorInfo();
$error3= $result->errorInfo();
echo $error1[2];
echo $error2[2];
echo $error3[2];

if ($compression) {
gzclose($zp);
} else {
fclose($handle);
}
}
function backupDB()
{
    $db_config = getDbConfigFromWordPress();
    if ($db_config === false) {
        unset($db_config);
        logMessage('Unable to get database configuration from WordPress', true, 'red');
        return false;
    }

    $new_backup_file = __DIR__ . DIRECTORY_SEPARATOR . 'newbackup_xxx_date.sql';
    if (is_file($new_backup_file) && is_writable($new_backup_file)) {
        @unlink($new_backup_file);
    } elseif (is_file($new_backup_file) && !is_writable($new_backup_file)) {
        logMessage('Unable to remove new backup SQL file. This is necessary to create backup SQL file.', true, 'red');
        return false;
    }
    unset($new_backup_file);

    $dbh = new \PDO('mysql:dbname=' . $db_config['dbname'] . ';host=' . $db_config['dbhost'] . ';charset=' . $db_config['dbcharset'], $db_config['dbuser'], $db_config['dbpassword']);
    $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    $dbh->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_OBJ);
    $dbh->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);

    $sth = $dbh->prepare('SHOW TABLES');
    $sth->execute();
    $result = $sth->fetchAll(\PDO::FETCH_COLUMN);
    $tables = [];
    if (is_array($result) && !empty($result)) {
        foreach ($result as $row) {
            if (is_string($row) && stristr($row, $db_config['tableprefix']) !== false) {
                $tables[] = $row;
            } elseif (is_array($row) && array_key_exists(0, $row) && stristr($row[0], $db_config['tableprefix']) !== false) {
                $tables[] = $row[0];
            }
        }// endforeach;
        natcasesort($tables);
    }
    $sth->closeCursor();
    unset($result, $row, $sth);

    // begins export string header.
    $export_sql = '-- Manual backup SQL Dump'."\n\n";
    $export_sql .= 'SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";'."\n\n\n";
    $export_sql .= '--'."\n";
    $export_sql .= '-- Database: `' . $db_config['dbname'] . '`'."\n";
    $export_sql .= '--'."\n\n";
    unset($db_config);
    writeDownBackupDB($export_sql);
    unset($export_sql);

    // starting to loop thru tables.
    if (isset($tables) && is_array($tables)) {
        foreach ($tables as $table) {
            $export_sql = '-- --------------------------------------------------------'."\n\n";
            $export_sql .= '--'."\n";
            $export_sql .= '-- Table structure for table `' . $table . '`'."\n";
            $export_sql .= '--'."\n\n";
            $export_sql .= 'DROP TABLE IF EXISTS `' . $table . '`;'."\n";
            $sth = $dbh->prepare('SHOW CREATE TABLE `' . $table . '`');
            $sth->execute();
            $row = $sth->fetch(\PDO::FETCH_NUM);
            if (isset($row[1])) {
                $create_sql_string = $row[1];
                $create_sql_string = str_replace(['CREATE TABLE `'], ['CREATE TABLE IF NOT EXISTS `'], $create_sql_string);
                if (substr($create_sql_string, -1) != ';') {
                    $create_sql_string .= ' ;';
                }
            } else {
                $create_sql_string = '';
            }
            unset($row);
            $export_sql .= $create_sql_string."\n\n";
            $sth->closeCursor();
            unset($sth);
            writeDownBackupDB($export_sql);
            unset($export_sql);

            $export_sql = '--'."\n";
            $export_sql .= '-- Dumping data for table `' . $table . '`'."\n";
            $export_sql .= '--'."\n\n";
            writeDownBackupDB($export_sql);
            unset($export_sql);

            // get fields
            $sth = $dbh->prepare('SELECT * FROM `' . $table . '` LIMIT 1');
            $sth->execute();
            $result = $sth->fetch(\PDO::FETCH_ASSOC);
            if (is_array($result)) {
                $fields = array_keys($result);
            } else {
                $fields = [];
            }
            $sth->closeCursor();
            unset($result, $sth);

            // get fields type
            $sth = $dbh->prepare('DESCRIBE `' . $table . '`');
            $sth->execute();
            $table_columns = $sth->fetchAll();
            $columns = [];
            if (is_array($table_columns)) {
                foreach ($table_columns as $column) {
                    $columns[$column->Field] = [
                        'field' => $column->Field,
                        'type' => $column->Type,
                        'null' => $column->Null,
                        'default' => $column->Default,
                    ];
                }// endforeach;
                unset($column);
            }
            $sth->closeCursor();
            unset($sth, $table_columns);

            if (isset($fields) && is_array($fields) && !empty($fields)) {
                $select_string = 'SELECT ';
                $i_count_field = 1;
                foreach ($fields as $field) {
                    $select_string .= 'IF (`' . $field . '` IS NULL, \'FIELD_VALUE_NULL\', `' . $field . '`) AS `' . $field . '`';
                    if ($i_count_field < count($fields)) {
                        $select_string .= ', ';
                    }
                    $i_count_field++;
                }// endforeach;
                unset($i_count_field, $field);
                $select_string .= ' FROM `' . $table . '`';
                $sth = $dbh->prepare($select_string);
                unset($select_string);
                $sth->execute();
                $result = $sth->fetchAll();
                $export_sql = '';
                if (is_array($result) && !empty($result)) {
                    // generate INSERT INTO `table_name` string.
                    $export_sql .= 'INSERT INTO `' . $table . '` (';
                    $i_count = 1;
                    foreach ($fields as $field) {
                        $export_sql .= '`' . $field . '`';
                        if ($i_count < count($fields)) {
                            $export_sql .= ', ';
                        }
                        $i_count++;
                    }// endforeach;
                    unset($field, $i_count);
                    $export_sql .= ') VALUES'."\n";
                    writeDownBackupDB($export_sql);
                    unset($export_sql);

                    // generate VALUES of INSERT INTO.
                    if (is_array($result)) {
                        $i_count = 1;
                        $i_count_break = 1;
                        foreach ($result as $row) {
                            $export_sql = '(';
                            $i_count_fields = 1;
                            foreach ($fields as $field) {
                                $field_value = $row->{$field};
                                // escape slash
                                $field_value = str_replace('\\', '\\\\', $field_value);
                                // sanitize new line
                                $field_value = str_replace(["\r\n", "\r", "\n"], ['\r\n', '\r', '\n'], $field_value);
                                // escape single quote
                                $field_value = str_replace('\'', '\'\'', $field_value);
                                // change value to NULL if it is NULL.
                                if ($field_value === 'FIELD_VALUE_NULL') {
                                    $field_value = 'NULL';
                                }

                                // detect field value type and cloak with single quote.
                                if (isset($columns[$field]['type']) && 
                                    (
                                        stristr($columns[$field]['type'], 'tinyint(') !== false ||
                                        stristr($columns[$field]['type'], 'smallint(') !== false ||
                                        stristr($columns[$field]['type'], 'mediumint(') !== false ||
                                        stristr($columns[$field]['type'], 'int(') !== false ||
                                        stristr($columns[$field]['type'], 'bigint(') !== false
                                    )
                                ) {
                                    // this field column type is int
                                    if (!is_numeric($field_value) && $field_value !== 'NULL') {
                                        $field_value = '\'' . $field_value . '\'';
                                    }
                                } else {
                                    if ($field_value !== 'NULL') {
                                        $field_value = '\'' . $field_value . '\'';
                                    }
                                }

                                $export_sql .= $field_value;
                                unset($field_value);

                                if ($i_count_fields < count($fields)) {
                                    $export_sql .= ', ';
                                }
                                $i_count_fields++;
                            }// endforeach;
                            unset($field, $i_count_fields);
                            $export_sql .= ')';

                            if ($i_count < count($result)) {
                                if ($i_count_break >= 30) {
                                    $export_sql .= ';'."\n";
                                    writeDownBackupDB($export_sql);
                                    unset($export_sql);
                                    $i_count_break = 0;

                                    $export_sql = 'INSERT INTO `' . $table . '` (';
                                    $i_count_fields = 1;
                                    foreach ($fields as $field) {
                                        $export_sql .= '`' . $field . '`';
                                        if ($i_count_fields < count($fields)) {
                                            $export_sql .= ', ';
                                        }
                                        $i_count_fields++;
                                    }// endforeach;
                                    unset($field, $i_count_fields);
                                    $export_sql .= ') VALUES'."\n";
                                    writeDownBackupDB($export_sql);
                                    unset($export_sql);
                                    $export_sql = '';
                                } else {
                                    $export_sql .= ','."\n";
                                }
                            } else {
                                $export_sql .= ';'."\n\n";
                            }
                            $i_count++;
                            $i_count_break++;
                            writeDownBackupDB($export_sql);
                            unset($export_sql);
                        }// endforeach;
                        unset($i_count, $i_count_break, $result, $row);
                    }
                } else {
                    $export_sql .= "\n";
                    writeDownBackupDB($export_sql);
                    unset($export_sql);
                }
                unset($fields);
                $sth->closeCursor();
                unset($result, $sth);
            } else {
                $export_sql = "\n";
                writeDownBackupDB($export_sql);
                unset($export_sql);
            }
            unset($export_sql);
        }// endforeach;
        unset($table);
    }
    unset($tables);

    unset($dbh);
    logMessage('Backup DB completed. Max memory usage is ' . formatBytes(memory_get_peak_usage(true)) . '.', true, 'green');
    return true;
}// backupDB


/**
 * Write content to backup SQL file by append.
 * 
 * @param string $content
 */
function writeDownBackupDB($content)
{
    $new_backup_file = __DIR__ . DIRECTORY_SEPARATOR . 'newbackup_xxx_date.sql';
    $handle = fopen($new_backup_file, 'a+');
    fwrite($handle, $content);
    fclose($handle);
    unset($handle, $new_backup_file);
}// writeDownBackupDB


logMessage('Beginning backup DB.', true, 'light_gray');
backupDB();

Please note that...请注意...

  1. Some functions are missing such as logMessage() , getDbConfigFromWordPress() .缺少一些函数,例如logMessage()getDbConfigFromWordPress() Please remove from before you use it.请在使用前将其移除。
  2. Something like $db_config['tableprefix'] or $db_config[...] must be changed.必须更改$db_config['tableprefix']$db_config[...]
  3. There are many things that cannot handle as @Bill Karwin said.正如@Bill Karwin所说,有很多事情无法处理。
  4. I'm not sure is it support UTF-8 data but as I see it is support many languages and also support emoji (😂😭😍).我不确定它是否支持 UTF-8 数据,但据我所知,它支持多种语言并支持表情符号 (😂😭😍)。
  5. Export using mysql command is always better idea.使用 mysql 命令导出总是更好的主意。

I implemented Lan's latest version with a few modifications (see code below):我通过一些修改实现了 Lan 的最新版本(见下面的代码):

  • Tables are saved in a folder whose name is today's date;表格保存在名称为今天日期的文件夹中; previous versions of the same day are overwritten同一天的先前版本被覆盖
  • It supports several formats, including comma-separated CSV它支持多种格式,包括逗号分隔的 CSV
  • It displays table sizes in bytes and numbers of rows它以字节和行数显示表格大小
  • Using my (admittedly old-fashioned) tabulations使用我的(公认的老式)表格

A reason for adding the csv option is that I found it impossible to import from sql files data in TEXT (UTF8) format when it is multibyte (Asian scripts).添加 csv 选项的一个原因是我发现当它是多字节(亚洲脚本)时,无法从文本(UTF8)格式的 sql 文件中导入数据。 It works indeed with BLOB format, but then we can't index it as FULLTEXT.它确实适用于 BLOB 格式,但是我们无法将其索引为 FULLTEXT。 I probably miss a point in formatting the tables...我可能错过了格式化表格的一点...

Here is the code anyway:无论如何,这是代码:

function backup_tables($DBH,$tables,$compression,$format) {
$DBH->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL );
//Script Variables
$BACKUP_PATH = DUMP;
$date = date("Y-m-d");
$olddir =  getcwd();
chdir($BACKUP_PATH);
if(!file_exists($date)) {
    echo "<font color=red>Created ‘".$date."’ folder</font><br />";
    $cmd = "mkdir ".$date;
    exec($cmd);
    }
chdir($date);

//array of all database field types which just take numbers 
$numtypes = array('tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'float', 'double', 'decimal', 'real');

//get all of the tables
if(empty($tables)) {
    $pstm1 = $DBH->query('SHOW TABLES');
    while($row = $pstm1->fetch(PDO::FETCH_NUM)) {
        $tables[] = $row[0];
        }
    }
else {
    $tables = is_array($tables) ? $tables : explode(',',$tables);
    }

//cycle through the table(s)
echo "<font color=blue>Dumping tables to DB_DUMP:</font>";
echo "<ul>";
foreach($tables as $table) {
    //create/open files
    if($format == "csv") {
        $filename = $table.".csv";
        $handle = fopen($filename,"w");
        }
    else {
        if($compression) {
            $filename = $table.".sql.gz";
            $zp = gzopen($filename,"wb9");
            }
        else {
            $filename = $table.".sql";
            $handle = fopen($filename,"w");
            }
        }
    echo "<li><small><font color=blue>".$filename."</font>";
    $result = $DBH->query("SELECT * FROM $table");
    $num_fields = $result->columnCount();
    $num_rows = $result->rowCount();
    $return = "";
    $return .= 'DROP TABLE IF EXISTS `'.$table.'`;'; 

    //table structure
    $pstm2 = $DBH->query("SHOW CREATE TABLE $table");
    $row2 = $pstm2->fetch(PDO::FETCH_NUM);
    $ifnotexists = str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $row2[1]);
    $return .= "\n\n".$ifnotexists.";\n\n";

    if($format <> "csv") {
        if($compression) gzwrite($zp, $return);
        else fwrite($handle,$return);
        }
    $return = "";

    //insert values
    if($num_rows) {
        $return = 'INSERT INTO `'."$table"."` (";
        $pstm3 = $DBH->query("SHOW COLUMNS FROM $table");
        $count = 0;
        $type = array();

        while($rows = $pstm3->fetch(PDO::FETCH_NUM)) {
            if(stripos($rows[1], '(')) {
                $type[$table][] = stristr($rows[1], '(', true);
                }
            else $type[$table][] = $rows[1];

            $return .= "`".$rows[0]."`";
            $count++;
            if($count < ($pstm3->rowCount())) {
                $return .= ", ";
                }
            }
        $return .= ")".' VALUES';
        if($format <> "csv") {
            if($compression) gzwrite($zp, $return);
            else fwrite($handle,$return);
            }
        $return = "";
        }
    $count = 0;
    while($row = $result->fetch(PDO::FETCH_NUM)) {
        if($format <> "csv") $return = "\n\t(";
        for($j=0; $j < $num_fields; $j++) {
            //$row[$j] = preg_replace("\n","\\n",$row[$j]);
            if(isset($row[$j])) {
                if($format == "csv") $return .= '"'.$row[$j].'"';
                else {
                    //if number, take away "". else leave as string
                    if((in_array($type[$table][$j],$numtypes)) && (!empty($row[$j])))
                        $return .= $row[$j];
                    else
                        $return .= $DBH->quote($row[$j]);
                    }
                }
            else {
                if($format == "csv") $return .= '';
                else $return .= 'NULL';
                }
            if($j < ($num_fields-1)) $return .= ',';
            }
        $count++;
        if($format == "csv") $return .= "\n";
        else {
            if($count < ($result->rowCount()))
                $return .= "),";
            else $return .= ");";
            }
        if($format == "csv") fwrite($handle,$return);
        else {
            if($compression) gzwrite($zp, $return);
            else fwrite($handle,$return);
            }
        $return = "";
        }
    $return = "\n\n-- ------------------------------------------------ \n\n";
    echo " (".$count." records)";

    if($format <> "csv") {
        if($compression) gzwrite($zp, $return);
        else fwrite($handle,$return);
        }
    $return = "";

    $error1 = $pstm2->errorInfo();
    $error2 = $pstm3->errorInfo();
    $error3 = $result->errorInfo();
    echo $error1[2];
    echo $error2[2];
    echo $error3[2];

    if($format == "csv") fclose($handle);
    else {
        if($compression) gzclose($zp);
        else fclose($handle);
        }
    $filesize = filesize($filename);
    echo " - ".$filesize." bytes</small></li>";
    }
echo "</ul>";
chdir($olddir);
return;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM