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%';
命令执行结果发现也不是库字符集的问题
3、原始数据线上复现
3.1、拿到原始数据,线上重建一张表,客户端写入测试:
3.2、为什么?为什么插入成功了?为什么会有告警?告警的信息是什么?
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、数据是真的被截断了吗?
数据库字段保存的数据长度:
实际数据的长度:
把contentcontent的字段类型从text改成 mediumtext,再插入:
从上面看,数据确实被截断了。
三、问题解决
1、修改JDBC连接MySQL的URL,允许插入的数据超过表字段定义的长度后自动截断 ;
2、修改表字段的格式,扩大该字段能存取的最大长度
3、修改业务侧校验规则,对超过长度的数据,在代码层面进行截断