惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 青石路

Claude Code安装,接入阿里云百炼模型,蹭蹭免费额度 异源数据同步 → 记一次 DataX 已同步数据量优化 明明连接的是Redis的DB0,为什么能查到DB3的数据? 用了MySQL的INSERT ON DUPLICATE KEY UPDATE,怎么还报唯一索引冲突错误 那些年不该放到事务中的操作,你实现过哪些 关于布尔类型的变量不要加 is 前缀,被网友们吐槽了,特来完善下 都说了布尔类型的变量不要加 is 前缀,非要加,这不是坑我了嘛 安全漏洞修复导致SpringBoot2.7与Springfox不兼容,问题排查与处理 记一次SQL隐式转换导致精度丢失问题的排查 → 不规范就踩坑 经由同个文件多次压缩的文件MD5都不一样问题排查,感慨AI的强大! 记一次cannot access its superinterface问题的的排查 → 强如Spring也一样写Bug SpringBoot支持Kafka多源配置的同时还要支持启停配置化,是真的会玩 如果XXL-JOB执行器在执行某任务中被重启了,重启后该任务能够被自动弥补调度吗 Spring Boot读取外部配置文件失败,原因绝对出乎你意料 不依赖 Spring,你会如何自实现 RabbitMQ 消息的消费(一) 异源数据同步 → DataX 同步启动后如何手动终止? 异源数据同步 → 如何获取 DataX 已同步数据量? 记一次 RabbitMQ 消费者莫名消失问题的排查 不升级 POI 版本,如何生成符合新版标准的Excel 2007文件 以MySQL为例,来看看maven-shade-plugin如何解决多版本驱动共存的问题? maven 插件之 maven-shade-plugin,解决同包同名 class 共存问题的神器
TINYINT(1) 类型的字段,明明数据存的是 2,为什么查出来是 true
青石路 · 2026-01-19 · via 博客园 - 青石路

开心一刻

大学期间,跟初恋谈了一段刻骨铭心的恋情,因为某些原因,大学毕业后分手了。
如今大学毕业已经10年,听说她很早就出国了,而我,很早就成的哥了。
昨天,初恋坐上了我的的车,我一眼就认出了她,她亦如当初模样,而我却满脸沧桑。
我不敢打招呼,默默的听着她打电话,讲述着国外的种种。
快到目的地的时候,她放下了电话说:我已经把我这10年的经历都说给你听了,你连句你好都不说吗
我知道电话那头没有任何人,她是故意说给我听的,我哽咽着颤抖的说到:你好
她深情的看着我,问道:我们还回得去吗
我疑惑的望向她,说到:回去?回去可以啊,但得加钱......

TINYINT

关于 TINYINT,我相信大家都知道它,是数据库的一种数据类型,说的详细点,它是数据库的一种数字类型,再详细点,它是数据库的一种整数类型;需要注意的是,它并非 SQL 标准整数类型

SQL标准整数类型:INTEGER(orINT) and SMALLINT

而是某些数据库的拓展整数类型,所以并非所有的关系型数据库都支持 TINYINT,支持的数据库类型包括 MySQLMariaDBSQL Server;我们基于 MySQL 来看看 TINYINT

MySQL 官方对整数类型有如下介绍

整数类型

除了 TINYINT,MySQL 还拓展出了 MEDIUMINTBIGINT,这些都不是 SQL 标准整数类型,在做不同库数据迁移的时候需要考虑这些点

标准 SQL,便于迁移;做表设计的时候,尽量用 SQL 标准数据类型

TINYINT 存储空间占 1 字节,有符号的值范围是 -128 到 127,无符号的值范围是 0 到 255

TINYINT 的基本介绍已经完成,下面开始实操环节,开始之前我先问你们一个问题

在实际项目中,你们一般用 TINYINT 存什么 ?

是不是用来存枚举值?例如这样

`exec_status` TINYINT DEFAULT 0 COMMENT '执行-状态,0:等待中,1:执行中,2:成功,3:失败,4:终止'

甚至在枚举值少的时候,会使用 TINYINT(1),对不对?重点来了

640 (8)

