[php] PDO MySQL : PDO :: ATTR_EMULATE_PREPARES를 사용합니까?

이것이 내가 지금까지 읽은 내용입니다 PDO::ATTR_EMULATE_PREPARES.

  1. MySQL의 기본 준비가 쿼리 캐시를 우회하므로 PDO의 준비 에뮬레이션이 성능면에서 더 좋습니다 .
  2. MySQL의 기본 준비는 보안에 더 적합합니다 (SQL 주입 방지) .
  3. MySQL의 기본 준비는 오류보고에 더 적합합니다 .

더 이상이 진술이 얼마나 사실인지 모르겠습니다. MySQL 인터페이스를 선택할 때 가장 큰 관심사는 SQL 주입을 방지하는 것입니다. 두 번째 관심사는 성능입니다.

내 응용 프로그램은 현재 절차 적 MySQLi (준비된 문 없음)를 사용하고 쿼리 캐시를 상당히 많이 사용합니다. 단일 요청에서 준비된 명령문을 거의 재사용하지 않습니다. 명명 된 매개 변수와 준비된 명령문의 보안을 위해 PDO로 이동하기 시작했습니다.

나는 사용 MySQL 5.1.61하고PHP 5.3.2

PDO::ATTR_EMULATE_PREPARES활성화 상태로 두어야 합니까? 쿼리 캐시의 성능과 준비된 명령문의 보안을 모두 가질 수있는 방법이 있습니까?



답변

귀하의 우려에 답하려면

  1. MySQL> = 5.1.17 (또는 PREPAREEXECUTE문에 대해> = 5.1.21 ) 은 쿼리 캐시에서 준비된 문을 사용할 수 있습니다 . 따라서 MySQL + PHP 버전은 쿼리 캐시와 함께 준비된 문을 사용할 수 있습니다. 그러나 MySQL 문서의 쿼리 결과 캐싱에 대한주의 사항에주의하십시오. 캐시 할 수 없거나 캐시하더라도 쓸모없는 쿼리에는 여러 종류가 있습니다. 내 경험상 쿼리 캐시는 어쨌든 그리 큰 승리는 아닙니다. 쿼리와 스키마는 캐시를 최대한 활용하기 위해 특별한 구성이 필요합니다. 종종 애플리케이션 수준의 캐싱은 결국 장기적으로 필요합니다.

  2. 기본 준비는 보안에 아무런 차이가 없습니다. 의사 준비된 명령문은 여전히 ​​쿼리 매개 변수 값을 이스케이프하며 바이너리 프로토콜을 사용하는 MySQL 서버 대신 문자열이있는 PDO 라이브러리에서 수행됩니다. 즉, 동일한 PDO 코드가 EMULATE_PREPARES설정에 관계없이 주입 공격에 똑같이 취약하거나 취약하지 않습니다 . 유일한 차이점은 매개 변수 대체가 EMULATE_PREPARES발생하는 위치입니다.를 사용하면 PDO 라이브러리에서 발생합니다. 없이는 EMULATE_PREPARESMySQL 서버에서 발생합니다.

  3. EMULATE_PREPARES그렇지 않으면 실행 시간이 아닌 준비 시간에 구문 오류가 발생할 수 있습니다. 와 EMULATE_PREPARESPDO는 실행 시간까지 MySQL로 제공하는 쿼리를 가지고 있지 않기 때문에에만 실행시 구문 오류를 얻을 것이다. 참고 이것은 당신이 쓸 것 코드에 영향을 미친다 ! 특히 사용하는 경우 PDO::ERRMODE_EXCEPTION!

추가 고려 사항 :

  • prepare()(기본 준비된 문 사용 )에는 고정 비용이 있으므로 prepare();execute()기본 준비된 문을 사용하는 경우 에뮬레이트 된 준비된 문을 사용하여 일반 텍스트 쿼리를 실행하는 것보다 약간 느릴 수 있습니다. 많은 데이터베이스 시스템에서 a에 대한 쿼리 계획 prepare()도 캐시되고 여러 연결과 공유 될 수 있지만 MySQL이이를 수행한다고 생각하지 않습니다. 따라서 여러 쿼리에 대해 준비된 문 개체를 재사용하지 않으면 전체 실행 속도가 느려질 수 있습니다.

