找回密码
立即注册
搜索
发回帖 发新帖

5554

积分

0

好友

714

主题
发表于 2026-9-23 00:29:31 | 查看: 179| 回复: 0

表里有这么两个字段:

user_name varchar(50)

user_name varchar(500)

如果实际都只存一个 "zhangsan",varchar(500) 会不会比 varchar(50) 多占 10 倍空间?

不会。

这个问题我见过不少次,有人甚至为了“以后方便扩展”,建表时统一 varchar(500)、varchar(1000)。这地方我一般不这么干。

不是因为 varchar(500) 一定性能差,而是字段长度这个东西一旦随手放大,后面真正容易出问题的是索引、行大小,还有 Java 这一层的数据约束。

先看 MySQL 到底怎么存。

VARCHAR 是变长字段。

比如都是 utf8mb4:

create table customer_profile (
id bigint primary key,
    short_name varchar(50),
    long_name varchar(500)
) engine = InnoDB
default charset = utf8mb4;

两列都存:

dongge

MySQL 并不会给 short_name 提前留 50 个字符,也不会给 long_name 提前留 500 个字符。

存多少,主要就放多少。

VARCHAR 实际存储大致是:

长度信息 + 实际字符串

MySQL 官方文档也是这么定义的:VARCHAR 保存实际数据,并额外使用 1 或 2 个字节记录长度。最大可能值不超过 255 字节时使用 1 个长度字节,可能超过 255 字节时使用 2 个。

这里就有个容易漏掉的小区别。

utf8mb4 一个字符最多可能占 4 字节。

所以:

varchar(50)
最大数据长度约为 50 × 4 = 200 字节
长度信息:1 字节

varchar(500)
最大数据长度约为 500 × 4 = 2000 字节
长度信息:2 字节

也就是说,同样存 "dongge",两者实际存储空间差距非常小,不是什么 10 倍。

但这不代表:

varchar(50)

和:

varchar(500)

可以随便选。

真正的差距在后面。

第一个坑:索引

我看到一个字符串字段长度比较大时,第一反应一般不是磁盘空间,而是先看看它有没有进索引。

比如:

create table order_search (
id bigint primary key,
    buyer_name varchar(500) not null,
    biz_tag varchar(500) not null,
key idx_buyer_tag (buyer_name, biz_tag)
) engine = InnoDB
default charset = utf8mb4;

这时候就不能只看“实际业务里名字一般就十几个字”。

索引建的是字段定义允许的键值范围。

两个 varchar(500),按照 utf8mb4 最坏情况算:

500 × 4 + 500 × 4 = 4000 字节

而 InnoDB 在常见的 16KB page、DYNAMIC/COMPRESSED 行格式下,索引键长度上限是 3072 字节;页大小更小时限制还会继续下降。

这种表结构,我第一眼就不太信它能老老实实按你的想法工作。

所以经常能看到有人最后改成前缀索引:

create index idx_buyer_tag
on order_search (buyer_name(80), biz_tag(40));

能不能这么改,还得看数据区分度,不能闭着眼截 80 个字符。

至少得先查:

select
count(*) as total_rows,
count(distinct left(buyer_name, 40)) as p40,
count(distinct left(buyer_name, 80)) as p80
from order_search;

我一般宁愿多跑一次这种 SQL,也不愿意看到个长字符串就随手给它前缀索引。

第二个区别:整行大小

MySQL 对一行能够定义的数据大小是有限制的,所有列不是想写多长就写多长。

比如这种表:

create table import_record (
id bigint primary key,
    source_name varchar(5000),
    source_path varchar(5000),
    error_detail varchar(5000),
    raw_message varchar(5000)
) charset = utf8mb4;

一眼看过去就有点别扭。

四个字段如果都按照 utf8mb4 最大容量估:

5000 × 4 × 4

已经 80000 字节了。

MySQL 本身对一行所有列有 65535 字节的限制,VARCHAR 的长度字节同样要算进去;InnoDB 自己还有页内行存储和页外存储机制。

所以建表时很可能直接给你:

ERROR 1118 (42000): Row size too large

这种字段如果真的是错误详情、原始报文、第三方返回内容,我反而会重新判断它是不是应该用 TEXT,而不是一路:

varchar(1000)
varchar(5000)
varchar(10000)

往上加。

字段类型不是俄罗斯套娃。

回到 Java 的实际问题

Java 里的:

private String userName;

它不知道数据库后面到底是:

varchar(50)

还是:

varchar(500)

你完全可以往里面塞一个 300 个字符的字符串。

真正执行 INSERT 时数据库才发现:

insert into customer_profile(user_name)
values (?)

结果字段只有 varchar(50)。

生产环境一般都会开严格 SQL 模式,这时候超过长度会直接报错;非严格模式下则可能发生截断并产生 warning。MySQL 官方对这两种行为都有明确说明。

所以我更喜欢在入口把长度挡掉,而不是一路让数据跑到 DAO,最后等数据库告诉我:

Data too long for column 'user_name'

Spring 项目里可以这么写:

public record RenameRequest(
        String userName
){
public String checkedName(){
        String value = userName == null ? "" : userName.strip();

int chars = value.codePointCount(0, value.length());
if (chars == 0 || chars > 50) {
throw new IllegalArgumentException("用户名长度不合法");
        }

return value;
    }
}

为什么这里没直接写:

userName.length() > 50

因为 Java 的 String.length() 算的是 UTF-16 code unit。

碰到某些 emoji,一个你肉眼看到的字符,在 Java 里可能占两个 char。

而 MySQL 的:

varchar(50)

这个 50 表示的是字符数,不是字节数。

你也可以自己在库里看:

select
char_length('程序员东哥') as chars,
length('程序员东哥') as bytes;

CHAR_LENGTH() 看字符数。

LENGTH() 看字节数。

这两个函数我排字符集问题时经常一起用,光看 Java 日志里的字符串,很多时候根本看不出来数据库到底按多少字节存了。

如果项目用 JPA,还有一个地方别搞混:

@Column(name = "user_name", length = 50)
private String userName;

这里的 length = 50 属于持久化映射里的列长度定义,尤其会影响 Schema Generation;它不是一个可靠的接口参数运行时校验器。Jakarta Persistence 的 @Column.length 默认值本身就是 255。

想在请求入口做校验,可以再配 Bean Validation:

@Size(max = 50)
private String userName;

但再抠细一点,@Size 对 CharSequence 检查的是字符序列长度。

如果你的业务允许大量 emoji,又要求 Java 校验规则和 MySQL 字符语义尽量一致,我还是会自己用 codePointCount() 控一遍。

最后再回到最开始的问题。

如果只存同样的 "dongge":

varchar(50)
varchar(500)

实际数据占用不会差 10 倍,网络传输也不会因为声明成 500 就平白多传几百个字符。

但我仍然不会把能确定 50 个字符的字段写成 500。

用户昵称,就按业务允许的昵称长度来。

订单号能确定 64,就别写 500。

外部流水号最大 128,就写 128。

真不知道上限、而且内容本来就是一坨不可控文本,再考虑 TEXT。

VARCHAR 后面这个数字,看着只是一个长度。

表小的时候确实没什么感觉。

等它进了联合索引,旁边又塞了十几个大 VARCHAR,Java 接口还完全不做长度校验,这个数字就没那么随便了。




上一篇:Jane Street另类数据方法论:时间机器陷阱与point-in-time回测纪律
下一篇:面试官问异地多活:数据双写、脑裂保护与单元化路由怎么解?
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-11-9 19:11 , Processed in 0.099660 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表