☰
字符数与字节数的陷阱:SQL筛选无效推文用CHAR_LENGTH还是LENGTH?
2026/10/14 19:06:26 网站建设 项目流程

如果你面试时遇到这样一张表:每条记录是一条推文,里面有推文ID和内容字段,让你把“无效的推文”筛出来——绝大多数人的第一反应是LENGTH(content) > 15。但如果表中恰好有中文内容,这题就变成了一个陷阱。所谓“无效的推文”,在很多在线判题系统里指的是内容字符数超过15的那部分记录。这个设定看上去人畜无害,实际考的是你知不知道CHAR_LENGTH和LENGTH的区别,以及能不能读懂“字符数”和“字节数”这两个概念的差异。

这题适合正在准备数据库岗位面试的人、刚接触SQL的开发者,以及那些写了好几年SELECT却从来没踩过编码坑的“熟练工”。无论你是哪种,把这题完全吃透,绝对值回票价。

1. 题目拆解:无效的推文到底在考什么

1.1 业务场景还原

先还原一下真实场景。一个社交平台允许用户发布推文,但产品经理规定:内容长度一旦超过15个字符,这条推文就无法正常展示,系统会把它标记为“无效”。现在给你一张表Tweets,表结构大致如下:

字段名类型
tweet_idint
contentvarchar(200)

tweet_id是推文唯一标识,content是推文正文。题目要求:写一条SQL查询,返回所有无效推文的tweet_id。

这个场景在真实业务里非常常见。比如微博限制140个汉字,Twitter早期限制140个英文字符,后来变成280。一旦发布内容超过限制,后端要么直接拒绝,要么先落库再异步标记为异常。面试题把“超过15个字符”作为判定条件,纯粹是为了把计算量控制在合理范围内,让你把注意力放在SQL函数的选型上。

1.2 判定规则背后的设计逻辑

为什么偏偏是15?这个数字本身没有魔法。它足够小,方便你在脑子里手算;又足够大,能容纳像“hello world”这样12个字符的常规内容。更重要的是,它能制造出“长度边界”的讨论点:到底是大于15算无效,还是大于等于15算无效?

从设计上看,题目给的规则是“内容长度严格大于15”,也就是> 15,不是>= 15。这意味着正好15个字符的推文依然有效。很多人在这一步栽跟头,因为他们先入为主地认为“超过”就包含等于。实际上英文里的“超过”通常对应“大于”,而SQL里边界条件差一个符号,结果集就会多一条记录。

这道题最核心的考点,是隐藏的字符集问题。如果content里只有英文字母和数字,LENGTH(content)和CHAR_LENGTH(content)的结果完全一致,因为一个英文字符正好占一个字节。可一旦内容里出现中文、日文、韩文或者emoji表情,这两个函数的结果就会分道扬镳。面试官想看的,就是你能不能敏锐地意识到这个差异。

2. 建表与数据准备:先把战场铺好

2.1 标准建表语句

在本地数据库里复现这道题,不需要复杂的工具,一个MySQL实例就够了。我用的是MySQL 8.0,其他版本也都适用。首先建表:

CREATE TABLE Tweets ( tweet_id INT PRIMARY KEY, content VARCHAR(200) ) DEFAULT CHARSET = utf8mb4;

我把字符集显式设成utf8mb4,这一步很重要。utf8mb4是MySQL里支持完整Unicode的字符集,一个中文字符占3个字节,一个emoji表情占4个字节。如果不指定字符集,某些老版本MySQL默认的latin1或者utf8mb3可能导致你根本无法插入表情符号,后面的测试就无从谈起。

2.2 制造测试数据的小技巧

只建表不插数据是没法验证SQL正确性的。我构造了下面这组数据,故意混合了英文、中文和表情,用来暴露LENGTH和CHAR_LENGTH的差异:

INSERT INTO Tweets (tweet_id, content) VALUES (1, 'Hello World'), (2, '这是一条测试推文'), (3, '123456789012345'), (4, '1234567890123456'), (5, 'abcde12345'), (6, 'I love SQL'), (7, '中文推文测试内容'), (8, 'Mixed 中英 mixed 内容'), (9, 'emoji表情😀测试'), (10, 'a');

这十条数据不是随便拍的。第3条恰好15个英文/数字字符,属于边界有效数据;第4条是16个字符,属于无效数据;第7条包含连续的中文,第9条带emoji,这些都是测试字符函数的好材料。

插入完成后,先执行一条简单查询看看全部数据:

SELECT tweet_id, content, CHAR_LENGTH(content) AS char_cnt, LENGTH(content) AS byte_cnt FROM Tweets;

这一步非常值得做。你会在结果里直观看到:第7条“中文推文测试内容”只有8个字符,但字节数是24;第2条“这是一条测试推文”也是8个字符,字节数也是24。如果你只盯着LENGTH,就会错误地以为这些推文都超过了15字节(实际上它们确实超过了),从而把它们全部判为无效。但这与原意“字符数超过15”完全不符。

