PDO MySQL : PDO :: ATTR_EMULATE_PREPARES를 사용합니까?


117

이것이 내가 지금까지 읽은 내용입니다 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활성화 상태로 두어야 합니까? 쿼리 캐시의 성능과 준비된 명령문의 보안을 모두 가질 수있는 방법이 있습니까?


3
솔직히? MySQLi를 계속 사용하십시오. 이미 준비된 명령문을 사용하여 작동하고 있다면 PDO는 기본적으로 무의미한 추상화 계층입니다. 편집 : PDO는 백엔드로 들어가는 데이터베이스가 확실하지 않은 그린 필드 응용 프로그램에 정말 유용합니다.
jmkeyes 2012

1
죄송합니다. 제 질문은 전에 불분명했습니다. 나는 그것을 편집했다. 애플리케이션은 현재 MySQLi에서 준비된 명령문을 사용하지 않습니다. mysqli_run_query (). 내가 읽은 내용에서 MySQLi 준비된 문은 쿼리 캐시를 우회합니다.
Andrew Ensley

답변:


108

귀하의 우려에 답하려면

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

26
다시 # 2 : MySQL이 매개 변수 (기본 준비된 문에 대한)로받는 값은 SQL 에 대해 전혀 구문 분석되지 않습니다 . 따라서 주입의 위험은 PDO의 준비 에뮬레이션을 사용하는 것보다 낮아야 합니다 . 이스케이프의 결함 (예 : mysql_real_escape_string멀티 바이트 문자에 대한 과거 문제 )이 여전히 주입 공격에 노출 될 수 있습니까?
eggyal

2
@eggyal, 준비된 명령문이 구현되는 방법에 대해 가정하고 있습니다. PDO는 에뮬레이트 된 이스케이프 준비에 버그가있을 수 있지만 MySQL에도 버그가있을 수 있습니다. AFAIK, 에뮬레이트 된 준비에서 매개 변수 리터럴이 이스케이프되지 않은 상태로 전달 될 수있는 문제가 발견되지 않았습니다.
Francis Avila

2
멋진 대답이지만 질문이 있습니다. EMULATION을 끄면 실행 속도가 느려지지 않습니까? PHP는 유효성 검사를 위해 준비된 문을 MySQL에 보낸 다음 매개 변수 만 보내야합니다. 따라서 준비된 문을 5 번 사용하면 PHP는 MySQL과 6 번 (5 번 대신) 대화합니다. 이로 인해 속도가 느려지지 않습니까? 게다가 PDO가 MySQL보다 유효성 검사 프로세스에서 버그를 가질 가능성이 더 크다고 생각합니다 ...
Radu Murzea 2013

6
이 답변 에서 만든 요점은 mysql_real_escape_string내부적으로 사용하여 진술 에뮬레이션을 다시 준비 하고 결과적으로 발생할 수있는 취약점 (매우 특별한 경우)에 유의하십시오.
eggyal 2013-06-12

6
+1 좋은 답변! 그러나 기록을 위해 기본 준비를 사용하면 MySQL 서버 측에서도 매개 변수가 이스케이프되거나 SQL 쿼리로 결합되지 않습니다. 매개 변수를 실행하고 제공 할 때까지 쿼리가 구문 분석되고 MySQL의 내부 데이터 구조로 변환되었습니다. 이 프로세스를 설명하는 MySQL 옵티 마이저 엔지니어가이 블로그를 읽으십시오. guilhembichot.blogspot.com/2014/05/… PDO 코드가 올바르게 이스케이프를 수행한다고 믿는 한 이것이 기본 준비가 더 낫다는 의미는 아닙니다. 하다).
Bill Karwin

9

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


3
이 우려는 2019 년에도 유효합니까?!
oldboy 19

8

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를 선호한다고 생각합니다.


그 정보에 감사드립니다. 쿼리 캐시를 사용할 수 없다는 것이 성능에 어떤 영향을 미쳤거나 이전에 사용 했습니까?
Andrew Ensley

어쨌든 여러 수준에서 캐시를 사용하고 있다고 프레임 워크로 말할 수는 없습니다. 하지만 항상 명시 적으로 SELECT SQL_CACHE <rest of statement>를 사용할 수 있습니다.
Will Morgan

SELECT SQL_CACHE 옵션이 있는지조차 몰랐습니다. 그러나 여전히 작동하지 않는 것 같습니다. 문서에서 : "쿼리 결과는 캐시 가능한 경우 캐시됩니다 ..." dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html
Andrew Ensley

예. 이는 플랫폼 특성보다는 쿼리의 특성에 따라 다릅니다.
Will Morgan은

"그때까지 읽은 내용에서 준비된 명령문이 포함 된" 다른 무언가로 인해 캐시 할 수 없는 경우아니면 쿼리 결과가 캐시 됩니다 "라는 의미로 읽었습니다. 그러나 Francis Avila의 답변 덕분에 더 이상 내 버전의 MySQL에 해당하지 않는다는 것을 알고 있습니다.
Andrew Ensley

6

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


흥미로운 점입니다. 에뮬레이션이 서버 측 구문 분석을 실행 단계로 연기한다고 생각합니다. 큰 문제는 아니지만 (잘못된 SQL은 결국 실패 할 것임) prepare예정된 작업을 수행 하도록 하는 것이 더 깔끔 합니다. (또한, 나는 항상 클라이언트 측 매개 변수 파서는 반드시 자체의 버그가 것으로 가정했습니다.)
알바로 곤잘레스

1
관심이 있으시다면 IDK를 참고 하세요. PDO에서 발견 한 다른 가짜 행동에 대한 이 있습니다. 여러 쿼리 처리가 부족한 것 같습니다.
quickshiftin 2014-07-15

방금 GitHub의 일부 마이그레이션 라이브러리를 살펴 보았습니다. 아시다시피, 라이브러리 는 제 블로그 게시물과 거의 동일합니다.
quickshiftin 2014-07-15

6

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

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


5

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

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

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

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


1
어쨌든 쿼리 캐싱은 사라졌습니다 . 쿼리 캐시는 MySQL 5.7.20에서 더 이상 사용되지 않으며 MySQL 8.0에서 제거되었습니다.
Álvaro González

0

기록을 위해

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 진수 값은 구성에 관계없이 항상 문자열로 반환됩니다.


십진수 값은 항상 반환됩니다. 문자열은 유일한 올바른 방법입니다
상식

예, MySQL의 관점에서 볼 때 PHP 측면에서는 잘못되었습니다. Java와 C # 모두 Decimal을 숫자 값으로 간주합니다.
magallanes

아니, 그렇지 않습니다. 그것은 전체 컴퓨터 과학에 모두 맞습니다. 그것이 틀렸다고 생각한다면, 다른 유형의 임의 정밀도가 필요합니다
상식
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.