TINYINT(1) 中的 1 表示什么?很多小伙伴会理所当然的回答道:数值范围,TINYINT(1) 表示的是数值范围是 -9 到 9,或者 0 ~ 9

对不对呢?我们验证下就知道了。基于 MySQL 8.0.31,我们创建表

CREATE TABLE `tbl_qsl_job` (
  `id` int NOT NULL COMMENT '主键',
  `exec_status` tinyint(1) DEFAULT 0 COMMENT '执行-状态,0:等待中,1:执行中,2:成功,3:失败,4:终止',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

插入一条记录

INSERT INTO tbl_qsl_job(id, exec_status) VALUES(1, 12);

结果会怎么样,超出范围报错?

实际是插入成功

tinyint1_插入12成功

道心是不是碎了一地?

掀桌子

给你们 10 秒钟,收拾下破碎的道心;收拾好了之后我们一起看看官方说明:Numeric Type Attributes

MySQL supports an extension for optionally specifying the display width of integer data types in parentheses following the base keyword for the type. For example, INT(4) specifies an INT with a display width of four digits. This optional display width may be used by applications to display integer values having a width less than the width specified for the column by left-padding them with spaces. (That is, this width is present in the metadata returned with result sets. Whether it is used is up to the application.)

The display width does not constrain the range of values that can be stored in the column. Nor does it prevent values wider than the column display width from being displayed correctly. For example, a column specified as SMALLINT(3) has the usual SMALLINT range of -32768 to 32767, and values outside the range permitted by three digits are displayed in full using more than three digits.

相信你们都能看懂,INT(n) 中的 n 表示的是显示宽度,INT(4) 表示的是显示宽度为四位数的 INT。应用程序可以采用左填充空格的方式来填充宽度不够列指定宽度的整数值(也就是说,此宽度会作为结果集的元数据返回,是否使用取决于应用程序)

显示宽度不会限制列存储的数值范围,也不会截断比列显示宽度更宽的值。例如,SMALLINT(3) 类型的列的存数范围与 SMALLINT 一样,也是 -32768 到 32767,超出三位数的值会完整显示

关于整数类型显示宽度,我们可以进行如下总结

类型后面的 n,是显示宽度,表示显示时最少占 n 个字符宽度,既不会限制列存储的数值范围,也不会截断比列显示宽度更宽的值,

显示宽度会作为结果集的元数据返回,是否使用取决于应用程序(这个伏笔,后面会呼应,值得我们留意)

2 变 true

我们把 tbl_qsl_job 中 id = 1 的记录的状态改成 2(因为没有枚举值 12)

UPDATE tbl_qsl_job SET exec_status = 2 WHERE id = 1;

基于 SpringBoot 2.7.18spring-jdbc 5.3.31 HikariCP 4.0.3mysql-connector-java 8.0.25 构建查询

package com.qsl;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;

import javax.annotation.Resource;

/**
 * @author youzb
 */
@SpringBootTest(classes = Application.class)
public class QslJobTest {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Test
    public void getByJdbcTemplate() {
        SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet("SELECT * FROM tbl_qsl_job WHERE id = 1");
        SqlRowSetMetaData metaData = sqlRowSet.getMetaData();
        while (sqlRowSet.next()) {
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                System.out.print(sqlRowSet.getObject(i) + " ");
            }
            System.out.println();
        }
    }
}

你们觉得执行结果是怎样的,是不是 1 2

2变true

可以看到,结果并不是 1 2,而是 1 true,是不是有点懵?

明明数据存的是 2,为什么查出来结果是 true?

懵

首先我们可以确定的是,数据库存储的值是没问题的,因为通过 navicat 查到的结果是 2(也说明 navicat 没有对这个值做特殊转换)

数据库中值是2

回到我们的程序,数据库中的 2 到程序控制台的 true,经过了那些环节,我们是不是能整理出来?

数据流转

数据库我们已经确定没问题了,至于是 mysql-connector-java 8.0.25HikariCP 4.0.3spring-jdbc 5.3.31 谁做了特殊处理,需要我们进一步排查了;最直接的方式就是 Debug 嘛,断点我已经替你们打好