최종 권장 사항 으로 이전 버전의 MySQL + PHP에서는 준비된 명령문을 에뮬레이션해야한다고 생각하지만 최신 버전에서는 에뮬레이션을 꺼야합니다.

PDO를 사용하는 몇 가지 앱을 작성한 후 가장 좋은 설정이라고 생각하는 PDO 연결 기능을 만들었습니다. 다음과 같은 것을 사용하거나 선호하는 설정을 조정해야합니다.

/**
 * Return PDO handle for a MySQL connection using supplied settings
 *
 * Tries to do the right thing with different php and mysql versions.
 *
 * @param array $settings with keys: host, port, unix_socket, dbname, charset, user, pass. Some may be omitted or NULL.
 * @return PDO
 * @author Francis Avila
 */
function connect_PDO($settings)
{
    $emulate_prepares_below_version = '5.1.17';

    $dsndefaults = array_fill_keys(array('host', 'port', 'unix_socket', 'dbname', 'charset'), null);
    $dsnarr = array_intersect_key($settings, $dsndefaults);
    $dsnarr += $dsndefaults;

    // connection options I like
    $options = array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    );

    // connection charset handling for old php versions
    if ($dsnarr['charset'] and version_compare(PHP_VERSION, '5.3.6', '<')) {
        $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$dsnarr['charset'];
    }
    $dsnpairs = array();
    foreach ($dsnarr as $k => $v) {
        if ($v===null) continue;
        $dsnpairs[] = "{$k}={$v}";
    }

    $dsn = 'mysql:'.implode(';', $dsnpairs);
    $dbh = new PDO($dsn, $settings['user'], $settings['pass'], $options);

    // Set prepared statement emulation depending on server version
    $serverversion = $dbh->getAttribute(PDO::ATTR_SERVER_VERSION);
    $emulate_prepares = (version_compare($serverversion, $emulate_prepares_below_version, '<'));
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, $emulate_prepares);

    return $dbh;
}


답변

PDO::ATTR_EMULATE_PREPARESPHP pdo_mysql가 .NET에 대해 컴파일되지 않은 경우 비활성화 (기본 준비 설정) 에주의하십시오 mysqlnd.

old libmysql는 일부 기능과 완전히 호환되지 않기 때문에 다음 과 같은 이상한 버그가 발생할 수 있습니다.

  1. 다음과 같이 바인딩 할 때 64 비트 정수의 최상위 비트 손실 PDO::PARAM_INT(0x12345678AB는 64 비트 컴퓨터에서 0x345678AB로 잘림)
  2. 다음과 같은 간단한 쿼리를 만들 수 없음 LOCK TABLES( SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet예외 발생)
  3. 다음 쿼리 전에 결과에서 모든 행을 가져 오거나 커서를 닫아야합니다 ( mysqlnd또는 에뮬레이션을 사용하면 자동으로이 작업을 수행하고 mysql 서버와 동기화되지 않습니다).

사용되는 다른 서버로 마이그레이션 할 때이 버그는 내 간단한 프로젝트에 파악 libmysql하기위한 pdo_mysql모듈. 더 많은 버그가있을 수 있습니다. 또한 새로운 64 비트 데비안 jessie에서 테스트 한 결과 나열된 모든 버그는 I 때 발생 apt-get install php5-mysql하고 apt-get install php5-mysqlnd.

PDO::ATTR_EMULATE_PREPARES(기본값) true로 설정 – PDO는이 모드에서 전혀 준비가 문을 사용하지 않기 때문에 이러한 버그는, 어쨌든 발생하지 않습니다. 따라서 ( “mysqlnd”하위 문자열 이 phpinfo 섹션의 “클라이언트 API 버전”필드에 나타나지 않음) pdo_mysql기반으로 사용하는 경우 – 끄면 안됩니다 .libmysqlpdo_mysqlPDO::ATTR_EMULATE_PREPARES


답변

5.1을 실행할 때 에뮬레이트 준비 기능을 끄겠습니다. 즉, PDO가 기본 준비된 명령문 기능을 활용한다는 의미입니다.

