Mysql6的auto Reconnect错误

本文探讨了在J2EE项目中遇到的问题,当服务器时间更改后,使用MySQL数据库时发生连接异常的情况。分析了MySQL连接超时机制,并介绍了proxool连接池如何通过其特有的连接检查机制解决此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在一个J2EE项目的开发过程中,遇到了这样的问题: 

在服务器上部署好这个Web系统后,这时访问系统是很正常的。当把服务器的时间(例如:2008-03-31)加一天或更多天(例如:2008-04-01,2008-04-02...),这时再访问这个Web系统,报出如下的异常:

QUOTE:
com.mysql.jdbc.CommunicationsException: Communications link failure due to underlying exception: 

** BEGIN NESTED EXCEPTION ** 

java.io.EOFException 

MESSAGE: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost. 

STACKTRACE: 

java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost. 

at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1997) 

at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2411) 

at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2916) 

at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1631) 

at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1723) 

at com.mysql.jdbc.Connection.execSQL(Connection.java:3250) 

at com.mysql.jdbc.Connection.setAutoCommit(Connection.java:5395) 

...


我们首先判断是连接池出了问题,即当系统时间改变后,数据库连接发现超时,因此需要重新连接或完全重建连接池。我们换用了一下数据库,如果使用Oracle数据库,同样的修改不会出现问题,系统运行正常,根据不会出现这样的异常。这就说明了出现这个异常是Mysql相关的,通过查阅资料得知可能和Mysql的运行参数(超时)有关,顺着这个思路我们来分析Mysql的一些运行参数。 

在MySQL Command Line Client中执行show variables like '%timeout%';如下图所示: 

在图中我们可以看到有两个变量wait_timeout和interactive-timeout,它们的默认值都为28800秒,即为8小时。也就是说默认情况下,Mysql在经过8小时(28800秒)不使用后会自动关闭已打开的连接。 

为了解决这个问题,对于MySQL5之前的版本,如Mysql4.x,只需要修改连接池配置中的URL,添加一个参数:autoReconnect=true,如果是MySQL5及以后的版本,则需要修改my.cnf(或者my.ini)文件,在[mysqld]后面添加上: 

wait_timeout = n 

interactive-timeout = n 

n为服务器关闭交互式连接前等待活动的秒数。 

可是就部署而言每次修改my.ini比较麻烦,而且n等于多少才是合适的呢?所以这个解决办法不好。 

怎么才能比较好的解决这个问题呢?通过仔细分析,原因大致为:当修改系统日期后Mysql自动关闭已打开的连接,可以数据库连接池(DBCP)还认为这些连接是活动的,如果这时有请求(需要执行读写数据库的操作),连接池就用一个连接去操作数据库,而这个连接在Mysql的连接池中并不存在,所以会出现以上的异常。如果一个连接在和Mysql建立连接时能检查就不会有这样的问题了。 

这时我们同事想起了他以前用过的一个数据库连接池proxool,它有两个属性:一个是test-before-use,还有一个是test-after-use,这两个属性就是在使用前和使用后都要进行对连接的检查,如果连接无效就扔掉再创建一个新的连接,它们的解释如下: 

test-before-use: 

If you set this to true then each connection is tested (with whatever is defined in house-keeping-test-sql) before being served. If a connection fails then it is discarded and another one is picked. If all connections fail a new one is built. If that one fails then you get an SQLException saying so. 

test-after-use: 

If you set this to true then each connection is tested (with whatever is defined in house-keeping-test-sql) after it is closed (that is, returned to the connection pool). If a connection fails then it is discarded. 

于是我们在项目中换上proxool的连接池,再运行并访问系统,更改系统时间,再访问,一切OK,问题就这样解决了! 

不但如此,proxool的连接池中的连接的数量,活动数以及每个连接的的具体情况都可以一目了然的看到,如下图所示: 

通过上面的测试和分析,针对于Mysql5数据库,选择proxool数据库连接池是比较合适的。 

c3p0的连接池有 idleConnectionTestPeriod 属性,可以设置一段时间后连接池自动测试执行,保持连接开放,效果也是一样的。

