TiDB AUTO_RANDOM 超大主键前端精度丢失排查:JavaScript Number 限制与解决方案
前端长整型主键“失踪”记
——一次
ArrayIndexOutOfBoundsException
的排查全过程
一、事故现场
最近在维护 SMS-OFFICE 后台系统时,运维同事反馈:
点击「短信详情」或「邮箱账号详情」时,偶尔弹窗空白、日志报错:
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
抓了一条完整链路,发现请求 URL 是:
/secured/lookSmsMessage.html?contentId=7205759403792883606
而数据库中明明存在这条记录!为什么 SELECT … WHERE content_id = ?
一条都查不到?
二、线索一:前端 JSON 对象异常
打开浏览器 DevTools ➜ Network ➜ Preview,注意到 DataTables 接口返回:
{"contentId": 7205759403792883606,…
}
字段是 裸数字,但和数据库里的大整数完全一致。那问题出在哪里?
三、线索二:JS Number 的“安全整数”
JavaScript 只有一种数值类型 Number
,实现为 IEEE-754 双精度浮点。
最大安全整数 为
Number.MAX_SAFE_INTEGER === 9_007_199_254_740_991
任何大于此值的整数,低位全部失真。
主键 | 十进制 | 是否 > 9e15 |
---|---|---|
720 575 940 379 288 3606 | ≈ 7.2 × 10¹⁸ | ✔ |
288 230 376 151 801 751 | ≈ 2.88 × 10¹⁷ | ✔ |
在控制台验证一下:
JSON.stringify(7205759403792883606) // "7205759403792884000"
浏览器早在解析 JSON 时,就把它“改写”成了一个近似值 ——
后端再跟这个错误 ID 去查数据库,当然一条也没有。
四、导致的连锁反应
-
DataTables 渲染
列内contentId
被当成 number 存进rowData
,精度丢失。 -
按钮拼接 URL
onclick="lookData(' + rdata.contentId + ')"
结果
rdata.contentId
已经变成错误值。 -
后端查询为空 ➜
list.get(0)
抛IndexOutOfBoundsException
.
五、最终定位:纯前端精度问题
-
数据库:MySQL / TiDB 的 BIGINT,范围 ±9 × 10¹⁸,没问题。
-
后端:Java
long
同样能装下。 -
真正掉链子 的是浏览器的
Number
精度。
六、修复方案
1. 让主键永远当「字符串」
- onclick="lookData(' + rdata.contentId + ')"
+ onclick="lookData(\'' + rdata.contentId + '\')"
-
只要在拼接时加一对 引号,JS 就会把它当作字符串传递。
-
后端 Spring MVC 可以自动把字符串转
Long
;
如果你愿意,也可以把参数类型改成String
,然后Long.valueOf()
。
2. Mapper / ResultMap 调整
<result column="content_id" property="contentId" jdbcType="VARCHAR"/>
3. 防御式编码
if (list.isEmpty()) {throw new NotFoundException("记录不存在");
}
4. 全链路自检脚本
// Chrome DevTools 快速检测 big int
function hasUnsafeId(json, key='contentId'){return json.some(r => Math.abs(r[key]) > Number.MAX_SAFE_INTEGER);
}
七、最佳实践小结
层 | 建议 |
---|---|
前端 | 所有主键字段一律用字符串;不要对大整数做数学运算。 |
后端 | 接口、DTO、Mapper 保持与前端一致的 String/Long;空列表先判空。 |
数据库 | 如需水平分片可继续用 AUTO_RANDOM ;只是传输层别当数值。 |
日志 | 记录原始请求参数,便于比对是否被截断。 |
八、一句话总结
当你在前端看到 18 位以上的主键时,第一反应:“加引号!”
避免 JS Number 精度地雷,你的分页、详情、批量操作都会更稳。