MySQL的SUBSTRING()按字符截取,但需client/connection/results三者均为utf8mb4;否则如latin1下会错解中文导致截取失败。

确认SUBSTRING是否真按字符截取
MySQL 的 SUBSTRING() 函数在语义上是按「字符」计数的,但前提是整个字符集链路一致。它不是无脑按字节切,也不是天然安全——只要 character_set_client、character_set_connection、character_set_results 三者中有一个是 latin1 或旧版 utf8(非 utf8mb4),传入的字符串就会被错误解码,SUBSTRING('你好', 1, 1) 可能返回 '' 而不是 '你'。
验证方法很简单:
SELECT LENGTH('你好'), CHAR_LENGTH('你好');若前者是 6、后者是 2,说明字段/字面量确实是 utf8mb4 编码;若两者都是 2,说明当前会话正在用 latin1 解析——那后续所有截取都不可信。
强制统一会话字符集再操作
别依赖配置文件或全局设置,每次连接后第一件事就是执行:
SET NAMES utf8mb4;
这条命令等价于同时设置 client、connection、results 为 utf8mb4。在应用层也要同步处理:
- JDBC URL 加
?useUnicode=true&characterEncoding=utf8mb4 - PHP PDO 连接时加
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4" - 命令行连接加参数:
mysql --default-character-set=utf8mb4 -u user -p
不这么做,SUBSTRING_INDEX(col, '|', 2) 中的中文分隔符「|」会被当成单字节定位,实际却占 3 字节,结果切偏、乱码、空值全都有可能。
用 LEFT() / RIGHT() 替代 SUBSTRING() 截前后段
想取前 3 个汉字?别写 SUBSTRING(str, 1, 3),直接用:
LEFT(str, 3)
LEFT() 和 RIGHT() 内部强制按字符计数,对 utf8mb4 中文、emoji、全角符号都稳定,且语法更直白、不易错。它们不会因为 pos=0 或负数 len 报错,也不会因字段为空而意外中断逻辑。
对比示例:
SELECT LEFT('✅订单管理', 4); -- 返回 '✅订单'(4 个字符)<br>SELECT SUBSTRING('✅订单管理', 1, 4); -- 行为相同,但语义模糊、易被误读为字节操作注意:LEFT() 不支持从末尾倒取,要取后 N 位必须用 RIGHT(),不能混用负数 pos。
大字段 or 不可控环境下的兜底方案
如果无法控制客户端字符集(比如第三方 ETL 工具直连),或者字段混合了 emoji + 中文 + 符号,SUBSTRING() 和 SUBSTRING_INDEX() 都可能失效。此时可临时兜底:
- 显式转换字符集:
SUBSTRING(CONVERT(col USING utf8mb4), 1, 5) - 避免在
WHERE子句中使用:这类转换无法走索引,查得慢,只适合 SELECT 投影层 - 更稳的做法是把截取逻辑移到应用层:PHP 用
mb_substr($str, 0, 5, 'UTF-8'),Python 用s[:5](前提是s是 Unicode 字符串)
真正容易被忽略的是:即使表结构、字段定义全是 utf8mb4,只要连接握手阶段没声明字符集,MySQL 就会在解析 SQL 字面量那一瞬间“失明”——你看到的乱码,往往不是截出来的,而是根本就没正确读进来。


















