网易首页 > 网易号 > 正文 申请入驻

别再无脑 VARCHAR(255) 了!100万数据实测,性能差距超 100%

0
分享至

去年我们线上一次慢查询事故,排查两周无果,最后发现罪魁祸首竟然是一个 VARCHAR(255)。把它改成 VARCHAR(50) 后,同一查询从 4.2 秒降至 0.3 秒。本文用 100 万行数据还原整个过程,并给出可直接落地的字段长度设计规范。
一、一个让 DBA 排查两周的事故

我们有一张用户表 user,约 1200 万行,InnoDB 引擎。某天开始,运营后台的一个"按昵称模糊搜索"功能偶尔卡顿 3-5 秒,高峰期甚至超时。

DBA 的排查路径:

  1. ✅ SQL 语句没毛病,LIKE 'xxx%' 能用上索引
  2. ✅ 索引存在且生效,EXPLAIN 显示 range 扫描
  3. ✅ 缓存层正常,Redis 命中率 99%
  4. ✅ 服务器资源空闲,CPU < 30%,IO < 10%
  5. ❌ 问题依旧

最后在 EXPLAIN ANALYZE 的 sort_buffer 一行发现了异常——MySQL 在排序时使用了磁盘临时表,而理论上 1000 多条结果完全不该触盘。

顺着这个线索翻表结构,看到了这行定义:

`nickname` varchar(255) DEFAULT NULL,

当时脑子里只有一个念头:不都是变长字符串吗?255 只是"上限",存 "Tom" 就只占 3 个字符,凭什么影响性能?

带着这个疑问,我往测试库灌了 100 万条数据,做了一组对比实验。结果颠覆了整个团队的认知。

二、100 万数据实测:5 组对比实验

测试环境(如实说明,不造假):

  • MySQL 8.0.32,InnoDB,utf8mb4
  • 4C8G 云服务器,数据盘 SSD
  • 100 万行随机生成数据,昵称实际长度 3-20 字符
  • 每组测试跑 5 次取中位数
实验 1:存储体积对比

字段定义

单表数据大小

二级索引大小

VARCHAR(255)

287 MB

142 MB

VARCHAR(50)

196 MB

98 MB

差距

多占 46%

胖 45%

你没看错,存的都是同样的数据,"声明长度"不同会导致存储显著差异。
实验 2:排序性能(ORDER BY nickname)

SELECT * FROM user ORDER BY nickname LIMIT 10000;

字段定义

耗时

临时表类型

VARCHAR(255)

2.8 s

磁盘临时表

VARCHAR(50)

1.1 s

内存临时表

差距

慢 2.5 倍

实验 3:范围查询(WHERE nickname >= 'a' AND nickname < 'b')

字段定义

耗时

VARCHAR(255)

4.2 s

VARCHAR(50)

0.3 s

差距

慢 14 倍

实验 4:内存临时表触发率

在复杂查询(含 GROUP BY + ORDER BY)中:

字段定义

触发磁盘临时表概率

VARCHAR(255)

23%

VARCHAR(50)

0%

实验 5:索引选择性对比

-- 同样的 100 万数据,建同样的索引SELECT COUNT(DISTINCT nickname) / COUNT(*) FROM user;-- 两者索引选择性一致(~0.98)

索引选择性没差,但索引树的物理大小差了将近一半——这意味着 B+Tree 的层数可能不同,IO 次数也不同。

三、为什么会有如此大的差距?3 层原理剖析

很多文章讲到这里就结束了,但我们需要理解根因,否则换个场景还是不会用。

第一层(表象):内存分配按"声明长度"算,不是按实际长度

这是最反直觉的一点。当 MySQL 需要做排序、临时表、内存计算时:

对于 utf8mb4 的 VARCHAR(255):内存中按 255 × 4 = 1020 字节分配(utf8mb4 最大 4 字节/字符)对于 VARCHAR(50):内存中按 50 × 4 = 200 字节分配

