萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

MySQL "Server has gone away" 徹底排查與解決方案

錯誤現象與根因分類

ERROR 2006 (HY000): MySQL server has gone away 或 ERROR 2013 (HY000): Lost connection to MySQL server during query 表示客戶端與服務器連接意外中斷。核心原因三類:

  1. 連接超時:空閒超過 wait_timeout/interactive_timeout
  2. 數據包過大:SQL/Blob 超過 max_allowed_packet
  3. 服務端異常:OOM Killer、Crash、主動殺進程、網絡中斷

場景 1:長連接/批量任務導致空閒超時

典型場景:PHP/Python/Java 長時間運行腳本、ETL 作業、數據遷移,中間間隔超過 8 小時 (默認 wait_timeout=28800) 無交互。

解決方案

-- 1. 服務端調大超時 (持久化生效,需重啟)
# /etc/my.cnf
[mysqld]
wait_timeout = 86400          # 24 小時
interactive_timeout = 86400   # 交互式客戶端

-- 2. 客戶端連接時指定 CLIENT_INTERACTIVE (臨時生效)
# PHP PDO
$pdo = new PDO("mysql:host=127.0.0.1;dbname=test", "user", "pass", [
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET SESSION wait_timeout=86400",
    PDO::ATTR_PERSISTENT => true  # 或用持久連接
]);

# Python MySQLdb
conn = MySQLdb.connect(host='127.0.0.1', user='user', passwd='pass', db='test',
                       connect_timeout=10, read_timeout=86400, write_timeout=86400,
                       client_flag=MySQLdb.constants.CLIENT.INTERACTIVE)

# Java HikariCP
dataSource.setConnectionInitSql("SET SESSION wait_timeout=86400");
dataSource.setMaximumPoolSize(20);
dataSource.setMinimumIdle(5);
dataSource.setIdleTimeout(600000);  # 10 分鐘空閒回收,避免撞服務端超時

連接池最佳實踐

  • 啟用 testOnBorrow / validationQuery: SELECT 1,借連接前心跳檢測
  • 設置 maxLifetime < 服務端 wait_timeout,主動回收老連接
  • 關閉 autoReconnect=true (JDBC 老版本),改用連接池自帶重試邏輯

場景 2:大 SQL / Blob 字段超過 max_allowed_packet

典型場景:批量 INSERT 巨大 SQL、存儲 Base64 圖片/文件、mysqldump 導出含大字段、LOAD DATA 導入大行。

解決方案

-- 1. 服務端調大 (持久化,需重啟)
# /etc/my.cnf
[mysqld]
max_allowed_packet = 256M       # 視業務最大 Blob 大小而定,建議 64M-1G

[client]
max_allowed_packet = 256M       # mysqldump/mysql 客戶端也要跟著調大

-- 2. 會話級臨時調大 (無需重啟,僅當前連接)
SET SESSION max_allowed_packet = 256M;

-- 3. 應用層拆分大 SQL
# 批量 INSERT 分批,每批 1000-5000 行
# 大 Blob 分塊寫入或存對象存儲 (S3/OSS) 只存 URL

mysqldump 導出導入避坑

# 導出:避免 --opt 產生超大 INSERT (默認開啟 --extended-insert)
mysqldump -uroot -p --single-transaction --quick --max_allowed_packet=256M \
  --net_buffer_length=16384 db_name > dump.sql

# 導入:調大客戶端 packet
mysql -uroot -p --max_allowed_packet=256M db_name < dump.sql

# 或分批導入
split -l 10000 dump.sql chunk_
for f in chunk_*; do mysql -uroot -p db_name < $f; done

場景 3:服務端異常 (OOM/Crash/主動 Kill)

排查步驟

  1. 檢查錯誤日志:grep -i -e "killed" -e "out of memory" -e "assertion" -e "signal" /var/log/mysql/error.log
  2. 檢查系統日志:dmesg -T | grep -i -e mysql -e oom -e kill 看是否被 OOM Killer 殺死
  3. 檢查慢查詢/死鎖:pt-query-digest slow.log、SHOW ENGINE INNODB STATUS
  4. 檢查連接數爆發:SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW PROCESSLIST;

常見服務端故障對策

  • 調整 LB idle_timeout > wait_timeout;啟用 TCP Keepalive
  • 故障類型 表徵 解決方案
    OOM Killer dmesg 顯示 Out of memory: Kill process mysqld 降低 innodb_buffer_pool_size、增加 Swap、限制 max_connections、升級內存
    InnoDB Assertion/Crash error.log 有 assertion failure / segmentation fault 升級 MySQL 版本、檢查硬件 (ECC 內存)、innodb_force_recovery 恢復數據
    主動 KILL error.log: "Killed by user root" 審計誰執行了 KILL,限制 SUPER 權限
    網絡中斷 防火牆/交換機/負載均衡超時斷開長連接
    磁盤滿/IO 凍結 df -h 顯示 100%、iostat %util 100% 清理 Binlog、擴容磁盤、優化慢查詢減少 IOPS

    應用層重試與熔斷模式

    # Python 示例:指數退避重試 + 熔斷
    import time, random, pymysql
    from contextlib import contextmanager
    
    class CircuitBreaker:
        def __init__(self, failure_threshold=5, recovery_timeout=30):
            self.failures = 0
            self.threshold = failure_threshold
            self.recovery_timeout = recovery_timeout
            self.last_failure = 0
            self.open = False
        
        def call(self, func, *args, **kwargs):
            if self.open and time.time() - self.last_failure > self.recovery_timeout:
                self.open = False
                self.failures = 0
            if self.open:
                raise Exception("Circuit breaker open")
            try:
                result = func(*args, **kwargs)
                self.failures = 0
                return result
            except (pymysql.err.OperationalError, pymysql.err.InterfaceError) as e:
                if e.args[0] in (2006, 2013, 2055):  # Gone away / Lost connection
                    self.failures += 1
                    self.last_failure = time.time()
                    if self.failures >= self.threshold:
                        self.open = True
                raise
    
    @contextmanager
    def mysql_retry(max_retries=3, base_delay=0.5):
        for attempt in range(max_retries):
            try:
                yield
                return
            except (pymysql.err.OperationalError, pymysql.err.InterfaceError) as e:
                if e.args[0] not in (2006, 2013, 2055) or attempt == max_retries - 1:
                    raise
                delay = base_delay * (2 ** attempt) + random.uniform(0, 0.1)
                time.sleep(delay)
                # 重新獲取連接 (連接池會自動處理)
        raise Exception("Max retries exceeded")

    預防性配置清單

    [mysqld]
    # 連接管理
    wait_timeout = 86400
    interactive_timeout = 86400
    max_connections = 2000
    max_connect_errors = 100000
    thread_cache_size = 100
    
    # 數據包
    max_allowed_packet = 256M
    net_buffer_length = 16384
    net_read_timeout = 60
    net_write_timeout = 60
    
    # 穩定性
    innodb_buffer_pool_size = 70%_RAM
    innodb_log_file_size = 2G
    innodb_flush_log_at_trx_commit = 1
    sync_binlog = 1
    innodb_deadlock_detect = ON
    
    # 監控
    slow_query_log = 1
    long_query_time = 1
    log_queries_not_using_indexes = 0
    log_error = /var/log/mysql/error.log
    log_error_verbosity = 3

    核心原則:客戶端主動管理連接生命周期 (連接池 + 心跳 + 重試),服務端配置足夠緩衝 (timeout/packet/buffer),監控覆蓋錯誤日志/系統日志/慢查詢/連接數,做到早發現、快恢復、不丟數據。

    copyright © 萬盛學電腦網 all rights reserved