Como depurar consultas de banco de dados PDO?


140

Antes de mudar para o PDO, criei consultas SQL em PHP concatenando seqüências de caracteres. Se eu receber um erro de sintaxe do banco de dados, eu poderia apenas repetir a string de consulta SQL final, tentar por mim mesma no banco de dados e ajustá-la até corrigir o erro e depois inseri-la novamente no código.

As instruções PDO preparadas são mais rápidas, melhores e mais seguras, mas uma coisa me incomoda: nunca vejo a consulta final como ela é enviada ao banco de dados. Quando recebo erros sobre a sintaxe no meu log do Apache ou no meu arquivo de log personalizado (registro erros dentro de um catchbloco), não consigo ver a consulta que os causou.

Existe uma maneira de capturar a consulta SQL completa enviada pelo PDO ao banco de dados e registrá-la em um arquivo?


4
Ele está registrado em um arquivo: /var/log/mysql/*. Os parâmetros vinculados ao PDO não podem causar erros de sintaxe, portanto, tudo o que você precisa é da consulta SQL preparada.
Xeoncross 10/10

1
consulte o código em stackoverflow.com/questions/210564/… (não na resposta aceita). Não que tenha havido algumas atualizações postadas.
Mawg diz que restabelece Monica

1
Uma linha simples através do Compositor: github.com/panique/pdo-debug
SliQ

2
A resposta de Xeoncross me ajudou. Aqui está um artigo explicando como ativar esse recurso. É desativado como padrão em muitas instalações de servidores. pontikis.net/blog/how-and-when-to-enable-mysql-logs
Apr

2
Tente comvar_dump($pdo_instance->debugDumpParams())
Daniel Petrovaliev

Respostas:


99

Você diz o seguinte:

Eu nunca vejo a consulta final como ela é enviada para o banco de dados

Bem, na verdade, ao usar instruções preparadas, não existe uma " consulta final " :

  • Primeiro, uma declaração é enviada ao banco de dados e preparada lá
    • O banco de dados analisa a consulta e cria uma representação interna dela
  • E, quando você liga variáveis ​​e executa a instrução, somente as variáveis ​​são enviadas ao banco de dados
    • E o banco de dados "injeta" os valores em sua representação interna da instrução


Então, para responder sua pergunta:

Existe uma maneira de capturar a consulta SQL completa enviada pelo PDO ao banco de dados e registrá-la em um arquivo?

Não: como não há " consulta SQL completa " em nenhum lugar, não há como capturá-la.


A melhor coisa que você pode fazer, para fins de depuração, é "reconstruir" uma consulta SQL "real", injetando os valores na cadeia SQL da instrução.

O que eu costumo fazer, nesse tipo de situação, é:

  • eco o código SQL que corresponde à instrução, com espaços reservados
  • e use var_dump (ou equivalente) logo após, para exibir os valores dos parâmetros
  • Isso geralmente é suficiente para ver um possível erro, mesmo se você não tiver nenhuma consulta "real" que possa ser executada.

Isso não é ótimo quando se trata de depuração - mas esse é o preço das instruções preparadas e as vantagens que elas trazem.


1
Ótima explicação - obrigado. Aparentemente, eu tinha apenas idéias confusas de como isso funciona. Eu acho que quando a instrução é preparada, o objeto resultante contém um hash ou ID numérico que pode ser enviado de volta para o banco de dados com os parâmetros de plug-in.
Nathan Long

De nada :-) ;;; Não sei como isso é implementado em detalhes, mas suponho que seja algo parecido - o resultado é exatamente assim; enfim ;;; essa é uma das coisas boas das instruções preparadas: se você precisar executar a mesma consulta várias vezes, ela será enviada ao banco de dados e preparada apenas uma vez: para cada execução, apenas os dados serão enviados.
Pascal MARTIN

1
Atualização: Aaron Patterson mencionou no Railsconf 2011 que ele adicionou declarações mais preparadas ao Rails, mas que o benefício é muito mais pesado no PostgreSQL do que no MySQL. Ele disse que isso ocorre porque o MySQL não cria o plano de consulta até que você execute a consulta preparada.
Nathan Long

85

Procurando no log do banco de dados

Embora Pascal MARTIN esteja correto, o DOP não envia a consulta completa ao banco de dados de uma só vez, ryeguy a sugestão do de usar a função de log do banco de dados na verdade me permitiu ver a consulta completa montada e executada pelo banco de dados.

Veja como: (Estas instruções são para o MySQL em uma máquina Windows - sua milhagem pode variar)

  • Em my.ini, sob a [mysqld]seção, adicionar um logcomando, comolog="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
  • Reinicie o MySQL.
  • Ele começará a registrar todas as consultas nesse arquivo.

Esse arquivo crescerá rapidamente, exclua-o e desative o registro quando terminar de testar.


1
Apenas uma nota - eu tive que escapar das barras no meu.ini. Portanto, minha entrada parecia algo como log = "C: \\ temp \\ MySQL \\ mysql.log".
18711 Jim

4
Isso pode funcionar dependendo da configuração de PDO::ATTR_EMULATE_PREPARES. Veja esta resposta para mais informações: stackoverflow.com/questions/10658865/#answer-10658929
webbiedave

23
Eu odeio a DOP por causa disso.
Salman

1
@webbiedave - oh, uau! Sua resposta vinculada implica que minha resposta funcione apenas quando o PDO não estiver funcionando da melhor maneira possível, mas enviando toda a consulta para compatibilidade com versões anteriores da versão antiga do MySQL ou de um driver antigo. Interessante.
Nathan Long

13
No MySQL 5.5+ você precisa em general_logvez de log. Veja dev.mysql.com/doc/refman/5.5/en/query-log.html
Adrian Macneil

17

Claro que você pode depurar usando este modo {{ PDO::ATTR_ERRMODE }} Basta adicionar uma nova linha antes da sua consulta e mostrar as linhas de depuração.

$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$db->query('SELECT *******');  

Você não ligaria ->queryao usar instruções preparadas?
EoghanM

17

Provavelmente, o que você deseja fazer é usar debugDumpParams () no identificador da instrução. Você pode executá-lo a qualquer momento após vincular valores à consulta preparada (não é necessárioexecute() da instrução).

Ele não cria a instrução preparada para você, mas mostra seus parâmetros.


2
O único problema é que ele gera a depuração em vez de armazená-la internamente sem 'ecoá-la'. Não posso registrar dessa maneira.
Ricardo Martins

3
Você pode usar o buffer de saída (ob_start () ...) para armazenar a saída e registrá-la.
Cranio

bugs.php.net/bug.php?id=52384 fixa em 7,1 você pode ver a valores :) pouco tarde mas é php
Sander Visser

12

Um post antigo, mas talvez alguém ache isso útil;

function pdo_sql_debug($sql,$placeholders){
    foreach($placeholders as $k => $v){
        $sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql);
    }
    return $sql;
}

1
Para uma função semelhante que também pode lidar com parâmetros numéricos, veja minha resposta (graças a um comentarista no php.net).
Matt Browne

9

Aqui está uma função para ver qual será o SQL efetivo, acrescido de um comentário de "Mark" em php.net :

function sql_debug($sql_string, array $params = null) {
    if (!empty($params)) {
        $indexed = $params == array_values($params);
        foreach($params as $k=>$v) {
            if (is_object($v)) {
                if ($v instanceof \DateTime) $v = $v->format('Y-m-d H:i:s');
                else continue;
            }
            elseif (is_string($v)) $v="'$v'";
            elseif ($v === null) $v='NULL';
            elseif (is_array($v)) $v = implode(',', $v);

            if ($indexed) {
                $sql_string = preg_replace('/\?/', $v, $sql_string, 1);
            }
            else {
                if ($k[0] != ':') $k = ':'.$k; //add leading colon if it was left out
                $sql_string = str_replace($k,$v,$sql_string);
            }
        }
    }
    return $sql_string;
}

Por que "Mark" usa dois pontos antes de $ k in str_replace(":$k" ....? Os índices associativos já o possuem na matriz $ params.
277 Alan

Boa pergunta ... isso pode explicar: stackoverflow.com/questions/9778887/… . Pessoalmente, usei essa função para depurar consultas do Doctrine e acho que o Doctrine usa parâmetros numerados em vez de nomeados, portanto não percebi esse problema. Atualizei a função para que ela funcione com ou sem dois pontos principais agora.
Matt Browne

note que esta solução substitui :name_longpor :name. Pelo menos se :namevier antes :name_long. As instruções preparadas do MySQL podem lidar com isso corretamente, então não deixe que isso o confunda.
Zim84

8

Não. As consultas PDO não são preparadas no lado do cliente. O PDO simplesmente envia a consulta SQL e os parâmetros para o servidor de banco de dados. O banco de dados é o que faz a substituição (dos ?). Você tem duas opções:

  • Use a função de log do seu banco de dados (mas mesmo assim é normalmente mostrada como duas instruções separadas (por exemplo, "não final") pelo menos no Postgres)
  • Envie a consulta SQL e os paramaters e junte você mesmo

Eu nunca pensei em verificar o log do banco de dados. Estou bisbilhotando no diretório MySQL e não vejo nenhum arquivo de log, mas talvez o log seja uma opção que eu precise ativar em algum lugar.
Nathan Long

Sim, você precisa ativá-lo. Não sei os detalhes, mas, por padrão, ele não registra todas as consultas.
Ryeguy

5

quase nada foi dito sobre a exibição de erros, exceto verificar os logs de erros, mas há uma funcionalidade bastante útil:

<?php
/* Provoke an error -- bogus SQL syntax */
$stmt = $dbh->prepare('bogus sql');
if (!$stmt) {
    echo "\PDO::errorInfo():\n";
    print_r($dbh->errorInfo());
}
?>

