今天在利用mysql.connector包,向mysql插入大量随机数据时,出现以下报错:
关键信息:
mysql.connector.errors.OperationalError: 2013 (HY000): Lost connection to MySQL server during query
初步判断和超时有关系,查询mysql上关于超时的配置:
show global variables like '%timeout';
其中有个关键变量net_write_timeout和net_read_timeout
net_read_timeout和net_write_timeout这个参数只对TCP/IP链接有效,分别是数据库等待接收客户端发送网络包和发送网络包给客户端的超时时间,其余参数讲解可参考此篇优秀博客:深入理解Mysql 超时配置项 - 掘金 (juejin.cn)
这里由于我是写数据,所以优先关心net_write_timeout,发现值只有60,那么试着将其值调大
set global net_write_timeout = 9000;
再次执行python批量插入脚本,还是出错,那么再以同样方法调大net_read_timeout的值,还是错误。查看日志发现:Got a packet bigger than 'max_allowed_packet' bytes
查看一下max_allowed_packet的值为4194304 Bit约为4M:
mysql> show global variables like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| max_allowed_packet | 4194304 |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
该值的意义是控制单个 SQL 语句或查询的最大大小的参数。它限制了服务器接收或发送的单个数据包的最大字节数 ,而我在python脚本中需要插入40w的数据
import uuid
import mysql.connector
from faker import Faker
if __name__ == '__main__':
print('开始执行')
# 创建连接
conn = mysql.connector.connect(
host="ambari-hadoop1",
user="root",
password="123456",
database="Test"
)
# 创建游标
cursor = conn.cursor()
# 批量插入数据
fake = Faker()
insert_query = "INSERT INTO Test.Test1 (Column1, Column2, Column3) VALUES (%s, %s, %s)"
data_to_insert = [(str(uuid.uuid4()), fake.word(), fake.word()) for _ in range(400000)]
cursor.executemany(insert_query, data_to_insert)
# 提交更改
conn.commit()
# 关闭连接
cursor.close()
conn.close()
所以问题的根本在于:使用了 executemany
方法批量插入大量的数据,这可能导致单个 SQL 语句的大小超过了 MySQL 服务器的 max_allowed_packet
。
第一种解决办法:调大max_allowed_packet的值,但是这个值越大越好吗?显然不是,过大的max_allowed_packet值会占用更多的网络带宽意味着单个查询可能需要更多的内存。结合资源、带宽和批量插入数据的实际情况,设置值为max_allowed_packet=20M,在my.cnf中添加:
重启服务生效。
第二种方法:分批插入数据 ,修改后的脚本如下:
import uuid
import mysql.connector
from faker import Faker
def chunked_insert(cursor, data, chunk_size=1000):
for i in range(0, len(data), chunk_size):
chunk = data[i:i+chunk_size]
print(i)
cursor.executemany(insert_query, chunk)
conn.commit()
if __name__ == '__main__':
print('开始执行')
# 创建连接
conn = mysql.connector.connect(
host="ambari-hadoop1",
user="root",
password="123456",
database="Test"
)
# 创建游标
cursor = conn.cursor()
# 批量插入数据
fake = Faker()
insert_query = "INSERT INTO Test.Test1 (Column1, Column2, Column3) VALUES (%s, %s, %s)"
data_to_insert = [(str(uuid.uuid4()), fake.word(), fake.word()) for _ in range(400000)]
# 分批插入数据
chunked_insert(cursor, data_to_insert)
# 关闭连接
cursor.close()
conn.close()
同时使用以上两种解决办法,成功插入40w数据