PDO MySQL: Sử dụng PDO :: ATTR_EMULATE_PREPARES hay không?


117

Đây là những gì tôi đã đọc cho đến nay PDO::ATTR_EMULATE_PREPARES:

  1. Mô phỏng chuẩn bị của PDO tốt hơn cho hiệu suất do chuẩn bị riêng của MySQL bỏ qua bộ đệm truy vấn .
  2. Chuẩn bị tự nhiên của MySQL là tốt hơn cho bảo mật (ngăn chặn SQL Injection) .
  3. Chuẩn bị tự nhiên của MySQL là tốt hơn để báo cáo lỗi .

Tôi không biết sự thật của những câu nói này là như thế nào nữa. Mối quan tâm lớn nhất của tôi trong việc chọn giao diện MySQL là ngăn chặn SQL Injection. Mối quan tâm thứ hai là hiệu suất.

Ứng dụng của tôi hiện đang sử dụng MySQLi theo thủ tục (không có câu lệnh được chuẩn bị) và sử dụng bộ đệm truy vấn khá nhiều. Nó sẽ hiếm khi sử dụng lại các báo cáo đã chuẩn bị trong một yêu cầu. Tôi bắt đầu chuyển sang PDO cho các tham số được đặt tên và bảo mật của các câu lệnh được chuẩn bị.

Tôi đang sử dụng MySQL 5.1.61PHP 5.3.2

Tôi có nên để lại PDO::ATTR_EMULATE_PREPARESkích hoạt hay không? Có cách nào để có cả hiệu năng của bộ đệm truy vấn và bảo mật của các câu lệnh đã chuẩn bị không?


3
Thành thật? Chỉ cần tiếp tục sử dụng MySQLi. Nếu nó đã hoạt động bằng cách sử dụng các câu lệnh được chuẩn bị theo đó, thì PDO về cơ bản là một lớp trừu tượng vô nghĩa. EDIT : PDO thực sự hữu ích cho các ứng dụng trường xanh, nơi bạn không chắc chắn cơ sở dữ liệu nào sẽ được đưa vào back-end.
jmkeyes

1
Xin lỗi, câu hỏi của tôi không rõ ràng trước đây. Tôi đã chỉnh sửa nó. Hiện tại, ứng dụng này không sử dụng các câu lệnh được chuẩn bị trong MySQLi; chỉ mysqli_run_query (). Từ những gì tôi đã đọc, các câu lệnh được chuẩn bị của MySQLi cũng bỏ qua bộ đệm truy vấn.
Andrew Oblley

Câu trả lời:


108

Để trả lời mối quan tâm của bạn:

  1. MySQL> = 5.1.17 (hoặc> = 5.1.21 cho các câu lệnh PREPAREEXECUTE) có thể sử dụng các câu lệnh được chuẩn bị trong bộ đệm truy vấn . Vì vậy, phiên bản MySQL + PHP của bạn có thể sử dụng các câu lệnh được chuẩn bị với bộ đệm truy vấn. Tuy nhiên, hãy lưu ý cẩn thận các cảnh báo cho kết quả truy vấn bộ đệm trong tài liệu MySQL. Có nhiều loại truy vấn không thể được lưu trong bộ nhớ cache hoặc vô dụng mặc dù chúng được lưu trữ. Theo kinh nghiệm của tôi, bộ đệm truy vấn thường không phải là một chiến thắng rất lớn. Các truy vấn và lược đồ cần xây dựng đặc biệt để sử dụng tối đa bộ đệm. Thường thì bộ nhớ đệm cấp ứng dụng cuối cùng vẫn cần thiết trong thời gian dài.

  2. Chuẩn bị bản địa không làm cho bất kỳ sự khác biệt cho an ninh. Các câu lệnh được chuẩn bị giả sẽ vẫn thoát các giá trị tham số truy vấn, nó sẽ chỉ được thực hiện trong thư viện PDO bằng các chuỗi thay vì trên máy chủ MySQL sử dụng giao thức nhị phân. Nói cách khác, cùng một mã PDO sẽ dễ bị tổn thương như nhau (hoặc không dễ bị tổn thương) đối với các cuộc tấn công tiêm chích bất kể EMULATE_PREPAREScài đặt của bạn . Sự khác biệt duy nhất là nơi xảy ra sự thay thế tham số - với EMULATE_PREPARES, nó xảy ra trong thư viện PDO; không có EMULATE_PREPARES, nó xảy ra trên máy chủ MySQL.

  3. Không có EMULATE_PREPARESbạn có thể nhận được lỗi cú pháp tại thời gian chuẩn bị thay vì tại thời gian thực hiện; với EMULATE_PREPARESbạn sẽ chỉ nhận được lỗi cú pháp tại thời điểm thực hiện vì PDO không có truy vấn để cung cấp cho MySQL cho đến khi thời gian thực hiện. Lưu ý rằng điều này ảnh hưởng đến mã bạn sẽ viết ! Đặc biệt nếu bạn đang sử dụng PDO::ERRMODE_EXCEPTION!

