Mysql sql_mode导致text存储异常

一、现象:

org.springframework.jdbc.UncategorizedSQLException: ### Error updating database. Cause: java.sql.SQLException: Incorrect string value: '\xE4\xB8\xA4\xE5\x90\x8D...' for column 'content' at row 1  SQL: INSERT INTO `anti_01` (`content`, `title`) ### Cause: java.sql.SQLException: Incorrect string value: '\xE4\xB8\xA4\xE5\x90\x8D...' for column 'content' at row 1 ; uncategorized SQLException for SQL []; SQL state [HY000]; error code [1366]; Incorrect string value: '\xE4\xB8\xA4\xE5\x90\x8D...' for column 'content' at row 1; nested exception is java.sql.SQLException: Incorrect string value: '\xE4\xB8\xA4\xE5\x90\x8D...' for column 'content' at row 1

二、问题定位

1、首先搜索SQL state [HY000]; error code [1366],网上清一色的说是字符编码的问题,所以决定查看一下表的字符编码:

ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,发现字符并无问题

2、表字符集没问题,那么是不是库的字符集问题呢?

执行命令:
show variables like 'char%';

Mysql sql_mode导致text存储异常

 

命令执行结果发现也不是库字符集的问题

3、原始数据线上复现

3.1、拿到原始数据,线上重建一张表,客户端写入测试:

Mysql sql_mode导致text存储异常

 

3.2、为什么?为什么插入成功了?为什么会有告警?告警的信息是什么?

Mysql sql_mode导致text存储异常

 

3.3、为什么定义为非空的字段,SQL里面没插入对应字段的值,也没有默认值,却插入成功了?

例如:title varchar(200) NOT NULL COMMENT '标题',是谁在帮我们改变了游戏规则?

3.4、sql_mode出场

这里需要我们了解一下sql_mode的概念:

MySQL服务器可以在不同的sql_mode下运行,并且可以根据sql_mode系统变量的值,为不同的客户机应用不同的模式。DBA可以将全局sql_mode设置为与站点服务器操作要求匹配,每个应用程序都可以将其会话sql_mode设置为自己的要求。

模式会影响MySQL支持的SQL语法,并且会执行数据验证检查。这使得在不同的环境中使用MySQL以及与其他数据库服务器一起使用时变得更加容易。

先来看看比较重要的几个sql_mode:

ANSI模式:宽松模式,对插入数据进行校验,如果不符合定义类型或长度,对数据类型调整或截断保存,报warning警告。

STRICT_TRANS_TABLES模式:严格模式,进行数据的严格校验,错误数据不能插入,报error错误。

是不是因为JDBC和MySQL的客户端设置的sql_mode不一致导致结果不一样呢?也就是说MySQL 客户端使用的是ANSI模式而JDBC使用的是STRICT_TRANS_TABLES模式?

3.5、查看MySQL 客户端的sql_mode:

(通过3.4可以知道sql_mode分为全局全局和会话级别,这里分别查看一下)

mysql> SELECT @@GLOBAL.sql_mode;
+------------------------+
| @@GLOBAL.sql_mode |
+------------------------+
| NO_ENGINE_SUBSTITUTION |
+------------------------+
1 row in set

mysql> SELECT @@SESSION.sql_mode;
+------------------------+
| @@SESSION.sql_mode |
+------------------------+
| NO_ENGINE_SUBSTITUTION |
+------------------------+
1 row in set

3.6、JDBC 如何设置sql_mode呢?Google一下

JDBC Driver默认会设置会话SQL_MODE='STRICT_TRANS_TABLES',可以通过"SELECT @@SESSION.sql_mode"语句来进行验证。

到这里,终于明白了为什么MySQL客户端插入可以成功,JDBC插入为什么会失败

3.7、如何改变JDBC的默认设置呢?

在JDBC URL中存在着"jdbcCompliantTruncation"这个参数,该参数可以控制是否开启"enforce JDBC compliance on truncation checks"功能,当我们通过JDBC URL设定"jdbcCompliantTruncation=false"之后,也就不会去默认设置SQL_MODE='STRICT_TRANS_TABLES'了。样例:jdbc:mysql://localhost:3306/table_name?jdbcCompliantTruncation=false

3.8、数据是真的被截断了吗?

 

数据库字段保存的数据长度:

Mysql sql_mode导致text存储异常

实际数据的长度:

Mysql sql_mode导致text存储异常

 

把contentcontent的字段类型从text改成 mediumtext,再插入:

Mysql sql_mode导致text存储异常

从上面看,数据确实被截断了。

三、问题解决

1、修改JDBC连接MySQL的URL,允许插入的数据超过表字段定义的长度后自动截断 ;

2、修改表字段的格式,扩大该字段能存取的最大长度

3、修改业务侧校验规则,对超过长度的数据,在代码层面进行截断

参考:https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html