【推荐】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.
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出现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)
来源:oschina
链接:https://my.oschina.net/csp277/blog/3152478