programing

PDO 데이터베이스 쿼리를 디버깅하는 방법

firstcheck 2022. 11. 7. 21:34
반응형

PDO 데이터베이스 쿼리를 디버깅하는 방법

PDO로 이동하기 전에 문자열을 연결하여 PHP에서 SQL 쿼리를 만들었습니다.데이터베이스 구문 오류가 발생하면 최종 SQL 쿼리 문자열을 에코하고 데이터베이스에서 직접 시도하여 오류를 수정할 때까지 조정한 후 다시 코드에 넣을 수 있습니다.

준비된 PDO 문장이 더 빠르고, 더 우수하고, 더 안전하지만, 한 가지 마음에 걸리는 점은 데이터베이스로 전송되는 최종 쿼리가 보이지 않는다는 것입니다. 내부 합니다).catch블록)을 클릭합니다.

PDO에서 데이터베이스로 전송된 전체 SQL 쿼리를 캡처하여 파일에 기록하는 방법이 있습니까?

다음과 같이 말합니다.

데이터베이스로 전송되는 최종 쿼리를 볼 수 없습니다.

음, 실제로 준비된 문장을 사용할 때, "최종 쿼리"같은 것은 없습니다.

  • DB를 사용합니다.
    • 데이터베이스는 쿼리를 해석하고 쿼리의 내부 표현을 구축합니다.
  • 또한 변수를 바인드하여 문을 실행하면 변수만 데이터베이스로 전송됩니다.
    • 데이터베이스는 스테이트먼트의 내부 표현에 값을 "주입"합니다.


요,여러분들의물음에대답해보세요.

PDO에서 데이터베이스로 전송된 전체 SQL 쿼리를 캡처하여 파일에 기록하는 방법이 있습니까?

아니요: "완전 SQL 쿼리"는 어디에도 없기 때문에 캡처할 방법이 없습니다.


디버깅을 위해 할 수 있는 최선의 방법은 값을 문의 SQL 문자열에 삽입하여 "실제" SQL 쿼리를 "재구성"하는 것입니다.

이런 상황에서 제가 주로 하는 일은 다음과 같습니다.

  • 플레이스 홀더를 사용하여 문에 해당하는 SQL 코드를 에코합니다.
  • 를 사용합니다.var_dump (또는 이에 상당하는) 바로 뒤에 모수 값을 표시합니다.
  • 일반적으로 실행할 수 있는 "실제" 쿼리가 없는 경우에도 오류가 발생할 수 있습니다.

디버깅에 관해서는 그다지 좋은 것은 아니지만, 이것은 준비된 스테이트먼트의 가격과 그것이 가져오는 이점입니다.

데이터베이스 로그 검색

Pascal MARTIN은 PDO가 완전한 쿼리를 한꺼번에 데이터베이스로 전송하지 않는 은 맞지만 DB의 로깅 기능을 사용하자는 Ryguy의 제안에 따라 완전한 쿼리를 데이터베이스에 의해 조립되고 실행되는 것을 볼 수 있었습니다.

다음 방법은 다음과 같습니다. (이 지침은 Windows 컴퓨터의 MySQL용입니다. 주행거리는 다를 수 있습니다.)

  • »my.ini의 ' ' '[mysqld] ", " log 「」, 「」등)log="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
  • MySQL을 재시작합니다.
  • 해당 파일의 모든 쿼리 로깅이 시작됩니다.

이 파일은 빠르게 증가하므로 테스트가 끝나면 반드시 삭제하고 로깅을 끄십시오.

이 하여 디버깅할 수 .{{ PDO::ATTR_ERRMODE }}쿼리 전에 새 행을 추가하면 디버깅 행이 표시됩니다.

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

debug Dump Params()를 사용하는 것이 좋습니다.준비된 쿼리에 값을 바인딩한 후 언제든지 실행할 수 있습니다(필요 없음).execute()이치노

준비된 스테이트먼트는 작성되지 않지만 파라미터가 표시됩니다.

오래된 포스트지만 아마 누군가가 이것을 유용하게 여길 것이다.

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

다음은 php.net의 "Mark" 코멘트에서 인용된 효과적인 SQL을 확인하는 함수입니다.

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

