VARCHAR(50) vs VARCHAR(500):存储一样大,排序却慢了 3 倍
👉这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
《项目实战(视频)》:从书中学,往事中“练”
《互联网高频面试题》:面朝简历学习,春暖花开
《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题
《精进 Java 学习指南》:系统学习,互联网主流技术栈
《必读 Java 源码专栏》:知其然,知其所以然
👉这是一个或许对你有用的开源项目
国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构
RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRM、AI大模型、IoT物联网等功能:
多模块:https://gitee.com/zhijiantianya/ruoyi-vue-pro
微服务:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK17/21+SpringBoot3、JDK8/11+Spring Boot2双版本
一个被忽视的设计规范:能短就别长
实验 1:存储空间——确实没区别
实验 2:索引查询——也基本没区别
实验 3:全表 + 排序——差距 3 倍出来了
真凶:sort_buffer 内存预估按声明长度算
决策矩阵:常见字段该给多少长度
4 个真实生产坑,按踩到概率从高到低
说到底
一个被忽视的设计规范:能短就别长
很多团队的建表规范里都有这一条:
「对可变长度字段,在满足业务的前提下,尽可能使用较短的长度」
但具体短多少?为什么要短?大部分人答不上来。最常见的「直觉式回答」是:
「
VARCHAR(50)比VARCHAR(500)节省存储」—— 错。「短一点查得快」—— 对,但只在特定 SQL 模式下成立。
要回答清楚,得做实验。下面这套实验从存储、索引查询、全表排序三个维度走一遍,最后会发现:90% 时间没区别,但有一种 SQL 模式差距 3 倍以上——这才是规范要短的真正原因。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
实验 1:存储空间——确实没区别
建两张结构完全一样、只差name字段长度的表:
CREATE TABLE category_info_varchar_50 ( id BIGINT NOTNULL AUTO_INCREMENT, name VARCHAR(50) NOTNULL, is_show TINYINT(4) NOTNULLDEFAULT0, sort INT NOTNULLDEFAULT0, -- 其他业务字段省略 PRIMARY KEY (id), KEY idx_name (name) ) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4; CREATETABLE category_info_varchar_500 ( id BIGINT NOTNULL AUTO_INCREMENT, name VARCHAR(500) NOTNULL, -- ... 其他字段同上 PRIMARY KEY (id), KEY idx_name (name) ) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;各灌 100 万条同样数据(写入数据的存储过程略,实际数据完全一致),然后查information_schema.TABLES:
SELECT table_name AS "表名", table_rows AS "记录数", TRUNCATE(data_length / 1024 / 1024, 2) AS "数据(MB)", TRUNCATE(index_length / 1024 / 1024, 2) AS "索引(MB)" FROM information_schema.TABLES WHERE table_schema = 'test_mysql_field';VARCHAR(50)表:
VARCHAR(500)表:
两张表的数据容量、索引容量完全相同——VARCHAR 是变长的,实际存储看的是写入内容长度,不是声明的最大长度。「存储空间不一样」的直觉第一关就废。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
实验 2:索引查询——也基本没区别
走索引的等值查询、IN 查询、ORDER BY 索引列查询:
-- 等值 SELECTnameFROM category_info_varchar_50 WHEREname = 'name100000'; -- 0.012s SELECTnameFROM category_info_varchar_500 WHEREname = 'name100000'; -- 0.012s -- ORDER BY 走索引 SELECTnameFROM category_info_varchar_50 ORDERBYname; -- 0.370s SELECTnameFROM category_info_varchar_500 ORDERBYname; -- 0.379s -- IN 查询(45 个值) SELECT * FROM category_info_varchar_50 WHEREnameIN (...); -- 0.011 ~ 0.014s SELECT * FROM category_info_varchar_500 WHEREnameIN (...); -- 0.012 ~ 0.014s走索引的 SQL 性能差距在 1ms 量级——可以认为没差。原因很简单:InnoDB 在 DYNAMIC / COMPRESSED 行格式下,索引 key 的单字段上限是 3072 字节——VARCHAR(500) utf8mb4满打满算 2000 字节,完整能进索引;查询走 B+Tree,比较时按实际值长度来。索引这条路上 50 和 500 没毛病。
仅在老的 REDUNDANT / COMPACT 行格式下,索引 key 上限是 767 字节——这种情况下
VARCHAR(500) utf8mb4才会被截断成 prefix 索引,但MySQL 5.7+ 默认就是 DYNAMIC,绝大多数项目不用担心。
实验 3:全表 + 排序——差距 3 倍出来了
不走索引的全表扫描 + 排序,差距瞬间出现:
无排序,两张表表现一致:
加ORDER BY name:
SELECT * FROM category_info_varchar_50 ORDER BY name; -- 1.498s SELECT * FROM category_info_varchar_500 ORDER BY name; -- 4.875sVARCHAR(50) 全表+排序耗时:
VARCHAR(500) 全表+排序耗时:
VARCHAR(500)慢了 3.25 倍。一个看着无所谓的字段长度选择,在排序场景下变成了真问题。
真凶:sort_buffer 内存预估按声明长度算
为什么会差这么多?看 SQL 执行 profile。
VARCHAR(50)的 profile——86% 时间在数据传输(Sending data):
关注两个状态变量:Created_tmp_files和sort_merge_passes:
Created_tmp_files = 3sort_merge_passes = 95
VARCHAR(500)的 profile——多了「临时表排序」环节:
Created_tmp_files = 4sort_merge_passes = 645—— 比 50 的版本暴涨 6.8 倍
sort_merge_passes是 MySQL 归并排序的次数——越大说明sort_buffer塞不下,越要靠磁盘临时文件归并,磁盘 IO 上来性能就崩。
关键来了:MySQL 在做排序前,会根据字段声明的最大长度估算每条记录在sort_buffer里的占位——VARCHAR(500)单条估到 500 字节 + 元数据开销,VARCHAR(50)估到 50 字节。同样 8MB 的sort_buffer_size:
字段长度 | 估算单条 | 装得下行数 | 100 万行需要归并次数 |
|---|---|---|---|
VARCHAR(50) | ~58 字节 | ~144,000 | ~7 轮 |
VARCHAR(500) | ~558 字节 | ~15,000 | ~67 轮 |
实际数据明明都是name123456这种十几字节的字符串,但 MySQL 不看这个——它只看声明长度。这就是规范让你「能短就短」的真正动机:在排序、分组、临时表场景下,声明长度直接决定内存利用率。
决策矩阵:常见字段该给多少长度
不是越短越好,太短了改起来更痛苦。给个参考:
字段类型 | 推荐长度 | 理由 |
|---|---|---|
用户名 / 昵称 | VARCHAR(64) | 主流业务 32-50 够用,留点余量 |
邮箱 | VARCHAR(128) | RFC 5321 上限 254,但实际 < 128 |
手机号 | CHAR(20) | 定长且固定,CHAR 比 VARCHAR 更适合 |
URL | VARCHAR(512)或 | 长 URL 现实存在,但避免 VARCHAR(2000) |
标题 / 名称 | VARCHAR(128) | 业务分类、文章标题、商品名都够 |
描述 / 富文本 | TEXT | 一旦超过 1KB 用 TEXT,别拿 VARCHAR(8000) 死撑 |
UUID / 雪花 ID | CHAR(36)/ | 定长用 CHAR,雪花用 BIGINT |
| 极不确定的开放字段 | JSON或拆出独立表 | 别用 VARCHAR(N) 当万能桶 |
为什么不无脑用 VARCHAR(255)?历史遗留——MySQL 5.0.3 之前 VARCHAR 上限是 255 字节,模板默认 255。现在 InnoDB 上 VARCHAR 最长 65535 字节,但越长在排序场景代价越大。
4 个真实生产坑,按踩到概率从高到低
坑 1:分页查询带排序,TP99 突然劣化(最常见)
ORDER BY name LIMIT 100, 10看起来很轻——前端列表分页谁不写?但只要 ORDER BY 字段没走索引、且字段是 VARCHAR(500+),sort_buffer 直接爆。监控里 TP99 飙升、慢查询日志炸出一堆Using filesort; Using temporary。
修法:要么给 ORDER BY 字段加索引,要么把字段长度收紧,要么调大sort_buffer_size(但调大全实例都受影响,要慎重)。
坑 2:临时表 GROUP BY 拼字段(常见)
SELECT category, COUNT(*) FROM ... GROUP BY category;GROUP BY 也走 sort_buffer 或临时表,逻辑同 ORDER BY。长 VARCHAR 字段做 GROUP BY 同样吃亏。
坑 3:UNION 把多张表的字段长度并起来(少见但破坏力大)
SELECT name FROM table_a -- VARCHAR(50) UNION SELECT name FROM table_b; -- VARCHAR(500)UNION 结果集字段长度按最大的那个算——本来 50 就够,UNION 一接,结果集里全按 500 估。复杂报表 SQL 把多个表的字段拼起来,性能突然崩就是这种问题。
坑 4:JSON字段当 VARCHAR 用(高级场景)
「业务字段不确定,先放 JSON」——三年后字段平均 5KB,涉及它的查询、排序、临时表全炸。JSON 比 VARCHAR(N) 对 sort_buffer 更不友好。该拆表必须拆,别拿 JSON 顶。
说到底
VARCHAR(50)和VARCHAR(500)在存储、走索引的查询上没区别——这条规范的真正意义在 sort_buffer 上。好的字段长度设计不是「能存就行」,是「让 MySQL 在排序时不被声明长度欺骗到磁盘上」。
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:
星球的内容包括:项目实战、面试招聘、源码解析、学习路线。
文章有帮助的话,在看,转发吧。 谢谢支持哟 (*^__^*)