bigywater.tech
Java · 367

SQLWarning:数据库的非致命信号

数据库说「我做了点处理但没有报错」的信号就是 SQLWarning。这篇看警告挂在哪、能保留多久、什么情况下会被吞掉,以及它们如何让数据被悄悄改写。

v=abc n=127
MySQL 非严格模式下插入 'abcdef' 与 300 到 varchar(3) / tinyint 的实际结果,只有警告没有异常
[1, 1, 1] 且无警告
MySQL 非严格模式下 executeBatch 三条超长插入的返回值与 ps.getWarnings()
PG 直接报错
PostgreSQL 插入超长字符串到 varchar(3):ERROR: value too long(22001),不是 SQLWarning

PostgreSQL:NOTICE 与 WARNING

实测 · 本机执行输出
  drop table if exists 不存在的表之后 stmt.getWarnings():1 条
        [1] PSQLWarning SQLState=00000 errorCode=0 消息=table "not_there" does not exist, skipping
  select noisy(3)(3 个 NOTICE + WARNING + INFO + LOG + DEBUG)之后 stmt.getWarnings():5 条
        [1] PSQLWarning SQLState=00000 errorCode=0 消息=notice 1
        [2] PSQLWarning SQLState=00000 errorCode=0 消息=notice 2
        [3] PSQLWarning SQLState=00000 errorCode=0 消息=notice 3
        [4] PSQLWarning SQLState=01000 errorCode=0 消息=a warning
        …
  同一时刻 connection.getWarnings():(null,没有警告)
  stmt.clearWarnings() 之后:(null,没有警告)
  先 select noisy(1) 再 select 1(同一个 Statement)之后 stmt.getWarnings():(null,没有警告)(下一次 execute 会清空)
  client_min_messages = warning 之后 select noisy(3):2 条
        [1] PSQLWarning SQLState=01000 errorCode=0 消息=a warning
        [2] PSQLWarning SQLState=00000 errorCode=0 消息=an info
  client_min_messages = debug1:5 条
        [1] PSQLWarning SQLState=00000 errorCode=0 消息=notice 1
        [2] PSQLWarning SQLState=01000 errorCode=0 消息=a warning
        [3] PSQLWarning SQLState=00000 errorCode=0 消息=an info
        [4] PSQLWarning SQLState=00000 errorCode=0 消息=a log
        …
  executeQuery 返回 ResultSet 之后 rs.getWarnings():(null,没有警告);stmt.getWarnings():3 条
        [1] PSQLWarning SQLState=00000 errorCode=0 消息=notice 1
        [2] PSQLWarning SQLState=01000 errorCode=0 消息=a warning
        [3] PSQLWarning SQLState=00000 errorCode=0 消息=an info
  PreparedStatement 执行后 ps.getWarnings():4 条
        [1] PSQLWarning SQLState=00000 errorCode=0 消息=notice 1
        [2] PSQLWarning SQLState=00000 errorCode=0 消息=notice 2
        [3] PSQLWarning SQLState=01000 errorCode=0 消息=a warning
        [4] PSQLWarning SQLState=00000 errorCode=0 消息=an info
  autoCommit=false 下手工 execute("commit") 之后(没有事务时 COMMIT 也会提示):(null,没有警告)
  autoCommit=true 下 execute("commit"):1 条
        [1] PSQLWarning SQLState=25P01 errorCode=0 消息=there is no transaction in progress
  create table if not exists(第二次,表已存在):1 条
        [1] PSQLWarning SQLState=42P07 errorCode=0 消息=relation "wtest" already exists, skipping
  PG 里超长字符串插入 varchar(3):PSQLException(22001/0): ERROR: value too long for type character varying(3)(PG 报错,不是警告)
  警告里 getSQLState / getErrorCode:00000 / 0;类型 org.postgresql.util.PSQLWarning;是否同时是 SQLException true;getNextWarning 链长度 4
  已关闭的 Statement 上 getWarnings():PSQLException: This statement has been closed.
  连接上的警告:(null,没有警告)

MySQL:非严格模式的警告

