“给用户名字段用 VARCHAR(50)
还是 VARCHAR(200)
?反正都是变长的,开大点没关系吧?” “这个状态字段只有几个数字,用 INT(1)
是不是比 INT(10)
更省空间?”
这些问题,是否也曾在你的脑海中盘旋?在数据库设计中,VARCHAR
和 INT
类型后面的数字 (N)
和 (M)
,像一对神秘的孪生兄弟,看似相似,实则天差地别。
目录
一. 核心谜题:VARCHAR(N) vs INT(M),数字代表什么?
VARCHAR(10) vs VARCHAR(100):空间占用对比
一. 核心谜题:VARCHAR(N)
vs INT(M)
,数字代表什么?
-
VARCHAR(N)
中的N
代表的是最大可存储的字符数。它定义了该字段的上限,告诉 MySQL:“这个字段最多能放 N 个字符,多了就报错!” -
INT(M)
中的M
代表的是显示宽度 (Display Width)。它是一个纯粹的、与存储无关的元数据,仅在与ZEROFILL
属性配合使用时,才在客户端显示结果时起到左侧补零、凑足 M 位宽度的作用。它不影响存储空间,也不限制存入值的范围。
二. VARCHAR(N)
:N 是上限,而非长度
VARCHAR
是 "Variable-length Character String" 的缩写,意为“可变长度字符串”。
底层存储原理:变长的艺术
无论是在 MyISAM 还是 InnoDB 中,VARCHAR
类型的实际存储空间都由两部分组成:
-
内容本身 (Content):你实际存入的字符串数据。
-
长度前缀 (Length Prefix):一个或两个字节,用于记录内容的实际长度。
这个“长度前缀”是实现变长的关键。当 MySQL 读取 VARCHAR
数据时,它会先读取这个前缀,得知后面紧跟着的内容有多长,然后再精确地读取相应长度的数据。
-
如果列的最大长度
N
小于等于 255 个字符,则使用 1 个字节来存储长度。 -
如果列的最大长度
N
大于 255 个字符,则使用 2 个字节来存储长度。
注意: 这里的长度单位取决于表的字符集。对于 utf8mb4
,一个字符最多可能占用4个字节。
VARCHAR(10)
vs VARCHAR(100)
:空间占用对比
现在,我们来解答那个经典问题。假设我们有一张使用 utf8mb4
字符集的表,里面有两个字段:v10 VARCHAR(10)
和 v100 VARCHAR(100)
。
当我们同时向这两个字段存入字符串 'hello'
时:
-
v10
(VARCHAR(10)
) 的存储:-
内容
'hello'
包含 5 个字符。 -
长度为 5,小于 255,所以长度前缀占用 1 个字节。
-
总占用空间 = 1 (长度) + 5 * (字符平均字节数) = 1 + 5 = 6 字节 (假设是纯英文字符)。
-
-
v100
(VARCHAR(100)
) 的存储:-
内容
'hello'
包含 5 个字符。 -
长度为 5,小于 255,所以长度前缀占用 1 个字节。
-
总占用空间 = 1 (长度) + 5 * (字符平均字节数) = 1 + 5 = 6 字节。
-
惊人的结论: 当存储相同内容时,VARCHAR(10)
和 VARCHAR(100)
占用的物理空间是完全相同的!N
的大小只决定了“天花板”有多高,而不决定你脚下的“地板”有多厚。
只有当 N
从小于等于255跨越到大于255时,长度前缀才会从1字节变为2字节,导致微小的存储差异。例如,VARCHAR(255)
和 VARCHAR(256)
在存储相同短字符串时,后者会多占用1个字节的长度前缀。
存储结构图解
从上图可见,只要最大长度N不超过255,存储相同内容时,其结构完全一致。当N超过255(如VARCHAR(300)),长度前缀会变为2字节。
三. INT(M)
:M 是宽度,而非容量
INT
或 INTEGER
是定长整数类型。
底层存储原理:固定的力量
与 VARCHAR
不同,INT
家族(TINYINT
, SMALLINT
, MEDIUMINT
, INT
, BIGINT
)都是定长 (Fixed-length) 类型。这意味着,无论你存入的数字是 1
还是 10000
,它占用的物理存储空间都是固定不变的。
-
TINYINT
: 1 字节 -
SMALLINT
: 2 字节 -
MEDIUMINT
: 3 字节 -
INT
: 4 字节 -
BIGINT
: 8 字节
INT(M)
中的 M
与存储完全无关。它只在一种情况下起作用:当该列同时设置了 ZEROFILL
属性时。
例如,一个字段定义为 id INT(5) ZEROFILL
:
-
当你存入
123
,查询时会显示00123
。 -
当你存入
123456
,它不会被截断,而是会完整显示123456
。M
不会限制值的范围。
INT(1)
vs INT(10)
:空间占用对比
现在我们来分析 INT(1)
和 INT(10)
。
-
INT(1)
:它是一个标准的INT
类型,占用 4 个字节。 -
INT(10)
:它也是一个标准的INT
类型,同样占用 4 个字节。
结论: INT(1)
和 INT(10)
占用的存储空间永远是相同的,都是 4 个字节。M
的值对存储空间没有任何影响。将 INT
的 M
值设为 1,并不能帮你节省任何空间。如果你想节省空间,应该选择更小的整数类型,如 TINYINT
。
存储结构图解
上图清晰地展示了,无论是 INT(1)
还是 INT(10)
,无论是存储小数字还是大数字,它们都固定占用 4 个字节的存储空间。
四. 终极对决:空间占用与性能考量
空间占用总结表
类型 | 存储内容 | 占用空间 (假设纯英文) | 解释 |
---|---|---|---|
VARCHAR(10) |
| 1 (长度) + 4 (内容) = 5 字节 | 变长,按需分配 |
VARCHAR(100) |
| 1 (长度) + 4 (内容) = 5 字节 | 变长,与 |
INT(1) |
| 4 字节 | 定长,与 |
INT(10) |
| 4 字节 | 定长,与 |
性能影响:变长与定长的博弈
虽然在存储相同短内容时,VARCHAR(10)
和 VARCHAR(100)
空间占用一样,但在性能上,选择一个更精确的 N
仍然是有意义的:
-
内存消耗:当 MySQL 在内存中(例如,为了排序或创建临时表)处理数据时,它可能会为
VARCHAR
分配N
定义的最大长度。一个过大的N
(如VARCHAR(2000)
) 可能会导致在内存中分配不必要的空间,尤其是在使用MEMORY
存储引擎的临时表时。 -
索引效率:索引对字段的长度有限制。虽然
VARCHAR
的索引长度可以自定义,但一个更短的列定义从根本上说更有利于索引效率。 -
数据验证:
N
是最后一道数据完整性防线,一个合理的N
可以防止异常的超长数据被插入。
而定长类型(如 INT
)在处理上通常更简单、更快速。因为不需要计算长度,CPU 在处理定长字段时效率更高,行在内存中的定位也更直接。
五. 设计准则与最佳实践
-
为
VARCHAR
选择合理的N
:遵循“最小化”原则。评估你的业务场景,为字段选择一个既能满足当前和可预见的未来需求,又不过于宽泛的N
值。例如,用户名可以设为VARCHAR(50)
,而不是VARCHAR(255)
。 -
正确选择
INT
家族:不要再纠结于INT(M)
的M
。根据你的数值范围选择最合适的类型。-
用户状态(0-10):
TINYINT
(1字节) 足够。 -
文章点赞数:
INT
(4字节) 或BIGINT
(8字节) 可能更合适。 -
主键 ID:
INT UNSIGNED
或BIGINT UNSIGNED
是常用选择。
-
-
忘记
ZEROFILL
:ZEROFILL
是一个很少使用的遗留特性,它会自动为列添加UNSIGNED
属性。在应用层处理格式化通常是更好的选择。除非有特殊需求,否则可以忽略INT
后面的(M)
。 -
VARCHAR
vsCHAR
:如果一个字符串字段的长度是固定的(如 MD5 摘要、UUID),使用定长的CHAR
类型通常比VARCHAR
性能更好。
通过遵循这些准则,你不仅能设计出存储高效的数据库,还能在性能上抢占先机。
六. 附录:图表
VARCHAR 存储结构图 :
INT 存储结构图 :