断点debug

此时到了 MySQL JDBC 驱动里面,我们来看一下 this.results 的信息,有几个点值得我们重点关注下

  1. 连接元信息

    连接元信息

    我们配置的连接 url 是

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/fnj_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    

    并未设置 tinyint1isBit 值,所以其值 true 是 mysql-connector-java 8.0.25 给的默认值

  2. 列元信息

    列元信息

    可以看到,字段 exec_status 的 length = 1,这个 length 表示什么呢,因为列 exec_status 的类型是整数类型,所以这个 length 是不是就是 显示宽度 ?不然还能代表什么?

    最重要的来了,mysqlTypenameBIT

    mysqltype_bit

    也就是说,mysql-connector-java 8.0.25 把 TINYINT(1) 解析成了 MysqlType.BIT,对应的 JAVA 类型就是 Boolean

既然 exec_status 的 JAVA 类型被解析成了 Boolean,其值 2 也就被解析成 true 了,所以标题的答案是不是就清楚了

mysql-connector-java 8.0.25 默认情况下,把 TINYINT(1) 解析成 JAVA 的 Boolean

问题又来了,mysql-connector-java 8.0.25 对 TINYINT 的解析逻辑是怎样的呢?我们跟下源码就知道了

mysqlType赋值逻辑

我们先定位到 com.mysql.cj.protocol.a.ColumnDefinitionReader 的 180 行,然后往上找到 mysqlType 赋值的地方(ColumnDefinitionReade 134 行)

MysqlType mysqlType = NativeProtocol.findMysqlType(this.protocol.getPropertySet(), colType, colFlag, colLength, tableName, originalTableName,
                collationIndex, encoding);

跟进 NativeProtocol.findMysqlType,我们能看到这样一段代码

tinyint赋值逻辑

第一个 if,我们分几部分解析下

  1. isUnsigned

    表示是否无符号,建表的时候,exec_status 并未明确指定 UNSIGNED,所以是有符号的,那么 isUnsigned 值是 false,取反则是 true

  2. length

    表字段的显示宽度,也就是 TINYINT(1) 中的 1,所以 length == 1 的结果是 true

  3. tinyInt1isBit

    mysql-connector-java 8.0.25 的 PropertyDefinitions 会静态初始化很多属性默认值,其中就包括 tinyInt1isBit

    tinyInt1isBit默认值

    tinyInt1isBit 的默认值是 true

    如果我们在数据库连接 url 中增加 tinyInt1isBit=false

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/fnj_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&tinyInt1isBit=false
    

    那么配置值会覆盖默认值

    tinyInt1isBit值覆盖

    setValueInternal(extractedValue, exceptionInterceptor);
    this.initialValue = this.value;
    

    这两行会分别将 valueinitialValue 设置成 false

    回到 url 未配置 tinyInt1isBit 的情况,tinyInt1isBit 的值是默认值 true

  4. 内嵌 if 的 transformedBitIsBoolean

    在分析 tinyInt1isBit 默认值的时候,也红框框住过 transformedBitIsBoolean 的默认值,是 false;没注意的小伙伴,可以往上翻一翻

所以 NativeProtocol.findMysqlType 的返回值是 MysqlType.BIT。如果 length > 1,那么返回的则是 MysqlType.TINYINT_UNSIGNEDMysqlType.TINYINT

自此,来龙去脉是不是清楚呢?

问题处理

既然已经找到问题原因,那么处理方式也就清晰了,有如下几种

  1. url 中配置 tinyInt1isBit=false

    这个会全局生效,有些需要将 TINYINT(1) 映射成 Boolean 的情况,会出问题

    老项目不推荐增加该配置,除非确定整个项目中没有 TINYINT(1) 映射成 Boolean 的情况

    新项目的话,可以增加该配置

  2. 字段类型调整成无符号

    增加 UNSIGNED 修饰,例如

    `exec_status` tinyint(1) unsigned DEFAULT 0 COMMENT '执行-状态,0:等待中,1:执行中,2:成功,3:失败,4:终止',
    

    但有个前提,枚举值不能出现负数

  3. 调大字段类型显示宽度

    直接使用 TINYINT(4) 或 TINYINT

