mysql bug 之 “on duplicate key update”

本文详细分析了一个SQL语句问题导致的异常情况,主要聚焦于'mysqljdbcjar'版本过低引发的SQL解析错误。通过深入探讨,最终定位到错误在于SQL语句中'onduplicate keyupdate'关键字的不当使用,特别是其中的换行符导致的解析失败。文章提供了正确的语法修正建议,并强调了更新JDBC版本以避免类似问题的重要性。

首先发现一个异常,徘徊好久不能解决!error log如下:

 

Caused by: java.sql.SQLException: Parameter index out of range (35 > number of parameters, which is 34).
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1073)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:987)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:982)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:927)
	at com.mysql.jdbc.PreparedStatement.checkBounds(PreparedStatement.java:3729)
	at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3713)
	at com.mysql.jdbc.PreparedStatement.setBytesNoEscapeNoQuotes(PreparedStatement.java:3463)
	at com.mysql.jdbc.PreparedStatement.setOneBatchedParameterSet(PreparedStatement.java:4264)
	at com.mysql.jdbc.PreparedStatement.executeBatchedInserts(PreparedStatement.java:1766)
	at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:1440)
	at org.apache.commons.dbcp.DelegatingStatement.executeBatch(DelegatingStatement.java:297)
	at org.apache.commons.dbcp.DelegatingStatement.executeBatch(DelegatingStatement.java:297)
	at com.ibatis.sqlmap.engine.execution.SqlExecutor$Batch.executeBatch(SqlExecutor.java:611)
	at com.ibatis.sqlmap.engine.execution.SqlExecutor.executeBatch(SqlExecutor.java:118)
	at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.executeBatch(SqlMapExecutorDelegate.java:759)
	at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.executeBatch(SqlMapSessionImpl.java:184)

 

mysql jdbc jar 版本:5.1.18

 

 

初步定位:sql语句问题!

 

<insert id="insert" parameterClass="***">
	insert into ****** ( 
	gmt_created,
	nick
	)
	values (
	#gmtCreated#,
	#nick#
	) on duplicate
	 key update 
		gmt_modified=#gmtModified#,
		nick=#nick#
</insert>
 

 

最终定位:

 

 on duplicate
	 key update 

应该为:

on duplicate key update 

 

问题原因:

    “ on duplicate key update ” 中如果有换行,sql解析就会失败!

 

注意:

mysql jdbc jar 版本5.1.8以下都存在一定程度的sql解析问题。最好能升级到最新版本!

如果sql解析异常,大多异常信息为:

Caused by: java.sql.SQLException: Parameter index out of range

 

 

 

