Tại sao tôi không nên sử dụng các hàm mysql_ * trong PHP?

Oct 12 2012

Lý do kỹ thuật cho lý do tại sao một người không nên sử dụng các mysql_*chức năng là gì? (ví dụ mysql_query(), mysql_connect()hoặc mysql_real_escape_string())?

Tại sao tôi nên sử dụng thứ khác ngay cả khi chúng hoạt động trên trang web của tôi?

Nếu chúng không hoạt động trên trang web của tôi, tại sao tôi lại gặp các lỗi như

Cảnh báo: mysql_connect (): Không có tệp hoặc thư mục như vậy

Trả lời

2118 Quentin Oct 12 2012 at 20:23

Phần mở rộng MySQL:

  • Không được phát triển tích cực
  • Được chính thức phản đối như của PHP 5.5 (phát hành tháng 6 năm 2013).
  • Đã bị xóa hoàn toàn kể từ PHP 7.0 (phát hành tháng 12 năm 2015)
    • Điều này có nghĩa là kể từ ngày 31 tháng 12 năm 2018, nó không tồn tại trong bất kỳ phiên bản PHP nào được hỗ trợ. Nếu bạn đang sử dụng một phiên bản PHP hỗ trợ nó, bạn đang sử dụng một phiên bản chưa được khắc phục sự cố bảo mật.
  • Thiếu giao diện OO
  • Không hỗ trợ:
    • Truy vấn không chặn, không đồng bộ
    • Câu lệnh chuẩn bị hoặc truy vấn được tham số hóa
    • Thủ tục lưu trữ
    • Nhiều câu lệnh
    • Giao dịch
    • Phương thức xác thực mật khẩu "mới" (được bật theo mặc định trong MySQL 5.6; bắt buộc trong 5.7)
    • Bất kỳ chức năng mới nào trong MySQL 5.1 trở lên

Vì nó không được dùng nữa nên việc sử dụng nó làm cho mã của bạn ít bằng chứng trong tương lai.

Việc thiếu hỗ trợ cho các câu lệnh chuẩn bị là đặc biệt quan trọng vì chúng cung cấp một phương pháp thoát và trích dẫn dữ liệu bên ngoài rõ ràng hơn, ít bị lỗi hơn so với việc thoát nó theo cách thủ công bằng một lệnh gọi hàm riêng biệt.

Xem so sánh các phần mở rộng SQL .

1303 NullPoiиteя Jan 01 2013 at 18:52

PHP cung cấp ba API khác nhau để kết nối với MySQL. Đây là các phần mở rộng mysql(bị loại bỏ của PHP 7) mysqliPDO.

Các mysql_*chức năng đã từng rất phổ biến, nhưng việc sử dụng chúng không được khuyến khích nữa. Nhóm tài liệu đang thảo luận về tình hình bảo mật cơ sở dữ liệu và hướng dẫn người dùng tránh xa tiện ích mở rộng ext / mysql thường được sử dụng là một phần của vấn đề này (kiểm tra php.internals: không dùng nữa ext / mysql ).

Và nhóm phát triển PHP sau đã đưa ra những quyết định để tạo ra E_DEPRECATEDlỗi khi người dùng kết nối với MySQL, dù là thông qua mysql_connect(), mysql_pconnect()hoặc các chức năng kết nối ngầm xây dựng trong ext/mysql.

ext/mysqlđã chính thức không được chấp nhận kể từ PHP 5.5 và đã bị xóa khỏi PHP 7 .

Xem Hộp màu đỏ?

Khi bạn truy cập bất kỳ mysql_*trang hướng dẫn chức năng nào , bạn sẽ thấy một hộp màu đỏ, giải thích rằng nó không nên được sử dụng nữa.

Tại sao


Di chuyển khỏi ext/mysqlnó không chỉ về bảo mật mà còn về quyền truy cập vào tất cả các tính năng của cơ sở dữ liệu MySQL.

ext/mysqlđược xây dựng cho MySQL 3.23 và chỉ có rất ít bổ sung kể từ đó trong khi chủ yếu giữ khả năng tương thích với phiên bản cũ này khiến mã khó bảo trì hơn một chút. Các tính năng bị thiếu không được hỗ trợ ext/mysqlbao gồm: ( từ hướng dẫn sử dụng PHP ).

Lý do không sử dụng mysql_*chức năng :

  • Không đang phát triển tích cực
  • Đã xóa kể từ PHP 7
  • Thiếu giao diện OO
  • Không hỗ trợ các truy vấn không chặn, không đồng bộ
  • Không hỗ trợ các câu lệnh đã chuẩn bị sẵn hoặc các truy vấn được tham số hóa
  • Không hỗ trợ các thủ tục được lưu trữ
  • Không hỗ trợ nhiều câu lệnh
  • Không hỗ trợ giao dịch
  • Không hỗ trợ tất cả các chức năng trong MySQL 5.1

Điểm trên trích từ câu trả lời của Quentin

Việc thiếu hỗ trợ cho các câu lệnh chuẩn bị là đặc biệt quan trọng vì chúng cung cấp một phương pháp thoát và trích dẫn dữ liệu bên ngoài rõ ràng hơn, ít bị lỗi hơn so với việc thoát nó theo cách thủ công bằng một lệnh gọi hàm riêng biệt.

Xem so sánh các phần mở rộng SQL .


Loại bỏ cảnh báo không dùng nữa

Trong khi mã đang được chuyển đổi thành MySQLi/ PDO, các E_DEPRECATEDlỗi có thể được loại trừ bằng cách đặt error_reportingtrong php.ini để loại trừE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Lưu ý rằng điều này cũng sẽ ẩn các cảnh báo không dùng nữa , tuy nhiên, có thể dành cho những thứ khác ngoài MySQL. ( từ hướng dẫn sử dụng PHP )

Bài báo PDO so với MySQLi: Bạn nên sử dụng cái nào? của Dejan Marjanovic sẽ giúp bạn lựa chọn.

Và một cách tốt hơn là PDOtôi đang viết một bài PDOhướng dẫn đơn giản .


Hướng dẫn PDO đơn giản và ngắn gọn