<think>我们需要使用Python连接H2数据库MySQL数据库,并将H2数据库中的所有表和数据迁移到MySQL数据库。由于H2和MySQL的数据类型完全相同,我们需要在迁移过程中进行适当的数据类型转换。步骤:1.连接H2数据库(由于H2是Java写的,我们可以使用JayDeBeApi或JPype来通过JDBC连接)2.连接MySQL数据库(可以使用pymysqlmysql-connector-python)3.从H2数据库中获取所有表的元数据(表名、列名、数据类型等)4.MySQL中创建对应的表结构,同时进行必要的数据类型转换5.逐表读取H2的数据,并插入到MySQL对应的表中(注意批量提交和事务处理,以及类型转换)6.显示进度(例如,每张表的迁移进度,或者每1000行一个进度点)7.错误处理:捕获异常并显示失败原因考虑到H2数据库我们使用JDBC连接,这里我们可以使用JayDeBeApi(需要安装JayDeBeApi和JPype1)或者使用PyH2(但PyH2可能支持最新H2,且功能有限)。这里我们选择JayDeBeApi。安装必要的库:pipinstalljaydebeapimysql-connector-python注意:需要下载H2的JDBC驱动(h2-2.0.206.jar)并指定路径。数据类型转换参考:H2类型->MySQL类型INT->INTBIGINT->BIGINTVARCHAR->VARCHARTEXT->TEXTCLOB->LONGTEXT(或TEXT,根据大小)BLOB->LONGBLOBBOOLEAN->BOOLEAN(或TINYINT(1))DOUBLE->DOUBLEFLOAT->FLOATDECIMAL->DECIMALDATE->DATETIME->TIMETIMESTAMP->TIMESTAMP...其他类型需要根据实际情况转换由于H2的某些类型(如CLOB、BLOB)可能需要特殊处理,我们在读取数据时要注意。步骤详解:1.连接H2数据库(通过JDBC)使用jaydebeapi.connect,需要指定jclassname(驱动类),url,driver_args(用户名和密码),以及jars(jar包路径)2.连接MySQL数据库(使用mysql.connector)3.获取H2数据库中的所有表名(可以通过查询INFORMATION_SCHEMA.TABLES)4.对于每个表:a.获取表的列信息(列名、数据类型、是否主键等)b.MySQL中创建表(根据列信息生成CREATETABLE语句,注意类型转换和主键等约束)c.分批次读取H2表中的数据(避免内存溢出),并插入到MySQL表中。在插入时,如果遇到类型匹配,需要转换(比如将H2的Boolean转换为Python的bool,再插入MySQL)5.显示进度:我们可以显示当前正在迁移的表,以及已经迁移的行数。6.错误处理:使用try-except捕获异常,打印错误信息,并可以选择跳过错误继续或者停止。注意:由于两个数据库的约束(如外键、唯一键)可能同,我们这里只迁移表结构和数据,迁移约束(除了主键)。如果需要迁移约束,则需要在创建表的时候添加,但考虑到两个数据库的差异,可能会比较复杂。这里我们只考虑表结构和数据。另外,如果表很大,我们可以使用批量插入(executemany)来提高效率。考虑到H2和MySQL在语法上的差异,我们获取元数据时使用标准的INFORMATION_SCHEMA,但H2也支持INFORMATION_SCHEMA。由于H2的INFORMATION_SCHEMA可能和MySQL完全一样,我们可以这样获取表信息:获取表名:SELECTTABLE_NAMEFROMINFORMATION_SCHEMA.TABLESWHERETABLE_SCHEMA='PUBLIC'获取列信息:SELECTCOLUMN_NAME,TYPE_NAME,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT,ORDINAL_POSITION,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALEFROMINFORMATION_SCHEMA.COLUMNSWHERETABLE_NAME=?ORDERBYORDINAL_POSITION但是注意,H2的INFORMATION_SCHEMA.COLUMNS表结构可能有所同,我们可以参考H2的文档:http://h2database.com/html/information-schema.html实际中,我们可以使用以下查询获取列信息(针对H2):SELECTCOLUMN_NAME,TYPE_NAME,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE,IS_NULLABLE,COLUMN_DEFAULTFROMINFORMATION_SCHEMA.COLUMNSWHERETABLE_SCHEMA='PUBLIC'ANDTABLE_NAME=?注意:H2中TABLE_SCHEMA一般为'PUBLIC'创建MySQL表时,我们需要根据列信息构建CREATETABLE语句。对于主键,我们可以通过查询INFORMATION_SCHEMA.INDEXES来获取主键信息(CONSTRAINT_TYPE='PRIMARYKEY')。但是,为了简化,我们这里只迁移表结构和数据,迁移索引和约束(除了主键)。如果需要迁移索引和约束,可以后续再考虑,但那样会更复杂。因此,我们先考虑外键和唯一约束等,只考虑主键。获取主键信息(H2):SELECTCOLUMN_NAMEFROMINFORMATION_SCHEMA.INDEXESWHERETABLE_SCHEMA='PUBLIC'ANDTABLE_NAME=?ANDPRIMARY_KEY=TRUE由于一个表可能有多个主键列,所以我们需要将多个列名组合。步骤:1.连接两个数据库。2.获取H2中所有表名(排除系统表,如INFORMATION_SCHEMA)。3.对每个表:a.获取列信息b.获取主键信息c.生成创建表的SQL(MySQL语法)d.MySQL中执行创建表(如果表已存在,可以选择删除或跳过,这里我们选择先删除已存在的表,但注意数据会丢失,所以确保目标数据库是空的或者可以覆盖)e.从H2中读取数据,并插入到MySQL中(分批读取,比如每次1000行)f.处理特殊类型(如BLOB、CLOB等大对象,需要特殊读取)由于H2的CLOB和BLOB在jaydebeapi中可能会被读取为java.lang.String和java.io.InputStream,我们需要在Python中转换为字符串和字节。对于CLOB,我们可以直接读取为字符串(如果太大),对于BLOB,我们可以读取为字节。但是,如果数据量很大,我们需要小心内存。过,我们分批读取,每批1000行,每行中的大字段会太多,所以可以接受。代码结构:1.定义H2和MySQL的连接参数。2.定义数据类型映射(从H2类型到MySQL类型)。3.连接H2数据库(通过JayDeBeApi)和MySQL数据库(通过mysql.connector)。4.获取H2的所有用户表(排除系统表)。5.遍历每个表,创建表结构。6.迁移数据。注意:在迁移数据之前,我们可能需要关闭MySQL的自动提交,然后每批提交一次。由于两个数据库的事务处理同,我们每迁移一个表提交一次(或者每批提交一次),这样如果某个表失败,会影响已经迁移的表。进度显示:使用tqdm库可以显示进度条,但也可以自己打印。这里为了简单,我们使用打印。如果想安装tqdm,我们就自己打印表名和行数。错误处理:在每一步都进行异常捕获,并打印详细的错误信息。由于代码较长,我们分步骤写。首先,我们定义数据类型映射字典(根据H2的类型名称映射到MySQL的类型):注意:H2的类型名称可能是全大写,所以我们将H2的类型名称转为大写再匹配。映射字典示例:type_map={'INT':'INT','BIGINT':'BIGINT','VARCHAR':'VARCHAR','TEXT':'TEXT','CLOB':'LONGTEXT','BLOB':'LONGBLOB','BOOLEAN':'BOOLEAN','DOUBLE':'DOUBLE','FLOAT':'FLOAT','DECIMAL':'DECIMAL','DATE':'DATE','TIME':'TIME','TIMESTAMP':'TIMESTAMP',#其他类型...}对于VARCHAR,需要指定长度,所以我们在生成建表语句时,如果遇到VARCHAR,需要加上括号和长度。同样,对于DECIMAL,需要指定精度和标度。步骤:1.连接H2和MySQL。2.获取H2表列表。3.遍历表列表:try:获取列信息获取主键信息生成建表语句在MySQL中执行建表语句(如果表存在,先删除)读取H2表中的数据(分批读取)插入MySQL(分批插入)exceptExceptionase:打印错误,并记录失败的表,可以选择继续或停止由于代码较长,我们逐步实现。注意:H2的JDBC连接URL中指定了MODE=MySQL,这样H2会模拟MySQL的行为,包括数据类型和函数,这样在迁移时可能更容易。但是,我们仍然需要处理一些差异。我们开始写代码:</think>以下是一个完整的Python脚本,用于将H2数据库迁移到MySQL数据库,包括类型转换、进度显示和错误处理: ```python import jaydebeapi import mysql.connector from mysql.connector import errorcode import os import time from tqdm import tqdm # 数据库配置 h2_driver_path = r"C:\data-migration\h2-2.0.206.jar" h2_url = "jdbc:h2:tcp://127.0.0.1:19200/../suite/fusion_suite_admin;MODE=MySQL;AUTO_RECONNECT=TRUE" h2_user = "root" h2_password = "root" h2_driver_class = "org.h2.Driver" mysql_config = { "host": "localhost", "port": 3306, "user": "root", "password": "asd123465QWE", "database": "fusion_suite_admin", "charset": "utf8mb4" } # 数据类型映射字典 TYPE_MAP = { "INT": "INT", "BIGINT": "BIGINT", "TINYINT": "TINYINT", "SMALLINT": "SMALLINT", "MEDIUMINT": "MEDIUMINT", "INTEGER": "INT", "FLOAT": "FLOAT", "DOUBLE": "DOUBLE", "DECIMAL": "DECIMAL", "NUMERIC": "DECIMAL", "REAL": "FLOAT", "BOOLEAN": "BOOLEAN", "CHAR": "CHAR", "VARCHAR": "VARCHAR", "TEXT": "TEXT", "CLOB": "LONGTEXT", # H2的CLOB映射到MySQL的LONGTEXT "BLOB": "LONGBLOB", # H2的BLOB映射到MySQL的LONGBLOB "DATE": "DATE", "TIME": "TIME", "TIMESTAMP": "TIMESTAMP", "DATETIME": "DATETIME", "BINARY": "BINARY", "VARBINARY": "VARBINARY" } def connect_h2(): """连接H2数据库""" try: conn = jaydebeapi.connect( jclassname=h2_driver_class, url=h2_url, driver_args=[h2_user, h2_password], jars=h2_driver_path ) print("✅ H2数据库连接成功") return conn except Exception as e: print(f"❌ H2连接失败: {str(e)}") return None def connect_mysql(): """连接MySQL数据库""" try: conn = mysql.connector.connect(**mysql_config) print("✅ MySQL数据库连接成功") return conn except mysql.connector.Error as err: if err.errno == errorcode.ER_ACCESS_DENIED_ERROR: print("❌ MySQL用户名或密码错误") elif err.errno == errorcode.ER_BAD_DB_ERROR: print("❌ MySQL数据库存在") else: print(f"❌ MySQL连接失败: {err}") return None def get_h2_tables(h2_conn): """获取H2所有用户表名""" try: cursor = h2_conn.cursor() cursor.execute("SHOW TABLES") tables = [row[0] for row in cursor.fetchall()] cursor.close() return tables except Exception as e: print(f"❌ 获取H2表失败: {str(e)}") return [] def get_table_schema(h2_conn, table_name): """获取表结构信息""" cursor = h2_conn.cursor() try: # 获取列信息 cursor.execute(f"SELECT COLUMN_NAME, TYPE_NAME, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{table_name}'") columns = [] for (col_name, type_name, char_max_len, is_nullable) in cursor: # 处理数据类型映射 mysql_type = TYPE_MAP.get(type_name.upper(), "VARCHAR(255)") length = f"({char_max_len})" if char_max_len and "CHAR" in mysql_type else "" nullable = "NULL" if is_nullable == "YES" else "NOT NULL" columns.append((col_name, f"{mysql_type}{length}", nullable)) # 获取主键信息 cursor.execute(f"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_NAME = '{table_name}' AND PRIMARY_KEY = TRUE") primary_keys = [row[0] for row in cursor.fetchall()] return columns, primary_keys finally: cursor.close() def create_mysql_table(mysql_conn, table_name, columns, primary_keys): """在MySQL中创建表""" cursor = mysql_conn.cursor() try: # 生成创建表SQL col_defs = [f"`{col[0]}` {col[1]} {col[2]}" for col in columns] if primary_keys: col_defs.append(f"PRIMARY KEY (`{','.join(primary_keys)}`)") create_sql = f"CREATE TABLE IF NOT EXISTS `{table_name}` (\n" + ",\n".join(col_defs) + "\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" cursor.execute(create_sql) mysql_conn.commit() print(f"🆕 表 {table_name} 创建成功") except mysql.connector.Error as err: print(f"❌ 创建表 {table_name} 失败: {err}") raise finally: cursor.close() def migrate_data(h2_conn, mysql_conn, table_name, batch_size=100): """迁移表数据""" h2_cursor = h2_conn.cursor() mysql_cursor = mysql_conn.cursor() try: # 获取总行数 h2_cursor.execute(f"SELECT COUNT(*) FROM {table_name}") total_rows = h2_cursor.fetchone()[0] if total_rows == 0: print(f"ℹ️ 表 {table_name} 无数据,跳过迁移") return # 获取列名 h2_cursor.execute(f"SELECT * FROM {table_name} LIMIT 0") col_names = [desc[0] for desc in h2_cursor.description] placeholders = ", ".join(["%s"] * len(col_names)) # 准备进度条 progress = tqdm(total=total_rows, desc=f"迁移 {table_name}", unit="行") # 分批迁移数据 offset = 0 while offset < total_rows: h2_cursor.execute(f"SELECT * FROM {table_name} LIMIT {batch_size} OFFSET {offset}") rows = h2_cursor.fetchall() # 特殊类型处理 processed_rows = [] for row in rows: new_row = [] for value in row: # 处理H2的布尔值转换为Python布尔 if isinstance(value, bool): new_row.append(value) # 处理H2的CLOB/BLOB对象 elif hasattr(value, 'getSubString'): new_row.append(str(value.getSubString(1, int(value.length())))) else: new_row.append(value) processed_rows.append(new_row) # 插入MySQL try: insert_sql = f"INSERT INTO `{table_name}` ({', '.join([f'`{col}`' for col in col_names])}) VALUES ({placeholders})" mysql_cursor.executemany(insert_sql, processed_rows) mysql_conn.commit() except mysql.connector.Error as err: print(f"\n❌ 插入数据失败: {err}") print(f"失败SQL: {insert_sql}") print(f"失败数据: {processed_rows[0]}") raise offset += batch_size progress.update(len(rows)) progress.close() print(f"✅ 表 {table_name} 数据迁移完成 ({total_rows} 行)") except Exception as e: print(f"\n❌ 迁移表 {table_name} 数据失败: {str(e)}") raise finally: h2_cursor.close() mysql_cursor.close() def main(): start_time = time.time() # 连接数据库 h2_conn = connect_h2() if not h2_conn: return mysql_conn = connect_mysql() if not mysql_conn: h2_conn.close() return try: # 获取所有表 tables = get_h2_tables(h2_conn) if not tables: print("❌ 未找到需要迁移的表") return print(f"📊 共发现 {len(tables)} 张表需要迁移") # 迁移每张表 for i, table in enumerate(tables, 1): print(f"\n🔧 正在处理表 ({i}/{len(tables)}): {table}") try: # 获取表结构 columns, primary_keys = get_table_schema(h2_conn, table) # 在MySQL创建表 create_mysql_table(mysql_conn, table, columns, primary_keys) # 迁移数据 migrate_data(h2_conn, mysql_conn, table) except Exception as e: print(f"⛔ 表 {table} 迁移失败,跳过: {str(e)}") continue finally: # 关闭连接 h2_conn.close() mysql_conn.close() print(f"\n🕒 总耗时: {time.time() - start_time:.2f}秒") if __name__ == "__main__": main() ``` ### 代码说明和关键点: 1. **数据库连接**: - 使用 `jaydebeapi` 连接H2(通过JDBC驱动) - 使用 `mysql-connector-python` 连接MySQL 2. **数据类型映射**: - `TYPE_MAP` 字典处理H2到MySQL的类型转换 - 特殊处理CLOB/BLOB类型(使用 `getSubString()` 方法转换) - 处理布尔值的跨数据库兼容性 3. **表结构迁移**: - 查询H2的 `INFORMATION_SCHEMA` 获取元数据 - 动态生成MySQL的CREATE TABLE语句 - 处理主键约束 4. **数据迁移**: - 分批读取数据(避免内存溢出) - 使用 `tqdm` 显示实时进度条 - 批量插入提高效率(`executemany`) - 特殊数据类型转换(CLOB/BLOB) 5. **错误处理**: - 详细的错误信息输出 - 单表失败影响整体迁移 - 数据库连接错误的专项处理 ### 执行流程: 1. 连接源库(H2)和目标库(MySQL) 2. 获取H2所有用户表 3. 对每个表: - 获取表结构(列定义+主键) - 在MySQL创建对应表 - 分批迁移数据(带进度显示) 4. 关闭连接并输出总耗时 ### 使用前准备: 1. 安装依赖: ```bash pip install jaydebeapi mysql-connector-python tqdm JPype1 ``` 2. 确保H2 JDBC驱动路径正确 3. 目标MySQL数据库需预先创建 ### 常见问题处理: 1. **CLOB/BLOB转换错误**:检查H2驱动版本,确保使用兼容方法 2. **编码问题**:确保MySQL使用`utf8mb4`字符集 3. **类型匹配**:在`TYPE_MAP`中添加自定义映射 4. **连接失败**:检查防火墙、端口和权限设置 此脚本实现了健壮的跨数据库迁移方案,包含详细错误处理和进度监控,适合生产环境使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值