( link de origem )

é claro que esse código pode ser modificado para ser usado como mensagem de exceção ou qualquer outro tipo de tratamento de erro


2
Este é o caminho errado. A DOP é inteligente o suficiente para tornar esse código inútil. Apenas diga a ele para lançar exceções sobre erros. O PHP fará o resto, muito melhor do que essa função limitada. Além disso, por favor , aprender a não imprimir todos os erros diretamente no browser. Existem maneiras melhores.
Seu senso comum

3
essa é a documentação oficial e, é claro, ninguém imprimiria esse erro na produção. Novamente, este é um exemplo do site oficial (php.net); veja o link abaixo do exemplo de código. E com certeza muito melhor é usar parâmetros adicionais $ db-> setAttribute (PDO :: ATTR_ERRMODE, PDO :: ERRMODE_EXCEPTION) dentro da instanciação DOP, mas, infelizmente, você não pode ter acesso a esse código
Zippp

4

por exemplo, você tem esta instrução pdo:

$query="insert into tblTest (field1, field2, field3)
values (:val1, :val2, :val3)";
$res=$db->prepare($query);
$res->execute(array(
  ':val1'=>$val1,
  ':val2'=>$val2,
  ':val3'=>$val3,
));

agora você pode obter a consulta executada definindo uma matriz como esta:

