5

この関数はこちら http://davidwalsh.name/backup-mysql-database-php

しばらく前からインターネット上に出回っており、かなり有名ですが、古い MySQL API を使用しています。誰も同じですが、PDOにいますか? そうでない場合は、誰かが作成したいですか?

PDOがSHOW CREATE TABLEを実行しないことをどこかで読みました-そうですか?

4

7 に答える 7

9

そのバックアップ スクリプトはばかげているので、別のバージョンを作成するべきではありません。以前にそのスクリプトと同様の試みを見たことがありますが、多くの問題があります。

  • バックティックでテーブル名を区切らない
  • NULL を処理しない
  • 文字セットを処理しない
  • バイナリデータを扱いません
  • ビューをバックアップしません
  • TRIGGER、STORED PROCEDURE、STORED FUNCTION、または EVENT をバックアップしません。
  • 廃止された mysql 拡張機能を使用します (しかし、これが PDO バージョンが必要な理由ですよね?)
  • 適切な MySQL エスケープ関数の代わりに、addslashes() を使用します。
  • コンテンツ全体を出力する前に、すべてのテーブルのすべてのデータを1 つの非常に長い文字列に追加します。これは、データベース全体を 1 つの文字列に格納できる必要があることを意味します。これは、ほぼ確実に PHP の最大メモリ制限を超えてしまいます。

不幸な David Walsh バックアップ スクリプトに関する私の過去の回答も参照してください。


あなたのコメントについて:

リンク先のページのコメントを読んでください。多くの人が問題を特定しており、中には修正または少なくとも提案がある人もいます。

このスクリプトがすべてを 1 つの文字列に追加するという事実は問題だと思いますが、最初に出力ファイルを開き、ループ中に各行のデータを出力してからファイルを閉じるようにスクリプトを変更することは難しくありません。ループの後。スクリプトがそうしない理由がわかりません。しかし、スクリプトが十分にテストされていないことは明らかです。

とにかく、私はこの車輪を再発明しようとはしません。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 の最大メモリ制限が破られます。
  • PDO::quote() の代わりに addslashes() を使用します。
  • 関数の最後でのみクエリ エラーをチェックします。
  • 失敗したファイルの作成をチェックしません。
  • 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 ステートメントで、テーブルを 1 回だけ区切ります。

すべての $tables が区切られていません。それらはすべて " " で区切られている必要がありますか?

MySQL は常にバックティックを識別子の区切り記号として認識し、文字列/日付の単一引用符を認識します。ただし、二重引用符は、前述の SQL_MODE によって意味が変わります。復元する MySQL インスタンスでどの SQL_MODE が有効であるかを想定することはできないため、識別子にはバックティックを使用し、文字列には単一引用符を使用することをお勧めします。テーブルをクエリするときにそれらを区切る理由は、SQL 予約語であるテーブル名や特殊文字などを含むテーブル名がある可能性があるためです。

区切り記号なしでフロートを mysql に挿入できますか、それとも '' が必要ですか? ありがとう

区切り記号なしですべての数値型を挿入できます。区切り文字が必要なのは文字列と日付だけです。dev.mysql.com/doc/refman/5.6/en/literals.html を参照してください。

于 2013-08-16T20:31:32.513 に答える
9

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);
    }
}
于 2013-08-18T01:07:56.973 に答える
1

David Walsh のオリジナルのバックアップ機能の PDO バージョンを作成し終えました。また、 Bill Karwin's answer
で言及されている問題に対処するためにそれを改善しました。NULL を処理し、個々の行を書き込むため、バッククォートなどを使用してメモリの問題が発生しません。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_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);
}
}
于 2013-08-17T14:28:43.407 に答える