sonar报错500 Packet for query is too large

记录一下自己的踩坑血泪史,昨天,金融组的项目经理问我是不是自己搭了一套自动化平台,有代码质量检测功能,给他做一下代码质量检测。我跟他说,没问题,我给你配置一下持续集成都行。

然后,我找他要了SVN地址,配置到jenkins,修改他的项目配置文件,配置了gradle的sonar插件,然后5分钟不到,全部配好,开扫。

瞟了眼日志,5000个java文件,有点愣,他说这项目做了好多年了,头一次做代码质量检测。

等了10分钟,终于扫描完毕,然后报了个500错误,失败了。

我的项目一直在自动构建,从来没失败过啊,心里有点慌,是不是自动构建环境哪里出问题了,赶紧构建了一下我自己的项目,2分钟后,我自己的项目成功构建完成。

那估计就是他的项目引发的问题了,shell中的日志被顶不见了,无奈又跑了一次他的项目,10分钟又过去了。

看到错误信息

INFO: Analysis report generated in 5163ms, dir size=86 MB

INFO: Analysis reports compressed in 8786ms, zip size=28 MB

ERROR: Error during SonarQube Scanner execution

ERROR: Failed to upload report - 500: An error has occurred. Please contact your administrator

没有详细信息,不知道什么问题,于是 -x又跑了一遍……10分钟又过去了

没啥特别有用的信息,还是说上传报告出错,500

想了想,500那就是sonarqube报错了,服务端应该有日志,于是去到sonarqube/logs/web.log找到具体的错误信息

2019.03.19 08:42:37 ERROR web[AWlGy9kW/lhOnmA/AAUn][o.s.s.w.WebServiceEngine] Fail to process request http://localhost:9000/api/ce/submit?projectKey=dfcwpc&projectName=dfcwpc
java.lang.IllegalStateException: Fail to insert data of CE task AWmTZm4R6CYg244CjrN5
    at org.sonar.db.ce.CeTaskInputDao.insert(CeTaskInputDao.java:56)

