¿Cómo depurar consultas de bases de datos PDO?


140

Antes de pasar a PDO, creé consultas SQL en PHP concatenando cadenas. Si recibí un error de sintaxis de la base de datos, podría hacerme eco de la cadena de consulta SQL final, probarlo yo mismo en la base de datos y ajustarlo hasta que solucione el error, luego volver a colocarlo en el código.

Las declaraciones PDO preparadas son más rápidas, mejores y más seguras, pero una cosa me molesta: nunca veo la consulta final, ya que se envía a la base de datos. Cuando recibo errores sobre la sintaxis en mi registro de Apache o en mi archivo de registro personalizado (registro errores dentro de un catchbloque), no puedo ver la consulta que los causó.

¿Hay alguna forma de capturar la consulta SQL completa enviada por PDO a la base de datos y registrarla en un archivo?


44
Que se registra en un archivo: /var/log/mysql/*. Los parámetros vinculados a PDO no pueden causar errores de sintaxis, por lo que todo lo que necesita es la consulta SQL preparada.
Xeoncross

1
vea el código en stackoverflow.com/questions/210564/… (no en la respuesta aceptada). No es que haya habido algunas actualizaciones publicadas.
Mawg dice que reinstalar a Monica el

1
Simple línea a través de Composer: github.com/panique/pdo-debug
Sliq

2
La respuesta de Xeoncross me ayudó. Aquí hay un artículo que explica cómo activar esta función. Está desactivado por defecto en muchas instalaciones de servidores. pontikis.net/blog/how-and-when-to-enable-mysql-logs
mrbinky3000

2
Probar convar_dump($pdo_instance->debugDumpParams())
Daniel Petrovaliev

Respuestas:


99

Tú dices esto :

Nunca veo la consulta final ya que se envía a la base de datos

Bueno, en realidad, cuando se usan declaraciones preparadas, no existe una " consulta final " :

  • Primero, se envía una declaración al DB y se prepara allí
    • La base de datos analiza la consulta y crea una representación interna de la misma.
  • Y, cuando vincula variables y ejecuta la instrucción, solo las variables se envían a la base de datos
    • Y la base de datos "inyecta" los valores en su representación interna de la declaración


Entonces, para responder a su pregunta:

¿Hay alguna forma de capturar la consulta SQL completa enviada por PDO a la base de datos y registrarla en un archivo?

No: como no hay una " consulta SQL completa " en ninguna parte, no hay forma de capturarla.


Lo mejor que puede hacer, para propósitos de depuración, es "reconstruir" una consulta SQL "real", inyectando los valores en la cadena SQL de la declaración.

Lo que suelo hacer, en este tipo de situaciones, es:

  • echo el código SQL que corresponde a la declaración, con marcadores de posición
  • y usar var_dump (o un equivalente) justo después, para mostrar los valores de los parámetros
  • Esto generalmente es suficiente para ver un posible error, incluso si no tiene ninguna consulta "real" que pueda ejecutar.

Esto no es bueno cuando se trata de depuración, pero ese es el precio de las declaraciones preparadas y las ventajas que aportan.


1
Gran explicación, gracias. Aparentemente solo tenía ideas borrosas de cómo funciona esto. Supongo que cuando se prepara la declaración, el objeto resultante contiene un hash o ID numérico que puede enviarse de vuelta a la base de datos con los parámetros para enchufar.
Nathan Long

De nada :-) ;;; No sé cómo se implementa esto en detalles, pero supongo que es algo así: el resultado es exactamente así, de todos modos ;; esa es una de las cosas buenas con las declaraciones preparadas: si tiene que ejecutar la misma consulta muchas veces, solo se enviará a la base de datos y se preparará una vez: para cada ejecución, solo se enviarán los datos.
Pascal MARTIN

1
Actualización: Aaron Patterson mencionó en Railsconf 2011 que agregó declaraciones más preparadas a Rails, pero que el beneficio es mucho mayor en PostgreSQL que en MySQL. Dijo que esto se debe a que MySQL en realidad no crea el plan de consulta hasta que ejecute la consulta preparada.
Nathan Long

85

Buscando en el registro de la base de datos

Aunque Pascal MARTIN tiene razón en que PDO no envía la consulta completa a la base de datos de una vez, la sugerencia de ryeguy de usar la función de registro de la base de datos realmente me permitió ver la consulta completa tal como fue ensamblada y ejecutada por la base de datos.

Aquí se explica cómo: (Estas instrucciones son para MySQL en una máquina con Windows; su kilometraje puede variar)

  • En my.ini, en la [mysqld]sección, agregue un logcomando, comolog="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
  • Reinicie MySQL.
  • Comenzará a registrar cada consulta en ese archivo.

Ese archivo crecerá rápidamente, así que asegúrese de eliminarlo y desactivar el registro cuando haya terminado la prueba.


1
Solo una nota: tuve que escapar de las barras en my.ini. Entonces, mi entrada se parecía a log = "C: \\ temp \\ MySQL \\ mysql.log".
Jim

44
Esto puede funcionar dependiendo de la configuración de PDO::ATTR_EMULATE_PREPARES. Consulte esta respuesta para obtener más información: stackoverflow.com/questions/10658865/#answer-10658929
webbiedave

23
Odio la DOP por esto.
Salman

1
@webbiedave - ¡oh, wow! Su respuesta vinculada implica que mi respuesta solo funciona cuando PDO no funciona de manera óptima, sino que envía toda la consulta para la compatibilidad con versiones anteriores de MySQL o un controlador anterior. Interesante.
Nathan Long

13
En MySQL 5.5+ necesitas en general_loglugar de log. Ver dev.mysql.com/doc/refman/5.5/en/query-log.html
Adrian Macneil

17

Seguro que puede depurar usando este modo {{ PDO::ATTR_ERRMODE }} Simplemente agregue una nueva línea antes de su consulta y luego mostrará las líneas de depuración.

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

Sin embargo, ¿no estarías llamando ->queryal usar declaraciones preparadas?
EoghanM

17

Probablemente lo que desea hacer es usar debugDumpParams () en el identificador de la instrucción. Puede ejecutarlo en cualquier momento después de vincular valores a la consulta preparada (no es necesario para execute()la instrucción).

No crea la declaración preparada para usted, pero mostrará sus parámetros.


2
El único problema es que genera la depuración en lugar de almacenarla internamente sin 'Echoing'. No puedo registrarlo de esta manera.
Ricardo Martins

3
Puede usar el almacenamiento en búfer de salida (ob_start () ...) para almacenar la salida y registrarla.
Cranio

bugs.php.net/bug.php?id=52384 corregido en 7.1 puedes ver los valores :) un poco tarde pero es php
Sander Visser

12

Una publicación antigua, pero tal vez alguien lo encuentre útil;

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

1
Para una función similar que también puede manejar parámetros numéricos, vea mi respuesta (gracias a un comentarista en php.net).
Matt Browne

9

Aquí hay una función para ver cuál será el SQL efectivo, adaptado de un comentario de "Mark" en 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 qué "Mark" usa dos puntos antes de $ k str_replace(":$k" ....? Los índices asociativos ya lo tienen en la matriz $ params.
Alan

Buena pregunta ... esto podría explicarlo: stackoverflow.com/questions/9778887/… . Personalmente, utilicé esta función para depurar consultas de Doctrine, y creo que Doctrine usa parámetros numerados en lugar de nombrados, por lo que no noté este problema. Actualicé la función para que funcione con o sin dos puntos principales ahora.
Matt Browne

tenga en cuenta que esta solución reemplaza :name_longcon :name. Al menos si :nameviene antes :name_long. Las declaraciones preparadas de MySQL pueden manejar esto correctamente, así que no dejes que eso te confunda.
Zim84

8

No. Las consultas PDO no están preparadas en el lado del cliente. PDO simplemente envía la consulta SQL y los parámetros al servidor de la base de datos. La base de datos es lo que hace la sustitución (de los ?'s). Tienes dos opciones:

  • Use la función de registro de su base de datos (pero incluso entonces normalmente se muestra como dos declaraciones separadas (es decir, "no final") al menos con Postgres)
  • Envíe la consulta SQL y los parámetros y recójelos usted mismo

Nunca pensé en revisar el registro de la base de datos. Estoy hurgando en el directorio MySQL y no veo ningún archivo de registro, pero tal vez el registro es una opción que tengo que activar en alguna parte.
Nathan Long

Sí, tienes que encenderlo. No sé los detalles, pero por defecto no registra todas las consultas.
ryeguy

5

casi no se dijo nada sobre la visualización de errores, excepto verificar los registros de errores, pero hay una funcionalidad bastante útil:

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

( enlace fuente )

Está claro que este código puede modificarse para usarse como mensaje de excepción o cualquier otro tipo de manejo de errores.


2
Esta es la manera incorrecta. PDO es lo suficientemente inteligente como para hacer que este código sea inútil. Solo dígale que arroje excepciones sobre errores. PHP hará el resto, mucho mejor que esta función limitada. También, por favor , aprender a no imprimir todos los errores directamente en el navegador. Hay mejores formas
Su sentido común

3
esa es la documentación oficial y, por supuesto, nadie iba a imprimir ese error en la producción, nuevamente este es un ejemplo del sitio oficial (php.net), vea el enlace debajo del ejemplo de código. Y seguramente mucho mejor es usar parámetros adicionales $ db-> setAttribute (PDO :: ATTR_ERRMODE, PDO :: ERRMODE_EXCEPTION) dentro de la instancia de PDO pero desafortunadamente no pudo tener acceso a ese código
Zippp

4

por ejemplo tienes esta declaración 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,
));

ahora puede obtener la consulta ejecutada definiendo una matriz como esta:

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

1
Trabajó para mi. Tiene un error en el segundo ejemplo de código: ));debe ser );(solo un paréntesis).
Jasom Dotnet

2

Buscando en internet encontré esto como una solución aceptable. Se utiliza una clase diferente en lugar de PDO y las funciones de PDO se invocan mediante llamadas a funciones mágicas. No estoy seguro de que esto cree serios problemas de rendimiento. Pero se puede usar hasta que se agregue una función de registro sensible a PDO.

Entonces, según este hilo , puede escribir un contenedor para su conexión PDO que puede iniciar sesión y arroja una excepción cuando obtiene un error.

Aquí hay un ejemplo simple:

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 pueda usar esa clase en lugar de PDOStatement:

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

Aquí una implementación de decorador PDO 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 MySQL en WAMP , deberá editar my.ini (por ejemplo, en wamp \ bin \ mysql \ mysql5.6.17 \ my.ini)

y agregar a [mysqld]:

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

1

Aquí hay una función que hice para devolver una consulta SQL con parámetros "resueltos".

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

Asumiendo que ejecutas así

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

Esta función NO agrega comillas a las consultas, pero hace el trabajo por mí.


0

El problema que tuve con la solución para capturar exenciones de PDO para propósitos de depuración es que solo capturó exenciones de PDO (duh), pero no detectó errores de sintaxis que se registraron como errores de php (no estoy seguro de por qué esto es así, pero " por qué "es irrelevante para la solución). Todas mis llamadas PDO provienen de una clase de modelo de tabla única que extendí para todas mis interacciones con todas las tablas ... esto complicaba las cosas cuando intentaba depurar código, porque el error registraba la línea de código php donde estaba mi llamada de ejecución llamé, pero no me dijo de dónde se estaba haciendo la llamada. Usé el siguiente código para resolver este 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;
        }            
    }
}

Entonces, el código anterior detecta AMBAS excepciones PDO Y errores de sintaxis php y los trata de la misma manera. Mi controlador de errores se parece a esto:

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

Si alguien tiene mejores ideas sobre cómo obtener información relevante para mi controlador de errores que configurar el modelo de tabla como una variable global, me complacería escucharlo y editar mi código.


0

Este código funciona muy bien para mí:

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

No olvide reemplazar $ data y $ query por sus nombres


0

Yo uso esta clase para depurar PDO (con 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

He creado un proyecto / repositorio moderno cargado por Composer para exactamente esto aquí:

pdo-debug

Encuentre la casa de GitHub del proyecto aquí , vea una publicación de blog que lo explica aquí . Una línea para agregar en su composer.json, y luego puede usarla así:

echo debugPDO($sql, $parameters);

$ sql es la instrucción SQL sin procesar, $ parámetros es una matriz de sus parámetros: la clave es el nombre del marcador de posición (": user_id") o el número del parámetro sin nombre ("?"), el valor es ... bueno, el valor.

La lógica detrás: este script simplemente graduará los parámetros y los reemplazará en la cadena SQL provista. Súper simple, pero súper efectivo para el 99% de sus casos de uso. Nota: Esta es solo una emulación básica, no una depuración PDO real (ya que esto no es posible ya que PHP envía SQL sin procesar y parámetros separados al servidor MySQL).

Muchas gracias a bigwebguy y Mike del hilo de StackOverflow Obteniendo una cadena de consulta SQL sin procesar de PDO para escribir básicamente toda la función principal detrás de este script. Big up!


0

Cómo depurar consultas de bases de datos mysql de PDO en Ubuntu

TL; DR Registre todas sus consultas y siga el registro de mysql.

Estas instrucciones son para mi instalación de Ubuntu 14.04. Ejecute el comando lsb_release -apara obtener su versión. Su instalación puede ser diferente.

Active el inicio de sesión en mysql

  1. Vaya a la línea de cmd de su servidor de desarrollo
  2. Cambie los directorios cd /etc/mysql. Deberías ver un archivo llamadomy.cnf . Ese es el archivo que vamos a cambiar.
  3. Verifica que estás en el lugar correcto escribiendo cat my.cnf | grep general_log. Esto filtra el my.cnfarchivo por ti. Debería ver dos entradas: #general_log_file = /var/log/mysql/mysql.log&&#general_log = 1 .
  4. Elimine el comentario de esas dos líneas y guárdelas a través del editor de su elección.
  5. Reinicie mysql: sudo service mysql restart .
  6. Es posible que también deba reiniciar su servidor web. (No recuerdo la secuencia que usé). Para instalar mi, eso es Nginx: sudo service nginx restart.

¡Buen trabajo! Estás listo. Ahora todo lo que tiene que hacer es seguir el archivo de registro para que pueda ver las consultas PDO que hace su aplicación en tiempo real.

Sigue el registro para ver tus consultas

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

Su salida se verá más o menos así:

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 

Cualquier consulta nueva que haga su aplicación aparecerá automáticamente , siempre y cuando continúe siguiendo el registro. Para salir de la cola, golpeacmd/ctrl c .

Notas

  1. Cuidado: este archivo de registro puede ser enorme. Solo estoy ejecutando esto en mi servidor de desarrollo.
  2. ¿El archivo de registro se está volviendo demasiado grande? Truncarlo. Eso significa que el archivo permanece, pero los contenidos se eliminan. truncate --size 0 mysql.log.
  3. Es genial que el archivo de registro enumere las conexiones mysql. Sé que uno de esos es de mi código mysqli heredado del que estoy haciendo la transición. El tercero es de mi nueva conexión PDO. Sin embargo, no estoy seguro de dónde viene el segundo. Si conoce una forma rápida de encontrarlo, avíseme.

Crédito y gracias

Enorme agradecimiento a la respuesta anterior de Nathan Long para que la inspo descubra esto en Ubuntu. También a dikirill por su comentario sobre la publicación de Nathan que me llevó a esta solución.

Te amo stackoverflow!


0

En el entorno Debian NGINX hice lo siguiente.

Vaya a /etc/mysql/mysql.conf.deditar mysqld.cnfsi encuentra, log-error = /var/log/mysql/error.logagregue las siguientes 2 líneas debajo.

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

Para ver los registros goto /var/log/mysqly tail -f mysql.log

Recuerde comentar estas líneas una vez que haya terminado con la depuración si está en el entorno de producción, elimine mysql.logya que este archivo de registro crecerá rápidamente y puede ser enorme.


no todos usan mysql.
Temido punto y coma
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.