字節一面:20億手機號存儲選int還是string?varchar還是char?為什么?
前言
大家好,我是田螺。
最近一位星球粉絲說,他去面試了字節,問了這么一道題,20億手機號存儲,選int還是string?varchar還是char?為什么?
他支支吾吾回答了幾句,好像看起來,面試官面色凝重,對他不是很滿意,果然最好還是掛了。。。
本文跟大家聊聊我的思路。
- 20億數據,用Int存儲存在哪些問題?
- 面試官的隱藏考察點
- 日常開發避坑點
1. 20億數據,用Int或者BigInt能有在哪些問題?
1.1 int存得下11位數字嘛?
首先,我們都知道手機號,是11位的數字,比如13728199213.
在Java中,int是 32位,最大值為 2^31 - 1 = 2,147,483,647。約等于 2×10?。顯然,如果用int,根本存不下 11位的手機號碼。
要想存得下,得用64位的Long類型,也就是對應數據庫的bigInt。
1.2 數據完整性
例如手機號01324567890,用Long存會變成1324567890,直接破壞數據完整性。
Long phoneNumber =01324567890L; //編譯報錯,Java不允許前導0的Long整數
并且,有時候,有些手機號可能包含國家代碼如(+86),或者有些時候,是有連字符的,比如137-2819-9213. 這些原因都導致不能用整型類型存儲。
1.3 查詢麻煩
比如,你要查找,手機號是137開頭的手機號號碼,如果用BigInt(Long類型)需先轉字符串再模糊匹配,效率暴跌。
2. 用String有哪些好處
- 保真:數字、符號、前導零全能存,原樣保留。
- 靈活:支持模糊查詢、國際號碼,擴展無憂。
- 省心:無需擔心溢出或格式轉換問題。
CREATE TABLE user_tab (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用戶ID',
phone_number VARCHAR(20) NOT NULL COMMENT '手機號',
PRIMARY KEY (id),
UNIQUE KEY idx_phone (phone_number)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用戶表';
2. 面試官的隱藏考察點
面試的時候,面試官主要考察候選人的一些業務擴展性、數據容錯性、思考問題全面性等能力。我們先通過:為什么用 VARCHAR(20) 而不是 VARCHAR(11),來給面試官秀一波肌肉~~
2.1 為什么用 VARCHAR(20) 而不是 VARCHAR(11)
我們就拿手機號來說,為什么更建議用 VARCHAR(20),而不是VARCHAR(11)呢?
因為我們都知道,手機號是11位的,為什么不直接用VARCHAR(11)呢?
如果你日常開發中,就有思考數據容錯性習慣的話,就會想到:
- 如果遇到國際號碼:+8613822223333(14位)
- 帶國家碼的號碼:008613822223333(15位)
- 分機號:13822223333#123(超11位)
這些場景,都會導致VARCHAR(11)報錯崩盤。
其次就是業務擴展性思考:VARCHAR(11)只能存純11位數字,假設未來業務需要:
- 支持座機號(如010-62223333,含橫杠)
- 支持虛擬號(如17012341234-5678)
- 支持其他登錄方式(如郵箱+手機號混合存儲)
因此,字段長度和類型需提前為業務變化留余地,避免頻繁改表。這就是日常開發中的,業務擴展性思維思考。
還有數據容錯性思考,
- 輸入不可控性:用戶可能輸入帶空格/符號的號碼(如138 2222 3333),直接存原始值更方便清洗。
- 設計妥協:若強制用VARCHAR(11),需在代碼層嚴格過濾非數字字符,增加復雜度。
還有思考問題全面性,比如存儲成本思考。
- VARCHAR(11):最大占 11字節(utf8mb4下1字符占4字節,但數字和+號只占1字節)
- VARCHAR(20):最大占 20字節
- 20億數據相差僅約 18GB(和用BIGINT的16GB對比,總成本仍可接受)。
所以面試官期待的答案公式
合理長度 = 基礎需求 + 國際擴展 + 容錯緩沖
當然,這個不是固定答案,主要還是面試的時候,你回答面試官的思路和表達,最好體現你有這幾個方面的思考:業務擴展性、數據容錯性、思考問題全面性。
2.2 極端場景
如果手機號是純數字,并且第一位不是0的話,可以用BIGINT的,但是永遠不要使用INT。通過這些極端場景的舉例,也體現你思考問題全面性的一個能力。
3. 日常開發避坑點
設計手機號存儲的時候,有哪些需要避的坑的。
主要有這幾個吧:
3.1 字段長度設計過小
用 VARCHAR(11) 只存純數字,遇到 +8613822223333(14位)直接截斷。
用 VARCHAR(20) 兼容國際號、分機號(如 13822223333#123)。'
3.2 字符集和排序規則
使用 utf8 字符集,無法存儲 emoji 或特殊符號
用 utf8mb4 + utf8mb4_unicode_ci,兼容所有 Unicode 字符(如 + * #)。
3.3 索引設計不當
未對手機號加唯一索引,導致重復數據。
添加 UNIQUE 約束:ALTER TABLE user ADD UNIQUE INDEX idx_phone (phone);
3.4 數據清洗與校驗缺失
用戶輸入 138-2222-3333 或 138 222 23333,直接存儲導致格式混亂。
- 入庫前統一清洗:移除空格、橫杠等符號,只保留 + 和數字。
- 正則校驗:例如 ^+?\d{8,20}$(允許帶 + 號的 8~20 位數字)。
3.5 忽視隱私與安全
明文存儲手機號,泄露用戶隱私。
- 加密存儲:使用 AES 加密或數據庫內置加密函數。
- 脫敏顯示:查詢結果返回 138****3333。
3.5 風控校驗
// 嚴格校驗(11位純數字,無國際碼)
String regex = "^1(3[0-9]|4[579]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\\d{8}$";
// 寬松校驗(允許帶國際碼,如+86 13812345678)
String looseRegex = "^(\\+\\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}$";