举一反三

既然是 mysql-connector-java 8.0.25 做了默认值的处理,那么 MyBatis-Plus 查的结果是不是也是将 2 处理成 true 呢?我们试一下就知道了,引入 MyBatis-Plus 3.5.7,测试代码很简单

@TableName("tbl_qsl_job")
public class QslJob {

    private Integer id;
    private Integer execStatus;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getExecStatus() {
        return execStatus;
    }

    public void setExecStatus(Integer execStatus) {
        this.execStatus = execStatus;
    }
}

	@Resource
    private QslJobDao qslJobDao;
    
    @Test
    public void testMybatisPlus() {
        QslJob qslJob = qslJobDao.selectById(1);
        System.out.println(qslJob.getId() + " " + qslJob.getExecStatus());
    }

你们觉得执行结果是怎样的,报错?输出 1 true?还是输出 1 2?执行下就知道了

tinyint(1)_mybatis-plus

查询结果正常!

按前面的分析,mysql-connector-java 8.0.25 返回 exec_status 的值是 true,Boolean 强转 Integer 会报错,即使不报错,结果也应该是 1(0 = false,1 = true)嘛,怎么会是 2 呢?

好难 不该学编程的

Debug 下你们就明白了

mybatis_getInt

Mybatis 需要将 mysql-connector-java 8.0.25 查到的 ResultSet 映射成 QslJob,就需要用到类型处理器;QslJob 实体的 execStatus 是 Integer 类型的,那肯定用 IntegerTypeHandler 进行处理嘛,自然而然就用到 rs.getInt ,查到的结果自然就是 2 了。

我们再回过头去看 SqlRowSet,如果我们用 sqlRowSet.getInt 替换 sqlRowSet.getObject,是不是就行了?

@Test
public void getByJdbcTemplate() {
    SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet("SELECT * FROM tbl_qsl_job WHERE id = 1");
    SqlRowSetMetaData metaData = sqlRowSet.getMetaData();
    while (sqlRowSet.next()) {
        for (int i = 1; i <= metaData.getColumnCount(); i++) {
            System.out.print(sqlRowSet.getInt(i) + " ");
        }
        System.out.println();
    }
}

运行下,我们会发现报错了

getInt报

这又是为什么?

原因在 JdbcTemplate 的数据提取

extractData

我们对 rs 进行一下 Evaluate,结果如下

rs_evaluate

此时 rs.getInt 能正常获取到 2,这说明什么?

mysql-connector-java 8.0.25 虽然把 TINYINT(1) 映射成了 JAVA 的 Boolean,但其查到的 ResultSet 中的数据仍是与数据库中数据一致的原始数据

HikariCP 4.0.3 也并未对原始数据做转换处理

我们继续跟进 rse.extractData(rs),跟进两层会来到关键点

cachedRowSet

rowSet.populate(rs) 会把 ResultSet rs 中的数据填充到 CachedRowSetImpl 的 rvh

rhv

我们接着跟进 sqlRowSet.getInt(i),答案即将揭晓

getInt_error

红框框住是不是就会抛异常了?我们继续跟进 this.getCurrentRow,会看到我们刚刚讲到的 rvh

protected BaseRow getCurrentRow() {
    return (BaseRow)(this.onInsertRow ? this.insertRow : (BaseRow)((BaseRow)this.rvh.get(this.cursorPos - 1)));
}

是不是就呼应上了?这里有两个呼应

  1. 显示宽度

    前面已经讲到,显示宽度会作为结果集的元数据返回,是否使用取决于应用程序

    spring-jdbc 的 SqlRowSetResultSetExtractor#createSqlRowSet 方法用到了 CachedRowSetImpl#populate,在进行数据填充的过程中,会调用

    调用myql驱动的getObject

    mysql-connector-java 8.0.25 的 ResultSetImpl#getObject(int) 方法

    BIT处理

    是不是相当于用到了显示宽度?

  2. rvh

    CachedRowSetImpl#populate 方法,将数据填充到了 Vector