$assoc=array(
  ':val1'=>$val1,
  ':val2'=>$val2,
  ':val3'=>$val3,
);
$exQuery=str_replace(array_keys($assoc), array_values($assoc), $query);
echo $exQuery;

1
Trabalhou para mim. Você tem um erro no segundo exemplo de código: ));deve ser );(apenas um colchete).
Jasom Dotnet

2

Pesquisando na internet, achei isso uma solução aceitável. Uma classe diferente é usada em vez das funções DOP e DOP são chamadas por meio de chamadas de funções mágicas. Não tenho certeza se isso cria sérios problemas de desempenho. Mas pode ser usado até que um recurso de registro sensível seja adicionado ao DOP.

Portanto, de acordo com esse encadeamento , você pode escrever um invólucro para a conexão PDO, que pode registrar e gerar uma exceção quando receber um erro.

Aqui está um exemplo simples:

class LoggedPDOSTatement extends PDOStatement    {

function execute ($array)    {
    parent::execute ($array);
    $errors = parent::errorInfo();
    if ($errors[0] != '00000'):
        throw new Exception ($errors[2]);
    endif;
  }

}

para que você possa usar essa classe em vez de PDOStatement:

$this->db->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('LoggedPDOStatement', array()));

Aqui, uma implementação do decorador da DOP mencionada:

class LoggedPDOStatement    {

function __construct ($stmt)    {
    $this->stmt = $stmt;
}

function execute ($params = null)    {
    $result = $this->stmt->execute ($params); 
    if ($this->stmt->errorCode() != PDO::ERR_NONE):
        $errors = $this->stmt->errorInfo();
        $this->paint ($errors[2]);
    endif;
    return $result;
}

function bindValue ($key, $value)    {
    $this->values[$key] = $value;    
    return $this->stmt->bindValue ($key, $value);
}

function paint ($message = false)    {
    echo '<pre>';
    echo '<table cellpadding="5px">';
    echo '<tr><td colspan="2">Message: ' . $message . '</td></tr>';
    echo '<tr><td colspan="2">Query: ' . $this->stmt->queryString . '</td></tr>';
    if (count ($this->values) > 0):
    foreach ($this->values as $key => $value):
    echo '<tr><th align="left" style="background-color: #ccc;">' . $key . '</th><td>' . $value . '</td></tr>';
    endforeach;
    endif;
    echo '</table>';
    echo '</pre>';
}

function __call ($method, $params)    {
    return call_user_func_array (array ($this->stmt, $method), $params); 
}

}

2

Para registrar o MySQL no WAMP , você precisará editar o my.ini (por exemplo, em wamp \ bin \ mysql \ mysql5.6.17 \ my.ini)

e adicione a [mysqld]:

general_log = 1
general_log_file="c:\\tmp\\mysql.log"

1

Aqui está uma função que eu fiz para retornar uma consulta SQL com parâmetros "resolvidos".

