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


2502

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

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

Nếu họ không làm việc trên trang web của tôi, tại sao tôi lại gặp lỗi như

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


Lỗi giống như: Lỗi nghiêm trọng: Lỗi chưa được xử lý: Gọi đến hàm không xác định mysql_connect () ...
Bimal Poudel

21
Không tán thành một mình là lý do đủ để tránh chúng
Sasa1234 17/12/17

Câu trả lời:


2088

Phần mở rộng MySQL:

  • Không phát triển tích cực
  • Được chính thức phản đối kể từ phiên bản 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 được hỗ trợ nào. Nếu bạn đang sử dụng một phiên bản PHP hỗ trợ nó, thì bạn đang sử dụng một phiên bản không khắc phục được vấn đề bảo mật.
  • Thiếu giao diện OO
  • Không hỗ trợ:
    • Các truy vấn không đồng bộ, không đồng bộ
    • Báo cáo đã chuẩn bị hoặc truy vấn tham số
    • Thủ tục lưu trữ
    • Nhiều báo cáo
    • Giao dịch
    • Phương thức xác thực mật khẩu "mới" (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.

Thiếu hỗ trợ cho các câu lệnh được chuẩn bị đặc biệt quan trọng vì chúng cung cấp một phương thức thoát và trích dẫn dữ liệu bên ngoài rõ ràng hơn, ít lỗi hơn so với thoát 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 .


287
Không tán thành một mình là lý do đủ để tránh chúng. Một ngày nào đó họ sẽ không ở đó và bạn sẽ không vui nếu bạn dựa vào họ. Phần còn lại chỉ là một danh sách những điều sử dụng các tiện ích mở rộng cũ đã khiến mọi người không học.
Tim Post

111
Khấu hao không phải là viên đạn ma thuật mà mọi người dường như nghĩ rằng nó là. Bản thân PHP sẽ không ở đó một ngày nào đó, nhưng chúng tôi dựa vào các công cụ mà chúng tôi có ngày hôm nay. Khi chúng tôi phải thay đổi công cụ, chúng tôi sẽ.
Các cuộc đua nhẹ nhàng trong quỹ đạo

133
@LightnessRacesinOrbit - Khấu hao không phải là một viên đạn ma thuật, đó là một lá cờ có nội dung "Chúng tôi nhận ra điều này thật tệ vì vậy chúng tôi sẽ không hỗ trợ nó lâu hơn nữa". Mặc dù việc chứng minh mã trong tương lai tốt hơn là một lý do chính đáng để tránh xa các tính năng không dùng nữa, nhưng đó không phải là tính năng duy nhất (hoặc thậm chí là chính). Thay đổi công cụ vì có công cụ tốt hơn, không phải vì bạn bị ép buộc. (Và thay đổi công cụ trước khi bạn bị buộc phải có nghĩa là bạn không học những cái mới chỉ vì mã của bạn đã ngừng hoạt động và cần sửa chữa ngày hôm qua, đó là thời điểm tồi tệ nhất để học các công cụ mới).
Quentin

18
Một điều mà tôi chưa thấy đề cập đến về việc thiếu các tuyên bố chuẩn bị là vấn đề hiệu suất. Mỗi khi bạn đưa ra một tuyên bố, một cái gì đó phải biên dịch nó để daemon MySQL có thể hiểu nó. Với API này, nếu bạn phát hành 200.000 truy vấn tương tự trong một vòng lặp, thì đó là 200.000 lần truy vấn phải được biên dịch để MySQL hiểu nó. Với các câu lệnh được chuẩn bị, nó được biên dịch một lần và sau đó các giá trị được tham số hóa thành SQL được biên dịch.
Goldentoa11

20
@symcbean, Nó chắc chắn không hỗ trợ báo cáo chuẩn bị. Thực tế đó là lý do chính khiến nó không được dùng nữa. Không có (dễ sử dụng) các câu lệnh được chuẩn bị, phần mở rộng mysql thường trở thành nạn nhân của các cuộc tấn công tiêm nhiễm SQL.
rustyx

1287

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

Các mysql_*chức năng được sử dụ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à giáo dục 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 điều này (kiểm tra php.i INTERNals: deprecating 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 bị từ chối kể từ phiên bản PHP 5.5 và đã bị xóa kể từ phiên bản PHP 7 .

Thấy hộp đỏ không?

Khi bạn vào 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 nó không nên được sử dụng nữa.

Tại sao


Di chuyển khỏi ext/mysqlkhông chỉ là về bảo mật, mà còn về việc có 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 cho mã khó bảo trì hơn một chút. Thiếu các tính năng không được hỗ trợ bởi 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 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 đồng bộ, không đồng bộ
  • Không hỗ trợ các câu lệnh được chuẩn bị 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 báo cáo
  • 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 dẫn từ câu trả lời của Quentin

Thiếu hỗ trợ cho các câu lệnh được chuẩn bị đặc biệt quan trọng vì chúng cung cấp một phương thức thoát và trích dẫn dữ liệu bên ngoài rõ ràng hơn, ít lỗi hơn so với thoát 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 .


Ức chế cảnh báo khấu hao

Trong khi mã đang được chuyển đổi thành MySQLi/ PDO, các E_DEPRECATEDlỗi có thể được loại bỏ bằng cách cài đặ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ấu hao khác , 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 viết PDO so với MySQLi: Bạn nên sử dụng cái nào? bởi Dejan Marjanovic sẽ giúp bạn lựa chọn.

Và một cách tốt hơn là PDO, và bây giờ tôi đang viết một 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. tôn PDO - Đối tượng dữ liệu PHP - là lớp truy cập cơ sở dữ liệu cung cấp một phương thức truy cập thống nhất vào nhiều cơ sở dữ liệu.

văn bản thay thế


Kết nối với MySQL

Với mysql_*chức năng hoặc chúng ta có thể nói theo cách cũ (không dùng nữa 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 . Hàm tạo chấp nhận các tham số để chỉ định hàm tạo của cơ sở dữ liệu PDOchủ yếu lấy bốn tham 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; đây là mới trong PDO. A DSNvề cơ bản 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-8, nhưng đôi khi nó gây ra lỗi, vì vậy tốt hơn là sử dụng utf8.

Nếu có bất kỳ lỗi kết nối, nó sẽ ném 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 trong 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ố đưa 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, nên PDOthực hiện mô phỏng chuẩn bị. Nó cũng cho phép bạn tự kích hoạt mô phỏng này. Để sử dụng các câu lệnh được chuẩn bị phía máy chủ riêng, 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 chế độ mô phỏng để sử dụng PDOmột cách an toàn.

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

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

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ể thiết lập 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 so với 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à một cách tốt để xử lý lỗi vì chúng tôi không thể xử lý sự cố die. Nó sẽ chỉ kết thúc tập lệnh đột ngột và sau đó báo lỗi cho 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à để tin tặc đẫm máu khám phá lược đồ của bạn. Thay phiên, các giá trị trả về củamysql_* 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 tôi làm với PDOnên được bọc trong một try- catchkhối. Chúng ta có thể ép buộcPDO vào một trong ba chế độ lỗi bằng cách đặt thuộc tính chế độ lỗi. Ba chế độ xử lý lỗi dưới đây.

  • PDO::ERRMODE_SILENT. Nó chỉ đặt mã lỗi và hoạt động tương tự nhưmysql_* nơi bạn phải kiểm tra từng kết quả và sau đó xem xét$db->errorInfo(); để nhận chi tiết lỗi.
  • PDO::ERRMODE_WARNING Nâ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ị dừng lại.)
  • PDO::ERRMODE_EXCEPTION: Ném ngoại lệ. Nó đại diện cho một lỗi được nêu ra bởi PDO. Bạn không nên ném PDOExceptiontừ mã của riêng bạn. 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 như or die(mysql_error());khi nó không bị bắt. Nhưng không giống như or die(), PDOExceptioncó thể được bắt và xử lý một cách duyên dáng 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ể gói nó trong try- catch, như dưới đây:

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 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, nó có thể có ý nghĩa hơn để bắt nó ở bên ngoài chức năng gọi các PDOcông cụ:

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 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 sản xuất bằng cách xoay display_errors offvà chỉ đọc nhật ký lỗi của bạn.

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 đi đây:


Chọn dữ liệu

Chọn hình ảnh PDO

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 điều này 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ư dưới đây ( query()), phương thức này trả về một PDOStatementđối tượng. Vì vậy, nếu bạn muốn lấy 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() , một 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à nói với PDO về cách bạn muốn dữ liệu được tìm nạp. Trong phần dưới đây tôi đang 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 nóiPDO để 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à các khóa. Có nhiều chế độ tìm nạp khác mà tôi sẽ giải thích từng cái 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

Thậm chí còn có nhiều sự lựa chọn hơn! Đọc về tất cả chúng trong PDOStatementtài liệu Fetch. .

Lấy số hàng :

Thay vì sử dụng mysql_num_rowsđể lấy số lượng hàng trả về, bạn có thể lấy PDOStatementvà thực hiện 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

Chèn và cập nhật hình ảnh PDO

Những gì chúng ta đ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::exec thực hiện một câu lệnh SQL và trả về số lượng các 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 truy vấn, đừng bao giờ thử như trên và ở đó cho câu lệnh chuẩn bị hoặc câu lệnh được tham số hóa .


Báo cáo chuẩn bị

H : Tuyên bố được chuẩn bị là gì và tại sao tôi cần chúng?
A. Một câu lệnh được chuẩn bị là một 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 từ Wikipedia ba 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ệ thống quản lý cơ sở dữ liệu (DBMS). Một số giá trị nhất định không được chỉ định, được gọi là tham số, giữ chỗ hoặc biến liên kết (được gắn 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 thực hiện nó.

  3. Thực thi : Sau đó, ứng dụng cung cấp các giá trị (hoặc liên kết) cho các tham số và DBMS thực thi câu lệnh (có thể trả về kết quả). Ứng dụng có thể thực thi 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 được chuẩn bị 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ử cái này với biến ở trên), một cái có giữ chỗ không tên và một cái có giữ chỗ được đặt tên.

H: Vậy bây giờ, tên giữ chỗ được đặt tên là gì và làm cách nào để sử dụng chúng?
A. Đặt tên được giữ chỗ. Sử dụng tên mô tả đứng 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ự của giá trị trong tên người giữ tên:

 $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 hay khác cho OOPbạn bè là trình giữ chỗ có 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);

H: Vậy bây giờ, giữ chỗ không tên là gì và làm cách nào để sử dụng chúng?
A. Hãy có 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 một chủ sở hữu 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 hiện câu lệnh. Trong ví dụ thứ hai, phần tử mảng thứ nhất chuyển sang phần thứ nhất ?và phần thứ hai sang phần thứ hai? .

LƯU Ý : Trong các phần giữ chỗ chưa được đặt tên, chúng ta phải quan tâm đến thứ tự đúng của các phần tử trong mảng mà chúng ta đang truyề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. Kiểm tra câu trả lời Các câu lệnh được chuẩn bị PDO có đủ để ngăn chặn việc tiêm 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 /*"));

15
Những gì tốt đọc ở trên nên đề cập đến: tuyên bố chuẩn bị lấy đi bất kỳ việc sử dụng có ý nghĩa của IN (...) construct.
Eugen Rieck 14/12/13

24
Câu hỏi là "Tại sao tôi không nên sử dụng các hàm mysql_ * trong PHP". Câu trả lời này, mặc dù ấn tượng và đầy đủ thông tin hữu ích, vượt quá phạm vi và như @trejder nói - 8 trong số 10 người sẽ bỏ lỡ thông tin đó đơn giản chỉ vì họ không có 4 giờ để cố gắng xử lý nó Điều này sẽ có giá trị hơn nhiều khi chia tay và được sử dụng như câu trả lời cho một số câu hỏi, chính xác hơn ,.
Alex McMillan

Persoanlly Tôi thích mysqli và PDO. Nhưng để xử lý die, tôi đã thử ngoại lệ function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();Nó hoạt động để ném ngoại lệ.
kuldeep.kamboj

bạn liệt kê Doesn't support non-blocking, asynchronous querieslà một lý do để không sử dụng mysql_ - bạn cũng nên liệt kê đó là lý do để không sử dụng PDO, vì PDO cũng không hỗ trợ điều đó. (nhưng MySQLi hỗ trợ nó)
hanshenrik

Có thể sử dụng Charset utf8mb4_unicode_ci vì tôi có một cơ sở dữ liệu đang sử dụng cái này không?
Ryan Stone

301

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

Xin 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 bị phản đối . Thấy hộp đỏ không? Tìm hiểu về các báo cáo đã chuẩn bị thay thế và sử dụng PDO hoặc MySQLi - bài viết này sẽ giúp bạn quyết định. Nếu bạn chọn PDO, đây là một hướng dẫn tốt .

Chúng ta hãy đi qua điều này, từng câu và giải thích:

  • Chúng không còn được duy trì, và chính thức bị phản đối

    Điều này có nghĩa là cộng đồng PHP đang dần bỏ hỗ trợ cho các hàm rất cũ này. Chúng có khả năng không tồn tại trong phiên bản tương lai (gần đây) của PHP! 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 xa (không phải vậy).

    MỚI! - ext / mysql hiện không còn chính thức kể từ phiên bản 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 tuyên bố đã chuẩn bị

    mysql_*tiện ích mở rộng không hỗ trợ các câu lệnh được chuẩn bị , đó là (trong số những thứ khác) là một biện pháp đối phó rất hiệu quả đối vớ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 MySQL, cho phép kẻ tấn công 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ể trên cơ sở dữ liệu của bạn.

    Để biết thêm thông tin, hãy xem Làm thế nào tôi có thể ngăn chặn SQL SQL trong PHP?

  • Thấy hộp đỏ không?

    Khi bạn đi đến 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 nó không nên được sử dụng nữa.

  • Sử dụng PDO hoặc MySQLi

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


6
Có một điều nữa: tôi nghĩ rằng chức năng đó vẫn tồn tại trong PHP chỉ vì một lý do - khả năng tương thích với các hệ thống CMS, thương mại điện tử, bảng thông báo cũ, v.v. Cuối cùng, nó sẽ bị xóa và bạn sẽ phải viết lại ứng dụng ...
Kamil

4
@Kamil: Điều đó đúng, nhưng đó không thực sự là lý do tại sao bạn không nên sử dụng nó. Lý do không sử dụng nó là vì nó cổ xưa, không an toàn, v.v. :)
Madara's Ghost

4
@Mario - Các nhà phát triển PHP có một quy trình và họ vừa bỏ phiếu ủng hộ chính thức loại bỏ ext / mysql vào ngày 5.5. Nó không còn là một vấn đề giả định.
SDC

2
Thêm một vài dòng bổ sung với một kỹ thuật đã được chứng minh như PDO hoặc MySQLi vẫn cho thấy sự dễ sử dụng mà PHP luôn cung cấp. Tôi hy vọng vì lợi ích của nhà phát triển, anh ấy / cô ấy biết rằng việc nhìn thấy các hàm mysql_ * thần thánh này trong bất kỳ hướng dẫn nào thực sự làm mất bài học, và nên nói với OP rằng loại mã này rất ít 10 năm trước - và nên đặt câu hỏi cho sự liên quan của hướng dẫn, quá!
FredTheWebGuy

1
Những gì câu trả lời nên đề cập một cách có chủ đích: tuyên bố chuẩn bị lấy đi bất kỳ việc sử dụng có ý nghĩa của IN (...) construct.
Eugen Rieck 14/12/13

217

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.

API cơ sở dữ liệu hiện đại chỉ dễ sử dụng hơ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 tuyệt vời (như đã thấy ở trên) , quá trình chuyển đổi sang PDO không quá khó khăn.

Viết lại một cơ sở mã lớn hơn cùng một lúc tuy nhiên cần có thời gian. Raison d'être cho sự thay thế trung gian này:

Các hàm pdo_ * tương đương thay cho mysql_ *

Sử dụng < pdo_mysql.php > bạn có thể chuyển đổi từ các hàm mysql_ cũ với nỗ lực tối thiểu . Nó thêm pdo_các hàm bao hàm thay thế các mysql_đối tác của chúng.

  1. Đơn giản là trong mỗi tập 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ố chức năng ở 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à vẫn trông 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ờ là lúc để thực sự sử dụng nó.

Thông số giới hạn 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 tham số hàm được phân cách bằng dấu phẩy vào pdo_query().
  • Đặt dấu hỏi ?là giữ chỗ nơi các biến trước đó.
  • Loại bỏ các 'trích dẫn đơn mà trước đó bao gồm các giá trị / biến chuỗi.

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

Các biến chuỗi thường không được nội suy vào SQL, nhưng được nối với các lệnh gọi ở 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 ?giữ chỗ được áp dụng, bạn không phải bận tâm về đ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 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 sự đằng sau nó.
  • Do đó cũng cho phép :nameddanh sách giữ chỗ sau này.

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

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

Thật đơn giản. Nhưng hãy quay trở 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 thoát khỏi mysql_và trốn thoát.

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

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ố bị ràng buộc, hãy xóa tất cả các phần thừapdo_real_escape_string cuộc gọi .

Đặc biệt bạn nên sửa chữa bất kỳ sanitizehoặc cleanhoặc filterThishoặc clean_datachức năng như đã quảng cáo bằng cách hướng dẫn hẹn hò trong một hình thức này hay cách khác:

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

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

  • Thứ tự đúng sẽ là: không dùng stripslashesnhư cuộc gọi trong cùng, sau đó trim, sau đó strip_tags, htmlentitiescho bối cảnh đầu ra, và cuối cùng _escape_stringlà ứng dụng của nó sẽ trực tiếp dẫn đến giao diện SQL.

  • 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 luồng cơ sở dữ liệu và ứng dụng của bạn mong đợi các chuỗi an toàn theo ngữ cảnh HTML. Thêm một nhận xét rằng nó chỉ áp dụng HTML thoát.

  • 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 stripslashes()trong chức năng khử trùng của bạn, nó có thể cho thấy sự giám sát ở cấp độ cao hơn.

    Ghi chú lịch sử trên magic_quotes. Tính năng đó bị phản đối đúng. Nó thường được mô tả không chính xác là bảo mật thất bại tính năng . Nhưng Magic_quotes cũng là một tính năng bảo mật thất bại như những quả bóng tennis đã thất bại 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 "các trích dẫn sẽ được tự động thoát ra giúp việc truyền dữ liệu biểu mẫu trực tiếp đến các truy vấn msql " dễ dàng hơn . Đáng chú ý là nó an toàn khi sử dụng với mQuery , vì chỉ hỗ trợ ASCII.
    Sau đó, PHP3 / Zend giới thiệu lại Magic_quotes cho MySQL và viết sai nó. 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.

Báo cáo 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ó sẽ không phức tạp hơn để bạn theo dõi. Đây cũng là nỗ lực không ngừng của MySQL để phân tách mã và dữ liệu một lần nữa.

Việc tiêm SQL đơn giản là khi dữ liệu chảy vào mã bối cảnh . Một máy chủ cơ sở dữ liệu sau đó không thể phát hiện ra nơi PHP ban đầu dán các biến 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à giá trị ngữ cảnh SQL trong mã PHP của bạn. Nhưng nó không bị xáo trộn một lần nữa trong 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 được cung cấp 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 những lợi thế dễ đọc của việc bỏ mysql_ . Thỉnh thoảng cũng có một lợi thế về hiệu suất (lặp lại INSERT với chỉ các giá trị khác nhau) do sự phân tách dữ liệu / mã kỹ thuật và hiển thị này.

Coi chừng ràng buộc 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ể nhận dạng tên / bảng định danh cột trắng, giúp xây dựng mệnh đề động hoặc chỉ liệt kê các giá trị mảng đơn giản.

Sử dụng lai PDO

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

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à một truy xuất mảng trực tiếp và đầy đủ:

$result->fetchAll();

Bạn sẽ nhận được nhiều cảnh báo hữu ích hơn trong hầu hết các trường hợp so với PDO hoặc mysql_ thường cung cấp sau khi 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 để bỏ mysql_.

Chỉ cần chuyển sang không hoàn toàn cắt nó. pdo_query()cũng chỉ là một lối vào nó.

Trừ khi bạn cũng giới thiệu ràng buộc tham số hoặc có thể sử dụng một cái 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 làm nản lòng người mới. (Giáo dục thường hoạt động tốt hơn cấm.)

Mặc dù nó đủ điều kiện cho loại đơn giản nhất có thể hoạt động, nhưng nó vẫn là mã rất thử nghiệm. Tôi chỉ 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 trừu tượng hóa cơ sở dữ liệu PHP và duyệt một chút. Luôn luôn có và sẽ có rất nhiều thư viện tuyệt vời cho các nhiệm vụ như vậy.

Nếu bạn muốn đơn giản hóa tương tác cơ sở dữ liệu của mình hơn nữa, những người 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 giữ một giao diện cơ sở dữ liệu thô.


8
Hãy cẩn thận với pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);chức năng - tức là:pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');
rickyduck

@Tom Chắc chắn, mặc dù nó không được duy trì nhiều (0.9.2 là lần cuối cùng), bạn có thể tạo một tài khoản hóa thạch , thêm vào wiki hoặc gửi báo cáo lỗi (không cần đăng ký IIRC).
mario

pdo_real_escape_string() <- Đây có phải là một chức năng thực sự không, tôi không thể tìm thấy bất kỳ tài liệu nào cho nó? Xin vui lòng gửi một nguồn cho điều này.
Ryan Stone

144

Các mysql_chức năng:

  1. đã hết hạn - chúng không còn được duy trì nữa
  2. không cho phép bạn di chuyển dễ dàng đến phụ trợ cơ sở dữ liệu khác
  3. không hỗ trợ báo cáo đã 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 tiêm

18
# 2 cũng đúng như vậymysqli_
eggyal

16
công bằng mà nói, với các biến thể trong phương ngữ SQL, thậm chí PDO không cung cấp cho bạn số 2 với bất kỳ mức độ chắc chắn nào. Bạn sẽ cần một trình bao bọc ORM thích hợp cho điều đó.
SDC

các mysql_*chức năng là một vỏ vào chức năng mysqlnd cho các phiên bản PHP mới hơn. Vì vậy, ngay cả khi thư viện máy khách cũ không còn được duy trì nữa, mysqlnd vẫn được duy trì :)
hakre

Vấn đề là không có nhiều nhà cung cấp dịch vụ lưu trữ web có thể hỗ trợ phong cách thiết kế hướng đối tượng như vậy do phiên bản php lỗi thời
Raju yourPepe

@RajuGujarati vì vậy hãy tìm một máy chủ web có thể. Nếu máy chủ web của bạn không có, rất có thể chúng dễ bị tấn công trên máy chủ của chúng.
Alnitak

106

Nói về lý do kỹ thuật , chỉ có một số ít, cực kỳ cụ thể và hiếm khi được sử dụng. Nhiều khả năng bạn sẽ không bao giờ sử dụng chúng trong cuộc sống của bạn.
Có lẽ tôi quá thờ ơ, 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ộ
  • thủ tục lưu trữ trả về nhiều 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 để tránh xa phần 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 đề phi 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 hàm này với các phiên bản PHP hiện đại sẽ đưa ra các thông báo ở mức không dùng nữa. Họ chỉ đơn giản là có thể được tắt.
  • trong một tương lai xa, chúng có thể được gỡ bỏ 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 hoster sẽ vui lòng biên dịch PHP với nó, vì họ không muốn mất các khách hàng có trang web hoạt động trong nhiều thập kỷ.
  • sức đề kháng mạnh mẽ từ cộng đồng Stackoverflow. Trong khi bạn đề cập đến các 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 là dễ bị lỗi và sai. Chỉ vì tất cả những hướng dẫn và hướng dẫn sử dụng này dạy bạn sai cách. Không phải chính các chức năng - tôi phải nhấn mạnh nó - nhưng cách chúng được sử dụng.

Vấn đề sau này là một vấn đề.
Nhưng, theo tôi, giải pháp đề xuất cũng không tốt hơn.
Đối với tôi, dường như quá lý tưởng một giấc mơ 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 cùng một lúc. Nhiều khả năng họ sẽ chỉ thay đổi mysql_ * thành mysqli_ * một cách máy móc, để lại cách tiếp cận như vậy . Đặc biệt bởi vì mysqli làm cho các tuyên bố chuẩn bị sử dụng đau đớn và rắc rối đáng kinh ngạc.
Chưa kể rằng các câu lệnh được chuẩn bị sẵn không đủ để bảo vệ khỏi các lệnh SQL và cả mysqli và PDO đều không cung cấp giải pháp.

Vì vậy, thay vì chống lại sự 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 đáng kể, như

  • Không hỗ trợ Thủ tục lưu trữ (chúng tôi đã sử dụng mysql_query("CALL my_proc");từ lâu)
  • Không hỗ trợ Giao dịch (giống như trên)
  • Không hỗ trợ nhiều Báo cáo (ai cần chúng?)
  • Không được phát triển tích cực (vậy điều gì? Nó có ảnh hưởng đến bạn theo cách thực tế nào không?)
  • Thiếu giao diện OO (để tạo giao diện chỉ mất vài giờ)
  • Không hỗ trợ Báo cáo đã chuẩn bị hoặc Truy vấn tham số

Điểm 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 cho sự an toàn. Chúng tôi có thể dễ dàng giả mạo các báo cáo được 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);

voila , 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 hộp màu đỏ trong hướng dẫn sử dụng, một vấn đề về sự 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à tìm kiếm API để tạo một lớp, 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 tiễn 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à 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ố đó làm cho PDO được phân biệt rõ ràng với mysqli:

    • Không giống như mysqli, PDO có thể liên kết 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 trở nên khả thi mà không cần một vài 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ể làm điều đó khi 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 tiết kiệm cho mình rất nhiều vấn đề đau đầu khi sử dụng các câu lệnh được chuẩn bị sẵn, thì PDO - một lần nữa - là lựa chọn duy nhất.
Tuy nhiên, PDO cũng không phải là 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ó các định danh động không thể bị ràng buộc bằng cách sử dụng các câu lệnh được chuẩn bị. Có các truy vấn động với một số lượng tham số không xác định khiến 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 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, sẽ thực hiện tất cả các công việc bẩn thỉu là ràng buộc, lặp, xử lý lỗi, v.v. bên trong, làm cho mã ứng dụng DRY sạch sẽ. Đặc biệt đố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ỉ cần chuyển sang PDO hoặc mysqli là không đủ. Người ta phải sử dụng ORM hoặc trình xây dựng truy vấn hoặc bất kỳ lớp trừu tượng hóa cơ sở dữ liệu nào thay vì gọi các hàm API thô trong mã của họ.
Và ngược lại - nếu bạn có một lớp trừu tượng giữa mã ứng dụng và API mysql - thì thực sự không có vấn đề gì được sử dụng công cụ nào. Bạn có thể sử dụng ext mys mys cho đến khi nó không 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, có tất cả các mã ứng dụng còn nguyên vẹn.

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

$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 duy nhất 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 mà bạn sẽ cần với các câu lệnh được chuẩn bị Mysqli thô. Lưu ý rằng xử lý lỗi, định hình, 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 sáu đến mười lần - trong tất cả các vị trí giữ chỗ, liên kết và định nghĩa truy vấn đượ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à là lớp trừu tượng, không chỉ hữu ích cho các ví dụ ngớ ngẩn từ hướng dẫn sử dụng mới bắt đầu mà còn để giải quyết bất kỳ vấn đề thực tế nào.


20
mysql_*làm cho lỗ hổng rất dễ xảy ra. Vì PHP được sử dụng bởi rất nhiều người dùng mới làm quen, nên mysql_*có hại trong thực tế, ngay cả khi trên lý thuyết, nó có thể được sử dụng mà không gặp trở ngại nào.
Madara's Ghost

4
everything is parameterized and safe- nó có thể được tham số hóa, nhưng chức năng của bạn không sử dụng các câu lệnh được chuẩn bị thực sự .
uınbɐɥs

6
Làm thế nào Not under active developmentchỉ dành cho việc tạo nên '0,01%'? Nếu bạn xây dựng một cái gì đó với chức năng đứng yên này, hãy cập nhật phiên bản mysql của bạn trong một năm và kết nối với một hệ thống không hoạt động, tôi chắc chắn có rất nhiều người đột nhiên trong đó '0,01%'. Tôi muốn nói điều đó deprecatednot under active developmentcó liên quan chặt chẽ. Bạn có thể nói rằng "không có lý do [xứng đáng] cho nó, nhưng thực tế là khi được đưa ra một lựa chọn giữa các lựa chọn, no active developmentcó tệ như deprecatedtôi nói không?
Nanne

1
@MadaraUchiha: Bạn có thể giải thích các lỗ hổng rất dễ xảy ra không? Đặc biệt là trong trường hợp những lỗ hổng tương tự không ảnh hưởng đến PDO hoặc MySQLi ... Bởi vì tôi không biết về một lỗ hổng duy nhất mà bạn nói đến.
ircmaxell

4
@ShaquinTrifonoff: chắc chắn, nó không sử dụng các báo cáo đã chuẩn bị. Nhưng PDO cũng không , mà hầu hết mọi người khuyên dùng trên MySQLi. Vì vậy, tôi không chắc chắn rằng có một tác động đáng kể ở đây. Đoạn mã trên (với một chút phân tích cú pháp) là những gì PDO làm khi bạn chuẩn bị một câu lệnh theo mặc định ...
ircmaxell

97

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

Khi sử dụng các mysql_*chức năng, bạn phải nhớ chạy các tham số do người dùng cung cấp thông qua mysql_real_escape_string(). Nếu bạn quên chỉ ở một nơi hoặc nếu bạn chỉ thoát được 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.

Sử dụng các câu lệnh được chuẩn bị trong PDOhoặc mysqlisẽ làm cho nó để các loại lỗi lập trình này khó thực hiện hơn.


3
Thật không may, hỗ trợ kém trong MySQLi_ * để truyền một số lượng tham số khác nhau (chẳng hạn như khi bạn muốn truyền danh sách các giá trị để kiểm tra trong mệnh đề IN) khuyến khích không sử dụng tham số, khuyến khích sử dụng chính xác các truy vấn được nối tương tự mà để lại các cuộc gọi MySQL_ * dễ bị tổn thương.
Khởi động

5
Nhưng, một lần nữa, sự không an toàn không phải là vấn đề cố hữu của các hàm mysql_ *, mà là vấn đề sử dụng không chính xác.
Agamemnus

2
@Agamemnus Vấn đề là mysql_ * giúp dễ dàng thực hiện "sử dụng không chính xác", đặc biệt là đối với các lập trình viên thiếu kinh nghiệm. Các thư viện triển khai các câu lệnh được chuẩn bị làm cho loại lỗi đó trở nên khó khăn hơn.
Trott

75

Bởi vì (trong số các lý do khác), việc đảm bảo dữ liệu đầu vào được vệ sinh khó khăn hơn nhiều. Nếu bạn sử dụng truy vấn tham số, như với truy vấn 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 hàm cũ sẽ cho phép thực thi nhiều câu lệnh cho mỗi truy vấn, do đó, một thứ như bugger 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 bboy-tables.com .


10
The old functions will allow executing of multiple statements per query- không, họ sẽ không. Loại tiêm đó là không thể với ext / mysql - cách duy nhất loại tiêm này có thể với PHP và MySQL là khi sử dụng MySQLi và mysqli_multi_query()hàm. Kiểu tiêm có thể xảy ra với ext / mysql và chuỗi không thoát là những thứ như ' OR '1' = '1trích xuất dữ liệu từ cơ sở dữ liệu không có nghĩa là có thể truy cập được. Trong một số trường hợp nhất định, có thể đưa ra các truy vấn phụ, tuy nhiên vẫn không thể sửa đổi cơ sở dữ liệu theo cách này.
DaveRandom

64

Câu trả lời này được viết để chỉ ra mức độ tầm thường của việc bỏ qua mã xác thực người dùng PHP được viết kém, cách thức (và sử dụng cái gì) các cuộc tấn công này hoạt động và cách thay thế các chức năng 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ể có 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, xin 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)

Khi đã xong, 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 cho quản trị viên trên trang web (được đơn giản hóa nhưng hoạt động nếu bạn sao chép và sử dụng tập lệnh đó để kiểm tra):

<?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>

Có vẻ đủ hợp pháp từ cái nhìn đầu tiên.

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

Rực rỡ, không nhập vào như sau:

user: bob
pass: somePass

và gửi nó.

Đầu ra 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!

Vâng, không thực sự. Hãy nói rằng người dùng là một người nhỏ bé thông minh. Hãy nói người đó là tôi.

Nhập vào như 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 chỉ cho phép tôi nhập phần quản trị viên siêu bảo vệ của bạn với 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 với mã tôi đã cung cấp và chạy mã PHP này - cái nhìn thoáng qua THỰC SỰ có vẻ 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 VÀO.

Vì vậy, hãy xem xét những gì đã sai, và tại sao tôi chỉ vào hang siêu người 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 chúng trực tiếp đến cơ sở dữ liệu. Tôi đã xây dựng đầu vào theo cách tht sẽ THAY ĐỔI truy vấn mà bạn đang thực sự chạy. Vì vậy, nó được cho là gì, và cuối cùng nó là gì?

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

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

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

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

Tuy nhiên, đây không phải là về việc mọi người la hét với bạn bây giờ, đây là về 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à làm thế nào chúng ta có thể sửa chữa nó?

Đây là một cuộc tấn công tiêm SQL cổ điển. Một trong những đơn giản nhất cho vấn đề đó. Trên quy mô của các vectơ tấn công, đây là một đứa trẻ đang 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 các hàm thực sự cũ và không 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ũ, nó đã lỗi thời và chỉ trong vài phút, tôi đã vượt qua nó mà không đổ mồ hôi nhiều.

Bây giờ, bạn có các tùy chọn tốt hơn để 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ó pro và con, nhưng cá nhân tôi thấy rằng pro vượt xa con lừa. Nó 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 bất cứ thứ gì đẫm máu - 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 hay và sạch sẽ. Tôi thích sạch sẽ.

Bây giờ, hãy xem lại 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ả đều đượ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 được chuẩn bị. Bây giờ, những gì một tuyên bố trước bạn yêu cầu? Đó là một cách để nói với cơ sở dữ liệu trước khi chạy truy vấn, chúng ta sẽ chạy truy vấn nào. 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 chọn id, userid và truyền từ 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ó hiện đang mong đợi.

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

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: Kiểm tra đã 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 chap 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ư sau:

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

Đây là lý do tại sao bạn bị mắng 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ể được bỏ qua ngay cả khi đang thử. Xin 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 nó 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 là đây là mã HOÀN HẢO. Có rất nhiều điều bạn có thể làm để cải thiện nó, ví dụ sử dụng mật khẩu băm, đảm bảo rằng khi bạn lưu trữ thông tin nhạy cảm trong cơ sở dữ liệu, bạn không lưu trữ nó trong 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ị tiêm cũ của mình thành mã này, bạn sẽ RẤT NHIỀU cách viết mã tốt - và thực tế là bạn đã nhận đượ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ỉ thực hiện loại này mã khi viết 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 mà hầu như không hoạt động.


2
Cảm ơn bạn vì câu trả lời! Có +1 của tôi! Điều đáng chú ý là mysql_*bản thân nó không an toàn, nhưng nó thúc đẩy mã không an toàn thông qua các hướng dẫn xấu và thiếu API chuẩn bị tuyên bố thích hợp.
Madara's Ghost

2
mật khẩu chưa được xóa, oh kinh dị! = oP Nếu không +1 để giải thích chi tiết.
mật mã ツ

33

Phần mở rộng MySQL là phần mở rộng lâu đời nhất trong ba phần 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 bị phản đốihai lựa chọn thay thế khác vì những cải tiến được thực hiện trong các bản phát hành mới hơn của cả PHP và MySQL.

  • MySQLi là phần mở rộng 'được 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 đối tượng chức năng và hướng đối tượng cho nhà phát triển và một vài điều 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 đã được trải rộng trước đó trên các phần mở rộng truy cập cơ sở dữ liệu chính, như MySQL, PostgreQuery, 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 đi vào PDO và nó được coi là phương pháp làm việc phù hợp với cơ sở dữ liệu theo mã chuyên nghiệp, hiện đại.


21

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

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

  • Giao diện hướng đối tượng
  • Hỗ trợ cho Báo cáo đã chuẩn bị
  • Hỗ trợ cho nhiều báo cáo
  • Hỗ trợ giao dịch
  • Khả năng sửa lỗi nâng cao
  • 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 (Đối tượng dữ liệu PHP).

  • API hỗ trợ Báo cáo được chuẩn bị phía máy chủ: Được hỗ trợ bởi MYSQLi và PDO
  • API hỗ trợ Báo cáo được chuẩn bị phía khách hàng: Chỉ được PDO hỗ trợ
  • API hỗ trợ các thủ tục lưu trữ: Cả MySQLi và PDO
  • API hỗ trợ nhiều Báo cáo và tất cả chức năng của MySQL 4.1+ - Được hỗ trợ bởi MySQLi và chủ yếu là bởi 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 dùng nữa trong các phiên bản sau.


2
Câu trả lời của bạn quá dài, trong khi tóm tắt thực sự là "mysql ext không còn nữa". Đó là tất cả
Ý thức chung của bạn

1
@YourCommonSense Câu trả lời của tôi là tại sao mysqli thay thế mysql. Vấn đề là không nói Mysqli tồn tại đến ngày hôm nay vì vậy hãy sử dụng nó .. Mọi người đều biết điều đó!
Ani Menon

1
Chà, ngoài thực tế là không ai hỏi tại sao mysqli thay thế mysql, nó cũng không trả lời câu hỏi này. Nó trả lời tại sao mysqli được giới thiệu. Nhưng nó không giải thích được tại sao mysql và mysqli không được phép sống song song
Ý thức chung của bạn

@YourCommonSense Ngoài ra, câu hỏi của OP là "Tại sao tôi nên sử dụng cái gì khác ngay cả khi chúng hoạt động trên trang web của tôi?" và đó là lý do tôi chỉ ra những thay đổi và cải tiến. Bạn có thể xem tất cả các câu trả lời khác mà họ còn dài vì vậy tôi nghĩ tôi nên tóm tắt nó.
Ani Menon

6

Có thể định nghĩa gần như tất cả các mysql_*chức năng bằng mysqli hoặc PDO. Chỉ cần đưa chúng lên trên ứ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;
}

Thay vào đó để hiển thị liên kết cho giải pháp của bạn, vui lòng thêm chúng ở đây dưới dạng câu trả lời.
amarnath

1

Các hàm tương tự như thế này mysql_connect(), mysql_query()loại là các phiên bản trước đó là các hàm PHP tức là (PHP 4) và hiện không được sử dụng.

Chúng được thay thế bởi mysqli_connect(), mysqli_query()tương tự trong PHP5 mới nhất.

Đây là lý do đằng sau lỗi.


2
PHP 5 đã không được mới nhất trong hơn 2 năm nay.
Madara's Ghost

1

MySQL không dùng nữa trong PHP 5.5.0 và bị xóa trong PHP 7.0.0. Đối với một ứng dụng lớn và cũ, điều này rất khó để tìm kiếm và thay thế từng chức năng.

Chúng ta có thể sử dụng các hàm MySQL bằng cách tạo một hàm bao cho mỗi bên dưới đang chạy mã. Bấm vào đây


-9

Các hàm mysql_ * không được dùng nữa (kể từ PHP 5.5 ) do thực tế là các hàm và cấu trúc mã tốt hơn đã được phát triển. Thực tế là chức năng này không được dùng nữa có nghĩa là sẽ không còn nỗ lực nào để cải thiện nó về hiệu suất và bảo mật, điều đó có nghĩa là nó ít bằng chứng trong tương lai .

Nếu bạn cần thêm lý do:

  • Các hàm mysql_ * không hỗ trợ các câu lệnh được chuẩn bị.
  • Các hàm mysql_ * không hỗ trợ liên kết các tham số.
  • Các hàm mysql_ * thiếu chức năng cho Lập trình hướng đối tượng.
  • Danh sách cứ kéo dài ...

18
Câu trả lời này đã lỗi thời. Bên cạnh đó, nó không thêm bất cứ điều gì hữu ích vào các câu trả lời đã tồn tại.
Ý 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.