Q. Câu hỏi đầu tiên trong đầu tôi là: `` PDO` là gì?

A. “ PDO - Đối tượng dữ liệu PHP - là một lớp truy cập cơ sở dữ liệu cung cấp một phương pháp truy cập thống nhất vào nhiều cơ sở dữ liệu.”


Kết nối với MySQL

Với mysql_*hàm hoặc chúng ta có thể nói theo cách cũ (không được dùng trong PHP 5.5 trở lên)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

Với PDO: Tất cả những gì bạn cần làm là tạo một PDOđối tượng mới . Các nhà xây dựng chấp nhận các thông số để xác định các nguồn cơ sở dữ liệu PDOcủa nhà xây dựng chủ yếu là mất bốn thông số đó là DSN(tên nguồn dữ liệu), và tùy chọn username, password.

Ở đây tôi nghĩ bạn đã quen thuộc với tất cả ngoại trừ DSN; cái này mới trong PDO. DSNVề cơ bản, A là một chuỗi các tùy chọn cho biết PDOtrình điều khiển nào sẽ sử dụng và chi tiết kết nối. Để tham khảo thêm, hãy kiểm tra PDO MySQL DSN .

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

Lưu ý: bạn cũng có thể sử dụng charset=UTF-8nhưng đôi khi nó gây ra lỗi nên tốt hơn hết bạn nên sử dụng utf8.

Nếu có bất kỳ lỗi kết nối nào, nó sẽ ném ra một PDOExceptionđối tượng có thể bị bắt để xử lý Exceptionthêm.

Đọc tốt : Kết nối và Quản lý kết nối ¶

Bạn cũng có thể chuyển một số tùy chọn trình điều khiển dưới dạng một mảng cho tham số thứ tư. Tôi khuyên bạn nên chuyển tham số đặt PDOvào chế độ ngoại lệ. Bởi vì một số PDOtrình điều khiển không hỗ trợ các câu lệnh chuẩn bị sẵn, vì vậy hãy PDOthực hiện mô phỏng bản chuẩn bị. Nó cũng cho phép bạn kích hoạt mô phỏng này theo cách thủ công. Để sử dụng các câu lệnh được chuẩn bị từ phía máy chủ gốc, bạn nên đặt nó một cách rõ ràng false.

Cách khác là tắt mô phỏng chuẩn bị được bật trong MySQLtrình điều khiển theo mặc định, nhưng nên tắt mô phỏng chuẩn bị để sử dụng PDOan toàn.

Sau đây tôi sẽ giải thích lý do tại sao nên tắt chế độ chuẩn bị thi đua. Để tìm lý do, vui lòng kiểm tra bài đăng này .

Nó chỉ có thể sử dụng được nếu bạn đang sử dụng phiên bản cũ MySQLmà tôi không khuyến khích.

Dưới đây là một ví dụ về cách bạn có thể làm điều đó:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Chúng ta có thể đặt các thuộc tính sau khi xây dựng PDO không?

, chúng tôi cũng có thể đặt một số thuộc tính sau khi xây dựng PDO bằng setAttributephương thức:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Xử lý lỗi


Xử lý lỗi dễ dàng PDOhơn nhiều mysql_*.

Một thực tế phổ biến khi sử dụng mysql_*là:

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die()không phải là cách tốt để xử lý lỗi vì chúng tôi không thể xử lý sự cố trong die. Nó sẽ chỉ kết thúc kịch bản một cách đột ngột và sau đó lặp lại lỗi ra màn hình mà bạn thường KHÔNG muốn hiển thị cho người dùng cuối của mình và để cho những tên hacker có máu phát hiện ra lược đồ của bạn. Ngoài ra, các giá trị trả về của các mysql_*hàm thường có thể được sử dụng cùng với mysql_error () để xử lý lỗi.

PDOcung cấp một giải pháp tốt hơn: ngoại lệ. Bất cứ điều gì chúng ta làm với PDOnên được gói trong một try- catchkhối. Chúng tôi có thể buộc PDOvào một trong ba chế độ lỗi bằng cách đặt thuộc tính chế độ lỗi. Dưới đây là ba chế độ xử lý lỗi.

  • PDO::ERRMODE_SILENT. Nó chỉ đặt mã lỗi và hoạt động khá giống với mysql_*nơi bạn phải kiểm tra từng kết quả và sau đó xem xét $db->errorInfo();để biết chi tiết lỗi.
  • PDO::ERRMODE_WARNINGNâng cao E_WARNING. (Cảnh báo thời gian chạy (lỗi không nghiêm trọng). Việc thực thi tập lệnh không bị tạm dừng.)
  • PDO::ERRMODE_EXCEPTION: Ném ngoại lệ. Nó đại diện cho một lỗi do PDO nêu ra. Bạn không nên ném PDOExceptionmã của riêng mình. Xem Ngoại lệ để biết thêm thông tin về các ngoại lệ trong PHP. Nó hoạt động rất giống or die(mysql_error());, khi nó không bị bắt. Nhưng không giống như or die(), PDOExceptioncó thể bị bắt và xử lý một cách khéo léo nếu bạn chọn làm như vậy.

Đọc tốt :

Giống:

$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

Và bạn có thể bọc nó vào try- catch, như bên dưới:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) {
    echo "An Error occured!"; //User friendly message/message you want to show to user
    some_logging_function($ex->getMessage());
}

Bạn không cần phải xử lý với try- catchngay bây giờ. Bạn có thể bắt nó bất cứ lúc nào thích hợp, nhưng tôi thực sự khuyên bạn nên sử dụng try- catch. Ngoài ra, có thể có ý nghĩa hơn nếu bắt nó ở bên ngoài hàm gọi PDOthứ:

function data_fun($db) {
    $stmt = $db->query("SELECT * FROM table");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db);
}
catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

Ngoài ra, bạn có thể xử lý bằng cách or die()hoặc chúng tôi có thể nói như thế mysql_*, nhưng nó sẽ thực sự đa dạng. Bạn có thể ẩn các thông báo lỗi nguy hiểm trong quá trình sản xuất bằng cách lật display_errors offvà chỉ đọc nhật ký lỗi của mình.

Bây giờ, sau khi đọc tất cả những điều trên, bạn có thể nghĩ: những gì heck là khi tôi chỉ muốn bắt đầu nghiêng đơn giản SELECT, INSERT, UPDATE, hoặc DELETEbáo cáo? Đừng lo lắng, chúng tôi bắt đầu:


Chọn dữ liệu

Vì vậy, những gì bạn đang làm mysql_*là:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());

$num_rows = mysql_num_rows($result);

while($row = mysql_fetch_assoc($result)) {
    echo $row['field1'];
}

Bây giờ PDO, bạn có thể làm như sau:

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

Hoặc là

<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

//Use $results

Lưu ý : Nếu bạn đang sử dụng phương thức như bên dưới ( query()), phương thức này trả về một PDOStatementđối tượng. Vì vậy, nếu bạn muốn tìm nạp kết quả, hãy sử dụng nó như trên.

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

Trong Dữ liệu PDO, nó được lấy thông qua ->fetch()phương thức xử lý câu lệnh của bạn. Trước khi gọi tìm nạp, cách tiếp cận tốt nhất sẽ là cho PDO biết bạn muốn dữ liệu được tìm nạp như thế nào. Trong phần dưới đây, tôi giải thích điều này.

Chế độ tìm nạp

Lưu ý việc sử dụng PDO::FETCH_ASSOCtrong fetch()fetchAll()mã ở trên. Điều này yêu PDOcầu trả về các hàng dưới dạng một mảng kết hợp với tên trường là khóa. Có nhiều chế độ tìm nạp khác nữa mà tôi sẽ giải thích từng chế độ một.

Trước hết, tôi giải thích cách chọn chế độ tìm nạp:

 $stmt->fetch(PDO::FETCH_ASSOC)

Ở trên, tôi đã được sử dụng fetch(). Bạn cũng có thể dùng:

Bây giờ tôi đến chế độ tìm nạp:

  • PDO::FETCH_ASSOC: trả về một mảng được lập chỉ mục theo tên cột như được trả về trong tập kết quả của bạn
  • PDO::FETCH_BOTH (mặc định): trả về một mảng được lập chỉ mục bởi cả tên cột và số cột được lập chỉ mục 0 như được trả về trong tập kết quả của bạn

Có nhiều sự lựa chọn hơn nữa! Đọc về tất cả chúng trong PDOStatementtài liệu Tìm nạp. .

Nhận số hàng :

Thay vì sử dụng mysql_num_rowsđể lấy số hàng được trả về, bạn có thể lấy một PDOStatementvà làm rowCount(), như:

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

Lấy ID được chèn lần cuối

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

Chèn và Cập nhật hoặc Xóa câu lệnh

Những gì chúng tôi đang làm trong mysql_*chức năng là:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

Và trong pdo, điều tương tự có thể được thực hiện bằng cách:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

Trong truy vấn trên, PDO::execthực hiện một câu lệnh SQL và trả về số hàng bị ảnh hưởng.

Chèn và xóa sẽ được đề cập sau.

Phương pháp trên chỉ hữu ích khi bạn không sử dụng biến trong truy vấn. Nhưng khi bạn cần sử dụng một biến trong một truy vấn, đừng bao giờ thử như trên và ở đó đối với câu lệnh chuẩn bị hoặc câu lệnh được tham số hóa .


Tuyên bố chuẩn bị

Q. Một tuyên bố chuẩn bị là gì và tại sao tôi cần chúng?
A. Câu lệnh chuẩn bị sẵn là câu lệnh SQL được biên dịch trước có thể được thực thi nhiều lần bằng cách chỉ gửi dữ liệu đến máy chủ.

Quy trình công việc điển hình của việc sử dụng một tuyên bố đã chuẩn bị như sau ( trích dẫn từ Wikipedia 3 3 điểm ):

  1. Chuẩn bị : Mẫu câu lệnh được tạo bởi ứng dụng và được gửi đến hệ quản trị cơ sở dữ liệu (DBMS). Các giá trị nhất định không được xác định, được gọi là tham số, trình giữ chỗ hoặc biến liên kết (có nhãn ?bên dưới):

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. DBMS phân tích cú pháp, biên dịch và thực hiện tối ưu hóa truy vấn trên mẫu câu lệnh và lưu trữ kết quả mà không cần thực thi nó.

  3. Thực thi : Sau đó, ứng dụng cung cấp (hoặc liên kết) các giá trị cho các tham số và DBMS thực thi câu lệnh (có thể trả về một kết quả). Ứng dụng có thể thực hiện câu lệnh bao nhiêu lần tùy ý với các giá trị khác nhau. Trong ví dụ này, nó có thể cung cấp 'Bánh mì' cho tham số đầu tiên và 1.00cho tham số thứ hai.

Bạn có thể sử dụng một câu lệnh đã soạn sẵn bằng cách bao gồm các trình giữ chỗ trong SQL của bạn. Về cơ bản có ba cái không có chỗ dành sẵn (đừng thử điều này với biến ở trên của nó), một cái có chỗ dành sẵn không tên và một cái có chỗ dành sẵn được đặt tên.

Q. Vậy bây giờ, các trình giữ chỗ được đặt tên là gì và làm cách nào để sử dụng chúng?
A. Trình giữ chỗ được đặt tên. Sử dụng tên mô tả đặt trước dấu hai chấm thay vì dấu chấm hỏi. Chúng tôi không quan tâm đến vị trí / thứ tự giá trị trong tên giữ chỗ:

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

Bạn cũng có thể liên kết bằng cách sử dụng một mảng thực thi:

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Một tính năng thú vị khác dành cho OOPbạn bè là trình giữ chỗ được đặt tên có khả năng chèn các đối tượng trực tiếp vào cơ sở dữ liệu của bạn, giả sử các thuộc tính khớp với các trường được đặt tên. Ví dụ:

class person {
    public $name;
    public $add;
    function __construct($a,$b) {
        $this->name = $a;
        $this->add = $b;
    }

}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

Q. Vậy bây giờ, trình giữ chỗ không tên là gì và làm cách nào để sử dụng chúng?
A. Hãy lấy một ví dụ:

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));

Ở trên, bạn có thể thấy những cái ?đó thay vì một cái tên như trong phần giữ chỗ tên. Bây giờ trong ví dụ đầu tiên, chúng ta gán các biến cho các trình giữ chỗ khác nhau ( $stmt->bindValue(1, $name, PDO::PARAM_STR);). Sau đó, chúng tôi gán giá trị cho các trình giữ chỗ đó và thực thi câu lệnh. Trong ví dụ thứ hai, phần tử mảng đầu tiên chuyển đến phần tử đầu tiên ?và phần tử thứ hai đến phần tử thứ hai ?.

LƯU Ý : Trong phần giữ chỗ không tên, chúng ta phải quan tâm đến thứ tự thích hợp của các phần tử trong mảng mà chúng ta đang chuyển cho PDOStatement::execute()phương thức.


SELECT, INSERT, UPDATE, DELETEChuẩn bị các truy vấn

  1. SELECT:

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id));
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
  2. INSERT:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    
  3. DELETE:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
    
  4. UPDATE:

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();
    

GHI CHÚ:

Tuy nhiên PDOvà / hoặc MySQLikhông hoàn toàn an toàn. Chọn câu trả lời Các câu lệnh được chuẩn bị sẵn PDO có đủ để ngăn chặn việc đưa vào SQL không? bởi ircmaxell . Ngoài ra, tôi đang trích dẫn một số phần từ câu trả lời của anh ấy:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));
305 Madara'sGhost Oct 12 2012 at 20:28

Đầu tiên, hãy bắt đầu với nhận xét tiêu chuẩn mà chúng tôi dành cho mọi người:

Vui lòng không sử dụng các mysql_*chức năng trong mã mới . Chúng không còn được duy trì và chính thức không được dùng nữa . Nhìn thấy ô màu đỏ ? Thay vào đó, hãytìm hiểu về các câu lệnh đã chuẩn bị và sử dụng PDO hoặc MySQLi - bài viết này sẽ giúp bạn quyết định điều đó. Nếu bạn chọn PDO, đây là một hướng dẫn tốt .

Hãy xem xét điều này, từng câu một và giải thích:

  • Chúng không còn được duy trì và chính thức không được dùng nữa

    Điều này có nghĩa là cộng đồng PHP đang dần bỏ hỗ trợ cho các chức năng rất cũ này. Chúng có khả năng không tồn tại trong phiên bản PHP trong tương lai (gần đây)! Việc tiếp tục sử dụng các chức năng này có thể phá vỡ mã của bạn trong tương lai (không quá xa).

    MỚI! - ext / mysql hiện đã chính thức không được chấp nhận kể từ PHP 5.5!

    Mới hơn! ext / mysql đã bị xóa trong PHP 7 .

  • Thay vào đó, bạn nên tìm hiểu về các câu đã chuẩn bị sẵn

    mysql_*tiện ích mở rộng không hỗ trợ các câu lệnh đã chuẩn bị sẵn , đây là (trong số những thứ khác) là một biện pháp đối phó rất hiệu quả chống lại SQL Injection . Nó đã sửa một lỗ hổng rất nghiêm trọng trong các ứng dụng phụ thuộc vào MySQL, cho phép kẻ tấn công có quyền truy cập vào tập lệnh của bạn và thực hiện bất kỳ truy vấn nào có thể có trên cơ sở dữ liệu của bạn.

    Để biết thêm thông tin, hãy xem Làm cách nào để ngăn chặn việc đưa SQL vào PHP?

  • Xem Hộp màu đỏ?

    Khi bạn vào bất kỳ mysqltrang hướng dẫn chức năng nào , bạn sẽ thấy một hộp màu đỏ, giải thích rằng nó không nên được sử dụng nữa.

  • Sử dụng PDO hoặc MySQLi

    Có các lựa chọn thay thế tốt hơn, mạnh mẽ hơn và được xây dựng tốt, PDO - Đối tượng cơ sở dữ liệu PHP , cung cấp cách tiếp cận OOP hoàn chỉnh để tương tác cơ sở dữ liệu và MySQLi , là một cải tiến cụ thể của MySQL.

221 mario Dec 25 2013 at 06:30

Dễ sử dụng

Các lý do phân tích và tổng hợp đã được đề cập. Đối với những người mới đến, có một động lực đáng kể hơn để ngừng sử dụng các hàm mysql_ ngày.

Các API cơ sở dữ liệu hiện đại dễ sử dụng hơn.

Nó chủ yếu là các tham số ràng buộc có thể đơn giản hóa mã. Và với các hướng dẫn xuất sắc (như đã thấy ở trên), việc chuyển đổi sang PDO không quá khó khăn.

Tuy nhiên, việc viết lại một cơ sở mã lớn hơn cùng một lúc sẽ mất thời gian. Raison d'être cho giải pháp thay thế trung gian này:

Hàm pdo_ * tương đương thay cho mysql_ *

Sử dụng < pdo_mysql.php > bạn có thể chuyển từ các hàm mysql_ cũ mà không tốn nhiều công sức . Nó thêm pdo_các trình bao bọc chức năng thay thế các mysql_đối tác của chúng.

  1. Đơn giản là trong mỗi kịch bản lệnh gọi phải tương tác với cơ sở dữ liệu. include_once("pdo_mysql.php");

  2. Loại bỏ mysql_tiền tố hàm ở mọi nơi và thay thế nó bằng pdo_.

    • mysql_connect() trở thành pdo_connect()
    • mysql_query() trở thành pdo_query()
    • mysql_num_rows() trở thành pdo_num_rows()
    • mysql_insert_id() trở thành pdo_insert_id()
    • mysql_fetch_array() trở thành pdo_fetch_array()
    • mysql_fetch_assoc() trở thành pdo_fetch_assoc()
    • mysql_real_escape_string() trở thành pdo_real_escape_string()
    • và như thế...

  3. Mã của bạn sẽ hoạt động giống nhau và hầu như vẫn giống nhau:

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }
    

Et voilà.
Mã của bạn đang sử dụng PDO.
Bây giờ đã đến lúc thực sự sử dụng nó.

Các tham số ràng buộc có thể dễ sử dụng

Bạn chỉ cần một API ít khó sử dụng hơn.

pdo_query()thêm hỗ trợ rất dễ dàng cho các tham số ràng buộc. Chuyển đổi mã cũ rất đơn giản:

Di chuyển các biến của bạn ra khỏi chuỗi SQL.

  • Thêm chúng dưới dạng các tham số hàm được phân tách bằng dấu phẩy vào pdo_query().
  • Đặt dấu chấm hỏi ?làm chỗ dành sẵn cho các biến trước đó.
  • Loại bỏ các 'dấu ngoặc kép mà các giá trị / biến chuỗi đã bao gồm trước đó.

Lợi thế trở nên rõ ràng hơn đối với mã dài hơn.

Thông thường, các biến chuỗi không chỉ được nội suy vào SQL mà còn được nối với các lệnh gọi thoát ở giữa.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

Với ?trình giữ chỗ được áp dụng, bạn không phải bận tâm đến điều đó:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

Hãy nhớ rằng pdo_ * vẫn cho phép hoặc .
Chỉ cần không thoát khỏi một biến liên kết nó trong cùng một truy vấn.

  • Tính năng giữ chỗ được cung cấp bởi PDO thực đằng sau nó.
  • Do đó, :nameddanh sách giữ chỗ cũng được phép sau này.

Quan trọng hơn, bạn có thể chuyển các biến $ _REQUEST [] một cách an toàn sau bất kỳ truy vấn nào. Khi <form>các trường đã gửi khớp chính xác với cấu trúc cơ sở dữ liệu, nó thậm chí còn ngắn hơn:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

Rất đơn giản. Nhưng hãy quay lại với một số lời khuyên viết lại và lý do kỹ thuật về lý do tại sao bạn có thể muốn loại bỏ mysql_và thoát ra.

Sửa hoặc xóa bất kỳ sanitize()chức năng cũ nào

Khi bạn đã chuyển đổi tất cả các mysql_cuộc gọi thành pdo_queryvới các tham số ràng buộc, hãy xóa tất cả các pdo_real_escape_stringcuộc gọi thừa .

Đặc biệt, bạn nên sửa bất kỳ sanitizehoặc cleanhoặc filterThishoặc các clean_datachức năng như được quảng cáo bởi các hướng dẫn ngày tháng ở dạng này hay dạng khác:

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

Hầu hết lỗi rõ ràng ở đây là thiếu tài liệu. Đáng kể hơn, thứ tự lọc không chính xác.

  • Thứ tự đúng sẽ là: không được dùng stripslasheslàm lệnh gọi trong cùng, sau trimđó strip_tags, htmlentitiesđối với ngữ cảnh đầu ra, và cuối cùng _escape_stringlà khi ứng dụng của nó phải trực tiếp trước SQL xen kẽ.

  • Nhưng như bước đầu tiên, chỉ cần thoát khỏi_real_escape_string cuộc gọi.

  • Bạn có thể phải giữ phần còn lại của sanitize()chức năng của mình ngay bây giờ nếu cơ sở dữ liệu và luồng ứng dụng của bạn mong đợi các chuỗi HTML-ngữ cảnh-an toàn. Thêm nhận xét rằng nó chỉ áp dụng tính năng thoát HTML từ đó trở đi.

  • Xử lý chuỗi / giá trị được ủy quyền cho PDO và các câu lệnh được tham số hóa của nó.

  • Nếu có bất kỳ đề cập nào về stripslashes()chức năng làm vệ sinh của bạn, điều đó có thể cho thấy sự giám sát cấp cao hơn.

    Ghi chú lịch sử về magic_quotes. Tính năng đó đúng là không được dùng nữa. Tuy nhiên, nó thường được miêu tả không chính xác là tính năng bảo mật không thành công . Nhưng magic_quotes là một tính năng bảo mật không thành công cũng giống như các quả bóng quần vợt không thành nguồn dinh dưỡng. Đó đơn giản không phải là mục đích của họ.

    Việc triển khai ban đầu trong PHP2 / FI đã giới thiệu nó một cách rõ ràng chỉ với " dấu ngoặc kép sẽ được tự động thoát ra, giúp dễ dàng truyền dữ liệu biểu mẫu trực tiếp đến các truy vấn msql ". Đáng chú ý là nó vô tình an toàn khi sử dụng với mSQL , vì nó chỉ hỗ trợ ASCII.
    Sau đó, PHP3 / Zend đã giới thiệu lại magic_quotes cho MySQL và ghi sai tài liệu. Nhưng ban đầu nó chỉ là một tính năng tiện lợi , không có ý định bảo mật.

Các tuyên bố chuẩn bị khác nhau như thế nào

Khi bạn xáo trộn các biến chuỗi vào các truy vấn SQL, nó không chỉ phức tạp hơn để bạn làm theo. Đó cũng là nỗ lực không đáng có của MySQL để tách biệt mã và dữ liệu một lần nữa.

SQL injection đơn giản là khi dữ liệu tràn vào ngữ cảnh . Một máy chủ cơ sở dữ liệu sau đó không thể phát hiện ra nơi mà PHP ban đầu đã dán các biến vào giữa các mệnh đề truy vấn.

Với các tham số ràng buộc, bạn tách mã SQL và các giá trị ngữ cảnh SQL trong mã PHP của mình. Nhưng nó không bị xáo trộn một lần nữa ở hậu trường (ngoại trừ với PDO :: EMULATE_PREPARES). Cơ sở dữ liệu của bạn nhận được các lệnh SQL không biến đổi và các giá trị biến 1: 1.

Trong khi câu trả lời này nhấn mạnh rằng bạn nên quan tâm đến lợi thế dễ đọc của việc bỏ rơi mysql_. Đôi khi cũng có một lợi thế về hiệu suất (INSERT lặp lại chỉ với các giá trị khác nhau) do sự phân tách dữ liệu / mã kỹ thuật và dễ nhìn thấy này.

Hãy lưu ý rằng liên kết tham số vẫn không phải là giải pháp một cửa kỳ diệu chống lại tất cả các lần tiêm SQL. Nó xử lý việc sử dụng phổ biến nhất cho dữ liệu / giá trị. Nhưng không thể đưa tên cột / mã nhận dạng bảng vào danh sách trắng, trợ giúp với việc xây dựng mệnh đề động hoặc chỉ danh sách giá trị mảng thuần túy.

Sử dụng PDO kết hợp

Các pdo_*hàm trình bao bọc này tạo nên một API stop-gap thân thiện với mã hóa. (Đó là những gì MYSQLIcó thể xảy ra nếu không có sự thay đổi chữ ký chức năng theo phong cách riêng). Họ cũng để lộ PDO thực vào hầu hết các thời điểm.
Viết lại không phải dừng lại ở việc sử dụng các tên hàm pdo_ mới. Bạn có thể lần lượt chuyển đổi từng pdo_query () thành một lệnh gọi đơn giản $ pdo-> Chuẩn bị () -> Thực thi ().

Tuy nhiên, tốt nhất là bắt đầu đơn giản hóa lại. Ví dụ: tìm nạp kết quả chung:

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

Có thể được thay thế chỉ bằng một lần lặp foreach:

foreach ($result as $row) {

Hoặc tốt hơn là truy xuất mảng trực tiếp và đầy đủ:

$result->fetchAll();

Trong hầu hết các trường hợp, bạn sẽ nhận được nhiều cảnh báo hữu ích hơn PDO hoặc mysql_ thường cung cấp sau các truy vấn không thành công.

Sự lựa chọn khác

Vì vậy, điều này hy vọng hình dung một số lý do thực tế và một con đường đáng giá để bỏ mysql_.

Chỉ cần chuyển sang pdo không hoàn toàn cắt giảm nó. pdo_query()cũng chỉ là một giao diện người dùng của nó.

Trừ khi bạn cũng giới thiệu ràng buộc tham số hoặc có thể sử dụng thứ gì đó khác từ API đẹp hơn, đó là một công tắc vô nghĩa. Tôi hy vọng nó được miêu tả đủ đơn giản để không gây thêm sự nản lòng cho những người mới. (Giáo dục thường hiệu quả hơn sự cấm đoán.)

Mặc dù nó đủ điều kiện cho danh mục đơn giản nhất-điều-có-thể-có-thể-hoạt động, nhưng nó cũng vẫn là mã rất thử nghiệm. Tôi vừa mới viết nó vào cuối tuần. Tuy nhiên, có rất nhiều lựa chọn thay thế. Chỉ cần google cho cơ sở dữ liệu PHP trừu tượng và duyệt qua một chút. Luôn luôn có và sẽ có rất nhiều thư viện tuyệt vời cho những nhiệm vụ như vậy.

Nếu bạn muốn đơn giản hóa hơn nữa việc tương tác với cơ sở dữ liệu của mình, các trình lập bản đồ như Paris / Idiorm rất đáng để thử. Giống như không ai sử dụng DOM nhạt nhẽo trong JavaScript nữa, ngày nay bạn không cần phải trông nom một giao diện cơ sở dữ liệu thô.

150 Alnitak Oct 12 2012 at 20:22

Các mysql_chức năng:

  1. đã lỗi thời - chúng không còn được duy trì nữa
  2. không cho phép bạn dễ dàng di chuyển sang phần phụ trợ cơ sở dữ liệu khác
  3. không hỗ trợ các tuyên bố chuẩn bị, do đó
  4. khuyến khích các lập trình viên sử dụng phép nối để xây dựng các truy vấn, dẫn đến các lỗ hổng SQL injection
109 YourCommonSense Jan 02 2013 at 00:42

Nói về lý do kỹ thuật thì chỉ có một số, cực kỳ cụ thể và ít được sử dụng. Rất có thể bạn sẽ không bao giờ sử dụng chúng trong đời.
Có lẽ tôi quá thiếu hiểu biết, nhưng tôi chưa bao giờ có cơ hội sử dụng chúng những thứ như

  • truy vấn không chặn, không đồng bộ
  • các thủ tục được lưu trữ trả về nhiều tập kết quả
  • Mã hóa (SSL)
  • Nén

Nếu bạn cần chúng - đây chắc chắn là những lý do kỹ thuật để chuyển từ tiện ích mở rộng mysql sang một thứ gì đó phong cách và hiện đại hơn.

Tuy nhiên, cũng có một số vấn đề không liên quan đến kỹ thuật, có thể khiến trải nghiệm của bạn khó khăn hơn một chút

  • Việc sử dụng thêm các chức năng này với các phiên bản PHP hiện đại sẽ đưa ra các thông báo cấp không dùng nữa. Chúng chỉ đơn giản là có thể được tắt.
  • trong một tương lai xa, chúng có thể bị xóa khỏi bản dựng PHP mặc định. Cũng không phải là vấn đề lớn, vì mydsql ext sẽ được chuyển sang PECL và mọi nhà cung cấp dịch vụ sẽ sẵn lòng biên dịch PHP với nó, vì họ không muốn mất khách hàng có trang web đã hoạt động trong nhiều thập kỷ.
  • sự phản kháng mạnh mẽ từ cộng đồng Stackoverflow. Mỗi khi bạn đề cập đến những chức năng trung thực này, bạn sẽ được thông báo rằng chúng đang bị cấm kỵ nghiêm ngặt.
  • là một người dùng PHP trung bình, rất có thể ý tưởng của bạn về việc sử dụng các hàm này dễ xảy ra lỗi và sai. Chỉ vì vô số hướng dẫn và sách hướng dẫn này dạy bạn sai cách. Không phải bản thân các chức năng - tôi phải nhấn mạnh nó - mà là cách chúng được sử dụng.

Vấn đề thứ hai này là một vấn đề.
Nhưng, theo tôi, giải pháp được đề xuất cũng không tốt hơn.
Đối với tôi, có vẻ như một giấc mơ quá lý tưởng rằng tất cả những người dùng PHP đó sẽ học cách xử lý các truy vấn SQL đúng cách ngay lập tức. Nhiều khả năng họ sẽ chỉ thay đổi mysql_ * thành mysqli_ * một cách máy móc, giữ nguyên cách tiếp cận . Đặc biệt là bởi vì mysqli làm cho việc sử dụng các câu lệnh đã chuẩn bị trước rất khó khăn và rắc rối.
Chưa kể rằng các câu lệnh được chuẩn bị sẵn không đủ để bảo vệ khỏi việc tiêm SQL và cả mysqli lẫn PDO đều không đưa ra giải pháp.

Vì vậy, thay vì chống lại phần mở rộng trung thực này, tôi muốn chống lại các thực hành sai và giáo dục mọi người theo những cách đúng đắn.

Ngoài ra, có một số lý do sai hoặc không quan trọng, như

  • Không hỗ trợ Thủ tục được lưu trữ (chúng tôi đã sử dụng mysql_query("CALL my_proc");cho các lứa tuổi)
  • Không hỗ trợ Giao dịch (giống như ở trên)
  • Không hỗ trợ Nhiều câu lệnh (ai cần chúng?)
  • Không được phát triển tích cực (vậy thì sao? Nó ảnh hưởng đến bạn theo bất kỳ cách thực tế nào?)
  • Thiếu giao diện OO (để tạo một giao diện mất vài giờ)
  • Không hỗ trợ Câu lệnh soạn sẵn hoặc Truy vấn tham số hóa

Cái cuối cùng là một điểm thú vị. Mặc dù mysql ext không hỗ trợ các câu lệnh được chuẩn bị sẵn, nhưng chúng không cần thiết để đảm bảo an toàn. Chúng tôi có thể dễ dàng giả mạo các báo cáo đã chuẩn bị bằng cách sử dụng trình giữ chỗ được xử lý thủ công (giống như PDO):

function paraQuery()
{
    $args  = func_get_args();
    $query = array_shift($args);
    $query = str_replace("%s","'%s'",$query); 

    foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val);
    }

    $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

Thì đấy , mọi thứ đều được tham số hóa và an toàn.

Nhưng không sao, nếu bạn không thích ô màu đỏ trong sách hướng dẫn, một vấn đề lựa chọn sẽ nảy sinh: mysqli hay PDO?

Vâng, câu trả lời sẽ như sau:

  • Nếu bạn hiểu sự cần thiết của việc sử dụng lớp trừu tượng cơ sở dữ liệu và đang tìm kiếm một API để tạo một lớp, thì mysqli là một lựa chọn rất tốt, vì nó thực sự hỗ trợ nhiều tính năng dành riêng cho mysql.
  • Nếu, giống như đại đa số người dùng PHP, bạn đang sử dụng các lệnh gọi API thô ngay trong mã ứng dụng (về cơ bản là thực hành sai) - PDO là lựa chọn duy nhất , vì tiện ích mở rộng này giả vờ không chỉ là API mà là một bán DAL, vẫn chưa hoàn thiện nhưng cung cấp nhiều tính năng quan trọng, với hai trong số chúng khiến PDO được phân biệt nghiêm túc với mysqli:

    • không giống như mysqli, PDO có thể ràng buộc các trình giữ chỗ theo giá trị , điều này làm cho các truy vấn được xây dựng động khả thi mà không có một số màn hình mã khá lộn xộn.
    • không giống như mysqli, PDO luôn có thể trả về kết quả truy vấn trong một mảng thông thường đơn giản, trong khi mysqli chỉ có thể thực hiện điều đó trên các cài đặt mysqlnd.

Vì vậy, nếu bạn là một người dùng PHP trung bình và muốn đỡ phải đau đầu khi sử dụng các câu lệnh chuẩn bị sẵn, PDO - một lần nữa - là sự lựa chọn duy nhất.
Tuy nhiên, PDO cũng không phải là một viên đạn bạc và có những khó khăn.
Vì vậy, tôi đã viết các giải pháp cho tất cả các cạm bẫy phổ biến và các trường hợp phức tạp trong wiki thẻ PDO

Tuy nhiên, mọi người nói về tiện ích mở rộng luôn thiếu 2 sự thật quan trọng về Mysqli và PDO:

  1. Tuyên bố chuẩn bị không phải là một viên đạn bạc . Có những định danh động không thể bị ràng buộc bằng cách sử dụng các câu lệnh đã chuẩn bị. Có những truy vấn động với một số lượng tham số không xác định làm cho việc xây dựng truy vấn trở thành một nhiệm vụ khó khăn.

  2. Cả hai hàm mysqli_ * và PDO đều không nên xuất hiện trong mã ứng dụng.
    Phải có một lớp trừu tượng giữa chúng và mã ứng dụng, lớp này sẽ thực hiện tất cả các công việc bẩn thỉu như ràng buộc, lặp lại, xử lý lỗi, v.v. bên trong, làm cho mã ứng dụng KHÔ và sạch. Đặc biệt là đối với các trường hợp phức tạp như xây dựng truy vấn động.

Vì vậy, chỉ chuyển sang PDO hoặc mysqli là không đủ. Người ta phải sử dụng ORM, hoặc trình tạo truy vấn, hoặc bất kỳ lớp trừu tượng cơ sở dữ liệu nào thay vì gọi các hàm API thô trong mã của chúng.
Và ngược lại - nếu bạn có một lớp trừu tượng giữa mã ứng dụng của mình và API mysql - thì công cụ nào được sử dụng không thực sự quan trọng. Bạn có thể sử dụng mysql ext cho đến khi nó không còn được dùng nữa và sau đó dễ dàng viết lại lớp trừu tượng của bạn sang một công cụ khác, giữ nguyên tất cả mã ứng dụng.

Dưới đây là một số ví dụ dựa trên lớp safemysql của tôi để cho thấy một lớp trừu tượng như vậy phải như thế nào:

$city_ids = array(1,2,3);
$cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

So sánh một dòng này với số lượng mã bạn sẽ cần với PDO .
Sau đó, so sánh với số lượng mã điên cuồng mà bạn sẽ cần với các câu lệnh thô đã chuẩn bị sẵn của Mysqli. Lưu ý rằng xử lý lỗi, lập hồ sơ, ghi nhật ký truy vấn đã được tích hợp sẵn và đang chạy.

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

So sánh nó với các chèn PDO thông thường, khi mỗi tên trường đơn lẻ được lặp lại từ sáu đến mười lần - trong tất cả các định nghĩa truy vấn, liên kết và trình giữ chỗ được đặt tên này.

Một vi dụ khac:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

Bạn khó có thể tìm thấy một ví dụ cho PDO để xử lý trường hợp thực tế như vậy.
Và nó sẽ quá dài dòng và rất có thể không an toàn.

Vì vậy, một lần nữa - nó không chỉ là trình điều khiển thô nên là mối quan tâm của bạn mà còn là lớp trừu tượng, hữu ích không chỉ cho các ví dụ ngớ ngẩn từ sách hướng dẫn dành cho người mới bắt đầu mà còn để giải quyết bất kỳ vấn đề nào trong cuộc sống thực.

98 Trott Oct 12 2012 at 20:23

Có nhiều lý do, nhưng có lẽ lý do quan trọng nhất là các hàm đó khuyến khích các thực hành lập trình không an toàn vì chúng không hỗ trợ các câu lệnh đã soạn sẵn. Các câu lệnh chuẩn bị sẵn giúp ngăn chặn các cuộc tấn công SQL injection.

Khi sử dụng các mysql_*hàm, bạn phải nhớ chạy các thông số do người dùng cung cấp mysql_real_escape_string(). Nếu bạn quên chỉ ở một nơi hoặc nếu bạn tình cờ thoát chỉ một phần của đầu vào, cơ sở dữ liệu của bạn có thể bị tấn công.

Việc sử dụng các câu lệnh đã chuẩn bị sẵn trong PDOhoặc mysqlisẽ khiến cho các loại lỗi lập trình này khó mắc hơn.

77 enhzflep Oct 12 2012 at 20:24

Bởi vì (trong số các lý do khác), việc đảm bảo dữ liệu đầu vào được khử trùng sẽ khó hơn nhiều. Nếu bạn sử dụng các truy vấn tham số hóa, giống như với PDO hoặc mysqli, bạn hoàn toàn có thể tránh được rủi ro.

Ví dụ, ai đó có thể sử dụng "enhzflep); drop table users"làm tên người dùng. Các chức năng cũ sẽ cho phép thực thi nhiều câu lệnh cho mỗi truy vấn, vì vậy, một cái gì đó như trình kích hoạt khó chịu đó có thể xóa toàn bộ bảng.

Nếu một người sử dụng PDO của mysqli, tên người dùng cuối cùng sẽ là "enhzflep); drop table users".

Xem bobby-tables.com .

66 Fluffeh Sep 18 2013 at 19:28

Câu trả lời này được viết để cho thấy việc bỏ qua mã xác thực người dùng PHP được viết kém, cách (và cách sử dụng) các cuộc tấn công này hoạt động như thế nào và cách thay thế các hàm MySQL cũ bằng một câu lệnh được chuẩn bị an toàn - và về cơ bản, tại sao người dùng StackOverflow (có thể là với rất nhiều đại diện) đang sủa khi người dùng mới đặt câu hỏi để cải thiện mã của họ.

Trước hết, vui lòng tạo cơ sở dữ liệu mysql thử nghiệm này (tôi đã gọi là chuẩn bị của tôi):

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

Sau khi hoàn thành, chúng ta có thể chuyển sang mã PHP của mình.

Giả sử tập lệnh sau là quy trình xác minh dành cho quản trị viên trên một trang web (được đơn giản hóa nhưng hoạt động nếu bạn sao chép và sử dụng nó để thử nghiệm):

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }

    $database='prep';
    $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database");

    $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>";
    $result=mysql_query($sql);
    $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
        $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }
    mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Thoạt nhìn có vẻ hợp pháp.

Người dùng phải nhập thông tin đăng nhập và mật khẩu, phải không?

Tuyệt vời, không nhập vào những điều sau:

user: bob
pass: somePass

và nộp nó.

Kết quả như sau:

You could not be verified. Please try again...

Siêu! Hoạt động như mong đợi, bây giờ hãy thử tên người dùng và mật khẩu thực tế:

user: Fluffeh
pass: mypass

Kinh ngạc! Hi-fives tất cả các vòng, mã đã xác minh chính xác một quản trị viên. Thật hoàn hảo!

Chà, không hẳn. Giả sử người dùng là một người nhỏ thông minh. Hãy nói rằng người đó là tôi.

Nhập thông tin sau:

user: bob
pass: n' or 1=1 or 'm=m

Và đầu ra là:

The check passed. We have a verified admin!

Xin chúc mừng, bạn vừa cho phép tôi vào phần chỉ dành cho quản trị viên được bảo vệ siêu cao của bạn với việc tôi nhập tên người dùng sai và mật khẩu sai. Nghiêm túc mà nói, nếu bạn không tin tôi, hãy tạo cơ sở dữ liệu bằng mã tôi đã cung cấp và chạy mã PHP này - mà nhìn qua thì THỰC SỰ dường như xác minh tên người dùng và mật khẩu khá độc đáo.

Vì vậy, trong câu trả lời, ĐÓ LÀ TẠI SAO BẠN ĐANG CÓ MÀU VÀNG Ở.

Vì vậy, chúng ta hãy xem điều gì đã xảy ra và tại sao tôi lại vào hang động siêu-quản-trị-duy-nhất của bạn. Tôi đã đoán và cho rằng bạn đã không cẩn thận với đầu vào của mình và chỉ cần chuyển trực tiếp chúng vào cơ sở dữ liệu. Tôi đã xây dựng đầu vào theo cách sẽ THAY ĐỔI truy vấn mà bạn đang thực sự chạy. Vì vậy, nó đã được cho là gì, và nó rốt cuộc là gì?

select id, userid, pass from users where userid='$user' and pass='$pass'

Đó là truy vấn, nhưng khi chúng tôi thay thế các biến bằng các đầu vào thực tế mà chúng tôi đã sử dụng, chúng tôi nhận được như sau:

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

Hãy xem tôi đã xây dựng "mật khẩu" của mình như thế nào để trước tiên nó đóng một trích dẫn duy nhất xung quanh mật khẩu, sau đó giới thiệu một so sánh hoàn toàn mới? Sau đó, chỉ để an toàn, tôi đã thêm một "chuỗi" khác để câu trích dẫn duy nhất sẽ được đóng lại như mong đợi trong mã mà chúng tôi có ban đầu.

Tuy nhiên, đây không phải là việc mọi người la mắng bạn, đây là việc chỉ cho bạn cách làm cho mã của bạn an toàn hơn.

Được rồi, vậy điều gì đã xảy ra và chúng ta có thể sửa chữa nó như thế nào?

Đây là một cuộc tấn công SQL injection cổ điển. Một trong những đơn giản nhất cho vấn đề đó. Trên quy mô vectơ tấn công, đây là một đứa trẻ mới biết đi tấn công một chiếc xe tăng - và chiến thắng.

Vì vậy, làm thế nào để chúng tôi bảo vệ phần quản trị thiêng liêng của bạn và làm cho nó tốt đẹp và an toàn? Điều đầu tiên cần làm là ngừng sử dụng những hàm thực sự cũ và không còn được dùng nữa mysql_*. Tôi biết, bạn đã làm theo một hướng dẫn mà bạn tìm thấy trên mạng và nó hoạt động, nhưng nó đã cũ, đã lỗi thời và chỉ trong vài phút, tôi đã vượt qua nó mà không phải đổ mồ hôi nhiều.

Bây giờ, bạn có các tùy chọn tốt hơn khi sử dụng mysqli_ hoặc PDO . Cá nhân tôi là một fan hâm mộ lớn của PDO, vì vậy tôi sẽ sử dụng PDO trong phần còn lại của câu trả lời này. Có những người chuyên nghiệp và kẻ lừa đảo, nhưng cá nhân tôi thấy rằng người chuyên nghiệp hơn hẳn người chuyên nghiệp. Nó có thể di động trên nhiều công cụ cơ sở dữ liệu - cho dù bạn đang sử dụng MySQL hay Oracle hay chỉ là bất cứ thứ gì khác - chỉ bằng cách thay đổi chuỗi kết nối, nó có tất cả các tính năng ưa thích mà chúng tôi muốn sử dụng và nó rất đẹp và sạch sẽ. Tôi thích sạch sẽ.

Bây giờ, chúng ta hãy xem lại đoạn mã đó, lần này được viết bằng đối tượng PDO:

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }
    $isAdmin=false;

    $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
            $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Sự khác biệt chính là không có nhiều mysql_*chức năng. Tất cả được thực hiện thông qua một đối tượng PDO, thứ hai, nó đang sử dụng một câu lệnh đã chuẩn bị sẵn. Bây giờ, bạn hỏi một báo cáo viết sẵn là gì? Đó là một cách để cho cơ sở dữ liệu biết trước khi chạy một truy vấn, truy vấn mà chúng ta sẽ chạy là gì. Trong trường hợp này, chúng tôi nói với cơ sở dữ liệu: "Xin chào, tôi sẽ chạy một câu lệnh select muốn id, userid và truyền từ những người dùng bảng trong đó userid là một biến và pass cũng là một biến."

Sau đó, trong câu lệnh thực thi, chúng ta truyền vào cơ sở dữ liệu một mảng với tất cả các biến mà nó mong đợi.

Kết quả thật tuyệt vời. Hãy thử lại những kết hợp tên người dùng và mật khẩu trước đó:

user: bob
pass: somePass

Người dùng chưa được xác minh. Tuyệt vời.

Làm thế nào về:

user: Fluffeh
pass: mypass

Ồ, tôi chỉ có một chút phấn khích, nó đã hoạt động: Chi phiếu đã thông qua. Chúng tôi có một quản trị viên đã được xác minh!

Bây giờ, hãy thử dữ liệu mà một người thông minh sẽ nhập để cố gắng vượt qua hệ thống xác minh nhỏ của chúng tôi:

user: bob
pass: n' or 1=1 or 'm=m

Lần này, chúng tôi nhận được những điều sau:

You could not be verified. Please try again...

Đây là lý do tại sao bạn bị la khi đăng câu hỏi - đó là vì mọi người có thể thấy rằng mã của bạn có thể bị bỏ qua mà không cần cố gắng. Vui lòng sử dụng câu hỏi và câu trả lời này để cải thiện mã của bạn, để làm cho mã an toàn hơn và sử dụng các chức năng hiện tại.

Cuối cùng, điều này không có nghĩa rằng đây là mã HOÀN THÀNH. Có nhiều điều bạn có thể làm để cải thiện nó, ví dụ như sử dụng mật khẩu băm, đảm bảo rằng khi bạn lưu trữ thông tin có ý nghĩa trong cơ sở dữ liệu, bạn không lưu trữ nó ở dạng văn bản thuần túy, có nhiều cấp độ xác minh - nhưng thực sự, nếu bạn chỉ cần thay đổi mã dễ bị chèn cũ của mình thành mã này, bạn sẽ thật TUYỆT VỜI trong quá trình viết mã tốt - và thực tế là bạn đã hiểu được điều này và vẫn đang đọc cho tôi cảm giác hy vọng rằng bạn sẽ không chỉ triển khai loại mã này mã khi viết các trang web và ứng dụng của bạn, nhưng bạn có thể ra ngoài và nghiên cứu những thứ khác mà tôi vừa đề cập - và hơn thế nữa. Viết mã tốt nhất bạn có thể, không phải mã cơ bản nhất hầu như không hoạt động.

34 Alexander Sep 02 2015 at 14:20

Phần mở rộng MySQL là phần mở rộng lâu đời nhất trong số ba phần mở rộng và là cách ban đầu mà các nhà phát triển sử dụng để giao tiếp với MySQL. Phần mở rộng này hiện đang được tán thành ủng hộ của người kia hai lựa chọn thay thế vì những cải tiến thực hiện trong các phiên bản mới hơn của cả hai PHP và MySQL.

  • MySQLi là phần mở rộng 'cải tiến' để làm việc với cơ sở dữ liệu MySQL. Nó tận dụng các tính năng có sẵn trong các phiên bản mới hơn của máy chủ MySQL, hiển thị cả giao diện hướng chức năng và hướng đối tượng cho nhà phát triển và một vài thứ tiện lợi khác.

  • PDO cung cấp một API hợp nhất hầu hết các chức năng trước đây đã trải rộng trên các tiện ích mở rộng truy cập cơ sở dữ liệu chính, tức là MySQL, PostgreSQL, SQLite, MSSQL, v.v. Giao diện hiển thị các đối tượng cấp cao để lập trình viên làm việc với các kết nối cơ sở dữ liệu, truy vấn và bộ kết quả và trình điều khiển cấp thấp thực hiện giao tiếp và xử lý tài nguyên với máy chủ cơ sở dữ liệu. Rất nhiều cuộc thảo luận và công việc đang diễn ra trong PDO và nó được coi là phương pháp thích hợp để làm việc với cơ sở dữ liệu trong mã chuyên nghiệp, hiện đại.

22 AniMenon Sep 07 2016 at 22:06

Tôi thấy các câu trả lời trên thực sự dài dòng, vì vậy để tóm tắt:

Tiện ích mở rộng mysqli có một số lợi ích, những cải tiến chính so với tiện ích mở rộng mysql là:

  • Giao diện hướng đối tượng
  • Hỗ trợ cho các báo cáo chuẩn bị
  • Hỗ trợ nhiều câu lệnh
  • Hỗ trợ giao dịch
  • Nâng cao khả năng gỡ lỗi
  • Hỗ trợ máy chủ nhúng

Nguồn: Tổng quan về MySQLi


Như đã giải thích trong các câu trả lời ở trên, các lựa chọn thay thế cho mysql là mysqli và PDO (PHP Data Objects).

  • API hỗ trợ các Tuyên bố chuẩn bị phía máy chủ: Được hỗ trợ bởi MYSQLi và PDO
  • API hỗ trợ các Tuyên bố chuẩn bị phía máy khách: Chỉ được hỗ trợ bởi PDO
  • API hỗ trợ các thủ tục được lưu trữ: Cả MySQLi và PDO
  • API hỗ trợ Nhiều câu lệnh và tất cả chức năng của MySQL 4.1+ - Được hỗ trợ bởi MySQLi và hầu hết là PDO

Cả MySQLi và PDO đều được giới thiệu trong PHP 5.0, trong khi MySQL đã được giới thiệu trước PHP 3.0. Một điểm cần lưu ý là MySQL được bao gồm trong PHP5.x mặc dù không được dùng trong các phiên bản sau.

7 PavelTzonkov Jun 09 2017 at 13:24

Có thể xác định hầu hết các mysql_*chức năng bằng cách sử dụng mysqli hoặc PDO. Chỉ cần đưa chúng lên đầu ứng dụng PHP cũ của bạn và nó sẽ hoạt động trên PHP7. Giải pháp của tôi đây .

<?php

define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null;

function mysql_link($link=null) {
    return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link;
}

function mysql_connect($host, $user, $pass) {
    $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
    return $GLOBALS[MYSQL_LINK];
}

function mysql_pconnect($host, $user, $pass) {
    return mysql_connect($host, $user, $pass);
}

function mysql_select_db($db, $link=null) {
    $link = mysql_link($link);
    return mysqli_select_db($link, $db);
}

function mysql_close($link=null) {
    $link = mysql_link($link);
    return mysqli_close($link);
}

function mysql_error($link=null) {
    $link = mysql_link($link);
    return mysqli_error($link);
}

function mysql_errno($link=null) {
    $link = mysql_link($link);
    return mysqli_errno($link);
}

function mysql_ping($link=null) {
    $link = mysql_link($link);
    return mysqli_ping($link);
}

function mysql_stat($link=null) {
    $link = mysql_link($link);
    return mysqli_stat($link);
}

function mysql_affected_rows($link=null) {
    $link = mysql_link($link);
    return mysqli_affected_rows($link);
}

function mysql_client_encoding($link=null) {
    $link = mysql_link($link);
    return mysqli_character_set_name($link);
}

function mysql_thread_id($link=null) {
    $link = mysql_link($link);
    return mysqli_thread_id($link);
}

function mysql_escape_string($string) {
    return mysql_real_escape_string($string);
}

function mysql_real_escape_string($string, $link=null) {
    $link = mysql_link($link);
    return mysqli_real_escape_string($link, $string);
}

function mysql_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql);
}

function mysql_unbuffered_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql, MYSQLI_USE_RESULT);
}

function mysql_set_charset($charset, $link=null){
    $link = mysql_link($link);
    return mysqli_set_charset($link, $charset);
}

function mysql_get_host_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_host_info($link);
}

function mysql_get_proto_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_proto_info($link);
}
function mysql_get_server_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_server_info($link);
}

function mysql_info($link=null) {
    $link = mysql_link($link);
    return mysqli_info($link);
}

function mysql_get_client_info() {
    $link = mysql_link();
    return mysqli_get_client_info($link);
}

function mysql_create_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "CREATE DATABASE `$db`");
}

function mysql_drop_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "DROP DATABASE `$db`");
}

function mysql_list_dbs($link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, "SHOW DATABASES");
}

function mysql_list_fields($db, $table, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    $table = str_replace('`', '', mysqli_real_escape_string($link, $table));
    return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`");
}

function mysql_list_tables($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "SHOW TABLES FROM `$db`");
}

function mysql_db_query($db, $sql, $link=null) {
    $link = mysql_link($link);
    mysqli_select_db($link, $db);
    return mysqli_query($link, $sql);
}

function mysql_fetch_row($qlink) {
    return mysqli_fetch_row($qlink);
}

function mysql_fetch_assoc($qlink) {
    return mysqli_fetch_assoc($qlink);
}

function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) {
    return mysqli_fetch_array($qlink, $result);
}

function mysql_fetch_lengths($qlink) {
    return mysqli_fetch_lengths($qlink);
}

function mysql_insert_id($qlink) {
    return mysqli_insert_id($qlink);
}

function mysql_num_rows($qlink) {
    return mysqli_num_rows($qlink);
}

function mysql_num_fields($qlink) {
    return mysqli_num_fields($qlink);
}

function mysql_data_seek($qlink, $row) {
    return mysqli_data_seek($qlink, $row);
}

function mysql_field_seek($qlink, $offset) {
    return mysqli_field_seek($qlink, $offset);
}

function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
    return ($params === null)
        ? mysqli_fetch_object($qlink, $class)
        : mysqli_fetch_object($qlink, $class, $params);
}

function mysql_db_name($qlink, $row, $field='Database') {
    mysqli_data_seek($qlink, $row);
    $db = mysqli_fetch_assoc($qlink);
    return $db[$field];
}

function mysql_fetch_field($qlink, $offset=null) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    return mysqli_fetch_field($qlink);
}

function mysql_result($qlink, $offset, $field=0) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    $row = mysqli_fetch_array($qlink);
    return (!is_array($row) || !isset($row[$field]))
        ? false
        : $row[$field];
}

function mysql_field_len($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->length : false;
}

function mysql_field_name($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgname) ? $field->name : $field->orgname;
}

function mysql_field_table($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgtable) ? $field->table : $field->orgtable;
}

function mysql_field_type($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->type : false;
}

function mysql_free_result($qlink) {
    try {
        mysqli_free_result($qlink);
    } catch (Exception $e) {
        return false;
    }
    return true;
}