3. 核心SQL实现:从CHAR_LENGTH到LENGTH的陷阱

3.1 最简单的写法

先说结论,正确且规范的答案如下:

SELECT tweet_id FROM Tweets WHERE CHAR_LENGTH(content) > 15;

如果你用的是MySQL,还可以写CHARACTER_LENGTH(content),效果和CHAR_LENGTH一样。执行这条语句,得到的结果应该是tweet_id = 4和tweet_id = 8(取决于你插入的数据)。第4条是16个字符,超过15;第8条是“Mixed 中英 mixed 内容”,空格也算字符,数一下:Mixed(5) + 空格(1) + 中(1) + 英(1) + 空格(1) + mixed(5) + 空格(1) + 内容(2) = 17个字符,也超过15。其他数据字符数都没超过15,包括第9条:表情“😀”在utf8mb4下算1个字符,所以总字符数是“emoji表情😀测试”=5+2+1+2=10个字符,算是有效推文。

这个写法的关键点只有一个:统计的是“字符个数”,不是“字节个数”。CHAR_LENGTH函数返回的正是字符串的字符数量,无论这个字符是英文字母、中文汉字、日文假名还是emoji,都按1个字符计算。这才是产品经理在需求里说的“内容长度”。

3.2 为什么不能用LENGTH

这是整道题最容易踩的坑。LENGTH(content)返回的是字符串占用的字节数。在MySQL的utf8mb4字符集下:

  • 英文字母、数字、半角标点:1个字符 = 1个字节
  • 中文汉字、全角标点:1个字符 = 3个字节
  • emoji表情:1个字符 = 4个字节

所以如果你写成:

SELECT tweet_id FROM Tweets WHERE LENGTH(content) > 15;

会发生什么?以第2条“这是一条测试推文”为例,字符数是8,字节数是24,24 > 15为真,于是它被错误地判定为无效推文。第7条同理,也会被误判。第9条“emoji表情😀测试”字符数是10,字节数是5 + 2*3 + 4 + 2*3 = 21,也会被误判。一个10字符的正常推文被判无效,这显然不符合业务规则。

换个角度理解:LENGTH更适合用来计算字段占用的存储空间,比如判断一个varchar字段是否超出了某个物理存储上限;而CHAR_LENGTH才是人类理解的“文本长度”。在面试题里,只要没有特别说明“按字节计算长度”,一律默认按字符数计算。

3.3 不同数据库的字符长度函数对照

很多人在MySQL里写顺了,换到其他数据库就懵。这里给你一张对照表:

数据库按字符数计算按字节计算备注
MySQLCHAR_LENGTH/CHARACTER_LENGTHLENGTH注意字符集影响字节数
SQL ServerLENDATALENGTHLEN不统计尾随空格
OracleLENGTHLENGTHBLENGTH按字符数返回
PostgreSQLCHAR_LENGTH/LENGTHOCTET_LENGTHLENGTH在PG中默认按字符数
SQLiteLENGTH按字节SQLite的LENGTH返回的是字符数(对TEXT类型)

看到没?在Oracle和PostgreSQL里,默认的LENGTH就是按字符数计算的,跟MySQL的CHAR_LENGTH一致。如果你从Oracle转MySQL,很容易以为LENGTH万金油,结果就翻车了。所以面试时,如果你看到题目没指定数据库,最好先问一句“用的是哪种数据库”,或者直接写明你使用的是MySQL及你的依据。

4. 实战排查:当SQL跑不出预期结果时

4.1 典型错误:把NULL当成了普通数据

题目里的content字段没有标注NOT NULL,这意味着某些行可能存了NULL。CHAR_LENGTH(NULL)返回的是NULL,而NULL > 15的结果既不是真也不是假,而是NULL。在WHERE子句中,只有条件为真的行才会被返回,所以为NULL的行会被自动过滤掉。

这种做法对不对?要看业务怎么定义。如果产品经理说“内容为空的推文也属于无效”,那你的SQL就需要额外处理:

SELECT tweet_id FROM Tweets WHERE content IS NULL OR CHAR_LENGTH(content) > 15;

如果产品经理说“空内容算有效,或者根本不在考虑范围内”,那就不用加IS NULL。面试时主动提这个点,能显得你思考得很周全。

4.2 边界值测试:15个字符到底算不算无效

严格来说,条件> 15表示15个字符是合法边界。我在前面插入的第3条“123456789012345”正好15个字符,它不应该出现在查询结果里。但如果题目描述是“内容长度不少于15”,那就得用>= 15。一字之差,结果完全不同。

我的建议是,在写任何长度过滤SQL之前,先拿真实数据跑一轮边界测试。你可以用下面这条SQL把每条记录的字符数标出来:

SELECT tweet_id, content, CHAR_LENGTH(content) AS len, IF(CHAR_LENGTH(content) > 15, 'INVALID', 'VALID') AS status FROM Tweets ORDER BY len;