实测 · 本机执行输出
  设置 sql_mode = ''(非严格模式)后,会话 sql_mode = “”(空)
  非严格模式下插入(超长字符串、tinyint 超范围、非法日期、decimal 超范围)stmt.getWarnings():4 条
        [1] SQLWarning SQLState=01000 errorCode=1265 消息=Data truncated for column 'v' at row 1
        [2] SQLWarning SQLState=22003 errorCode=1264 消息=Out of range value for column 'n' at row 1
        [3] SQLWarning SQLState=22003 errorCode=1264 消息=Out of range value for column 'd' at row 1
        [4] SQLWarning SQLState=22003 errorCode=1264 消息=Out of range value for column 'dec_' at row 1
      实际存进去的值:v=abc n=127 d=0000-00-00 dec_=999.9(数据被静默截断 / 改写)
  紧接着在同一连接上用另一个 Statement 执行 show warnings(没有调用 getWarnings):[Warning 1265 Data truncated for column 'v' at row 1]
  再执行一次 show warnings(上一条语句是 show warnings 自己):1 行
  该 Statement s 此时 getWarnings():(null,没有警告)
  先正常插入、再超长插入后只看最后一条语句的 getWarnings():1 条
        [1] SQLWarning SQLState=01000 errorCode=1265 消息=Data truncated for column 'v' at row 1
  再来一条正常插入:(null,没有警告)
  严格模式下同样的超长字符串:MysqlDataTruncation(22001/1406): Data truncation: Data too long for column 'v' at row 1
  insert ignore 主键冲突:1 条
        [1] SQLWarning SQLState=23000 errorCode=1062 消息=Duplicate entry '1' for key 'w.PRIMARY';更新计数仍是 0
  select 1/0, cast('12abc' as signed), 'a'+1:结果 null / 12 / 1.0;rs.getWarnings():(null,没有警告);stmt.getWarnings():2 条
        [1] SQLWarning SQLState=22007 errorCode=1292 消息=Truncated incorrect INTEGER value: '12abc'
        [2] SQLWarning SQLState=22007 errorCode=1292 消息=Truncated incorrect DOUBLE value: 'a'
  update 把 tinyint 设成 999:3 条
        [1] SQLWarning SQLState=22003 errorCode=1264 消息=Out of range value for column 'n' at row 6
        [2] SQLWarning SQLState=22003 errorCode=1264 消息=Out of range value for column 'n' at row 7
        [3] SQLWarning SQLState=22003 errorCode=1264 消息=Out of range value for column 'n' at row 8
  max_error_count = 3 时同样的插入:3 条
        [1] SQLWarning SQLState=01000 errorCode=1265 消息=Data truncated for column 'v' at row 1
        [2] SQLWarning SQLState=22003 errorCode=1264 消息=Out of range value for column 'n' at row 1
        [3] SQLWarning SQLState=22003 errorCode=1264 消息=Out of range value for column 'd' at row 1
  drop table if exists 不存在的表:1 条
        [1] SQLWarning SQLState=42S02 errorCode=1051 消息=Unknown table 'lab.not_there'
  PreparedStatement 插入超长字符串 ps.getWarnings():1 条
        [1] SQLWarning SQLState=01000 errorCode=1265 消息=Data truncated for column 'v' at row 1
  executeBatch 3 条超长插入:返回 [1, 1, 1];ps.getWarnings():(null,没有警告)
  connection.getWarnings():(null,没有警告)
  (上面默认参数的连接在非严格模式下只得到警告;下面单独用 jdbcCompliantTruncation=true 重试)
  jdbcCompliantTruncation=true + 非严格模式,插入超长字符串:MysqlDataTruncation(22001/1406): Data truncation: Data too long for column 'v' at row 1

分步演示

SQLWarning
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64,JDK 21.0.12;PostgreSQL 16.15 与 MySQL 26.7.0(自己初始化的隔离实例,端口 9501 / 9601);驱动 PostgreSQL JDBC 42.7.4、MySQL Connector/J 9.1.0;MySQL 的 sql_mode 在会话里手工设为空以进入非严格模式
    • 没有验证:MySQL 连接上警告 getWarnings() 的懒加载时机(只观察到另一个 Statement 之后 s.getWarnings() 变成 null 的现象)、Oracle / SQL Server 的警告、jdbcCompliantTruncation 在不同驱动版本的默认值(这里默认连接得到警告,显式设为 true 得到异常)、DataTruncation(JDBC 标准类)与 MysqlDataTruncation 的关系、大批量 insert 的警告数量上限(max_error_count 默认 1024)
    京ICP备2026067502号-1京公网安备11010502063487号