segunda-feira, 19 de setembro de 2011

Backup de um banco de dados MySQL utilizando PHP

Fazer o backup do banco de dados é uma tarefa essencial para recuperar o sistema após um crash de hardware, ataque hacker, invasão alienígena etc...

Pensando nisso estou colocando nas minhas aplicações um botão de backup para que o usuário final possa fazer ele mesmo um backup do banco de dados e salvar em um Pendrive, mandar por e-mail para ele mesmo, ou mandar imprimir em formulário contínuo pra ver se chega na lua, sei lá.

Procurei um bom tanto na internet não achei algo que fosse 100% o que eu queria mas achei algo próximo que me ajudou a lot em http://www.devmedia.com.br/post-3925-Fazendo-backup-de-Mysql-atraves-de-PHP.html

Problema: se um usuário colocasse uma string com um ' no meio, ele fecha a string do sql que usa ' para delimitar a string.

Para isso escapamos as single-quotes ou aspas simples ' com um backslash ou barra invertida \ antes.

Um erro parecido acontece se no banco houver uma string que termina com backslash, pois isso anularia a single quote que fecha a string, então antes de backslash, deve-se colocar outro backslash.


Sem entrar em maiores detalhes sobre ordem dos fatores fiz uma outra adção para quem usa UTF-8 e quer validar os caracteres, erros com importações mal feitas podem ocasionar alguns erros na geração do script esse $checkUtf, se true remove caracteres que não sejam UTF-8 evitando esse pau.

Segue o código, com certeza aceito comentários e sugestões de melhoria.

<?php

// código baseado em http://www.devmedia.com.br/post-3925-Fazendo-backup-de-Mysql-atraves-de-PHP.html
// com melhorias dos comentários do mesmo post
// com adicional de escape string por Marcos Fedato marcosfedato.blogspot.com.br
// com checagem de caracteres UTF-8 por http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string

    $usuario = "user";
    $senha = "123";
    $dbname = "mydb";
    // use true se quiser remover caracteres que não sejam utf-8
    $checkUtf = false;
   
// conectando ao banco
    mysql_connect("localhost", $usuario, $senha) or die(mysql_error());
    mysql_select_db($dbname) or die(mysql_error());

// gerando um arquivo sql. Como?
// a função fopen, abre um arquivo, que no meu caso, será chamado como: nomedobanco.sql
// note que eu estou concatenando dinamicamente o nome do banco com a extensão .sql.
    $back = fopen($dbname . ".sql", "w");

// aqui, listo todas as tabelas daquele banco selecionado acima
    $res = mysql_list_tables($dbname) or die(mysql_error());

    // ultra importante para não dar erro nos primeiros inserts
    // principalmente de usar InnoDB e relacionar as tabelas
    fwrite($back, "set foreign_key_checks=0;\n\n");
   
    // regex para ver se o char é UTF-8
    // Link: http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string
    $regex1 = <<<'END'
/
  ( [\x00-\x7F]                 # single-byte sequences   0xxxxxxx
  | [\xC0-\xDF][\x80-\xBF]      # double-byte sequences   110xxxxx 10xxxxxx
  | [\xE0-\xEF][\x80-\xBF]{2}   # triple-byte sequences   1110xxxx 10xxxxxx * 2
  | [\xF0-\xF7][\x80-\xBF]{3}   # quadruple-byte sequence 11110xxx 10xxxxxx * 3
  )
| .                             # anything else
/x
END;
   
// resgato cada uma das tabelas, num loop
    while ($row = mysql_fetch_row($res))
    {
        $table = $row[0];
// usando a função SHOW CREATE TABLE do mysql, exibo as funções de criação da tabela,
// exportando também isso, para nosso arquivo de backup
        $res2 = mysql_query("SHOW CREATE TABLE $table");
// digo que o comando acima deve ser feito em cada uma das tabelas
        while ($lin = mysql_fetch_row($res2)) {
// instruções que serão gravadas no arquivo de backup
            fwrite($back, "\n#\n# Criação da Tabela : $table\n#\n\n");
            fwrite($back, "$lin[1] ;\n\n#\n# Dados a serem incluídos na tabela\n#\n\n");

// seleciono todos os dados de cada tabela pega no while acima
// e depois gravo no arquivo .sql, usando comandos de insert
            $res3 = mysql_query("SELECT * FROM $table");
            $first = true;
            while ($r = mysql_fetch_row($res3))
            {
                if ($first)
                {
                    $sql = "INSERT INTO $table VALUES ";
                    $first = false;
                }
                else
                {
                    $sql .= ',';
                }
               
               
                $sql .= "('";
               
                $imploded = '';
               
                $firstImplode = true;
               
                foreach ($r as $reg)
                {
                    if ($firstImplode)
                    {
                        $firstImplode = false;
                    }
                    else
                    {
                        $imploded .= "', '";
                    }
                   
                    if ($checkUtf)
                    {
                        $escaped = str_replace('\'', "\\'", str_replace('\\', "\\\\", preg_replace($regex1, '$1', $reg)));
                    }
                    else
                    {
                        $escaped = str_replace('\'', "\\'", str_replace('\\', "\\\\", $reg));
                    }
                    $imploded .= $escaped;
                }
               
                $sql .= $imploded;
               
                $sql .= "')\n";

            }
            if (!$first)
            {
                $sql .= ";\n";
                fwrite($back, $sql);
            }
        }
    }