Một sự xem xét bổ sung:

  • Có một chi phí cố định cho một prepare()(sử dụng các câu lệnh được chuẩn bị riêng), do đó, một prepare();execute()câu lệnh được chuẩn bị riêng có thể chậm hơn một chút so với việc đưa ra một truy vấn văn bản đơn giản bằng cách sử dụng các câu lệnh được chuẩn bị mô phỏng. Trên nhiều hệ thống cơ sở dữ liệu, kế hoạch truy vấn cho a prepare()cũng được lưu trong bộ nhớ cache và có thể được chia sẻ với nhiều kết nối, nhưng tôi không nghĩ MySQL làm điều này. Vì vậy, nếu bạn không sử dụng lại đối tượng câu lệnh đã chuẩn bị cho nhiều truy vấn thì việc thực thi tổng thể của bạn có thể chậm hơn.

Như một đề xuất cuối cùng , tôi nghĩ với các phiên bản cũ hơn của MySQL + PHP, bạn nên mô phỏng các câu lệnh đã chuẩn bị, nhưng với các phiên bản gần đây của bạn, bạn nên tắt phần mô phỏng.

Sau khi viết một vài ứng dụng sử dụng PDO, tôi đã tạo một chức năng kết nối PDO có cài đặt tốt nhất. Bạn có thể nên sử dụng một cái gì đó như thế này hoặc điều chỉnh các cài đặt ưa thích của bạn:

/**
 * 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
Re # 2: chắc chắn đánh giá cao rằng MySQL nhận như thông số (để báo cáo chuẩn bị bản địa) không được phân tích cú pháp cho SQL ở tất cả ? Vì vậy, nguy cơ tiêm phải thấp hơn so với sử dụng mô phỏng chuẩn bị của PDO, trong đó bất kỳ lỗ hổng nào trong việc trốn thoát (ví dụ như các vấn đề lịch sử mysql_real_escape_stringcó nhiều ký tự byte) vẫn sẽ để ngỏ cho các cuộc tấn công tiêm chích?
eggyal

2
@eggyal, bạn đang đưa ra các giả định về cách thực hiện các tuyên bố được chuẩn bị. PDO có thể có một lỗi trong phần chuẩn bị giả lập của nó, nhưng MySQL cũng có thể có lỗi. AFAIK, không có vấn đề nào được phát hiện với các chế phẩm mô phỏng có thể khiến các thông số bằng chữ thông qua không được giải thoát.
Francis Avila

2
Câu trả lời tuyệt vời, nhưng tôi có một câu hỏi: Nếu bạn tắt EMULATION, việc thực thi sẽ chậm hơn? PHP sẽ phải gửi câu lệnh đã chuẩn bị tới MySQL để xác thực và chỉ sau đó gửi các tham số. Vì vậy, nếu bạn sử dụng câu lệnh được chuẩn bị 5 lần, PHP sẽ nói chuyện với MySQL 6 lần (thay vì 5). Điều này sẽ làm cho nó chậm hơn? Bên cạnh đó, tôi nghĩ rằng có nhiều khả năng PDO có thể có lỗi trong quá trình xác nhận, thay vì MySQL ...
Radu Murzea

6
Lưu ý các điểm được đưa ra trong câu trả lời này đã chuẩn bị mô phỏng câu lệnh bằng cách sử dụng phần mysql_real_escape_stringdưới và các lỗ hổng hậu quả có thể phát sinh (trong các trường hợp cạnh rất cụ thể).
eggyal

6
+1 Câu trả lời hay! Nhưng đối với bản ghi, nếu bạn sử dụng chuẩn bị gốc, các tham số không bao giờ được thoát hoặc kết hợp vào truy vấn SQL ngay cả ở phía máy chủ MySQL. Tại thời điểm bạn thực hiện và cung cấp các tham số, truy vấn đã được phân tích cú pháp và chuyển đổi thành cấu trúc dữ liệu nội bộ trong MySQL. Đọc blog này bởi một kỹ sư tối ưu hóa MySQL giải thích quá trình này: guilhembichot.blogspot.com/2014/05/. Tôi không nói điều này có nghĩa là chuẩn bị tự nhiên là tốt hơn, vì chúng tôi tin tưởng mã PDO sẽ thoát chính xác (mà tôi làm).
Bill Karwin

9

Cảnh giác với việc vô hiệu hóa PDO::ATTR_EMULATE_PREPARES(bật chuẩn bị gốc) khi PHP của bạn pdo_mysqlkhông được biên dịch mysqlnd.

Vì cũ libmysqlkhông tương thích hoàn toàn với một số chức năng, nên nó có thể dẫn đến các lỗi lạ, ví dụ:

  1. Mất hầu hết các bit đáng kể cho số nguyên 64 bit khi liên kết dưới dạng PDO::PARAM_INT(0x12345678AB sẽ bị cắt thành 0x345678AB trên máy 64 bit)
  2. Không có khả năng thực hiện các truy vấn đơn giản như LOCK TABLES(nó ném SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yetngoại lệ)
  3. Cần tìm nạp tất cả các hàng từ kết quả hoặc đóng con trỏ trước khi truy vấn tiếp theo (với mysqlndhoặc được mô phỏng chuẩn bị tự động thực hiện công việc này cho bạn và không mất đồng bộ hóa với máy chủ mysql)

Những lỗi này tôi đã tìm ra trong dự án đơn giản của mình khi di chuyển sang máy chủ khác được sử dụng libmysqlcho pdo_mysqlmô-đun. Có lẽ còn nhiều lỗi nữa, tôi không biết. Ngoài ra tôi đã thử nghiệm trên jessie 64 bit mới, tất cả các lỗi được liệt kê xảy ra khi tôi apt-get install php5-mysqlvà biến mất khi tôi apt-get install php5-mysqlnd.

Khi PDO::ATTR_EMULATE_PREPARESđược đặt thành đúng (như mặc định) - dù sao thì các lỗi này không xảy ra, vì PDO hoàn toàn không sử dụng các câu lệnh được chuẩn bị trong chế độ này. Vì vậy, nếu bạn sử dụng pdo_mysqldựa trên libmysql(chuỗi con "mysqlnd" không xuất hiện trong phần "Phiên bản API khách hàng" trong pdo_mysqlphần phpinfo) - bạn không nên PDO::ATTR_EMULATE_PREPAREStắt.


3
mối quan tâm này vẫn còn hiệu lực trong năm 2019?!
oldboy

8

Tôi sẽ tắt các chế phẩm giả lập khi bạn đang chạy 5.1, điều đó có nghĩa là PDO sẽ tận dụng chức năng câu lệnh được chuẩn bị sẵn.

PDO_MYSQL sẽ tận dụng hỗ trợ câu lệnh được chuẩn bị sẵn có trong MySQL 4.1 trở lên. Nếu bạn đang sử dụng phiên bản cũ hơn của các thư viện máy khách mysql, PDO sẽ mô phỏng chúng cho bạn.

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

Tôi đã bỏ MySQLi cho PDO cho các câu lệnh được đặt tên và API tốt hơn.

Tuy nhiên, để được cân bằng, PDO hoạt động chậm hơn đáng kể so với MySQLi, nhưng đó là điều cần lưu ý. Tôi biết điều này khi tôi đưa ra lựa chọn và quyết định rằng API tốt hơn và sử dụng tiêu chuẩn công nghiệp quan trọng hơn việc sử dụng thư viện nhanh hơn không đáng kể có liên kết bạn với một công cụ cụ thể. FWIW Tôi nghĩ rằng nhóm PHP cũng đang tìm kiếm thuận lợi cho PDO hơn MySQLi trong tương lai.


Cảm ơn bạn đã cung cấp thông tin đó. Làm thế nào mà việc không thể sử dụng bộ đệm truy vấn đã ảnh hưởng đến hiệu suất của bạn hoặc thậm chí bạn đã sử dụng nó trước đây?
Andrew Oblley

Tôi không thể nói là khung tôi đang sử dụng bộ nhớ cache ở nhiều cấp độ. Mặc dù vậy, bạn luôn có thể sử dụng rõ ràng CHỌN SQL_CACHE <phần còn lại của câu lệnh>.
Will Morgan

Thậm chí còn không biết có tùy chọn CHỌN SQL_CACHE. Tuy nhiên, có vẻ như nó vẫn không hoạt động. Từ các tài liệu: "Kết quả truy vấn được lưu trong bộ nhớ cache nếu có thể lưu trong bộ nhớ cache ..." dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html
Andrew Oblley

Đúng. Điều đó phụ thuộc vào bản chất của truy vấn, chứ không phải là chi tiết cụ thể của nền tảng.
Will Morgan

Tôi đọc điều đó có nghĩa là "Kết quả truy vấn được lưu trong bộ nhớ cache trừ khi có thứ gì đó ngăn không cho bộ nhớ cache ", từ đó - từ những gì tôi đã đọc cho đến lúc đó - bao gồm các câu lệnh được chuẩn bị. Tuy nhiên, nhờ câu trả lời của Francis Avila, tôi biết rằng điều đó không còn đúng với phiên bản MySQL của tôi nữa.
Andrew Oblley

6

Tôi khuyên bạn nên kích hoạt các PREPAREcuộc gọi cơ sở dữ liệu thực tế vì việc mô phỏng không bắt được mọi thứ .., ví dụ: nó sẽ chuẩn bị INSERT;!

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

Đầu ra

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

Tôi sẵn sàng thực hiện một cú đánh hiệu suất cho mã thực sự hoạt động.

FWIW

Phiên bản PHP: PHP 5.4.9-4ubfox2.4 (cli)

Phiên bản MySQL: 5.5.34-0ubfox0


Đó là một điểm thú vị. Tôi đoán giả lập postpones phân tích phía máy chủ đến giai đoạn thực hiện. Mặc dù nó không phải là một vấn đề lớn (cuối cùng SQL sai sẽ thất bại), nó sẽ sạch hơn để preparethực hiện công việc mà nó được giao. (Ngoài ra, tôi luôn cho rằng trình phân tích cú pháp tham số phía máy khách nhất thiết phải có lỗi của chính nó.)
Álvaro González

1
IDK nếu bạn quan tâm, nhưng đây là một bài viết nhỏ về một số hành vi giả mạo khác mà tôi nhận thấy với PDO dẫn tôi xuống hố thỏ này để bắt đầu. Có vẻ như việc xử lý nhiều truy vấn là thiếu.
quickshiftin

Tôi chỉ xem xét một số thư viện di chuyển trên GitHub ... Bạn biết gì không, cái này khá giống với bài viết trên blog của tôi.
quickshiftin

5

Tại sao chuyển đổi mô phỏng thành 'false'?

Lý do chính cho việc này là để công cụ cơ sở dữ liệu thực hiện chuẩn bị thay vì PDO là truy vấn và dữ liệu thực tế được gửi riêng, làm tăng tính bảo mật. Điều này có nghĩa là khi các tham số được truyền cho truy vấn, các nỗ lực đưa SQL vào chúng bị chặn, vì các câu lệnh chuẩn bị của MySQL bị giới hạn trong một truy vấn duy nhất. Điều đó có nghĩa là một câu lệnh được chuẩn bị thực sự sẽ thất bại khi chuyển một truy vấn thứ hai trong một tham số.

Đối số chính chống lại việc sử dụng công cụ cơ sở dữ liệu cho việc chuẩn bị so với PDO là hai chuyến đi đến máy chủ - một cho việc chuẩn bị và một cho các tham số được thông qua - nhưng tôi nghĩ rằng bảo mật được thêm vào là đáng giá. Ngoài ra, ít nhất là trong trường hợp của MySQL, bộ nhớ đệm truy vấn không phải là vấn đề kể từ phiên bản 5.1.

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


1
Bộ nhớ đệm truy vấn không còn nữa: Bộ đệm truy vấn không dùng nữa kể từ MySQL 5.7.20 và bị xóa trong MySQL 8.0.
Álvaro González

5

Tôi ngạc nhiên không ai đề cập đến một trong những lý do lớn nhất để tắt thi đua. Khi mô phỏng, PDO trả về tất cả các số nguyên và nổi dưới dạng chuỗi . Khi bạn tắt mô phỏng, số nguyên và số float trong MySQL sẽ trở thành số nguyên và số float trong PHP.

Để biết thêm thông tin, hãy xem câu trả lời được chấp nhận cho câu hỏi này: PHP + PDO + MySQL: làm cách nào để trả về các cột số nguyên và số từ MySQL dưới dạng số nguyên và số trong PHP? .


0

Đối với hồ sơ

PDO :: ATTR_EMULATE_PREPARES = đúng

Nó có thể tạo ra một tác dụng phụ khó chịu. Nó có thể trả về giá trị int dưới dạng chuỗi.

PHP 7.4, pdo với mysqlnd.

Chạy truy vấn với PDO :: ATTR_EMULATE_PREPARES = true

Cột: id
Loại: số nguyên
Giá trị: 1

Chạy truy vấn với PDO :: ATTR_EMULATE_PREPARES = false

Cột: id
Loại: chuỗi
Giá trị: "1"

Trong mọi trường hợp, các giá trị thập phân luôn được trả về một chuỗi, bất kể cấu hình :-(


giá trị thập phân luôn được trả về một chuỗi là cách chính xác duy nhất
Ý thức chung của bạn

Có từ quan điểm của MySQL nhưng nó sai ở phía PHP. Cả Java và C # đều coi Thập phân là một giá trị số.
magallanes

Không, không phải. Tất cả đều đúng cho toàn bộ khoa học máy tính. Nếu bạn nghĩ đó là sai, thì bạn cần một loại khác, độ chính xác tùy ý
Ý thức chung của bạn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.