PDO_MYSQL은 MySQL 4.1 이상에있는 기본 준비된 문 지원을 활용합니다. 이전 버전의 mysql 클라이언트 라이브러리를 사용하는 경우 PDO가이를 에뮬레이트합니다.

http://php.net/manual/en/ref.pdo-mysql.php

준비된 명명 된 문과 더 나은 API를 위해 PDO 용 MySQLi를 버렸습니다.

그러나 균형을 맞추기 위해 PDO는 MySQLi보다 무시할 정도로 느리지 만 명심해야 할 사항입니다. 저는 이것을 선택했을 때 이것을 알았고, 당신을 특정 엔진에 연결하는 무시할 정도로 빠른 라이브러리를 사용하는 것보다 더 나은 API와 산업 표준을 사용하는 것이 더 중요하다고 결정했습니다. FWIW 저는 PHP 팀이 미래에도 MySQLi보다 PDO를 선호한다고 생각합니다.


답변

PREPARE에뮬레이션이 모든 것을 포착하지 못하기 때문에 실제 데이터베이스 호출을 활성화하는 것이 좋습니다 . 예를 들어 준비 할 것입니다 INSERT;!

var_dump($dbh->prepare('INSERT;'));
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
var_dump($dbh->prepare('INSERT;'));

출력

object(PDOStatement)#2 (1) {
  ["queryString"]=>
  string(7) "INSERT;"
}
bool(false)

실제로 작동하는 코드에 대한 성능 저하를 기꺼이 받아 들일 것입니다.

FWIW

PHP 버전 : PHP 5.4.9-4ubuntu2.4 (cli)

MySQL 버전 : 5.5.34-0ubuntu0


답변

에뮬레이션을 끄는 가장 큰 이유 중 하나를 언급 한 사람이 아무도 없다는 것에 놀랐습니다. 에뮬레이션을 켜면 PDO는 모든 정수와 부동 소수점을 문자열반환 합니다. 에뮬레이션을 끄면 MySQL의 정수와 부동 소수점은 PHP에서 정수와 부동 소수점이됩니다.

자세한 내용은이 질문에 대한 답변을 참조하십시오. PHP + PDO + MySQL : MySQL에서 정수 및 숫자 열을 PHP에서 정수 및 숫자로 반환하려면 어떻게해야합니까? .


답변

에뮬레이션을 ‘거짓’으로 전환하는 이유는 무엇입니까?

그 주된 이유는 PDO 대신 데이터베이스 엔진이 준비를 수행하도록하는 것은 쿼리와 실제 데이터가 별도로 전송되어 보안이 강화되기 때문입니다. 즉, 매개 변수가 쿼리에 전달되면 MySQL에서 준비된 문이 단일 쿼리로 제한되기 때문에 매개 변수에 SQL을 주입하려는 시도가 차단됩니다. 즉, 매개 변수에 두 번째 쿼리를 전달하면 실제 준비된 명령문이 실패합니다.

준비와 PDO를 위해 데이터베이스 엔진을 사용하는 것에 대한 주된 주장은 서버로의 두 번의 이동입니다. 하나는 준비를위한 것이고 다른 하나는 전달되는 매개 변수를위한 것입니다.하지만 추가 된 보안은 그만한 가치가 있다고 생각합니다. 또한 적어도 MySQL의 경우 버전 5.1부터 쿼리 캐싱이 문제가되지 않았습니다.

https://tech.michaelseiler.net/2016/07/04/dont-emulate-prepared-statements-pdo-mysql/


답변

기록을 위해

PDO :: ATTR_EMULATE_PREPARES = true

불쾌한 부작용이 발생할 수 있습니다. int 값을 문자열로 반환 할 수 있습니다.

PHP 7.4, pdo with mysqlnd.

PDO :: ATTR_EMULATE_PREPARES = true로 쿼리 실행

열 : id
유형 : 정수
값 : 1

PDO :: ATTR_EMULATE_PREPARES = false로 쿼리 실행

열 : id
유형 : string
값 : “1”

어쨌든 10 진수 값은 구성에 관계없이 항상 문자열로 반환됩니다.