<think>我们正在处理一个看似矛盾的情况:用户报告在只有查询操作(SELECT)的情况下出现了“Duplicate key 96000”错误。通常,重复键错误(Duplicate key)发生在插入(INSERT)或更新(UPDATE)操作违反唯一性约束时。因此,我们需要探究在仅执行查询操作时为何会出现此错误。 ### 原因分析 1. **错误报告不准确**: - 用户可能误认为当前操作是查询,但实际上有后台进程或应用逻辑在执行写入操作。 - 应用框架或ORM可能自动触发了写入操作(如Hibernate的自动脏检查)。 2. **数据库触发器(Triggers)**: - 在查询操作期间,可能触发了某个触发器,该触发器内部执行了INSERT或UPDATE操作,导致违反唯一约束。 3. **数据库复制或同步机制**: - 如果数据库处于复制环境(如MySQL主从复制),可能在从库上执行了写入操作(例如修复数据时),而该写入操作在从库上造成冲突。 4. **查询语句的副作用**: - 某些SQL扩展允许查询有副作用(如SQL Server的`SELECT INTO`),但标准SELECT不应有写入行为。 5. **中间件行为**: - 数据库连接池或代理可能在执行某些维护操作(如刷新缓存)时触发了写入。 6. **并发操作影响**: - 其他并发的写入操作(由其他会话执行)可能导致唯一索引的临时状态,但通常不会在SELECT时报错。 ### 诊断步骤 1. **检查完整错误日志**: - 获取完整的错误堆栈信息,确认错误发生的具体上下文(如SQL语句、时间戳、会话ID)。 2. **审查数据库活动**: - 在错误发生时,捕获数据库的活动会话: ```sql -- MySQL SHOW FULL PROCESSLIST; -- PostgreSQL SELECT * FROM pg_stat_activity; ``` 3. **审计触发器**: - 检查相关表上是否定义了触发器: ```sql -- MySQL SHOW TRIGGERS LIKE 'your_table'; -- PostgreSQL SELECT tgname FROM pg_trigger WHERE tgrelid = 'your_table'::regclass; ``` 4. **分析应用代码**: - 检查执行查询的代码路径,确认是否有隐藏的写入操作(如使用Spring Data JPA的`@Query`注解可能隐含修改操作)。 5. **监控复制状态**: - 如果使用复制,检查复制状态和错误日志: ```sql SHOW SLAVE STATUS; -- MySQL ``` ### 解决方案 1. **修复触发器逻辑**: - 如果触发器是罪魁祸首,修改其逻辑以避免重复键写入: ```sql CREATE TRIGGER example_trigger BEFORE INSERT ON your_table FOR EACH ROW BEGIN -- 添加防重复逻辑 IF (EXISTS(SELECT 1 FROM your_table WHERE unique_col = NEW.unique_col)) THEN SET NEW.unique_col = NULL; -- 或跳过插入 END IF; END; ``` 2. **隔离查询操作**: - 将查询操作路由到只读副本(若有),避免触发任何写入逻辑。 3. **审查数据库复制**: - 修复主从复制中的不一致: ```bash # MySQL 主从数据校验 pt-table-checksum --databases=your_db pt-table-sync --execute ``` 4. **应用层修正**: - 在代码中添加防护逻辑,确保查询操作不会意外触发写入: ```java @Transactional(readOnly = true) // Spring的只读事务 public List<Record> safeQuery() { return repository.findRecords(); } ``` ### 特殊情况:查询计划生成中的错误 - **数据库引擎缺陷**:极少数情况下,数据库优化器在生成查询计划时可能触发内部错误(如MySQLBug #NNNNN),但通常不会报告为“Duplicate key”。 ### 验证方法 1. 在测试环境模拟相同查询,使用相同数据集。 2. 逐步剥离应用层(如直接使用SQL客户端执行查询),确认错误是否重现。 > **关键提示**:真正的查询操作不会导致重复键错误。因此,必须深入挖掘背后的隐藏操作。[^1][^2] --- ### 相关问题 1. 如何检测数据库中的隐藏触发器? 2. MySQL主从复制冲突的常见解决方案有哪些? 3. 在Spring框架中如何强制事务为只读模式? 4. 数据库查询操作可能产生哪些副作用? 5. 如何审计数据库中的所有写入操作来源?</think>### 在只有查询操作时出现 "Duplicate key 96000" 错误的原因及解决方案 #### 一、问题本质分析 虽然"Duplicate key"错误通常与写入操作相关,但在**纯查询场景**中出现该错误可能有以下特殊原因: 1. **数据库连接池污染** - 应用层未正确释放数据库连接,导致前一个会话的写入操作错误(如Duplicate key)被传递给新查询会话 - 表现为:查询操作意外抛出写入错误(错误代码96000) 2. **数据库复制/同步机制故障** - 主从同步过程中出现数据不一致(如binlog重放错误) - 从库在响应查询时报告底层存储引擎的唯一键冲突 3. **查询优化器缺陷** - 特定数据库版本(如MySQL 5.x)的查询优化器在处理复杂查询时可能错误触发约束检查 - 典型场景:包含子查询的SELECT语句涉及唯一索引表 4. **内存数据结构损坏** - InnoDB缓冲池或MyISAM索引缓存发生位翻转 - 错误代码:`ER_DUP_KEY (96000)` #### 二、诊断步骤 ```sql -- 1. 检查错误日志定位源头 SHOW ENGINE INNODB STATUS; -- 查看最新错误上下文 -- 2. 验证表结构完整性(MySQL示例) CHECK TABLE your_table QUICK; -- 3. 检测复制一致性(主从环境) pt-table-checksum --databases=your_db ``` #### 三、针对性解决方案 1. **连接池污染修复** ```java // Java示例:强制重置污染连接 dataSource.getConnection().close(); // 废弃当前连接 Connection cleanConn = dataSource.getConnection(); // 获取新连接 ``` - 配置连接验证参数(JDBC URL添加): ``` ?testOnBorrow=true&validationQuery=SELECT 1 ``` 2. **主从同步修复** ```bash # 重新同步从库数据 mysql> STOP SLAVE; mysql> RESET SLAVE ALL; $ mysqldump --master-data=2 -u root -p master_db | mysql -u root -p slave_db ``` 3. **查询优化器规避** - 方案1:拆分复杂查询 ```sql -- 原始查询 SELECT * FROM tableA WHERE id IN (SELECT DISTINCT id FROM tableB); -- 改为分步执行 CREATE TEMPORARY TABLE tmp_ids SELECT DISTINCT id FROM tableB; SELECT * FROM tableA JOIN tmp_ids USING(id); ``` - 方案2:强制索引提示 ```sql SELECT /*+ INDEX(tableA) */ * FROM tableA WHERE ... ``` 4. **内存损坏恢复** ```sql -- InnoDB引擎修复 ALTER TABLE your_table ENGINE=INNODB; -- 重建表结构 -- MyISAM引擎修复 REPAIR TABLE your_table; ``` #### 四、预防措施 1. **连接池监控** ```mermaid graph LR A[应用启动] --> B[初始化连接池] B --> C{执行查询} C -->|成功| D[归还连接] C -->|失败| E[销毁连接] E --> F[创建新连接] ``` 2. **定期一致性检查** ```bash # 每周自动检查(crontab) 0 3 * * 1 /usr/bin/pt-table-checksum --quiet ``` 3. **数据库参数优化** ```ini # my.cnf 关键配置 [mysqld] innodb_buffer_pool_validate_on_resore=ON query_cache_type=0 # 避免复杂查询缓存 ``` > **关键结论**:纯查询操作报错通常指示底层基础设施问题而非SQL本身,需系统级排查[^1][^2] --- ### 相关问题 1. 如何检测MySQL主从数据不一致? 2. 数据库连接池的最佳配置参数有哪些? 3. InnoDB缓冲池损坏的常见症状是什么? 4. 复杂SQL查询优化的基本原则? 5. 如何监控数据库连接的生命周期?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值