아니요. PDO 쿼리는 클라이언트 측에서 준비되지 않았습니다.PDO는 SQL 쿼리 및 매개 변수를 데이터베이스 서버로 전송하기만 하면 됩니다.데이터베이스란 (데이터베이스의) 대체를 하는 것입니다.? 두 가 있습니다. s) ㄹ게요.

  • DB의 로깅 기능을 사용합니다(단, Postgres에서는 보통 두 개의 독립된 문장으로 표시됩니다(즉, 적어도 "최종"은 아닙니다).
  • SQL 쿼리와 매개 변수를 출력하여 직접 통합합니다.

에러 로그의 체크 이외에는, 에러 표시에 대해서는 거의 언급하지 않았습니다만, 다음과 같은 편리한 기능이 있습니다.

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

(소스 링크)

이 코드가 예외 메시지 또는 다른 종류의 오류 처리로 사용되도록 수정될 수 있음은 분명하다.

예를 들어 다음과 같은 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,
));

이제 다음과 같은 배열을 정의하면 실행된 쿼리를 얻을 수 있습니다.

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

인터넷을 검색한 결과, 이것이 납득할 수 있는 해결책으로 판명되었습니다.PDO 대신 다른 클래스가 사용되며 매직 함수 호출을 통해 PDO 함수가 호출됩니다.이로 인해 심각한 성능 문제가 발생하지는 않습니다.단, PDO에 적절한 로깅 기능이 추가될 때까지 사용할 수 있습니다.

따라서 이 스레드에 따라 PDO 연결에 대한 래퍼를 작성할 수 있습니다. 이 래퍼는 오류가 발생했을 때 로그를 기록하고 예외를 발생시킬 수 있습니다.

다음으로 간단한 예를 제시하겠습니다.

class LoggedPDOSTatement extends PDOStatement    {

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

}

PDOStatement 대신 이 클래스를 사용할 수 있습니다.

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

다음은 PDO 데코레이터 구현입니다.

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

}

MySQL을 WAMP에 기록하려면 my.ini를 편집해야 합니다(예를 들어 wamp\bin\mysql\mysql5.6.17\my.ini).

[mysqld]:

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

다음은 해결된 매개 변수를 사용하여 SQL 쿼리를 반환하기 위해 만든 함수입니다.

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

이렇게 실행한다고 가정하면

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

이 함수는 쿼리에 따옴표를 추가하지 않고 작업을 수행합니다.

바로 여기에 Composer가 로드한 최신 프로젝트/저장소를 만들었습니다.

pdo의 개재에 의한

프로젝트의 GitHub 홈페이지는 이쪽, 블로그 투고는 이쪽입니다.composer.json에 추가할 한 줄이면 다음과 같이 사용할 수 있습니다.

echo debugPDO($sql, $parameters);

$sql은 raw SQL 문이고 $parameters는 파라미터 배열입니다.키는 플레이스 홀더 이름(":user_id") 또는 이름 없는 파라미터 번호("?")입니다.값은 ..well, value 입니다.

이면의 논리는 다음과 같습니다.이 스크립트는 단순히 파라미터를 그레이딩하고 제공된 SQL 문자열로 바꿉니다.매우 심플하지만 99%의 사용 사례에 매우 효과적입니다.주의: 이것은 기본적인 에뮬레이션일 뿐 실제 PDO 디버깅은 아닙니다(PHP는 별도의 미가공 SQL과 파라미터를 MySQL 서버에 전송하기 때문에 불가능합니다).

stackOverflow 스레드bigwebguy와 Mike에게 큰 감사표합니다.이 스크립트의 주요 기능 전체를 쓰기 위해 PDO에서 원시 SQL 쿼리 문자열을 가져옵니다.크게!

Ubuntu에서 PDO mysql 데이터베이스 쿼리를 디버깅하는 방법

TL;DR 모든 쿼리를 기록하고 mysql 로그를 추적합니다.

우분투 14.04입니다.를 발행하다lsb_release -a원하는 버전을 얻습니다.인스톨이 다른 경우가 있습니다.

