一、面试现场:一道看似简单却暗藏杀机的题
“20亿手机号,你选int还是string存储?”
这是字节跳动一面的一道真题。一位星球粉丝面试时被问到,支支吾吾答了几句,面试官面色凝重,最后挂了。
说实话,这道题的表面是在考数据类型选择,实际上是在考三个工程思维:业务扩展性、数据容错性、思考问题全面性。
下面聊聊我的思路。
二、核心问题:为什么Int/Long都不靠谱
1、Int直接出局
手机号是11位数字,比如13728199213。
Java的int是32位,最大值2^31-1 = 2,147,483,647,约21亿。11位手机号最小也是10000000000(100亿),直接溢出。
结论:Int存手机号,从数学上就不成立。
2、Long的隐藏陷阱
那用64位的Long(对应MySQL的BIGINT)总行了吧?
坑一:前导零丢失
比如手机号01324567890,用Long存会变成1324567890。数据完整性直接被破坏。
更麻烦的是,Java不允许前导0的Long整数,编译都过不了:
Long phoneNumber = 01324567890L; // 编译报错
坑二:格式兼容性差
实际业务中,手机号可能带国家代码+86,或者带连字符137-2819-9213。这些Long都存不了。
坑三:查询效率暴跌
要查137开头的号码?用BIGINT得先转字符串再模糊匹配,索引直接失效。
结论:数值型存储破坏数据完整性,String是唯一选择。
三、进阶考察:为什么用VARCHAR(20)而不是VARCHAR(11)
好,确定用String了。面试官紧接着会问:手机号11位,为什么不用VARCHAR(11),而用VARCHAR(20)?
这是秀肌肉的时刻。
1、业务扩展性思维
VARCHAR(11)只能存纯11位数字,但业务会变:
- 国际号码:
+8613822223333(14位) - 带国家码:
008613822223333(15位) - 分机号:
13822223333#123(超11位) - 座机号:
010-62223333(含横杠)
字段长度提前留余地,避免频繁改表。这是架构师的基本嗅觉。
2、数据容错性思维
用户输入不可控:
- 带空格:
138 2222 3333 - 带横杠:
138-2222-3333
如果强制用VARCHAR(11),得在代码层严格过滤,增加复杂度。留缓冲空间,入库后清洗,更务实。
3、存储成本权衡
有人担心VARCHAR(20)浪费空间。算笔账:
VARCHAR(11):最大11字节VARCHAR(20):最大20字节- 20亿条数据相差:约18GB
对比BIGINT的16GB,这点成本换取灵活性,完全可接受。
面试官期待的答案公式:
合理长度 = 基础需求 + 国际扩展 + 容错缓冲
4、极端场景补充
如果手机号纯数字且第一位不是0,理论上可以用BIGINT。但加个注释:这是极端场景,生产环境永远不要赌这个假设。
体现思考问题全面性。
四、生产环境避坑清单
设计手机号存储,还有这些坑:
1、字符集陷阱
用utf8字符集,无法存储emoji或特殊符号。必须用utf8mb4 + utf8mb4_unicode_ci。
2、索引设计不当
一定要加唯一索引防重复:
ALTER TABLE user ADD UNIQUE INDEX idx_phone (phone);
3、数据清洗缺失
入库前统一清洗:移除空格、横杠,只保留+和数字。正则校验:^+?\d{8,20}$。
4、安全合规忽视
- 加密存储:AES加密,别明文存
- 脱敏显示:查询返回
138****3333
5、风控校验
严格版(11位纯数字):
"^1(3[0-9]|4[579]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$"
宽松版(允许国际码):
"^(\+\d{1,3})?1(3\d|4[579]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$"
五、总结
这道题考的不是技术选型,而是工程思维:
- 业务扩展性:字段设计为变化留余地
- 数据容错性:接受输入不确定性
- 思考问题全面性:成本、场景、极端情况都考虑到
下次设计字段时,多问一句:未来会不会变?