// fechar o arquivo que foi gravado
    fclose($back);
// gerando o arquivo para download, com o nome do banco e extensão sql.
    $arquivo = $dbname . ".sql";
    Header("Content-type: application/sql");
    Header("Content-Disposition: attachment; filename=$arquivo");
// lê e exibe o conteúdo do arquivo gerado
    readfile($arquivo);
?>

19 comentários:

  1. Olá, Marcos, tudo bem? Espero que sim!

    Então... Eu estou precisando fazer o upload de um arquivo .sql, e gostaria de fazê-lo através de um formulário HTML.

    Através deste formulário, o usuário encontraria o arquivo .sql a ser carregado, e os comandos PHP necessários executariam a tarefa.

    Você saberia e poderia me dizer como fazer isso? Caso negativo, poderia me indicar uma bibliografia a respeito?

    Obrigado!

    ResponderExcluir
  2. Olá,, muito bom o post foi muito útil pra mim,, vc só precisa corrigir um erro na linha 24 substitua
    $res = mysql_list_tables($dbname) or die(mysql_error());
    por
    $res = mysql_query("SHOW TABLES FROM $dbname") or die(mysql_error());

    aí não vai ter mais erro ... valeww

    ResponderExcluir
    Respostas
    1. Legal, comigo funcionou como eu fiz lá, talvez seja algum conflito de versão, obrigado por compartilhar, se alguém tiver problemas a solução está aqui.

      Excluir
  3. Boa noite.
    O arquivo veio em branco. Sabe dizer o que pode ter acontecido?
    Obrigado pelo post, e agradeço se poder responder minha pergunta.

    ResponderExcluir
    Respostas
    1. Oi Amaury, o código além de retornar o arquivo no final ele grava um arquivo "temporario" com a extensão SQL e com o nome do banco de dados dentro da pasta onde o arquivo PHP de backup está.

      Este arquivo existe na sua aplicação? ele está preenchido?

      Se ele não existir verifique as permissões de escrita de arquivo na sua pasta.

      Se ele existir e estiver vazio, tente apaga-lo manualmente e rodar o processo novamente.

      Excluir
    2. $res = mysql_list_tables($dbname) or die(mysql_error());
      por
      $res = mysql_query("SHOW TABLES FROM $dbname") or die(mysql_error());


      Provavelmente o seu problema é o que citaram a cima, da versao do seu php. ou da hospedagem. Experimente substituir a linha conforme citado.

      Excluir
  4. Excelente post! muito obrigado por nos compartilhar seu conhecimento.

    ResponderExcluir
  5. Muito bom usei e esta funcionando beleza

    Valeu obrigado

    ResponderExcluir
  6. Salvou minha vida!!!

    Tenho nem o que dizer, senão, OBRIGADO!!

    ResponderExcluir
  7. Funcionou direitinho..
    Mas tive q fazer a modificação que haseo comentou.

    ResponderExcluir
  8. Este comentário foi removido pelo autor.

    ResponderExcluir
  9. Nao gerou o download no meu, pode me ajudar??

    ResponderExcluir
    Respostas
    1. Mas ele gerou o arquivo na pasta?

      Use o comando ini_set("display_errors", 1);

      Logo no começo assim se der erro o php vai mostrar no browser e vc vai descobrir em que parte deu errado.

      Aqui nos comentarios tem um caminho colocado pelo haseo q patece ter ajudado alguns.

      Excluir
  10. Opaaa... Marcos, muito bom o post... pena que no meu deu um erro nas 17 primeiras linhas do arquivo gerado.

    O resto está corretíssimo.

    Se você puder me ajudar, por favor mandar um e-mail p/ mim eu respondo com o arquivo gerado.

    Grande abraço.

    Alex Alves de Almeida
    alexalvesdealmeida@gmail.com

    ResponderExcluir
  11. Marcos boa tarde, estou usando o seu script para teste no meu sistema, porem roda perfeitamente no servidor local porem não no servidor Web as versoes do phpmyadmin são respectivamente 5.5.27 e 5.0.33

    ResponderExcluir
  12. funcionou perfeitamente. a partir daqui posso enviar por email, registrar num banco de dados, etc

    ResponderExcluir