mysql 로그 사용

  1. dev server cmd 행으로 이동합니다.
  2. 변경 " " " " "cd /etc/mysql . 라는 예요.my.cnf그게 우리가 바꿀 파일이야
  3. 되어 있는지 .cat my.cnf | grep general_log은 " " 를 합니다.my.cnf파일링 해주세요. 두 가지 이 있을 거예요.#general_log_file = /var/log/mysql/mysql.log & & &#general_log = 1.
  4. 이 두 줄의 코멘트를 해제하고 원하는 편집기를 통해 저장할 수 있습니다.
  5. mysql : mysql :sudo service mysql restart
  6. (어느쪽이든)입니다: 설gingingin nginx 。sudo service nginx restart.

잘 만들었다!준비 다 됐어.이제 로그 파일을 추적하기만 하면 앱의 PDO 쿼리를 실시간으로 볼 수 있습니다.

로그를 테일링하여 쿼리를 표시합니다.

cmd "cmd" 를 합니다.tail -f /var/log/mysql/mysql.log.

출력은 다음과 같습니다.

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 

로그를 계속 추적하는 한 앱의 새로운 쿼리는 자동으로 표시됩니다.꼬리를 내리려면cmd/ctrl c.

메모들

  1. 주의: 이 로그 파일은 커질 수 있습니다.개발 서버에서만 실행 중입니다.
  2. 그그 파일 일?? 있지만 삭제되는 것을 .즉, 파일은 그대로 유지되지만 내용은 삭제됩니다. truncate --size 0 mysql.log.
  3. 로그 파일에 mysql 접속이 리스트 되어 있는 것을 확인합니다.그 중 하나는 제가 이행을 하는 레거시 mysqli 코드에 있는 거 알아요세 번째는 새로운 PDO 연결에서 얻은 것입니다.하지만, 두 번째가 어디서 오는지는 확실치 않다.찾을 수 있는 빠른 방법이 있으면 알려주세요.

신용과 감사

의 네이선 롱의 답변에 큰 함성을 지르며 우분투에서 이 문제를 해결하기 위해 인스포를 불러주세요.또한 네이쓴의 게시물에 대한 그의 코멘트에 대한 dikirill을 위해 나는 이 해결책으로 이끌었다.

stackoverflow 사랑해요!

디버깅을 위해 PDO 면제를 취득하는 솔루션에서 발생한 문제는 PDO 면제(duh)만 검출했을 뿐 php 오류로 등록된 구문 오류를 검출하지 못했다는 것입니다(이유는 모르겠지만 "why"는 솔루션과는 무관합니다).PDO 콜은 모두 단일 테이블 모델 클래스에서 모든 테이블과의 상호 작용에 대해 확장한 것입니다.코드를 디버깅하려고 할 때 복잡한 일이 있었습니다.이 에러에 의해, 실행 콜이 호출된 php 코드의 행이 등록됩니다만, 실제로 콜이 발신된 장소를 알려주지 않았습니다.이 문제를 해결하기 위해 다음 코드를 사용했습니다.

/**
 * 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;
        }            
    }
}

따라서 위의 코드는 PDO 예외와 php 구문 오류를 모두 포착하여 동일하게 취급합니다.에러 핸들러는 다음과 같습니다.

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

테이블 모델을 글로벌 변수로 설정하는 것보다 관련 정보를 에러 핸들러로 가져오는 방법에 대한 더 좋은 아이디어가 있다면 기꺼이 듣고 코드를 편집하겠습니다.

이 코드는 매우 효과적입니다.

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

$data와 $query를 이름으로 바꾸는 것을 잊지 마십시오.

이 클래스를 사용하여 PDO를 디버깅합니다(Log4 사용).PHP)

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

Debian NGINX 환경에서 다음을 수행했습니다.

에 가다/etc/mysql/mysql.conf.d편집하다mysqld.cnf찾으시면log-error = /var/log/mysql/error.log아래에 다음 두 줄을 추가합니다.

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

를 선택합니다./var/log/mysql ★★★★★★★★★★★★★★★★★」tail -f mysql.log

환경에 .mysql.log이 로그 파일은 빠르게 증가하고 커질 수 있습니다.

언급URL : https://stackoverflow.com/questions/2411182/how-to-debug-pdo-database-queries

반응형