找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖
Claude、GPT 海外模型 API 接入Claude skills 从入门到精通 吴恩达亲授 AI Agent 核心技能2026 瞪哥公务员考试全攻略 行测申论一站式系统备考
Agent 文心智能蒸馏模型实战 90G 课程智泊 AI 大模型训练营 基于 LangChain 的 RAG 与提示工程实战构建企业级 AI 大脑:大模型微调与 RAG / Agent 全栈实战

4981

积分

0

好友

645

主题
发表于 1 小时前 | 查看: 4| 回复: 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;

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

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

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

如果只存同样的 "dongge"

varchar(50)
varchar(500)

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

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

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

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

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

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

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

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

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




上一篇:AI 改 AI 靠什么不失控?RSI 六十年与三根缰绳全景
下一篇:一线研究员闭门讨论:蒸馏变难后,RSI与数据成新焦点
您需要登录后才可以回帖 登录 | 立即注册

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

GMT+8, 2026-9-23 02:16 , Processed in 0.645275 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

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