惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
大猫的无限游戏
大猫的无限游戏
爱范儿
爱范儿
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Register - Security
The Register - Security
IT之家
IT之家
博客园_首页
Microsoft Security Blog
Microsoft Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
I
InfoQ
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
M
MIT News - Artificial intelligence
博客园 - Franky
C
Check Point Blog
T
The Blog of Author Tim Ferriss
V
Visual Studio Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
美团技术团队
The Cloudflare Blog
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
The GitHub Blog
The GitHub Blog
D
DataBreaches.Net
Google DeepMind News
Google DeepMind News
V
V2EX
aimingoo的专栏
aimingoo的专栏
GbyAI
GbyAI
G
Google Developers Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42
罗磊的独立博客
量子位
MongoDB | Blog
MongoDB | Blog
Last Week in AI
Last Week in AI
Stack Overflow Blog
Stack Overflow Blog
小众软件
小众软件
D
Docker
人人都是产品经理
人人都是产品经理

光溯星河

【实践记录】重新部署 Tailscale DERP 并解决阿里云环境下HTTP reset问题 认知常新:工作一年之后对 AI 时代计算机行业的再思考 【辞旧迎新·2026丙午马年】春信策马,万事尽欢 【实践记录】N+1 查询问题:从一次跨地域接口超时说起 没有理由不记下这样一个可爱的日子 我曾以为只要我躲得够好,那些悲伤和艰苦就追不上我 关于时间的困惑 思源笔记API-MCP知识库方案 【实践记录】快速构建一套好用的zsh环境 今天的晚风有了秋天的味道 博客数据库从 MySQL 迁移至 PostgreSQL 后资源占用变化报告 【博客维护】博客迁移与可能的访问异常问题说明 数据库拆分策略:分区与分库分表的实现与应用场景对比 【实践手记】Git重写已提交代码历史信息 图像工具箱服务"Markr" Beta上线(持续迭代中) 【杂文·随笔】业余摄影:拍点什么好呢 【技术思辨】从空想走向实践:一场关于“架构设计”的认知碰撞 关于成长,我有一些话要说 理解RESTful API和gRPC:原理、技术、风格及应用对比
【MySQL】一次SQL空格引发的“灵异事件”的排查解决
标签 · 2025-08-09 · via 光溯星河

本文最后更新于 2025-08-20,最新编辑已超过90天,注意内容时效性。

起因是运营同学反馈在测试环境线上环境配置了一套相同的数据,但该数据在测试环境可以正常显示,线上却无法正常显示。而当时测试环境与线上环境的代码已经同步,初步检查数据后也确实没有发现配置错误或异常日志。

但整个代码筛选过滤条件也不多,一个个看也没发现任何一个地方会导致过滤失效的。这时同事敏锐地发现配置中有一个空项,其值并非NULL,而是空格,这在数据库开发中算常见的错误了。于是尝试直接在线上环境将其置为NULL后查看接口返回数据,正常了。

问题就这么简单吗?

首先要强调,将空值统一定义为NULL或空数据结构是必要且科学的,可以避免上述情况的出现。但是,问题就在但是后,测试环境同样是空格,对于同一套代码,为什么测试环境可见?

抛开宇宙射线爆发导致比特翻转、玄学力量导致机魂不悦等重大因素后,疑点落到了SQL查询上。DEBUG发现,这套SQL在测试环境和正式环境返回了不同的结果!

在测试环境下执行以下SQL:

SELECT '' = ' '; -- 返回 0 (false)
select support_language, support_language = '' , ' '='', LENGTH(support_language)  from mind_craft_characters where id=57; -- 返回值见下表
-- 补充嫌疑字段的DDL定义
`support_language` varchar(255) DEFAULT '' COMMENT '支持语言 [''zh'',''ja'',''en''],空表示都支持'
support_languagesupport_language = ''' ' = ''LENGTH(support_language)
101

如你所见,support_language字段长度为1,包含一个空格,MySQL认为其等价于'',但在SQL中写下的空格并不认为' '''是一个东西。那么线上环境无法筛出该数据的原因也呼之欲出了,线上环境执行该SQL得到的support_language = ''结果为false,那么这是为什么呢。

两个环境部署的MySQL版本均为8.0.x几乎可以排除由MySQL版本更新带来的策略不同问题。

经过反复的测试和对AI的拷打,同事从一大堆答案中找到了一句可靠的差异:排序差异

在 MySQL 中,字符比较的行为取决于排序规则COLLATE),不同的规则对字符的等价性有不同定义。你提供的两个查询结果差异源于 utf8mb4_general_ciutf8mb4_0900_ai_ci 对空格的权重处理不同。

验证实验SQL:

SELECT
  HEX(WEIGHT_STRING(' ' COLLATE utf8mb4_general_ci)),   -- 返回空(权重被忽略)
  HEX(WEIGHT_STRING(' ' COLLATE utf8mb4_0900_ai_ci));   -- 返回非空(明确权重)

结论似乎可以揭晓了。

最终结论

排序规则权重值 (HEX)说明
utf8mb4_general_ci0020空格的权重被定义为低优先级(直接使用 Unicode 码点 U+0020)
utf8mb4_0900_ai_ci0209空格的权重经过算法生成(符合 Unicode 9.0 标准,更精准)

1. 两个环境support_language = ''不一致的原因
utf8mb4_general_ci 在比较时会自动忽略尾随空格(非精确比较)。如果比较 'a ''a',也会返回 trueutf8mb4_0900_ai_ci严格遵循 Unicode 标准,权重的生成和比较逻辑一致,空格 ' ' 的权重(0209)与空字符串 ''(权重为空)明确不同。

2. 同一环境中,空字段与空格直接比较不一致的原因
那么对于support_language = ''true的测试环境,' ' = ''false,这又是为什么。问题出在字段取值和字面量比较上。

  • 字段比较:当比较字段值(即从表中读取)时,MySQL 会在 collation 的作用下自动去除尾随空格 —— 因此字段值中' ' = ''被视为相等。
  • 字面量比较:写在 SQL 里的两个字符串字面量,字面量比较不走字段比较路径,在实际比较时不会进行这种“尾随空格忽略”处理,因此直接比较' ' = ''返回了false

参考阅读

光溯星河

博学笃志,切问近思

本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 光溯星河