Функция резервного копирования PDO MySQL

Эта функция здесь http://davidwalsh.name/backup-mysql-database-php

Некоторое время витал в Интернете и довольно известен, но использует старый MySQL API. У кого-нибудь есть такое же но в PDO? Если нет, то кто-нибудь хочет его сделать?

Возможно ли, я где-то читал, что PDO не выполняет SHOW CREATE TABLE - это правильно?


person Lan    schedule 16.08.2013    source источник


Ответы (7)


Для всех, кто ищет функцию, которая действует как 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);
    }
}
person Lan    schedule 18.08.2013
comment
Выполнение сценария возвращает следующую ошибку: Неустранимая ошибка: вызов функции-члена columnCount () для не-объекта ... Возможно, было бы лучше отправить код в Github, чтобы каждый мог внести свой вклад в улучшение кода и исправление ошибок. - person eldblz; 27.08.2013
comment
нормально будет сделать. но если у вас есть это сообщение об ошибке, похоже, что вы не подключились к mysql, т.е. что-то не так с паролем, именем пользователя, именем базы данных или именем хоста - person Lan; 29.08.2013
comment
@Lan, если бы он не подключился, он получил бы ошибку намного раньше, чем вызов columnCount (). У вас нет хорошей проверки ошибок в этом скрипте, чтобы отловить неудачные операторы. - person Bill Karwin; 24.12.2013
comment
@Lan, например, а если он делает резервную копию таблицы с именем order? Это зарезервированное слово, поэтому тот факт, что вы не ограничили имя таблицы, вызывает синтаксическую ошибку при ее запуске SELECT * FROM order. Но вы не проверяете наличие ошибки, на которую указывает возвращаемое значение query(), поэтому вы никогда не узнаете. - person Bill Karwin; 24.12.2013

Этот сценарий резервного копирования нелеп, и никто не должен делать его другую версию. Я видел этот сценарий раньше, а также подобные попытки, и у них много проблем:

  • Не разделяет имена таблиц в обратных тиках
  • Не обрабатывает NULL
  • Не обрабатывает наборы символов
  • Не обрабатывает двоичные данные
  • Не выполняет резервное копирование ПРОСМОТРОВ
  • Не выполняет резервное копирование ТРИГГЕРОВ, СОХРАНЕННЫХ ПРОЦЕДУР, СОХРАНЕННЫХ ФУНКЦИЙ или СОБЫТИЙ
  • Использует устаревшее расширение mysql (но именно поэтому вам нужна версия PDO, не так ли?)
  • Использует addlashes () вместо соответствующей функции экранирования MySQL.
  • Добавляет все данные для всех таблиц в одну очень длинную строку перед выводом всего содержимого. Это означает, что вы должны иметь возможность хранить всю свою базу данных в одной строке, что почти наверняка приведет к превышению максимального лимита памяти PHP.

См. Также мой предыдущий ответ о неудачном сценарии резервного копирования Дэвида Уолша:


Повторите свой комментарий:

Прочтите комментарии на странице, на которую вы указали. Многие люди выявили проблемы, а у некоторых есть исправления или, по крайней мере, предложения.

Я думаю, что тот факт, что этот скрипт добавляет все в одну строку, является препятствием, но не должно быть сложно изменить скрипт, чтобы открыть выходной файл сначала, а затем выводить данные каждой строки во время цикл, затем закройте файл после цикла. Это своего рода легкая задача, я не уверен, почему сценарий этого не делает. Но совершенно очевидно, что сценарий не очень хорошо тестировался.

Но в любом случае я бы не стал изобретать это колесо. 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;
  • использование addlashes () вместо PDO :: quote ();
  • проверка на наличие ошибок запроса только в конце функции;
  • не проверять наличие неудачного создания файла;
  • расширение gzip не может быть загружено
  • Кроме того, вероятно, все еще не поддерживаются данные UTF8.

но он там доходит, не так ли?

Да, это лучше, чем оригинальный сценарий Дэвида Уолша. :-)

что не так с NULL для ''?

NULL - это не то же самое, что '' в SQL (кроме Oracle, но в этом случае они не соответствуют стандарту SQL). См. MySQL, лучше вставить NULL или пустую строку?

структура таблицы должна быть очень большой для максимального объема памяти. каждая вставляемая строка записывается в файл индивидуально, поэтому, опять же, строка должна быть очень большой для максимального объема памяти.

Я неправильно прочитал код проблемы ограничения памяти. Вы пишете вывод для каждой строки, так что это нормально (если строка не содержит капли размером 1 ГБ или что-то в этом роде).

Но вы не должны просто выводить один оператор 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

