产品文档里写着「标题不超过 200 字」,开发照着建了一个长度 200 的字段,上线当天就收到了报错:用户输入一段 90 个汉字的中文标题,写入失败。双方都很困惑——明明是「200 字」,怎么会不够?问题出在「字」这个字的两种口径上:产品说的是字符数,数据库实际校验的是字节数。
一个数「多少个字」,一个数「占多少空间」
字符数是按 Unicode 码点计数的,一个汉字、一个字母、一个 emoji 都算一个。它回答的是「这段文本有多少个符号」。
字节数是文本按某种编码写出后占用的存储空间。它回答的是「这段文本要占多少地方」。
在 ASCII 时代这两个数字几乎相等,因为一个字符就是一个字节。但 UTF-8 是变长编码,不同字符占用的字节数并不相同:
| 字符类型 | UTF-8 字节数 | 举例 |
|---|---|---|
| 英文、数字、半角标点 | 1 | a 7 , |
| 带音标字母、希腊字母 | 2 | é α |
| 常用汉字、日文假名 | 3 | 中 あ |
| emoji、生僻汉字 | 4 | 😀 |
所以一篇「800 字」的中文文章,UTF-8 字节数大约是 2400;同样 800 个字符的英文,字节数就是 800。差了整整三倍。
哪些场景看字节数
字节数不是学术概念,它在很多地方直接决定功能能不能用。
最典型的是索引长度限制。老版本 InnoDB 的单列索引前缀限制是 767 字节,在 utf8mb4 下每个字符最多占 4 字节,所以 varchar(255) 建索引时算出 1020 字节,直接超过上限报错。解决办法是缩短索引列长度,或者改用前缀索引 KEY (col(191))——191 这个数字之所以在社区里流传,正是因为它乘 4 之后刚好不超过 767。
第二类是传输层的体积限制。API 网关、反向代理、消息队列通常按字节设上限,请求体一旦超过阈值就被拒绝。日志系统也会按字节截断字段,中文日志因此比英文日志更早被切断。
第三类是计费与配额。短信按编码方式计费,中文用 UCS-2 编码,一条短信只能容纳 70 个字符,而英文用 GSM-7 编码可以放 160 个。同样的文字量,中英文的短信条数差别很大。
哪些场景看字符数
反过来,面向写作和展示的场景通常看字符数。
社交平台的字数限制基本都按字符计,用户在编辑器里看到的计数就是这个口径。搜索引擎对页面描述(meta description)的截断、列表页标题的截断,也都以字符为单位。
这就带来一个容易混淆的局面:同一个字段,前端校验用的是字符数,后端存储考虑的是字节数,两边如果不明确口径,就会出现「前端通过、后端报错」的情况。工程上的做法是在接口文档里把口径写清楚,例如统一写成「最多 200 个字符(UTF-8 编码后不超过 600 字节)」。
怎么快速估算
实际工作中不需要每次都去算编码,记住三条经验值就够了:英文和半角标点按 1 算,带音标的字母按 2 算,汉字按 3 算,emoji 按 4 算。
把文本按这几类分别统计再相加,误差通常可以接受。如果手上正好有一个能同时给出字符数和 UTF-8 字节数的统计工具,直接量一遍最稳妥——尤其是中英混排、夹带 emoji 的文本,手算很容易漏掉细节。
另外要注意换行符。Windows 风格的换行是 CR + LF 两个字节,Unix 风格是一个字节。一篇文章如果有几百行,仅换行符的差异就有几百字节。
检查清单
- 明确口径:写作场景看字符数,存储与传输场景看字节数
- 记住 UTF-8 的经验值:英文 1、带音标字母 2、汉字 3、emoji 4
- 建索引时注意字节上限,
utf8mb4下长字符串列建议用前缀索引 - 接口文档里同时写明字符数与字节数上限,避免前后端口径不一致
- 短信、日志截断、请求体限制这类场景按字节核算
- 中英混排、含 emoji 的文本直接用量具测一遍,不要手算