找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2085

积分

0

好友

273

主题
发表于 昨天 10:04 | 查看: 8| 回复: 0

从事后端开发时间久了,一个容易被忽略的常见问题浮出水面:许多数据库字段的默认值都被设置成了 NULL

在设计表结构时,我们往往为了图一时省事,抱着“先允许 NULL 吧,有问题以后再说”的心态。但当系统运行一段时间后,各种由 NULL 值引发的琐碎问题便会接踵而至,给查询、统计和程序逻辑带来不必要的麻烦。

从一个典型案例说起

假设我们有一个用户表,用于记录用户的基本信息,其初始设计如下:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `status` tinyint(4) DEFAULT NULL COMMENT '状态',
  `created_at` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

看起来是不是很眼熟?这种设计在新手甚至部分老手的项目中都很常见,本着“能 NULLNULL”的原则,似乎简化了设计。然而,当开始编写业务代码时,各种各样奇怪的问题就暴露出来了。

问题一:查询逻辑与预期不符

某天,产品经理提出需求:“统计一下有多少用户没有填写邮箱,我们需要针对这部分用户进行一波营销活动。”

你心想,这很简单,不就是查询邮箱为空的用户吗?于是写下了如下 SQL:

SELECT COUNT(*) FROM user WHERE email = NULL;

执行结果返回 0。你懵了:明明数据库里有很多用户都没填邮箱,怎么结果是 0?

问题出在对 NULL 的理解上。在 SQL 的世界里,NULL 代表“未知”,它不等于任何值,甚至不等于它自己。因此,判断是否为 NULL 必须使用 IS NULLIS NOT NULL,而不能用 =!=。正确的写法应该是:

SELECT COUNT(*) FROM user WHERE email IS NULL;

问题二:统计结果总是“差一点”

还是刚才的需求,现在你想反着来,统计一下填写了邮箱的用户有多少。你可能会下意识地写:

SELECT COUNT(*) FROM user WHERE email != '';

等等,这样写真的对吗?仔细一想,不对!因为 NULL 值不会被 != '' 这个条件包含进去。一个字段值为 NULL,代表未知,它既不等于空字符串,也不等于任何其他字符串。正确的写法需要同时排除 NULL 和空字符串:

SELECT COUNT(*) FROM user WHERE email IS NOT NULL AND email != '';

每次写这类条件都要额外记得处理 NULL,无形中增加了心智负担和出错概率。

问题三:聚合函数与字符串运算的“坑”

假设你想计算用户的平均年龄:

SELECT AVG(age) FROM user;

结果发现算出来的平均值比预想中要小。这是为什么?因为 AVGSUM 等聚合函数在执行时会自动忽略 NULL 值。如果你的表中有大量用户的 age 字段是 NULL,那么平均值就只基于那些有年龄记录的用户计算,结果自然不准确。

字符串运算的坑更隐蔽。例如,你想拼接用户名和邮箱信息:

SELECT CONCAT(username, '的邮箱是', email) FROM user;

只要 email 字段中有一个值是 NULL,那么整个 CONCAT 函数的结果就会变成 NULL。你甚至连“该用户邮箱为空”这样的信息都无法正常展示。

问题四:程序层级的空指针异常

假设你在 Java 程序中使用 MyBatis 查询用户信息:

User user = userMapper.findById(1L);
String email = user.getEmail();
if (email.equals("")) {
    // 做一些操作...
}

这段代码有很大概率会抛出 NullPointerException!因为 email 字段可能为 NULL,在 NULL 上调用 .equals() 方法必然导致空指针。

你可能会反驳:“我知道 NULL 会引发空指针,我每个地方都加上判空不就好了?” 理论上可以,但请想象一下,如果每个从数据库取出的字段都可能为 NULL,那么你的业务代码中将充斥着大量的 if (obj != null) 判断。代码变得臃肿不堪,可读性和可维护性大打折扣。

问题五:潜在的索引性能问题

NULL 值在索引中的处理方式比较特殊。虽然现代版本的 MySQL 对包含 NULL 的索引优化比早期版本要好,但在某些场景下依然存在问题。例如:

SELECT * FROM user WHERE email = ‘xxx@qq.com’;

如果 email 字段上建有索引,但该字段存在大量 NULL 值,在某些情况下可能会影响索引的选择性和查询优化器的判断,导致其可能放弃使用索引而选择全表扫描,从而影响查询性能。

正确的设计姿势是什么?

一个核心原则是:尽可能地将字段定义为 NOT NULL,并为其设置一个业务上合理的默认值。

根据这个原则,上面的用户表应该被重构为如下形式:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `nickname` varchar(50) NOT NULL DEFAULT '' COMMENT '昵称',
  `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
  `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
  `email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态 0-正常 1-禁用',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

进行这样的改造后,诸多问题迎刃而解:

  1. 查询简洁:查找未填邮箱的用户,直接用 WHERE email = '' 即可。
  2. 统计准确:计算平均年龄时,所有用户都会被计入(年龄为0)。如果业务上需要区分“未填写”和“0岁”,可以使用一个特殊的占位符,如 -1
  3. 程序健壮:代码中无需处处判空,因为字段值绝不可能是 NULL
  4. 索引高效:排除了 NULL 值,索引数据结构通常更紧凑,有利于提升查询效率。

哪些场景下可以(或应该)使用NULL?

当然,技术决策没有银弹。在某些特定场景下,使用 NULL 反而是更合适的选择:

  1. 需要区分“无值”和“未知值”:例如“婚姻状况”字段,”未婚”是一个确定的状态,而 NULL 可以清晰地表示“未知”或“未提供”。
  2. 具有特殊语义的字段:例如“逻辑删除时间戳”,NULL 可以非常自然地表示“未删除”,而一个具体的日期时间值则表示删除时间。
  3. 与必须使用NULL的第三方系统对接:当外部系统的数据模型强制要求使用 NULL 时,为了兼容性可能不得不保留。

但请注意,这类字段在你的整个数据库设计中应该是少数派。对于绝大多数表示属性状态的字段(如姓名、电话、邮箱等),完全可以用空字符串 (‘’)、0 或其他有意义的默认值来替代 NULL

许多系统的复杂性,并非源于功能繁多,而是由于数据的状态过于杂乱。而无节制的使用 NULL,往往是滋生这种杂乱的开始。养成在数据库设计初期就审慎思考字段是否为 NULL 的习惯,能为项目的长期稳健运行打下坚实基础。在 云栈社区,你也可以找到更多关于数据库设计与 后端开发 的深度讨论与实践经验。




上一篇:Linux用户与文件权限管理详解:从who到chown的实战命令指南
下一篇:奈飞收购华纳失败:流媒体格局、超级IP战略与AI冲击下的变局
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-10 08:00 , Processed in 0.525185 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表