PHP - 10 lỗi phổ biến mà các lập trình viên PHP thường mắc phải (Phần 2)
Bài đăng này đã không được cập nhật trong 7 năm
Index
- PHP - 10 lỗi phổ biến mà các lập trình viên PHP thường mắc phải Part 1
- PHP - 10 lỗi phổ biến mà các lập trình viên PHP thường mắc phải Part 2
- PHP - 10 lỗi phổ biến mà các lập trình viên PHP thường mắc phải Part 3
Sai lầm thứ 4: Thực hiện truy vấn trong vòng lặp
Không hiếm khi bạn sẽ gặp đoạn code giống như thế này:
$models = [];
foreach ($inputValues as $inputValue) {
$models[] = $valueRepository->findByValue($inputValue);
}
Mặc dù hoàn toàn không có gì sai ở đây, nhưng nếu bạn để ý logic trong code, bạn có thể thấy rằng giá trị $valueRepository->findByValue()
đang được gọi một cách vô tội vạ, cuối cùng kết quả của truy vấn của một số thứ, sẽ giống như thế này:
$result = $connection->query("SELECT
x,
yFROM
valuesWHERE
value=" . $inputValue);
Kết quả là, mỗi lần lặp của vòng lặp ở trên sẽ dẫn đến một truy vấn đến cơ sở dữ liệu. Ví dụ, nếu bạn lặp một mảng có 1000 giá trị, nó sẽ tạo ra 1000 truy vấn riêng biệt cho cơ sở dữ liệu. Nếu như việc này được gọi trong nhiều threads, nó có thể làm cho hệ thống bị ngừng hoạt động. Do đó cần phải xác định khi nào các truy vấn được thực hiện trong code của bạn. Bất cứ khi nào có thể, hãy thu thập các giá trị và sau đó chạy một truy vấn để lấy ra tất cả các kết quả.
Một ví dụ về một chỗ khá phổ biến để gặp phải việc thực hiện truy vấn không hiệu quả (hay truy vấn trong vòng lặp) là với danh sách các giá trị (ví dụ ID). Sau đó, để lấy dữ liệu đầy đủ cho mỗi ID, code sẽ lặp qua array và thực hiện truy vấn SQL riêng cho mỗi ID. Giống thế này:
$data = [];
foreach ($ids as $id) {
$result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id);
$data[] = $result->fetch_row();
}
Nhưng cùng một vấn đề như vậy có thể được giải quyết bằng cách hiệu quả hơn nhiều:
$data = [];
if (count($ids)) {
$result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids));
while ($row = $result->fetch_row()) {
$data[] = $row;
}
}
Do đó, điều quan trọng để nhận ra các truy vấn đang được thực hiện, hoặc trực tiếp hoặc gián tiếp, đó là bằng code của bạn. Bất cứ khi nào có thể, hãy thu thập các giá trị sau đó chạy một truy vấn để lấy tất cả các kết quả. Tuy nhiên, phải thận trọng khi thực hiện điều đó, nó có thể dẫn chúng ta tới một sai lầm phổ biến tiếp theo.
Sai lầm thứ 5: Sử dụng bộ nhớ không hiệu quả
Trong khi fetch nhiều record cùng một lúc chắc chắn sẽ hiệu quả hơn so với việc chạy một truy vấn duy nhất cho mỗi record, cách làm này có thể dẫn đến tình trạng "out of memory" trong khi sử dụng PHP's mysql extension.
Để chứng minh, chúng ta hãy xem xét một "test box" với giới hạn bộ nhớ là (512MB RAM), MySQL, và php-cli. Chúng ta sẽ khởi tạo một bảng cơ sở dữ liệu như sau:
// connect to mysql
$connection = new mysqli('localhost', 'username', 'password', 'database');
// khởi tạo một bảng 'test' có 400 trường
$query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT';
for ($col = 0; $col < 400; $col++) {
$query .= ", `col$col` CHAR(10) NOT NULL";
}
$query .= ');';
$connection->query($query);
// insert 2 triệu record
for ($row = 0; $row < 2000000; $row++) {
$query = "INSERT INTO `test` VALUES ($row";
for ($col = 0; $col < 400; $col++) {
$query .= ', ' . mt_rand(1000000000, 9999999999);
}
$query .= ')';
$connection->query($query);
}
Chúng ta sẽ kiểm tra việc sử dụng tài nguyên:
// connect to mysql
$connection = new mysqli('localhost', 'username', 'password', 'database');
echo "Before: " . memory_get_peak_usage() . "\n";
$res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1');
echo "Limit 1: " . memory_get_peak_usage() . "\n";
$res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000');
echo "Limit 10000: " . memory_get_peak_usage() . "\n";
Kết quả Output:
Before: 224704
Limit 1: 224704
Limit 10000: 224704
Tuyệt, có vẻ như truy vấn được quản lý an toàn về bộ nhớ. Tuy nhiên, để chắc chắn chúng ta hãy tăng giới hạn một lần nữa và đặt nó lên thành 100000. Và kết quả:
PHP Warning: mysqli::query(): (HY000/2013):
Lost connection to MySQL server during query in /root/test.php on line 11
Đã có chuyện gì xảy ra ?
Vấn đề ở đây là cách mà Module Mysql của PHP hoạt động, nó thực ra chỉ là một proxy cho libmysqlclient. Khi một phần của dữ liệu được chọn, nó sẽ chuyển trực tiếp vào bộ nhớ. Vì bộ nhớ này không được quản lý bởi PHP, nên memory_get_peak_usage()
sẽ không hiển thị bất kỳ sự gia tăng về sử dụng tài nguyên nào trong truy vấn của chúng ta. Điều này dẫn đến nhưng vấn đề như đã được giải thích ở trên, nơi chúng ta bị nhầm tưởng rằng việc quản lý bộ nhớ của chúng ta là tốt rồi. Nhưng trên thực thế, việc quản lý bộ nhớ của chúng ta thiếu sót nghiêm trọng và chung ta có thể gặp những vấn đề giống như ở trên.
Bạn có thể tránh vấn đề ở trên ít nhất bằng cách, sử dụng các module mysqlnd
. mysqlnd
được biên dịch như một phần mở rộng của PHP và nó sử dụng trình quản lý bộ nhớ của PHP.
Vì vậy, nếu chúng ta chạy thử nghiệm trên bằng cách sử dụng module mysqlnd
thay vì mysql
, chúng ta sẽ có được một bức tranh thực tế hơn về việc sử dụng bộ nhớ của chúng ta.
Before: 232048
Limit 1: 324952
Limit 10000: 32572912
Theo tài liệu PHP, mysql
sử dụng gấp đôi tài nguyên so với mysqlnd
để lư trữ dữ liệu. Do đó, với ví dụ ở trên sử dụng mysql
thực sự nó đã sử dụng bộ nhớ nhiều hơn được hiển thị (khoảng gấp đôi).
Để tránh các vấn đề như vậy, hãy xem xét kích thước giới hạn của các truy vấn của bạn và sử dụng một vòng lặp với số lần lặp lại nhỏ. Ví dụ:
$totalNumberToFetch = 10000;
$portionSize = 100;
for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) {
$limitFrom = $portionSize * $i;
$res = $connection->query(
"SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize");
}
Sai lầm thứ 6: Bỏ qua các vấn đề liên quan đến Unicode/UTF-8
Trong một số trường hợp, đây thực sự là một vấn đề trong chính PHP hơn là một cái gì đó bạn sẽ chạy trong quá trình debug PHP, nhưng nó lại chưa bao giờ được giải quyết đầy đủ. Điều cốt lõi ở trong PHP 6 là làm cho có thể nhận dạng được Unicode, nhưng đáng tiếc nó đã không được phát triển tiếp do PHP 6 bị đình chỉ trong năm 2010.
Dưới đây là một danh sách kiểm tra nhỏ để tránh những vấn đề liên quan trến Unicode/UTF-8 trong code của bạn: Nếu bạn không biết nhiều về Unicode và UTF-8, ít nhất bạn nên học các điều cơ bản:
- Hãy chắc chắn luôn sử dụng các chức năng
mb_ *
thay vì các hàm xử lý chuỗi cũ (đảm bảo phần mở rộng "multibyte" được xây dựng cùng với mã code PHP của bạn). - Hãy chắc chắn rằng cơ sở dữ liệu và bảng của bạn được thiết lập để sử dụng Unicode (nhiều bản xây dựng của MySQL theo mặc định vẫn sử dụng latin1).
- Hãy nhớ rằng
json_encode ()
chuyển đổi các ký hiệu non-ASCII (ví dụ: "Schrödinger" trở thành "Schr \ u00f6dinger") nhưngserialize()
thì không. - Đảm bảo rằng các file code PHP của bạn cũng được mã hoá UTF-8 để tránh xung đột khi nối chuỗi với hằng số chuỗi được mã hoá hoặc đã được config.
Bạn có thể đọc thêm về UTF-8 Primer cho PHP và MySQ tại đây được đăng tải bởi Francisco Claria. Hẹn gặp lại các bạn trong bài viết sau, với 4 sai lầm còn lại. Bài dịch còn nhiều sai sót, mong nhận được sự ghóp ý.
Tài liệu tham khảo
https://www.toptal.com/php/10-most-common-mistakes-php-programmers-make
All rights reserved