PDO MySQL备份功能

Lan*_*Lan 4 php mysql backup pdo database-backups

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

已经漂浮在互联网上一段时间,并且非常有名,但它在标准的mysql中.有没有人有相同但在PDO?如果没有,是否有人想制作一个?它是否有可能,我在某处读到PDO没有做SHOW CREATE TABLE - 是吗?

最后,有人可以解释一下这个功能和使用SELECT*INTO OUTFILE之间的区别吗?

(请不要将此标记为包含太多问题,它们都是紧密相关的,并且我确定答案对许多人都有用)

Lan*_*Lan 8

对于任何寻找像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);
}
}
Run Code Online (Sandbox Code Playgroud)

  • 运行该脚本会返回以下错误:致命错误:在非对象上调用成员函数columnCount()...也许最好将代码提交给Github,以便每个人都可以贡献来改进代码并修复错误. (2认同)

Bil*_*win 7

那个备份脚本很荒谬,没有人应该制作另一个版本.我之前看过那个剧本,以及类似的尝试,他们有很多问题:

  • 不在后面的刻度中分隔表名
  • 不处理NULL
  • 不处理字符集
  • 不处理二进制数据
  • 不备份VIEW
  • 不备份TRIGGER或存储过程或存储功能或事件
  • 使用过时的mysql扩展(但这就是你想要一个PDO版本的原因,不是吗?)
  • 使用addslashes()而不是正确的MySQL转义函数.
  • 在输出整个内容之前,将所有表的所有数据附加到一个非常长的字符串中.这意味着您必须能够将整个数据库存储在一个字符串中,这几乎肯定会破坏您的PHP最大内存限制.

另请参阅我过去关于不幸的David Walsh备份脚本的答案:


你的评论:

阅读您链接到的页面上的评论.很多人都发现了问题,有些人已经修复或者至少有建议.

这个脚本附加万事成一个字符串其实是个致命弱点,我想,但它不应该是很难改变脚本打开输出文件第一循环中,然后输出每行的数据,然后关闭该文件循环之后.这是一种不费吹灰之力,我不确定脚本为什么不这样做.但很明显,脚本没有经过良好的测试.

但无论如何,我不会试图重新发明这个轮子.Mysqldump或mydumper做得很好.FWIW,您不必在数据库所在的同一服务器上运行mysqldump.Mysqldump支持一个选项,--host因此您可以在任何地方运行mysqldump来备份远程数据库,只要防火墙不会阻止客户端连接.基本上,如果您可以从某个客户端主机将PHP应用程序连接到数据库,则可以连接mysqldump.

如果那真的不是一个选项,那么我会使用phpmyadmin的数据库转储功能.这些都是成熟且经过充分测试的,它们可以正确地丢弃所 这是一篇介绍如何使用转储功能的文章:

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


[从你的回答中复制我的评论:]

这是进入代码审查,这不是StackOverflow的目的.但简单地说:

  • 没有适当的NULL支持(你将它们转换为'');
  • 不一致地界定表名;
  • 使用非ANSI双引号作为字符串分隔符;
  • 在巨大的表上使用缓冲查询将破坏PHP最大内存限制;
  • 为巨大的表附加所有行将破坏PHP最大内存限制;
  • 使用addslashes()而不是PDO :: quote();
  • 仅在函数末尾检查查询错误;
  • 不检查文件创建失败;
  • 可能无法加载gzip扩展名
  • 此外,可能仍然不支持UTF8数据.

但它到了那里,不是吗?

是的,这比最初的David Walsh脚本更好.:-)

什么是NULL的错误''?

NULL与SQL中的''不同(Oracle中除外,但在这种情况下它们不符合SQL标准).看MySQL,最好插入NULL还是空字符串?

表结构必须非常大到最大内存.每个插入行都单独写入文件,所以行必须非常大到最大内存.

我误读了内存限制问题上的代码.您正在为每一行写输出,所以没关系(除非该行包含1GB blob或其他内容).

但是,您不应该只使用逗号分隔的行集输出单个INSERT语句.甚至mysqldump --extended-insert输出有限长度的数据,然后启动一个新的INSERT语句.标准是INSERT语句的长度是否适合选项参数--net-buffer-length.

什么是错误的""字符串分隔符?我如何获得ANSI?

在ANSI SQL中,单引号''用于分隔字符串文字或日期文字.双引号""用于分隔表名或列名等标识符.默认情况下,MySQL将它们视为相同,但这是非标准的.请参阅不同的数据库使用不同的名称引用 .如果您尝试在您拥有的MySQL服务器上导入备份数据SET SQL_MODE=ANSI_QUOTES,则导入将失败.

什么表没有分隔?

示例:query('SELECT * FROM '.$table);实际上,在查询中使用$ table的其他每种情况.您只在脚本输出的INSERT语句中对表进行了一次分隔.

所有$表都没有分隔,它们都需要与""?

MySQL总是将后标记识别为标识符分隔符,并将单引号识别为字符串/日期.但是双引号会改变含义,具体取决于我提到的SQL_MODE.您无法假设哪个SQL_MODE对您恢复的MySQL实例有效,因此最好使用标记的反向标记和字符串的单引号.在查询表时划分它们的原因是您可能具有SQL保留字的表名,或者包含特殊字符的表名等.

你可以将没有分隔符的浮点数插入到mysql中,还是需要''?谢谢

您可以插入没有分隔符的所有数字类型.只有字符串和日期需要分隔符.请参阅dev.mysql.com/doc/refman/5.6/en/literals.html


Flo*_*ser 5

按照/sf/answers/1279718121/mysqldump的建议,与exec. 归结起来是这样的:

<?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;
}
Run Code Online (Sandbox Code Playgroud)