 與 int(10) 的區(qū)別:顯示寬度背后的真相與版本演進)
先問個問題建表的時候看到int(10)你的第一反應(yīng)是什么我猜不少人和我一樣一開始以為它和varchar(10)類似表示這個字段最多只能存 10 個字符所以int(1)就只能存一位數(shù)字。這個理解錯得相當離譜但流傳又特別廣。做 MySQL 面試題收集久了你會發(fā)現(xiàn)int(1)和int(10)的區(qū)別幾乎是必問基礎(chǔ)題也是最能看出一個人到底有沒有真正落地寫過表的分水嶺。今天不繞彎子直接把結(jié)論放在最前面在 MySQL 里int(1)和int(10)在存儲、取值范圍、索引、排序等任何底層行為上完全一致它們都是同一個int類型都占 4 個字節(jié)。括號里的數(shù)字既不是長度限制也不是位數(shù)限制它只是 MySQL 里的一個“顯示寬度”概念。這篇文章主要面向剛接觸 MySQL 的同學也能幫自以為懂的老手梳理一下版本演進帶來的變化尤其是 MySQL 8.0.17 之后官方對顯示寬度的處理方式很多人還停在舊版本的認知里。1. 先搞清楚int(1) 和 int(10) 到底在比什么1.1 括號里的 M 是顯示寬度不是存儲長度先說一個最容易混淆的點。varchar(10)里的 10 是字符長度限制這個字符串最多存 10 個字符你寫第 11 個字符就會報錯。但int(M)里的 M 全稱叫 display width翻譯過來是“顯示寬度”它不是存儲長度更不是取值范圍。int 類型在 MySQL 內(nèi)部是固定長度永遠是 4 個字節(jié)你寫不寫括號、括號里寫 1 還是寫 10甚至寫 255磁盤上占的空間都一樣??梢赃@么理解int像是一個固定大小的容器容量從一開始就焊死了4 字節(jié)能裝多少就裝多少。括號里的數(shù)字只是給這個容器外面貼的一張標簽告訴客戶端“如果要用零填充顯示請補到幾位”。這張標簽改變不了容器本身的大小就像你在一個 4 升的桶上貼了“1L”或者“10L”的貼紙桶還是那個桶能裝的水還是 4 升區(qū)別僅僅是有人看貼紙時可能會誤以為桶的容量變了。還有一點值得記住顯示寬度在 MySQL 5.7 及更早版本里是有上限的最大是 255。你寫int(256)在舊版本里會直接報錯提示 display width out of range。但在 MySQL 8.0.19 之后這個限制基本失去意義因為顯示寬度已經(jīng)被官方廢棄了具體廢棄邏輯放到后面第 4 節(jié)細說。1.2 存儲和取值范圍兩個完全一樣這一節(jié)是很多人踩坑的根源。int(1)和int(10)既然都是 int那取值范圍就完全由 int 這個類型決定。有符號情況下int 的取值范圍是-2147483648到2147483647無符號情況下取值范圍是0到4294967295。換句話說int(1)完全可以存儲2147483647這個十位數(shù)它在存儲層面沒有任何限制。有些項目里字段寫成int(1)業(yè)務(wù)方就認為“這個字段只能存 0 到 9”結(jié)果某個金額超過 9 就報錯最后排查半天發(fā)現(xiàn)不是數(shù)據(jù)問題而是當初建表的人被這個括號騙了。這種問題在真實項目里出現(xiàn)過很多次尤其是從網(wǎng)上復制建表語句時特別容易留下這種歷史包袱。再補充一點性能層面的結(jié)論int(1)和int(10)的索引大小、索引比較代價、排序代價、JOIN 代價完全一樣因為它們本質(zhì)上就是同一個數(shù)據(jù)類型。你給int(1)建索引和給int(10)建索引沒有任何區(qū)別不要覺得位數(shù)寫小一點索引就更快這是不存在的。1.3 這個錯覺是怎么流傳開來的既然顯示寬度這么容易誤導人為什么還會被大量寫進建表語句我總結(jié)下來主要有三個來源。最直接的原因是把varchar的習慣遷移到了int上。很多人學 MySQL 時先學的字符串類型知道括號里的數(shù)字是最大長度于是看到別人的表里有int(10)想當然地以為這也是長度限制。網(wǎng)上大量“從零開始學 MySQL”的教程和問答里也充斥著類似說法一旦流傳開新手很難辨別。第二個來源是圖形化工具。Navicat、MySQL Workbench 這些工具在導出建表語句時經(jīng)常給你生成int(10)或者int(11)這樣的寫法。尤其是int(11)這個數(shù)字其實是有來歷的int 有符號類型能存儲最小值-2147483648算上負號一共 11 個字符所以顯示寬度取 11 就是為了完整顯示這個最小值。int(10) unsigned也類似因為無符號 int 的最大值4294967295剛好是 10 位。工具自動生成不代表這就是業(yè)務(wù)設(shè)計建議它只是照著元數(shù)據(jù)里的顯示寬度原樣導出而已。第三個來源就是歷史習慣。早期 MySQL 版本里官方文檔和不少經(jīng)典書籍里的示例表都寫著int(11)于是一代代開發(fā)者照抄。抄的人不知道為什么要寫 11但也不敢隨便改最后這種寫法就成了“行業(yè)潛規(guī)則”。你問他們?yōu)槭裁磳慽nt(10)最常見的回答就是“看別人都這么寫”。2. int(M) 唯一顯眼的作用zerofill 補零2.1 不加 zerofillM 基本就是個擺設(shè)如果在建表語句里只寫int(10)不給它加任何額外屬性那這個 10 你在查詢結(jié)果里根本感覺不到。比如你插入一個數(shù)字1查詢出來就是1它不會自動顯示成0000000001。所以可以說在不使用 zerofill 的情況下int(1)和int(10)從 SQL 執(zhí)行結(jié)果上完全無法區(qū)分。真正讓 M 起作用的是zerofill屬性中文叫“零填充”。當你把字段定義成int(10) zerofill時如果存儲的數(shù)值位數(shù)不足 10 位MySQL 在查詢結(jié)果里會自動在左邊補 0補到 10 位為止。比如插入1查詢出來是0000000001插入999查詢出來是0000000999。這里有個容易忽略的細節(jié)如果你定義的是int(1) zerofill那插入1顯示還是1因為 1 本身就已經(jīng)達到顯示寬度了。寬度不足時不會補零寬度足夠時需要補零這個邏輯和字符串填充非常像。因為int(1)的顯示寬度只有 1所以它幾乎不會觸發(fā)補零行為除非你存負數(shù)——但下面馬上要說zerofill 字段其實存不了負數(shù)。2.2 用了 zerofill 后會自動變成無符號這是 zerofill 里第二個容易踩的坑。在 MySQL 里一旦給整數(shù)列加上 zerofill 屬性該列會自動附帶 unsigned 屬性也就是變成無符號整數(shù)。官方文檔里明確寫了“ZEROFILL implies UNSIGNED”這不是可選行為而是強制的。什么意思呢如果你建了money int(10) zerofill那這個字段的取值范圍就不再是-2147483648到2147483647而是0到4294967295。你往里面插負數(shù)會直接報 Out of range數(shù)據(jù)根本寫不進去。很多人在做固定位數(shù)的編號、訂單號時指望用 zerofill 補零結(jié)果項目里又要存負數(shù)兩邊需求一沖突才發(fā)現(xiàn)這個隱含約束。從 MySQL 8.0.17 開始zerofill 屬性本身也已經(jīng)被官方標記為廢棄和顯示寬度一起進入淘汰流程。所以現(xiàn)在新項目里基本沒有任何理由再去用 zerofill 做業(yè)務(wù)邏輯。它看起來像是一個很方便的展示工具但實際會引入 unsigned 語義、版本兼容問題和跨庫遷移風險性價比很低。2.3 生產(chǎn)環(huán)境別把顯示寬度當業(yè)務(wù)約束我見過一些項目把int(4)當成“這里最多只能填四位數(shù)”來用這是非常危險的做法。因為顯示寬度根本不產(chǎn)生任何輸入限制你往int(4)里插入123456完全沒問題它照樣存進去查詢出來也照樣是123456。真正要限制數(shù)字大小要么用業(yè)務(wù)層校驗要么用更小范圍的類型比如smallint而不是指望括號里的數(shù)字。同理固定位數(shù)的業(yè)務(wù)編號、訂單號這類需求也不建議用int(M) zerofill去實現(xiàn)。原因有幾個第一補零只是查詢顯示效果導出的數(shù)據(jù)、通過客戶端查看的數(shù)據(jù)可能不帶補零數(shù)據(jù)一致性不直觀第二zerofill 隱含無符號業(yè)務(wù)字段一旦需要負數(shù)就廢了第三換數(shù)據(jù)庫或者換版本后行為可能不一致尤其 MySQL 8.0.19 之后顯示寬度基本被忽略你的int(10) zerofill在新版本里可能不再補零至少 DDL 層面已經(jīng)看不到那個 10 了。更穩(wěn)妥的方案是用 varchar 存儲固定位數(shù)的編號在業(yè)務(wù)代碼里用LPAD或者字符串格式化統(tǒng)一補零如果確實是純數(shù)字且需要參與數(shù)值運算那就用普通的 int 或 bigint展示時再格式化。數(shù)據(jù)庫層只負責存數(shù)據(jù)和保證數(shù)據(jù)完整性展示格式的活應(yīng)該交給應(yīng)用層。3. 為什么不看 int(1) 還是 int(10)要看 int 本身3.1 4 字節(jié)的 int 到底能存多少回到根子上MySQL 的整數(shù)類型是按字節(jié)數(shù)劃分的不是按括號里的數(shù)字劃分。TINYINT占 1 字節(jié)SMALLINT占 2 字節(jié)MEDIUMINT占 3 字節(jié)INT占 4 字節(jié)BIGINT占 8 字節(jié)。int(1)、int(10)、int都落在 INT 這一檔所以它們的容量天花板完全一致。字節(jié)數(shù)和位數(shù)之間的關(guān)系其實挺簡單一個字節(jié) 8 位intonation 4 個字節(jié)就是 32 位。有符號時最高位做符號位能表示的最大值就是2^31 - 1也就是 2147483647最小值是-2^31也就是 -2147483648。無符號時所有位都用來表示數(shù)值最大值就是2^32 - 1即 4294967295。只要理解了這一層就不會再被括號里的數(shù)字帶偏。這里可以順手記住一個常用判斷int有符號最大值是十位數(shù) 2147483647所以如果你的業(yè)務(wù)主鍵、自增 ID 有可能超過這個數(shù)比如幾億用戶表、日志流水表直接上bigint不要心存僥幸。顯示寬度救不了你類型選錯才是真正的大問題。3.2 int(1) 和 tinyint(1) 是兩碼事很多人還會把int(1)和tinyint(1)搞混因為它們都帶有(1)看起來好像差不多。實際上差別非常大int(1)是 4 字節(jié)的 int 類型取值范圍是完整的 int 范圍tinyint(1)是 1 字節(jié)的 tinyint 類型有符號范圍只有-128到127。它們的共同點是顯示寬度都寫著 1但底層數(shù)據(jù)類型完全不同。tinyint(1) 在 MySQL 生態(tài)里還有一個特殊身份它經(jīng)常被當作布爾類型使用。MySQL 里的BOOL和BOOLEAN其實都不是獨立的類型而是TINYINT(1)的別名。你建表寫flag BOOLEAN執(zhí)行SHOW CREATE TABLE看到的往往就是tinyint(1)。很多編程語言的驅(qū)動、ORM 框架在讀取元數(shù)據(jù)時也約定俗成地會把tinyint(1)映射成布爾值。所以這里有一條實用建議想存布爾狀態(tài)用tinyint(1)而不是int(1)。如果你用了int(1)存 0/1功能上沒問題但白白浪費 3 個字節(jié)而且一些 ORM 不會把它識別成布爾類型代碼里還要額外做轉(zhuǎn)換屬于給自己找麻煩。等到 MySQL 8.0.19 之后整數(shù)顯示寬度被廢棄唯獨tinyint(1)作為特殊情況被保留下來也正是因為 MySQL 生態(tài)里有大量代碼依賴tinyint(1)的布爾語義。3.3 M 不影響索引、排序和比較既然顯示寬度只是展示屬性那它對數(shù)據(jù)庫行為的影響面其實非常小。索引自然不用說了int(1)和int(10)建出來的索引結(jié)構(gòu)完全一樣B 樹的比較邏輯完全按 int 數(shù)值來不會因為顯示寬度不同而走不同的路徑。排序也是同一個道理。很多人問“mysql 排序”時遇到數(shù)字排序亂掉的問題通常是把數(shù)字存成了 varchar導致排序按字符串字典序走出現(xiàn) 1、10、2 這種結(jié)果。但如果字段是正兒八經(jīng)的 int 類型無論你寫int(1)還是int(10)排序都嚴格按數(shù)值大小來絕不會出現(xiàn)“1 排在 10 前面”這種字符串錯覺。WHERE 條件也一樣。WHERE id_a 1和WHERE id_b 1執(zhí)行計劃、查詢性能沒有差異。退一步說如果你真想讓數(shù)據(jù)庫層面的整數(shù)帶上前導零去排序、去展示那也得靠 zerofill 或者字符串類型普通的int(1)幫不上任何忙。4. MySQL 版本演進8.0.17 之后這個問題會被歷史淘汰4.1 官方為什么要廢棄顯示寬度看到這里你應(yīng)該也感覺到了int 顯示寬度這個設(shè)計相當別扭。它既不能限制存儲又不參與運算只在極少數(shù)配合 zerofill 的場景下影響查詢結(jié)果卻成功誤導了一大批開發(fā)者。MySQL 官方顯然也意識到了這個問題所以在 8.0.17 版本中正式將整數(shù)顯示寬度標記為廢棄并在后續(xù)版本中逐步移除了對它的支持。官方廢棄的理由其實很直白顯示寬度本質(zhì)上是客戶端展示層的東西不該出現(xiàn)在數(shù)據(jù)庫字段定義里。它給開發(fā)者造成了一種虛假的安全感讓人覺得括號里的數(shù)字能控制輸入長度實際上根本沒有約束力。再加上 zerofill 隱含 unsigned 這種隱藏語義進一步增加了使用成本整體設(shè)計得不償失廢棄只是時間問題。4.2 升級到 8.0.19 后建表行為的變化從 MySQL 8.0.19 開始整數(shù)類型的顯示寬度正式不再支持除了tinyint(1)這個布爾特例被保留其他int(1)、int(10)、int(11)之類的寫法在 DDL 解析后都會被忽略。你在建表語句里仍然可以寫int(10)而不報錯但執(zhí)行完再SHOW CREATE TABLE看到的通常就只是int不再有括號里的數(shù)字。這個變化對存量數(shù)據(jù)沒有任何影響因為底層存儲的始終是 4 字節(jié) int顯示寬度從來不會改變數(shù)據(jù)。真正值得注意的是元數(shù)據(jù)層面的差異如果你還在用 MySQL 5.7腳本里SHOW CREATE TABLE會原樣輸出int(10)如果升級到 8.0.19同樣一張表導出的 DDL 可能就變成int了。對于那些靠字符串比對 DDL 的巡檢工具、表結(jié)構(gòu) diff 工具、數(shù)據(jù)遷移平臺來說這種輸出差異需要提前適配。4.3 老 DDL 和工具生成的腳本怎么辦如果你維護的是老項目表結(jié)構(gòu)里到處都是int(10)、int(11)不要慌這些字段不需要重建表數(shù)據(jù)也不會出問題。顯示寬度被忽略之后最直接的影響只是元數(shù)據(jù)展示變了應(yīng)用代碼讀到的數(shù)據(jù)庫類型仍然是 int索引、主鍵、外鍵邏輯一概不變。真正需要動手檢查的是自動化工具鏈。比如你用 pt-online-schema-change 做在線 DDL 變更用 gh-ost 做無鎖表結(jié)構(gòu)遷移或者用自研的比對系統(tǒng)去比對測試庫和生產(chǎn)庫結(jié)構(gòu)當兩端 MySQL 版本不一致時可能出現(xiàn)“明明邏輯相同但字段定義字符串不同”的判定結(jié)果。建議在臨時環(huán)境里做一次 5.7 到 8.0 的升級演練把建表腳本重新導出統(tǒng)一改造成不帶顯示寬度的寫法讓工具鏈盡早適應(yīng)新格式。關(guān)于新項目的建表規(guī)范我的態(tài)度很明確直接寫int不要帶括號。沒有 zerofill 需求時int(1)和int(10)都是畫蛇添足有顯示需求時也該用應(yīng)用層格式化而不是靠數(shù)據(jù)庫的廢棄特性。保持 DDL 干凈比保護一個莫名其妙的舊習慣重要得多。5. 自己動手驗證一遍5 分鐘消除所有疑慮5.1 建表插入看真實存儲效果說再多不如跑一遍。下面這組 SQL 在 MySQL 5.7 或 8.0.18 及更早版本上執(zhí)行能完整看到顯示寬度和 zerofill 的效果如果你用的是 8.0.19也可以執(zhí)行只是最后一節(jié)SHOW CREATE TABLE的輸出里可能不再保留括號里的數(shù)字。CREATE TABLE int_demo ( id_a INT(1), id_b INT(10), id_c INT(1) ZEROFILL, id_d INT(10) ZEROFILL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO int_demo VALUES (1, 1, 1, 1); INSERT INTO int_demo VALUES (999, 999, 999, 999); INSERT INTO int_demo VALUES (2147483647, 2147483647, 2147483647, 2147483647);注意第三行插入的數(shù)字是 int 有符號最大值 2147483647四個列都能正常接收。如果你插入的是一億以上的數(shù)據(jù)id_c和id_d因為 zerofill 隱含 unsigned也能接收更大的值但id_a和id_b到了 4294967295 就會報 out of range這個對比很能說明 unsigned 帶來的邊界差異。5.2 查詢結(jié)果對比一下執(zhí)行下面這條查詢SELECT id_a, id_b, id_c, id_d FROM int_demo;在支持顯示寬度的版本里預(yù)期結(jié)果是這樣的列名類型定義插入 1 顯示插入 999 顯示插入 2147483647 顯示id_aint(1)19992147483647id_bint(10)19992147483647id_cint(1) zerofill19992147483647id_dint(10) zerofill000000000100000009992147483647可以看到id_a雖然是int(1)但它照樣顯示 999 和 2147483647完全沒有長度限制。id_d因為帶int(10) zerofill在數(shù)值不足 10 位時自動補零插入 1 顯示為0000000001插入 999 顯示為0000000999。而當數(shù)值超過顯示寬度時比如 2147483647 本身是 10 位已經(jīng)達到int(10)的顯示寬度所以不再補零。這里還有個細節(jié)值得順手驗證id_c是int(1) zerofill但插入 999 后顯示仍然是999MySQL 不會因為顯示寬度不足就截斷數(shù)據(jù)。它只會做補零從來不做截斷這一點也和字符串類型的行為完全不同。5.3 補零只是顯示存儲和運算仍是數(shù)字再用兩條查詢驗證 zerofill 的“虛”的一面。第一條用數(shù)字條件匹配SELECT id_a, id_b, id_d FROM int_demo WHERE id_d 1;如果你插入了第一行(1, 1, 1, 1)這條查詢能正常命中說明雖然id_d查詢顯示為0000000001但它在數(shù)據(jù)庫里存的仍然是數(shù)值 1比較時也按數(shù)值 1 處理不會因為顯示寬度變成字符串 “0000000001”。第二條做一次算術(shù)運算SELECT id_d, id_d 0 AS numeric_val FROM int_demo WHERE id_a 1;id_d 0的結(jié)果是 1而不是 0000000001。這從底層證明了補零只是查詢結(jié)果的展示效果存儲、計算、比較全部走 int 數(shù)值邏輯??赐赀@幾條 SQLint(1)和int(10)的區(qū)別應(yīng)該就不會再有任何疑問了。6. 實戰(zhàn)常見問題與面試速查6.1 高頻問題排查清單問題正確答案容易踩的坑int(1) 是不是最多只能存 9不是int(1) 仍是完整 int 范圍能存到 21 億以上把顯示寬度當成輸入長度限制int(10) 是不是最多只能存 10 位不是有符號 int 上限 2147483647 就是 10 位但這是類型決定不是寬度決定以為 10 是位數(shù)上限int(10) 和 int(11) 誰存得更多一樣多都是 int存儲范圍完全一致以為 11 比 10 多一位容量寫 int(255) 可以嗎舊版本顯示寬度上限是 255可以但不代表能存 255 位以為 255 是 255 位數(shù)int(10) unsigned 是什么意思無符號 int范圍 0 到 429496729510 是顯示寬度忽略 unsigned 帶來的范圍變化用了 zerofill 能存負數(shù)嗎不能zerofill 隱含 unsigned插入負數(shù)直接報錯拿 zerofill 做編號時突然存不進負數(shù)這張表基本覆蓋了我在社區(qū)里看到的高頻誤解。如果踩過其中任何一個把第 5 節(jié)的實驗 SQL 跑一遍比看任何文檔都管用。6.2 面試這樣回答才不翻車面試里被問到int(1)和int(10)的區(qū)別建議按三個層次回答既展示基礎(chǔ)扎實又體現(xiàn)對版本演進的關(guān)注。第一層直接說結(jié)論存儲層面沒區(qū)別都是 4 字節(jié) int取值范圍完全一致索引、排序、性能也沒有任何差異。第二層解釋括號里的數(shù)字是顯示寬度 display width主要配合 zerofill 使用不加 zerofill 時沒有任何實際效果。第三層補充版本演進從 MySQL 8.0.17 開始顯示寬度被廢棄8.0.19 之后整數(shù)類型不再支持顯示寬度僅tinyint(1)因為布爾語義被保留。如果面試官追問為什么老建表語句里老看到int(11)就把我前面說的原因拋出來int 有符號最小值是 -2147483648算上負號共 11 個字符所以顯示寬度取 11 是為了完整顯示最小值無符號 int 最大值是 4294967295共 10 位所以很多工具導出int(10) unsigned。能說到這個層面基本就是高分答案了。6.3 我在實際項目里的體會最后說點實在的。我最早寫表結(jié)構(gòu)時同樣以為int(1)只能存一位數(shù)后來在一場評審會上被 DBA 追問為什么總額字段要定義成int(1)當場翻官方文檔才徹底搞明白。那次經(jīng)歷讓我養(yǎng)成了一個習慣所有 int 列建表時不寫括號里的數(shù)字讓類型保持它本來的樣子需要 0/1 布爾就用 tinyint(1)需要更大范圍就上 bigint展示格式一律交給應(yīng)用層。這種習慣在 MySQL 8.0.19 之后尤其舒服因為官方已經(jīng)把顯示寬度移除寫不寫括號最終都會被忽略。與其守著舊習慣繼續(xù)寫int(10)不如現(xiàn)在就開始簡化 DDL。再遇到同事問“int(1) 和 int(10) 哪個大”把這篇里的 SQL 丟給他跑一遍他可能比你先頓悟。