mysql中cast/convert数值溢出默认不报错,而是静默截断或回绕(如cast(300 as tinyint)→127),数据已错却无感知;必须启用strict_trans_tables模式才报错,且严格模式不拦截select表达式溢出,需应用层预校验或改用decimal。

MySQL里用CAST或CONVERT转数值,不报错≠安全——默认静默截断或回绕,数据已错却毫无感知。
CAST/CONVERT在数值溢出时默认不报错
MySQL对CAST和CONVERT的溢出处理,完全依赖当前sql_mode。未启用严格模式时,它不会拒绝非法转换,而是硬塞进目标类型范围:
-
CAST(300 AS TINYINT)→ 存成127(TINYINT最大值),不是报错也不是NULL -
CONVERT(-129, SIGNED)→ 变成-128(下溢到最小值) -
CAST(9223372036854775808 AS BIGINT)→ 在部分 8.0+ 实例中返回NULL,但无警告,极易被忽略
这种“静默失败”比报错更危险:SQL执行成功,应用以为写入正确,下游报表、对账全跟着错。
必须启用STRICT_TRANS_TABLES才让CAST报错
仅改字段类型或换函数没用,关键得开严格模式。否则CAST永远不拦溢出:
- 临时生效:
SET sql_mode = 'STRICT_TRANS_TABLES'; - 永久生效:在
/etc/my.cnf的[mysqld]段加sql_mode = STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE - 验证是否生效:
SELECT @@sql_mode;,确认输出含STRICT_TRANS_TABLES
注意:STRICT_ALL_TABLES对非事务表有风险(可能部分插入后回滚),生产环境只用STRICT_TRANS_TABLES。
CAST(INT) vs CAST(DECIMAL):精度与上限的取舍
数值转换的目标类型选错,会同时丢精度和容错空间:
- 转
INT/BIGINT:小数直接截断,且仍有硬上限(BIGINT最大±9.2×10¹⁸) - 转
DECIMAL(20,0):可存20位整数(99999999999999999999),远超BIGINT,且所有运算按定点数执行,不回绕 - 别用
DECIMAL存主键——ORM映射易出错,索引比较逻辑也不同
示例:CAST('12345678901234567890' AS DECIMAL(20,0))安全;而CAST('12345678901234567890' AS BIGINT)在多数实例中静默变9223372036854775807。
WHERE条件里隐式转换比显式CAST更危险
你以为写了WHERE id = CAST(? AS UNSIGNED)就安全?其实更大的坑在没写CAST的地方:
-
WHERE int_col = '123'→ MySQL自动把每行int_col转字符串比较,索引失效,执行计划出现Index Cond: ((int_col)::text = '123'::text) -
WHERE bigint_col = 12345678901234567890→ 字面量超BIGINT范围,MySQL可能先转成DOUBLE再比较,精度丢失 - 参数化查询中,应用传
String型ID却字段是BIGINT,驱动隐式转换失败,结果不可控
真正稳妥的做法:应用层确保传入类型与字段一致,数据库侧用WHERE int_col = ?(?为整型参数),彻底避开任何转换。
最常被忽略的一点:严格模式只管INSERT/UPDATE,不管SELECT表达式里的溢出——SELECT CAST(col AS TINYINT) + 100照样静默出错。这类计算必须在应用层预校验范围,或改用DECIMAL兜底。