Caused by: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (34143478 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.
 

原来是mysql的数据包大小限制问题,赶紧查一下现在是多少

mysql> show variables like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name            | Value      |
+--------------------------+------------+
| max_allowed_packet       | 4194304    |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
---------------------  

才4M……话说刚才我们的包是多大来着,28M吧,怪不得报错,赶紧修改mysql设置

vim /etc/my.cnf,根据实际情况进行参数调整:
[mysqld]
max_allowed_packet = 100M 

重启mysql,这下改好了吧(一个小时已经过去了,同事已经向我投来了怀疑的眼光……而且,马上就要下班了)

重新运行扫描,10分钟又过去了,又报错了,客户端报错信息跟之前一样,还是500

赶紧看一眼服务端日志,内容如下:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. 

还是那一行代码,换了个错误信息。

行数据大于8126?查阅mysql相关文档,了解到

  • 一个16KB的InnoDB数据Page必须至少包含两行数据。此外,每个Page都有一个页眉和一个包含页面校验和和日志序列号的页脚,依此类推。这就是你的每行限制小于8KB的地方。
  • 固定大小的数据类型(如INTEGER,DATE,FLOAT,CHAR)存储在此主数据Page上,并计入行大小限制。
  • 可变大小的数据类型(如VARCHAR,TEXT,BLOB)存储在溢出页面上,因此它们不会完全计入行大小限制。在Antelope中,除了存储在溢出页面上之外,在主数据页面上还存储多达768个字节的此类列。Barracuda支持  动态行格式,因此它可能只在主数据页面上存储一个20字节的指针。
  • 可变大小的数据类型也以1个或多个字节为前缀来编码长度。而InnoDB行格式也有一个字段偏移数组。因此,他们的wiki中或多或少都记录了内部结构。

因此,我们要把问题所在的表的 ROW_FORMAT改成COMPRESSED或者DYNAMIC,应该就能解决问题。

于是,再次修改mysql设置

[mysqld]

#服务器发送和接受的最大包长度,当单行数据较大时,需要调整该参数。

max_allowed_packet = 100M 

#开启page独立空间

innodb_file_per_table = 1

#innodb的文件格式更改为Barracuda
innodb_file_format = Barracuda

Antelope是innodb-base的文件格式,Barracude是innodb-plugin后引入的文件格式,同时Barracude也支持Antelope文件格式。两者区别在于:

文件格式支持行格式特性
Antelope

(Innodb-base)

ROW_FORMAT=COMPACT

ROW_FORMAT=REDUNDANT

 

Compact和redumdant的区别在就是在于首部的存存内容区别。

compact的存储格式为首部为一个非NULL的变长字段长度列表

redundant的存储格式为首部是一个字段长度偏移列表(每个字段占用的字节长度及其相应的位移)。

在Antelope中对于变长字段,低于768字节的,不会进行overflow page存储,某些情况下会减少结果集IO.

Barracuda

(innodb-plugin)

ROW_FORMAT=DYNAMIC

ROW_FORMAT=COMPRESSED

 

这两者主要是功能上的区别功能上的。 另外在行里的变长字段和Antelope的区别是只存20个字节,其它的overflow page存储。

另外这两都需要开启innodb_file_per_table=1

(这个特性对一些优化还是很有用的)

重启数据库

然后需要修改问题所在表的行格式,然而,到底是哪张表出问题了呢?

没办法,开始翻看sonarqube的源码https://github.com/SonarSource/sonarqube

终于找到了

  public void insert(DbSession dbSession, String taskUuid, InputStream data) {
    long now = system.now();
    Connection connection = dbSession.getConnection();
    try (PreparedStatement stmt = connection.prepareStatement(
      "INSERT INTO ce_task_input (task_uuid, created_at, updated_at, input_data) VALUES (?, ?, ?, ?)")) {
      stmt.setString(1, taskUuid);
      stmt.setLong(2, now);
      stmt.setLong(3, now);
      stmt.setBinaryStream(4, data);
      stmt.executeUpdate();
      connection.commit();
    } catch (SQLException e) {
      throw new IllegalStateException("Fail to insert data of CE task " + taskUuid, e);
    }
  }

问题所在的表就是 ce_task_input,于是执行SQL

DROP TABLE ce_task_input;
      
CREATE TABLE `ce_task_input` (
  `task_uuid` VARCHAR(40) COLLATE utf8_bin NOT NULL,
  `input_data` LONGBLOB,
  `created_at` BIGINT(20) NOT NULL,
  `updated_at` BIGINT(20) NOT NULL,
  PRIMARY KEY (`task_uuid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;

这个时候已经晚上7点多了,同事已经要放弃了,叫我别折腾了,我说弄好了,这次肯定能成,然后让他跟我一起见证奇迹。

接着在大家的注视下,我执行了扫描命令,然而这次让我更加尴尬了,还是500,错误日志变了

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Row size too large (> 8126). Changing some columns to TEXT or BLOB may help. In current row format, BLOB prefix of 0 bytes is stored inline.

字面上的意思是让我把某些字段改成TEXT或者BLOB……WTF?特么就这4个字段,还有啥好改的

同事已经回家了,我已经忘记了晚饭的事,开始继续折腾……

谷歌,百度各种搜索,各种查,基本都是说上面的改innodb文件类型的方案,完全没有用……不知不觉就快9点了,家里的电脑坏了,儿子学不了英语,老婆火气特别大,赶紧关电脑回家。

今天早上刚到公司,继续磕mysql论坛,突然看到有人说设置innodb_log_file_size,日志文件大小,突然想到,sql日志里面是包含完整的sql内容的,日志文件大小也可能影响插入。

赶紧查一下,默认的sql日志文件是500M,果断改成1G

[mysqld]

#服务器发送和接受的最大包长度,当单行数据较大时,需要调整该参数。

max_allowed_packet = 100M 

#开启page独立空间

innodb_file_per_table = 1

#innodb的文件格式更改为Barracuda
innodb_file_format = Barracuda

#日志文件大小

innodb_log_file_size=1024M 

重启mysql,开始扫描,10分钟过去,终于成功了!

INFO: Analysis report generated in 5163ms, dir size=86 MB

INFO: Analysis reports compressed in 8786ms, zip size=28 MB

INFO: Analysis report uploaded in 2366ms INFO: ANALYSIS SUCCESSFUL, you can browse http://192.168.95.45:9000/dashboard/index/ly.mp:dfcwpc

INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report

INFO: More about the report processing at http://192.168.95.45:9000/api/ce/task?id=AWmVAuVJVEKL64HKj-4e INFO: Task total time: 6:23.107 s

INFO: ------------------------------------------------------------------------

INFO: EXECUTION SUCCESS

INFO: ------------------------------------------------------------------------

INFO: Total time: 6:24.156s INFO: Final Memory: 24M/1434M

INFO: ------------------------------------------------------------------------ 

 

 

<think>嗯,用户遇到了SonarQube报错,具体是关于unlessOperationOnImmutable的问题。首先,我需要理解这个报错的具体原因。根据经验,SonarQube通常会检测代码中的潜在问题,比如不可变对象被修改的情况。这个报错可能是因为在代码中对一个本应不可变的对象执行了修改操作,而SonarQube检测到了这一点。 接下来,我需要回忆一下Java中不可变对象的特点。不可变对象一旦创建,其状态不能被修改,任何修改操作都应该返回一个新的对象。常见的不可变类有String、BigInteger等。如果用户尝试对这些类的实例进行修改,比如使用concat方法,但实际上返回的是新对象,而原对象不变,这时候如果没有正确处理返回值,可能会导致Sonar报错。 用户可能是在代码中对一个不可变对象执行了某个操作,但未将结果赋值给新的变量,导致SonarQube认为这是无用的操作,从而触发unlessOperationOnImmutable规则。例如,使用String的concat方法但没有将结果保存,这样修改实际上不会影响原字符串,因此Sonar会提示这个问题。 修复方法应该是确保所有对不可变对象的操作结果都被正确使用。比如,将concat的结果赋值给一个新变量,或者直接使用返回值。同时,检查是否有不必要的操作,或者是否误用了可变对象的方法。 另外,可能需要查看具体的代码示例来更准确地诊断问题。比如,用户是否在代码中调用了类似不可变类的方法而未处理返回值,或者是否错误地尝试修改不可变对象的内部状态。如果有代码片段,可以更针对性地建议修改。 总结来说,解决方案包括:正确处理不可变对象方法的返回值,避免无用的操作,使用防御性拷贝,或者选择可变对象替代。同时,需要确保代码符合不可变对象的使用规范,避免SonarQube的误报或正确识别问题。</think>### 解决方案:修复 `unlessOperationOnImmutable` 导致的 SonarQube 报错 #### 问题根源分析 SonarQube 报错 `unlessOperationOnImmutable` 通常出现在对不可变(immutable)对象执行修改操作时,但未正确接收返回值或处理结果[^1]。例如: ```java String str = "hello"; str.concat(" world"); // 错误:未接收新字符串 ``` #### 修复步骤 1. **确认不可变对象类型** 检查代码中涉及的类是否属于不可变类型(如 `String`、`LocalDateTime`、`BigInteger` 等)。这类对象的方法调用(如 `concat`、`plus`)**不会修改原对象**,而是返回新对象。 2. **接收返回值** 所有修改操作必须显式接收返回值: ```java String str = "hello"; str = str.concat(" world"); // 正确:赋值给原变量 ``` 3. **使用防御性拷贝(如需要保留原对象)** 若需保留原始不可变对象,需显式创建新对象: ```java String original = "data"; String modified = new String(original.concat("_backup")); ``` 4. **替换为可变对象(可选)** 若频繁修改需求,改用 `StringBuilder` 等可变类型: ```java StringBuilder sb = new StringBuilder("hello"); sb.append(" world"); // 直接修改内部状态 ``` #### 示例修复对比 | 错误代码 | 正确代码 | 说明 | |---------|---------|------| | `list.stream().sorted()` | `List<T> sortedList = list.stream().sorted().toList()` | 流操作需显式收集结果 | | `StringUtils.trim(str)` | `str = StringUtils.trim(str)` | Apache Commons 工具类返回新对象 | #### 验证方法 1. 在 IDE 中执行静态代码分析(如 SonarLint) 2. 确保所有不可变对象的操作方法均有返回值接收 3. 检查代码是否符合 `Effective Java` 第 17 条「使不可变性最小化」原则[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值