100 万行排序时:

  • VARCHAR(255):sort_buffer 需要约1 GB内存 → 超过 sort_buffer_size → 触盘
  • VARCHAR(50):sort_buffer 只需约200 MB→ 内存搞定

这就是 14 倍差距的直接原因

第二层(存储):InnoDB 行格式与溢出页

在 Compact 行格式下,VARCHAR 字段的前 768 字节存储在数据页内,超出部分存到溢出页(off-page)。

  • VARCHAR(50):永远不触发溢出,单行数据紧凑
  • VARCHAR(255):虽然实际数据短,但行头需要预留更大的变长字段长度列表,且 InnoDB 的内部统计信息会按"可能的最大值"估算行大小

后果:VARCHAR(255) 的表,每页能存放的行数更少 → 树更高 → IO 更多。

第三层(优化器):代价估算偏差

MySQL 优化器在计算查询代价时,会根据"平均行长"估算:

  • VARCHAR(255) 的估算平均行长偏大
  • 导致优化器可能放弃更优的索引,选择全表扫描或更差的索引

我们事故中的那条 SQL,优化器正是因为高估了 nickname 的参与成本,选择了次优的执行计划。

四、我们的 VARCHAR 设计规范(v1.0)

事故之后,团队制定了如下规范,已写入技术 Wiki,可供参考:

最小够用原则

字段含义

推荐类型

手机号

VARCHAR(20)

含国家码也够

邮箱

VARCHAR(128)

RFC 5321 规定最大 254,留余量

用户名/昵称

VARCHAR(32)

VARCHAR(64)

业务约束 + 20% 余量

真实姓名

VARCHAR(50)

少数民族长姓名兼容

地址

VARCHAR(128)

或拆分成省市区字段

长地址用 TEXT

URL

VARCHAR(512)

更长用 TEXT

备注/描述

TEXT

不要用

VARCHAR(65535)

IP 地址

VARCHAR(45)

IPv6 最长 45 字符

MD5/SHA

CHAR(32)

CHAR(40)

定长用 CHAR

UUID

CHAR(36)

BINARY(16)

推荐转 binary 存储

⛔ 255 的禁用场景

  • ❌ 参与 WHERE 条件的字段
  • ❌ 参与 ORDER BY / GROUP BY 的字段
  • ❌ 大表(>100 万行)的高频查询字段
  • ❌ 联表 JOIN 的关联字段
✅ 必须用长文本的场景

-- ❌ 错误:用超大 VARCHAR 存长文本ALTER TABLE article ADD COLUMN content VARCHAR(65535);-- ✅ 正确:改用 TEXT,独立溢出页机制更高效ALTER TABLE article ADD COLUMN content TEXT;
存量表改造方案

-- Step 1: 评估实际最大长度SELECTMAX(CHAR_LENGTH(nickname)) AS max_len,AVG(CHAR_LENGTH(nickname)) AS avg_lenFROM user;-- Step 2: 按实际长度 * 1.2 收缩字段(选业务低峰期执行)ALTER TABLE user MODIFY nickname VARCHAR(64);-- Step 3: 观察索引大小变化SHOW INDEX FROM user;-- 或对比修改前后的 .ibd 文件大小
⚠️ 大表 ALTER TABLE 会锁表,建议使用 pt-online-schema-change 或 MySQL 8.0 的 ALGORITHM=INSTANT。
五、速查表(建议收藏)

┌─────────────────────────────────────────────┐│     VARCHAR 长度选型速查                     │├─────────────────────────────────────────────┤│  长度 <= 20    →  VARCHAR(20)               ││  长度 <= 50    →  VARCHAR(50)               ││  长度 <= 100   →  VARCHAR(128)              ││  长度 <= 200   →  VARCHAR(255)  慎用        ││  长度 > 200    →  TEXT                       ││                                              ││  索引字段      →  越小越好,严禁 255         ││  JOIN 关联字段 →  越小越好,严禁 255         ││  排序/分组字段 →  越小越好,严禁 255         │└─────────────────────────────────────────────┘
六、写在最后:从一次事故到一种工程素养

