解决mysql的gone away问题

喜你入骨 提交于 2020-01-06 21:55:31

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

背景

年底了,以年为单位的分表的数据量快达到巅峰了。最近经常收到DB相关的一些告警,经过一系列的排查,终于解决了告警,现整理如下。

问题描述:对海量数据的DB进行读写,报mysql gone away

原因分析:

mysql出现ERROR : (2006, 'MySQL server has gone away') 的问题意思就是指client和MySQL server之间的链接断开了。 这类问题主要是sql操作的时间过长,或者是传送的数据太大

1. 查看并调整timeout值

使用客户端工具或者Mysql命令行工具输入show global variables like '%timeout%';就会显示与timeout相关的属性,这里我用docker模拟了一个测试环境。

mysql> show variables like '%timeout%'; 
+-----------------------------+----------+
| Variable_name               | Value    |
+-----------------------------+----------+
| connect_timeout             | 10       |
| delayed_insert_timeout      | 300      |
| have_statement_timeout      | YES      |
| innodb_flush_log_at_timeout | 1        |
| innodb_lock_wait_timeout    | 50       |
| innodb_rollback_on_timeout  | OFF      |
| interactive_timeout         | 30       |
| lock_wait_timeout           | 31536000 |
| net_read_timeout            | 30       |
| net_write_timeout           | 60       |
| rpl_stop_slave_timeout      | 31536000 |
| slave_net_timeout           | 60       |
| wait_timeout                | 30       |
+-----------------------------+----------+
13 rows in set

wait_timeout:服务器关闭非交互连接之前等待活动的秒数,就是你在你的项目中进行程序调用

interactive_timeout: 服务器关闭交互式连接前等待活动的秒数,就是你在你的本机上打开mysql的客户端,cmd的那种

如果参数设置的不合理则进行相应的调整

2. 查看和修改传输包的最大值

Your SQL statement was too large.当查询的结果集超过 max_allowed_packet 也会出现这样的报错

show global variables like 'max_allowed_packet';
set global max_allowed_packet=1024*1024*16;

3.代码优化

如果修改了参数,还是有问题,多半是因为读写mysql的姿势不对。经过review代码,我发现存在的几个问题:

1.DB在异常的时候没有关闭;

2.读操作将几百万条数据一次性全读入内存,且写操作特别久(写操作会对DB加锁,导致其它使用者等待);

3.光标使用的不对,读写共用一个光标且使用完不关闭等问题

针对代码进行了以下修改:

1.将fetchall操作改为用SSCursor一条条读取,fetchall操作需要等待几分钟或者十几分钟

普通的操作无论是fetchall()还是fetchone()都是先将数据载入到本地再进行计算,大量的数据会导致内存资源消耗光。解决办法是使用SSCurosr光标来处理

参考:https://blog.csdn.net/jianhong1990/article/details/41209493

2.将读和写的光标分开

读写共用一个光标会带来很多问题,例如:读的光标还没有取完数据,就去写数据,会报 “Commands out of sync”异常

If you get Commands out of sync; you can't run this command now in your client code, you are calling client functions in the wrong order.This can happen, for example, if you are using mysql_use_result() and try to execute a new query before you have called mysql_free_result(). It can also happen if you try to execute two queries that return data without calling mysql_use_result() or mysql_store_result() in between.

https://stackoverflow.com/questions/8891179/mysql-error-2014-commands-out-of-sync-you-cant-run-this-command-now

3.分批次写

将写操作抽象成一个独立的接口,然后每次开启一个光标去操作写,如下:

def batch_write(db, sql, data, batch_size=20000):
    new_cursor = db.cursor(cursorclass=MySQLdb.cursors.SSCursor)
    sql1 = "set names utf8;"
    sql2 = sql
    # batch_size = 20000
    total = len(data)
    batch_times = total / batch_size
    try:
        new_cursor.execute(sql1)
        start = 0
        while batch_times > 0:
            batch_times -= 1
            if start + batch_size > total:
                break
            new_cursor.executemany(sql2, data[start:start + batch_size])
            start += batch_size
        new_cursor.executemany(sql2, data[start:total])
        db.commit()
    except MySQLdb.Error, e:
        new_cursor.close()
        print(e)
        print 'batch_write failed!', e
        db.rollback()
     

总结

1.DB的读写操作后一定要记得关闭光标和connection对象,异常场景也要确保能够关闭,否则会遇到too many connections的问题

2.DB读写操作大多是可以重复使用的,建议使用工具类进行抽象

3.建议使用连接池来处理DBUtils.PooledDB,见附录1

参考

解决mysql服务器在无操作超时主动断开连接的问题

mysql出现ERROR : (2006, 'MySQL server has gone away') 原因和解决方案

附录一

db_utils参考实现:异常处理还需要进一步优化

# -*- coding: utf-8 -*-
"""
数据库管理类
"""
import MySQLdb
from DBUtils.PooledDB import PooledDB
# 自定义的配置文件,主要包含DB的一些基本配置
from config import configs


# 数据库实例化类
class DbManager():

    def __init__(self):
        conn_kwargs = {'host': configs['DB_HOST'], 'port': configs['DB_PORT'], 'user': configs['DB_USER'],
                       'passwd': configs['DB_PASS'],
                       'db': configs['DB_NAME'], 'charset': "utf8"}
        self._pool = PooledDB(MySQLdb, mincached=0, maxcached=10, maxshared=10, maxusage=10000, **conn_kwargs)

    def getConn(self):
        return self._pool.connection()


_dbManager = DbManager()


def getConn():
    """ 获取数据库连接 """
    return _dbManager.getConn()


def executeAndGetId(sql, param=None):
    """ 执行插入语句并获取自增id """
    conn = getConn()
    cursor = conn.cursor()
    if param == None:
        cursor.execute(sql)
    else:
        cursor.execute(sql, param)
    id = cursor.lastrowid
    cursor.close()
    conn.close()

    return id


def execute(sql, param=None):
    """ 执行sql语句 """
    conn = getConn()
    cursor = conn.cursor()
    if param == None:
        rowcount = cursor.execute(sql)
    else:
        rowcount = cursor.execute(sql, param)
    cursor.close()
    conn.close()

    return rowcount


def queryOne(sql):
    """ 获取一条信息 """
    conn = getConn()
    cursor = conn.cursor(cursorclass=MySQLdb.cursors.DictCursor)
    rowcount = cursor.execute(sql)
    if rowcount > 0:
        res = cursor.fetchone()
    else:
        res = None
    cursor.close()
    conn.close()

    return res


def queryAll(sql):
    """ 获取所有信息 """
    conn = getConn()
    cursor = conn.cursor(cursorclass=MySQLdb.cursors.DictCursor)
    rowcount = cursor.execute(sql)
    if rowcount > 0:
        res = cursor.fetchall()
    else:
        res = None
    cursor.close()
    conn.close()

    return res


if __name__ == "__main__":
    res = queryAll('select * from t1 ')
    print str(res)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!