person Bill Karwin    schedule 16.08.2013
comment
ух ты. это хорошая обратная связь. Я собираюсь поместить его в PDO и устранить некоторые из недостатков, которые вы здесь подняли: - обратные тики, NULL, набор символов, добавленные косые черты, isset и память могут быть адресованы и исправлены. меня не слишком беспокоят ПРОСМОТРЫ, ТРИГГЕРЫ, СОБЫТИЯ или двоичные данные. вы должны подарить Дэвиду немного любви, он делает шаги в правильном направлении для человечества. что бы вы предложили использовать вместо этого, если вы не можете использовать команды mysqldump / shell? - person Lan; 17.08.2013
comment
спасибо за ответ, да, я потратил хороший час на чтение каждого комментария, была внесена модификация, чтобы сделать, как вы сказали, сначала создать файл, а затем записать каждую строку в файл - вот что я буду делать. Это интересно о удаленном дампе mysql, но для меня это бесполезно. Я делаю простой пакет CRM, который клиенты покупают и загружают на свой сервер. когда они выходят из crm (если это не было сделано в течение x времени), он сохраняет резервную копию их данных в корневой папке - person Lan; 17.08.2013
comment
Также обратите внимание, что некоторые комментаторы жалуются, что функция дампа не обрабатывает данные UTF8. Я бы порекомендовал вам написать тесты, которых Дэвид Уолш никогда не делал. Создайте образец базы данных с различными таблицами, нулевыми и ненулевыми данными, международными символами и т. Д. И убедитесь, что она работает для резервного копирования и восстановления. Вы можете сделать это сейчас или в аварийном режиме, когда ваши клиенты жалуются. - person Bill Karwin; 17.08.2013

Все PDO и ext/mysql - это команды переноса в базовую базу данных (в данном случае MySQL). То есть ничто не мешает PDO запускать SHOW CREATE TABLE или другие команды.

Для всех намерений и целей вы можете просто заменить:

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

И вместо

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

Использовать

$result = $link->query($query);
$result->fetch();
person Explosion Pills    schedule 16.08.2013
comment
да, я знаю, что его можно легко преобразовать в PDO, но я просто очень ленив и надеюсь, что кто-то другой сможет это сделать. ты можешь сделать это? Бьюсь об заклад, ты не осмелишься сделать это - person Lan; 16.08.2013
comment
@Lan, просящий кого-то написать для вас код в Stackoverflow, обычно не одобряется, и большинство людей (включая меня) не будут этого делать. Суть вопросов и ответов - предложить концептуальную помощь, которая будет применяться к множеству ситуаций, из которых другие могут извлечь уроки позже. - person Explosion Pills; 16.08.2013
comment
привет, я просто хочу спросить, можно ли создать кнопку на веб-странице, которая при нажатии будет создавать резервную копию всей базы данных. Я хотел создать резервную копию своей базы данных, и я понятия не имею, как сделать резервную копию. Я читал несколько статей, но не могу зарегистрировать простое приложение, более сложное для моего уровня. Я использую php pdo, моя база данных - это mysql, надеясь на ответ в ближайшее время - person Brownman Revival; 18.07.2015

В соответствии с рекомендациями https://stackoverflow.com/a/18281687/2259391 используйте 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;
}
person Florian Moser    schedule 05.07.2015