这个案例给我们团队的震撼,远不止"VARCHAR 别用 255"这么简单。

它让我们重新审视了所有"约定俗成"的写法——ORM 默认映射、框架模板的默认值、Stack Overflow 上的高赞回答,这些"看起来没错"的默认值,在百万级数据面前可能就是性能杀手。

回顾整个排查过程,最有价值的不是结论,而是这个方法论:

对任何"默认值"保持怀疑,用实测代替直觉。

下次当你准备敲下 VARCHAR(255) 的时候,先停三秒,问自己一个问题:

“这个字段,真的需要 255 吗?”

——如果答不上来,那就从 VARCHAR(50) 开始,让业务和数据告诉你答案。

互动话题:你们团队对 VARCHAR 长度有规范吗?遇到过因为字段定义导致的性能问题吗?评论区聊聊。 下篇预告:《我们删掉了一个索引,查询反而快了 10 倍》——关于 MySQL 索引选择的反向思考。 如果这篇文章对你有启发,欢迎点赞 + 在看 + 转发给更多被 VARCHAR(255) 坑过的同事。

附录:复现测试的核心脚本

-- 1. 建两张对比表CREATE TABLE user_255 (id INT PRIMARY KEY AUTO_INCREMENT,nickname VARCHAR(255),INDEX idx_nickname (nickname)CREATE TABLE user_50 (id INT PRIMARY KEY AUTO_INCREMENT,nickname VARCHAR(50),INDEX idx_nickname (nickname)-- 2. 用存储过程灌 100 万行随机数据(昵称长度 3-20)-- 详见文末 GitHub Gist 链接-- 3. 跑对比查询SELECT * FROM user_255 ORDER BY nickname LIMIT 10000;SELECT * FROM user_50 ORDER BY nickname LIMIT 10000;-- 4. 查看表大小SELECTtable_name,ROUND(data_length / 1024 / 1024, 2) AS data_mb,ROUND(index_length / 1024 / 1024, 2) AS index_mbFROM information_schema.tablesWHERE table_name IN ('user_255', 'user_50');
文中测试数据为示意方向,建议读者在自己的环境中复现,不同版本/配置下具体数值会有差异,但趋势一致:VARCHAR 长度越大,存储、索引、排序开销越大。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
1-1,大连英博弹尽粮绝,杨铭锐造点+斯坦丘破门,刘祝润绝平助攻

1-1,大连英博弹尽粮绝,杨铭锐造点+斯坦丘破门,刘祝润绝平助攻

替补席看球
2026-08-19 21:40:27
冲撞陆桥:乌南大反攻新进展

冲撞陆桥:乌南大反攻新进展

书生论剑
2026-08-19 00:54:09
越扒越有!打赏千万要求女主播陪睡后续,聊天记录曝光隐秘细节

越扒越有!打赏千万要求女主播陪睡后续,聊天记录曝光隐秘细节

文刀贰
2026-08-17 21:48:39
秋收起义六位领导人:毛主席成了一代伟人,剩下五人结局令人唏嘘

秋收起义六位领导人:毛主席成了一代伟人,剩下五人结局令人唏嘘

磊子讲史
2026-08-19 14:46:25
长江十年行丨向江图强,向江而“让”——一个鄱阳湖渔村的退与进

长江十年行丨向江图强,向江而“让”——一个鄱阳湖渔村的退与进

中国日报网
2026-08-18 12:36:12
老婆的闺蜜身材火辣,饭后让我送她回家,她的一句话让我惊掉下巴

老婆的闺蜜身材火辣,饭后让我送她回家,她的一句话让我惊掉下巴

千秋文化
2026-08-17 19:32:41
从高考状元到猥琐酒局主角:那个屠龙少年,是怎样变成恶龙的?

从高考状元到猥琐酒局主角:那个屠龙少年,是怎样变成恶龙的?

迷世书童
2026-08-19 00:19:04
澳门冠军赛名单:大批亚洲球员退赛,国乒主将调休,韩乒全部弃赛

澳门冠军赛名单:大批亚洲球员退赛,国乒主将调休,韩乒全部弃赛

锐评利物浦
2026-08-19 15:31:09
万亿税源也保不住它?烟草这场“大地震”,震出了国家一盘大棋

万亿税源也保不住它?烟草这场“大地震”,震出了国家一盘大棋

吕醿极限手工
2026-07-27 18:27:24
莫言:永远不要期望一个没有血缘关系的人,能站在你的角度替你考虑|成年人最大的清醒

莫言:永远不要期望一个没有血缘关系的人,能站在你的角度替你考虑|成年人最大的清醒

杏花烟雨江南的碧园
2026-07-27 11:15:03
英博对阵海港时启用了脑震荡换人,全场共完成6人次换人

英博对阵海港时启用了脑震荡换人,全场共完成6人次换人

懂球帝
2026-08-19 22:03:08
《牛来》冲向全球,海外社媒炸了!

《牛来》冲向全球,海外社媒炸了!

互联网品牌官
2026-08-19 19:50:46
A股收评:超5000股下跌!沪指跌2.4%失守3900点,创业板指跌6.26%,宇树上市首日收涨460.34%

A股收评:超5000股下跌!沪指跌2.4%失守3900点,创业板指跌6.26%,宇树上市首日收涨460.34%

和讯网
2026-08-19 15:44:03
黄景瑜全家出游的画面太温馨了!他带父母去了家乡丹东大孤山,在当地高端餐厅吃海鲜。

黄景瑜全家出游的画面太温馨了!他带父母去了家乡丹东大孤山,在当地高端餐厅吃海鲜。

手工制作阿歼
2026-08-18 04:19:17
日媒终于慌了:中方连照面都不认了

日媒终于慌了:中方连照面都不认了

娱乐的宅急便
2026-08-19 07:18:52
徐良是国家级战斗英雄还是叛徒逃兵,杀人犯,公理何在?

徐良是国家级战斗英雄还是叛徒逃兵,杀人犯,公理何在?

疯狂的小历史
2026-08-18 10:04:55
转会费超5000万镑!阿森纳签下28岁英格兰后卫 世界杯季军战曾破门

转会费超5000万镑!阿森纳签下28岁英格兰后卫 世界杯季军战曾破门

狍子歪解体坛
2026-08-19 22:05:16
比美俄还狠!坚持清算日本天皇血债血偿,这国才是日军真正的噩梦

比美俄还狠!坚持清算日本天皇血债血偿,这国才是日军真正的噩梦

壹知眠羊
2026-08-18 07:04:49
国防部发布正式通告:马德雷山号再不拖走,就要采取非常手段了

国防部发布正式通告:马德雷山号再不拖走,就要采取非常手段了

面包夹知识
2026-08-19 16:11:33
天安门广场70年未解谜:纪念碑上155字竟藏毛主席的深谋远虑

天安门广场70年未解谜:纪念碑上155字竟藏毛主席的深谋远虑

兴趣知识
2026-08-12 16:14:31
2026-08-19 23:07:00
呼呼历史论
呼呼历史论
分享有趣的历史
777文章数 17945关注度
往期回顾 全部

科技要闻

宇树的悬念还在后面

头条要闻

"三孩非亲生"男子:孩子已交接 回去烧掉结婚照结束了

头条要闻

"三孩非亲生"男子:孩子已交接 回去烧掉结婚照结束了

体育要闻

拥有“儿皇梦”的罗德里,为何选择巴萨?

娱乐要闻

章子怡财路遭到质疑,套现3亿冲上热搜

财经要闻

内部反腐,让大疆错过宇树250亿收益?

汽车要闻

小米澎程N70体验 后排空间夸张亦可旋转对坐

态度原创

艺术
本地
健康
游戏
公开课

艺术要闻

14位俄罗斯画家笔下的冬季早晨

本地新闻

邂逅活珊瑚的西沙,感受独属于国人的浪漫

这种脊柱侧弯,运动能救!

R星网站暗中更新:为《GTA6》新消息准备的?

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版