这样你能直观看到所有记录的边界情况,避免在“恰好等于15”的那条记录上翻车。真实项目里,边界问题往往是线上事故的源头。比如你上线一个功能,把超过140字的内容禁掉,但用户正好发了一篇140字的文章,如果条件写成了>= 140,文章就被误杀了。

4.3 性能与索引优化思路

说句实在话,这个题目规模下的性能优化属于“杀鸡用牛刀”。但面试里很多人喜欢追问“如果Tweets表有几亿行怎么办”。这时候你不能只说“加索引”,因为CHAR_LENGTH(content)对content字段做了函数运算,普通索引根本用不上。

在MySQL 8.0里,可以通过函数索引来优化:

CREATE INDEX idx_tweets_content_len ON Tweets ((CHAR_LENGTH(content)));

然后在查询里直接写CHAR_LENGTH(content) > 15,优化器可能会走这个索引。如果你用的是PostgreSQL,可以建表达式索引:

CREATE INDEX idx_tweets_content_len ON Tweets (CHAR_LENGTH(content));

如果数据库不支持函数索引,另一个思路是引入一个冗余字段content_len,在插入或更新时用程序算好字符长度存进去,然后对这个字段建普通索引。代价是写入时多一次计算,但查询时会非常快。这本质上是用空间换时间,也是很多内容平台在做的方案。

还有一个容易被忽略的点:如果表里95%的数据都是有效推文,只有5%是无效的,那即使不用索引,直接全表扫描也未必慢。优化器会根据统计信息决定是否走索引。所以不要一上来就说“加索引”,先分析数据分布,反而显得你更有经验。

5. 延伸思考:从面试题到生产环境的内容治理

5.1 长度校验在其他场景的应用

“无效的推文”只是数据质量校验的一个缩影。同样的逻辑完全可以套用到用户名注册、评论审核、商品标题、文章摘要这些场景。比如用户注册时要求昵称不超过20个字符,如果用LENGTH去判断,一个10个汉字昵称会被误判为“长度超标”,用户会被卡在注册页面,体验极差。

再比如电商平台,商品标题有字符数限制,超出部分会被截断显示。如果你在导出数据时用错了字节函数,导出的文件里中文标题全是乱码或者被截断,运营同事第二天就会来找你。所以我一直强调,只要字符串可能包含非英文字符,就必须使用按字符计算的函数,这个习惯一定要刻进肌肉记忆里。

5.2 同一套逻辑怎么扩展成存储过程

如果业务里不止要查询无效推文,还要定期把无效内容标记出来,甚至批量下线,那可以封装一个存储过程。以MySQL为例:

DELIMITER // CREATE PROCEDURE MarkInvalidTweets() BEGIN UPDATE Tweets SET status = 'INVALID' WHERE CHAR_LENGTH(content) > 15; END// DELIMITER ;

你可以用事件调度器每小时执行一次:

CREATE EVENT MarkInvalidEvent ON SCHEDULE EVERY 1 HOUR DO CALL MarkInvalidTweets();

这里有一个实际工作中的细节:直接对大表做UPDATE可能会锁表很长时间,影响线上读写。更稳妥的做法是先查出无效推文的ID,分批更新,每次只更新几百条,配合LIMIT和主键范围控制。不要试图用一个巨大的UPDATE语句解决问题,生产环境会给你教训。

5.3 个人实操经验与避坑心得

这题我在实际面试候选人时问过不止一次,很多人能在30秒内写出SELECT tweet_id FROM Tweets WHERE LENGTH(content) > 15,并自信满满地提交。这时候我会追加一句:“如果content里面有中文呢?”场面通常会沉默几秒。这个反应比我期待看到的“哦对,应该用CHAR_LENGTH”更加说明问题——他之前根本没考虑过字符集。

从这件事我学到的是,简单SQL题考察的往往不是记忆,而是你潜意识里的判断模型。你写SQL时有没有想过数据本身长什么样?表里存的是英文还是中文?字段编码是什么?NULL会不会出现?这些“废话”一样的问题,才是区分普通开发者和靠谱开发者的分水岭。

另一个心得是,测试数据一定要包含emoji。很多人知道中文有3个字节,却忽略了utf8mb4中的表情占4个字节。如果面试官再问深一层“为什么用CHAR_LENGTH而不是LENGTH”,你能答出“LENGTH按字节返回,中文和emoji会放大实际长度”,并且顺带说出“在utf8mb4下中文占3字节,emoji占4字节”,基本就能过关了。

最后再分享一个小技巧:写完SQL后,先手动数一下每条测试数据的字符数,再对比查询结果。这个习惯看起来很笨,但真能救你。尤其处理线上数据时,不要盲目相信任何函数,先跑一条SELECT CHAR_LENGTH(content) FROM Tweets LIMIT 10看看实际输出,再下结论。数据不会骗你,最多是你选错了尺子。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询