function paramToString($query, $parameters) {
    if(!empty($parameters)) {
        foreach($parameters as $key => $value) {
            preg_match('/(\?(?!=))/i', $query, $match, PREG_OFFSET_CAPTURE);
            $query = substr_replace($query, $value, $match[0][1], 1);
        }
    }
    return $query;
    $query = "SELECT email FROM table WHERE id = ? AND username = ?";
    $values = [1, 'Super'];

    echo paramToString($query, $values);

Supondo que você execute assim

$values = array(1, 'SomeUsername');
$smth->execute($values);

Esta função NÃO adiciona aspas às consultas, mas faz o trabalho para mim.


0

O problema que tive com a solução para capturar isenções de DOP para fins de depuração é que ele só capturou isenções de DOP (duh), mas não encontrou erros de sintaxe que foram registrados como erros de php (não sei por que, mas " por que "é irrelevante para a solução). Todas as minhas chamadas PDO vêm de uma única classe de modelo de tabela que eu estendi para todas as minhas interações com todas as tabelas ... isso complicou quando eu estava tentando depurar código, porque o erro registrava a linha do código php onde minha chamada de execução era ligou, mas não me disse de onde a ligação estava sendo feita. Usei o seguinte código para resolver esse problema:

/**
 * Executes a line of sql with PDO.
 * 
 * @param string $sql
 * @param array $params
 */
class TableModel{
    var $_db; //PDO connection
    var $_query; //PDO query

    function execute($sql, $params) { 
        //we're saving this as a global, so it's available to the error handler
        global $_tm;
        //setting these so they're available to the error handler as well
        $this->_sql = $sql;
        $this->_paramArray = $params;            

        $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->_query = $this->_db->prepare($sql);

        try {
            //set a custom error handler for pdo to catch any php errors
            set_error_handler('pdoErrorHandler');

            //save the table model object to make it available to the pdoErrorHandler
            $_tm = $this;
            $this->_query->execute($params);

            //now we restore the normal error handler
            restore_error_handler();
        } catch (Exception $ex) {
            pdoErrorHandler();
            return false;
        }            
    }
}

Portanto, o código acima captura ambas as exceções DOP e erros de sintaxe php e os trata da mesma maneira. Meu manipulador de erros é algo como isto:

function pdoErrorHandler() {
    //get all the stuff that we set in the table model
    global $_tm;
    $sql = $_tm->_sql;
    $params = $_tm->_params;
    $query = $tm->_query;

    $message = 'PDO error: ' . $sql . ' (' . implode(', ', $params) . ") \n";

    //get trace info, so we can know where the sql call originated from
    ob_start();
    debug_backtrace(); //I have a custom method here that parses debug backtrace, but this will work as well
    $trace = ob_get_clean();

    //log the error in a civilized manner
    error_log($message);

    if(admin(){
        //print error to screen based on your environment, logged in credentials, etc.
        print_r($message);
    }
}

Se alguém tiver idéias melhores sobre como obter informações relevantes para meu manipulador de erros do que definir o modelo de tabela como uma variável global, ficaria feliz em ouvi-lo e editar meu código.


0

esse código funciona muito bem para mim:

echo str_replace(array_keys($data), array_values($data), $query->queryString);

Não se esqueça de substituir $ data e $ query pelos seus nomes


0

eu uso essa classe para depurar o PDO (com Log4PHP )

<?php

/**
 * Extends PDO and logs all queries that are executed and how long
 * they take, including queries issued via prepared statements
 */
class LoggedPDO extends PDO
{

    public static $log = array();

    public function __construct($dsn, $username = null, $password = null, $options = null)
    {
        parent::__construct($dsn, $username, $password, $options);
    }

    public function query($query)
    {
        $result = parent::query($query);
        return $result;
    }

    /**
     * @return LoggedPDOStatement
     */
    public function prepare($statement, $options = NULL)
    {
        if (!$options) {
            $options = array();
        }
        return new \LoggedPDOStatement(parent::prepare($statement, $options));
    }
}

/**
 * PDOStatement decorator that logs when a PDOStatement is
 * executed, and the time it took to run
 * @see LoggedPDO
 */
class LoggedPDOStatement
{

    /**
     * The PDOStatement we decorate
     */
    private $statement;
    protected $_debugValues = null;

    public function __construct(PDOStatement $statement)
    {
        $this->statement = $statement;
    }

    public function getLogger()
    {
        return \Logger::getLogger('PDO sql');
    }

    /**
     * When execute is called record the time it takes and
     * then log the query
     * @return PDO result set
     */
    public function execute(array $params = array())
    {
        $start = microtime(true);
        if (empty($params)) {
            $result = $this->statement->execute();
        } else {
            foreach ($params as $key => $value) {
                $this->_debugValues[$key] = $value;
            }
            $result = $this->statement->execute($params);
        }

        $this->getLogger()->debug($this->_debugQuery());

        $time = microtime(true) - $start;
        $ar = (int) $this->statement->rowCount();
        $this->getLogger()->debug('Affected rows: ' . $ar . ' Query took: ' . round($time * 1000, 3) . ' ms');
        return $result;
    }

    public function bindValue($parameter, $value, $data_type = false)
    {
        $this->_debugValues[$parameter] = $value;
        return $this->statement->bindValue($parameter, $value, $data_type);
    }

    public function _debugQuery($replaced = true)
    {
        $q = $this->statement->queryString;

        if (!$replaced) {
            return $q;
        }

        return preg_replace_callback('/:([0-9a-z_]+)/i', array($this, '_debugReplace'), $q);
    }

    protected function _debugReplace($m)
    {
        $v = $this->_debugValues[$m[0]];

        if ($v === null) {
            return "NULL";
        }
        if (!is_numeric($v)) {
            $v = str_replace("'", "''", $v);
        }

        return "'" . $v . "'";
    }

    /**
     * Other than execute pass all other calls to the PDOStatement object
     * @param string $function_name
     * @param array $parameters arguments
     */
    public function __call($function_name, $parameters)
    {
        return call_user_func_array(array($this->statement, $function_name), $parameters);
    }
}

0

Eu criei um projeto / repositório moderno carregado pelo Composer para exatamente isso aqui:

pdo-debug

Encontre a home do GitHub do projeto aqui , veja uma postagem no blog explicando aqui . Uma linha para adicionar ao seu compositor.json e, em seguida, você pode usá-lo assim:

echo debugPDO($sql, $parameters);

$ sql é a instrução SQL bruta, $ parameters é uma matriz de seus parâmetros: A chave é o nome do espaço reservado (": user_id") ou o número do parâmetro não nomeado ("?"), o valor é .. bem, o valor.

A lógica por trás: Este script simplesmente classifica os parâmetros e os substitui na string SQL fornecida. Super simples, mas super eficaz para 99% dos seus casos de uso. Nota: Esta é apenas uma emulação básica, não uma depuração real do PDO (pois isso não é possível porque o PHP envia SQL e parâmetros brutos para o servidor MySQL separados).

Muito obrigado a bigwebguy e Mike, do thread StackOverflow. Obtendo a string de consulta SQL bruta do PDO por escrever basicamente toda a função principal por trás desse script. Engrandecer!


0

Como depurar consultas de banco de dados mysql PDO no Ubuntu

TL; DR Registre todas as suas consultas e siga o log do mysql.

Estas instruções são para a minha instalação do Ubuntu 14.04. Emita o comando lsb_release -apara obter sua versão. Sua instalação pode ser diferente.

Ative o login no mysql

  1. Vá para a linha cmd do servidor de desenvolvimento
  2. Alterar diretórios cd /etc/mysql. Você deve ver um arquivo chamadomy.cnf . Esse é o arquivo que vamos mudar.
  3. Verifique se você está no lugar certo digitando cat my.cnf | grep general_log. Isso filtra o my.cnfarquivo para você. Você verá duas entradas: #general_log_file = /var/log/mysql/mysql.log&& #general_log = 1.
  4. Remova o comentário dessas duas linhas e salve pelo editor de sua escolha.
  5. Mysql Restart: sudo service mysql restart.
  6. Você também pode precisar reiniciar o servidor da web. (Não me lembro da sequência que usei). Para minha instalação, que é nginx: sudo service nginx restart.

Bom trabalho! Você está pronto. Agora tudo o que você precisa fazer é ajustar o arquivo de log para que você possa ver as consultas DOP que seu aplicativo faz em tempo real.

Tail o registro para ver suas consultas

Digite este cmd tail -f /var/log/mysql/mysql.log.

Sua saída será mais ou menos assim:

73 Connect  xyz@localhost on your_db
73 Query    SET NAMES utf8mb4
74 Connect  xyz@localhost on your_db
75 Connect  xyz@localhost on your_db
74 Quit 
75 Prepare  SELECT email FROM customer WHERE email=? LIMIT ?
75 Execute  SELECT email FROM customer WHERE email='a@b.co' LIMIT 5
75 Close stmt   
75 Quit 
73 Quit 

Quaisquer novas consultas feitas por seu aplicativo serão exibidas automaticamente , desde que você continue seguindo o registro. Para sair da cauda, ​​pressionecmd/ctrl c .

Notas

  1. Cuidado: esse arquivo de log pode ficar enorme. Só estou executando isso no meu servidor de desenvolvimento.
  2. O arquivo de log está ficando muito grande? Trunque. Isso significa que o arquivo permanece, mas o conteúdo é excluído. truncate --size 0 mysql.log.
  3. Legal que o arquivo de log lista as conexões mysql. Eu sei que um deles é do meu código mysqli legado do qual estou fazendo a transição. O terceiro é da minha nova conexão DOP. No entanto, não sei de onde vem o segundo. Se você souber uma maneira rápida de encontrá-lo, entre em contato.

Crédito e agradecimento

Um enorme grito à resposta de Nathan Long acima, para que o inspo descubra isso no Ubuntu. Também para dikirill pelo seu comentário no post de Nathan que me levou a esta solução.

Te amo stackoverflow!


0

No ambiente Debian NGINX, fiz o seguinte.

Vá para /etc/mysql/mysql.conf.deditar, mysqld.cnfse você log-error = /var/log/mysql/error.logadicionar, adicione as 2 linhas abaixo.

general_log_file        = /var/log/mysql/mysql.log
general_log             = 1

Para ver os logs irem /var/log/mysqle tail -f mysql.log

Lembre-se de comentar essas linhas quando terminar a depuração, se você estiver no ambiente de produção excluído, mysql.logpois esse arquivo de log aumentará rapidamente e poderá ser enorme.


nem todo mundo usa mysql.
Temido ponto e vírgula 24/06
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.