Cách sửa lỗi '2006 MySQL Server Has Gone Away' trong PHP PDO

intermediate🐘 PHP2026-05-28| PHP 7.4/8.x, MySQL 5.7/8.0, MariaDB, Linux (Ubuntu/CentOS), Docker

Error Message

PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
#php#pdo#mysql#cơ sở dữ liệu#kết nối#timeout

Vấn đề: Ngắt kết nối đột ngộtBạn đang chạy một cron job lúc 2 giờ sáng để đồng bộ hàng nghìn mặt hàng tồn kho, và giữa chừng, quá trình này bị dừng lại. Nhật ký (logs) của bạn hiển thị lỗi 2006 MySQL server has gone away khét tiếng. Điều này gây nhầm lẫn vì cơ sở dữ liệu vẫn đang chạy và bạn có thể đăng nhập qua terminal mà không gặp vấn đề gì. Về cơ bản, máy chủ MySQL đã "đóng cửa" với script của bạn, nhưng mã PHP vẫn cố gắng sử dụng một trình xử lý kết nối (connection handle) đã hết hạn.

Tại sao máy chủ lại ngắt kết nối?MySQL thường chấm dứt kết nối vì ba lý do cụ thể. Thứ nhất, truy vấn của bạn có thể quá lớn. Nếu bạn cố gắng gửi một tệp PDF 50MB đến máy chủ có giới hạn max_allowed_packet là 16MB, máy chủ sẽ ngắt liên kết ngay lập tức.

Thứ hai, script của bạn có thể quá chậm giữa các lần gọi cơ sở dữ liệu. Nếu mã PHP mất 20 phút để xử lý dữ liệu trước truy vấn tiếp theo, wait_timeout của MySQL sẽ hết hạn và đóng kết nối rảnh (idle connection). Cuối cùng, bản thân dịch vụ MySQL có thể đã khởi động lại hoặc bị trình Out of Memory (OOM) killer của Linux chấm dứt do sử dụng RAM quá cao.

Sửa lỗi bằng cấu hình: Tinh chỉnh hệ thốngCách nhanh nhất để xử lý các lần nhập dữ liệu lớn là tăng kích thước gói tin (packet size) và giới hạn thời gian chờ trong tệp my.cnf hoặc my.ini. Các thiết lập này xác định kích thước dữ liệu tối đa và thời gian rảnh mà máy chủ chấp nhận.

1. Tăng max_allowed_packetNếu bạn thực hiện các lệnh insert hàng loạt—ví dụ 5.000 dòng trong một câu lệnh—giới hạn mặc định 16MB thường là không đủ. Hãy nâng con số này lên 128MB hoặc 256MB để có thêm không gian.

# Nằm trong /etc/mysql/my.cnf hoặc /etc/my.cnf
[mysqld]
max_allowed_packet = 128M

2. Kéo dài wait_timeoutĐối với các background worker thực hiện các tính toán nặng giữa các lần cập nhật cơ sở dữ liệu, hãy tăng thời gian chờ rảnh. Mặc dù mặc định thường là 28.800 giây (8 giờ), các môi trường được quản lý như AWS RDS có thể thiết lập con số này thấp hơn đáng kể.

[mysqld]
wait_timeout = 28800
interactive_timeout = 28800

Sau khi lưu các thay đổi này, hãy áp dụng chúng bằng cách khởi động lại dịch vụ:

sudo systemctl restart mysql

Giải pháp lâu dài: Logic kết nối lại trong PHPCác thay đổi cấu hình rất hữu ích, nhưng chúng không phải là vạn năng. Trên một mạng chập chờn hoặc trong các tác vụ chạy lâu, bạn phải giả định rằng kết nối cuối cùng sẽ bị ngắt. Vì PDO của PHP không tự động kết nối lại, bạn cần một wrapper để phát hiện liên kết chết và khôi phục nó.

Phương pháp 'Ping'Trước khi chạy một truy vấn quan trọng bên trong một vòng lặp, hãy kiểm tra xem kết nối có còn hoạt động hay không. Bạn có thể thực hiện việc này bằng cách thực thi một truy vấn SELECT 1 nhẹ. Nếu nó thất bại với mã lỗi 2006, hãy bắt ngoại lệ (catch exception) và khởi tạo một đối tượng PDO mới.

/**
 * Đảm bảo kết nối PDO hoạt động trước khi tiếp tục
 */
function getActiveConnection($pdo, $dsn, $user, $pass, $options) {
    try {
        $pdo->query('SELECT 1');
    } catch (PDOException $e) {
        // Kiểm tra cụ thể lỗi 'Gone Away'
        if (strpos($e->getMessage(), '2006') !== false) {
            return new PDO($dsn, $user, $pass, $options);
        }
        throw $e;
    }
    return $pdo;
}

Xử lý xử lý hàng loạtKhi lặp qua các tập dữ liệu khổng lồ, đừng duy trì một kết nối cũ nếu có sự chậm trễ lớn giữa các bước. Hãy bọc việc thực thi của bạn trong một khối try-catch để xử lý việc thử lại (retry) một cách linh hoạt.

foreach ($largeDataSet as $data) {
    try {
        $stmt = $pdo->prepare("INSERT INTO logs (message) VALUES (?)");
        $stmt->execute([$data['message']]);
    } catch (PDOException $e) {
        if ($e->errorInfo[1] == 2006) {
            // Thiết lập lại kết nối và thử lại lệnh insert cụ thể
            $pdo = new PDO($dsn, $user, $pass, $options);
            $stmt = $pdo->prepare("INSERT INTO logs (message) VALUES (?)");
            $stmt->execute([$data['message']]);
        } else {
            throw $e;
        }
    }
}

Kiểm tra cơ sở hạ tầng và môi trườngNếu lỗi vẫn còn mặc dù đã thay đổi mã, vấn đề có thể nằm ở cấp độ hệ điều hành hoặc container. Kiểm tra nhật ký lỗi MySQL bằng lệnh tail -f /var/log/mysql/error.log. Nếu bạn thấy các thông báo 'mmap can't allocate' hoặc 'Out of memory', máy chủ của bạn đang hết RAM vật lý. Trong môi trường Docker, hãy đảm bảo container của bạn có giới hạn bộ nhớ đủ lớn; MySQL 8.0 đặc biệt tiêu tốn bộ nhớ và có thể bị treo nếu bị giới hạn dưới 1GB RAM.

Xác minh: Kiểm tra giải phápĐể xác minh việc sửa lỗi max_allowed_packet, hãy thử chọn (select) một chuỗi rất lớn từ cơ sở dữ liệu. Để kiểm tra thời gian chờ, hãy đặt wait_timeout thành 5 giây trong môi trường phát triển của bạn, thêm lệnh sleep(10) vào script PHP và xác nhận rằng logic kết nối lại của bạn bắt được lỗi thành công và khôi phục phiên làm việc.

Related Error Notes