Я только что закончил создавать версию PDO исходной функции резервного копирования Дэвида Уолша.
Я также улучшил ее, чтобы устранить проблемы, упомянутые в Ответ Билла Карвина; обрабатывает 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);
}
}
person Lan    schedule 17.08.2013
comment
Это касается обзора кода, который не является целью StackOverflow. Но вкратце: нет надлежащей поддержки NULL (вы конвертируете их в ''); непоследовательное разделение имен таблиц; использование двойных кавычек, отличных от ANSI, в качестве разделителей строк; использование буферизованных запросов к огромным таблицам нарушит максимальный предел памяти PHP; добавление всех строк для огромной таблицы нарушит ограничение максимальной памяти PHP; использование addlashes () вместо PDO :: quote (); проверка на наличие ошибок запроса только в конце функции; не проверять наличие неудачного создания файла; расширение gzip не может быть загружено - person Bill Karwin; 17.08.2013
comment
Кроме того, вероятно, все еще не поддерживаются данные UTF8. - person Bill Karwin; 17.08.2013
comment
ай. кодировка указана в связи. что не так с NULL для ''? структура таблицы должна быть очень большой для максимального объема памяти. каждая вставляемая строка записывается в файл индивидуально, поэтому, опять же, строка должна быть очень большой для максимального объема памяти. addlashes - это НЕ ТАК плохо, но он изменится на цитату PDO. добавлю проверку на gzip. Согласитесь, проверка ошибок может быть немного красивее. но он там доходит, не так ли? - person Lan; 17.08.2013
comment
также, что не так с разделителями строк? как мне получить ANSI? а какие таблицы не разграничены? Благодарность - person Lan; 17.08.2013
comment
Да, это лучше, чем оригинальный сценарий Дэвида Уолша. :-) - person Bill Karwin; 17.08.2013
comment
NULL - это не то же самое, что '' в SQL (кроме Oracle, но в этом случае они не соответствуют стандарту SQL). См. MySQL, лучше вставить NULL или пустую строку? - person Bill Karwin; 17.08.2013
comment
В ANSI SQL одинарные кавычки '' используются для разделения строковых литералов или литералов даты. Двойные кавычки "" используются для разделения идентификаторов, таких как имя таблицы или имена столбцов. По умолчанию MySQL обрабатывает их одинаково, но это нестандартно. См. Используют ли разные базы данных разные цитаты имен?. Если вы попытаетесь импортировать данные резервной копии на сервер MySQL, где у вас установлен SET SQL_MODE=ANSI_QUOTES, импорт не удастся. - person Bill Karwin; 17.08.2013
comment
Какие таблицы не были разделены? Пример: query('SELECT * FROM '.$table); и фактически каждый из других случаев, когда вы используете $table в запросе. Вы ограничили таблицу только один раз, в операторе INSERT, который выводит ваш скрипт. - person Bill Karwin; 17.08.2013
comment
Я неправильно прочитал код проблемы ограничения памяти. Вы пишете вывод для каждой строки, так что это нормально (если строка не содержит капли размером 1 ГБ или что-то в этом роде). Но вы не должны просто выводить один оператор INSERT с набором строк, разделенных запятыми. Даже mysqldump --extended-insert выводит данные конечной длины, а затем запускает новый оператор INSERT. Критерий состоит в том, соответствует ли длина оператора INSERT параметру параметра --net-buffer-length. - person Bill Karwin; 17.08.2013
comment
ах, да, сравнивал его с реальным файлом mysqldumped, я вижу, что настоящий просто вставляет «NULL» - так что я могу исправить это на моем. заменит двойные кавычки на одиночные, чтобы сделать его совместимым с ANSI - легко исправить. что касается границ таблиц; те, что в операторе INSERT, являются обратными галочками, и это просто для их печати. все таблицы $ не разделены, все ли они должны быть с? кстати, спасибо за помощь - person Lan; 17.08.2013
comment
MySQL всегда распознает обратные тики как разделители идентификаторов и одинарные кавычки для строк / дат. Но двойные кавычки меняют значение в зависимости от упомянутого мной SQL_MODE. Вы не можете предположить, какой SQL_MODE действует на экземпляр MySQL, который вы восстанавливаете, поэтому лучше всего использовать обратные тики для идентификаторов и одинарные кавычки для строк. Причина, по которой вы должны разделять их при запросе таблицы, заключается в том, что имена таблиц могут быть зарезервированными словами SQL или содержать специальные символы и т. Д. - person Bill Karwin; 17.08.2013
comment
здорово. извините, последний вопрос; вы можете вставлять числа с плавающей запятой без разделителей в mysql, или вам нужен ''? Благодарность - person Lan; 17.08.2013
comment
Вы можете вставлять все числовые типы без разделителей. В разделителях нужны только строки и даты. См. dev.mysql.com/doc/refman/5.6/en/literals .html - person Bill Karwin; 17.08.2013
comment
@BillKarwin есть ли какие-нибудь известные надежные сценарии резервного копирования PHP? Кажется, у всех них есть проблемы, и я искал один целую вечность, чтобы автоматизировать его с помощью cron. - person Sir; 24.12.2013
comment
@ Дэйв, я работаю над одним. Я реализую класс PHP, который имитирует mysqldump, и я тестирую его с помощью собственного набора тестов mysqldump. Я надеюсь представить его на конференции и выставке Percona Live MySQL Conference & Expo в апреле. 2014. Я создал для него репозиторий на github: github.com/billkarwin/cats-and-dogs - person Bill Karwin; 24.12.2013
comment
@BillKarwin круто. Я добавлю его в закладки и буду следить за ним :) - person Sir; 24.12.2013

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();

Обратите внимание, что ...

  1. Некоторые функции отсутствуют, например logMessage(), getDbConfigFromWordPress(). Удалите его перед использованием.
  2. Что-то вроде $db_config['tableprefix'] или $db_config[...] необходимо изменить.
  3. Есть много вещей, с которыми невозможно справиться, как сказал @Bill Karwin.
  4. Я не уверен, поддерживает ли он данные UTF-8, но, как я вижу, он поддерживает много языков, а также поддерживает эмодзи (????????????).
  5. Всегда лучше экспортировать с помощью команды mysql.
person vee    schedule 18.01.2017

Я реализовал последнюю версию Lan с небольшими изменениями (см. Код ниже):

  • Таблицы сохраняются в папке с сегодняшней датой; предыдущие версии того же дня перезаписываются
  • Он поддерживает несколько форматов, включая CSV с разделителями-запятыми.
  • Отображает размеры таблиц в байтах и ​​количество строк.
  • Используя мои (правда, устаревшие) таблицы

Причина для добавления опции csv заключается в том, что я обнаружил невозможность импорта из файлов sql данных в формате TEXT (UTF8), когда они многобайтовые (азиатские скрипты). Он действительно работает с форматом BLOB, но тогда мы не можем индексировать его как FULLTEXT. Я наверное упустил момент в форматировании таблиц ...

В любом случае вот код:

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;
}
person Bernard Bel    schedule 12.07.2017