千万别给数据库字段加默认值 null!真的会出问题

大家好,我是大华。

做后端时间久了,我们很容易忽略一个很常见的问题:很多数据库字段默认值都是 NULL。 在写表结构的时候,大家往往图省事:"先允许 NULL 吧,有问题以后再说"。 但系统跑一段时间之后,往往就会出现各种琐事。

看个案例

假设我们有个用户表,记录用户的信息:

sql 复制代码
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;

看着没问题吧?很多新手甚至老手都这么设计,字段能 NULL 就 NULL,省事!

但后面写业务的时候,你就会发现各种奇奇怪怪的问题。

问题一:查询结果不一样

某天产品经理跑过来说:"帮我统计一下有多少用户没填邮箱,我们要做一波营销活动。"

你心想:这还不简单?邮箱为空的用户呗!于是你写了:

sql 复制代码
SELECT COUNT(*) FROM user WHERE email = NULL;

结果返回0,你懵了:明明有很多用户没填邮箱啊,怎么会是0?

改一下:

sql 复制代码
SELECT COUNT(*) FROM user WHERE email IS NULL;

这次对了。在SQL里,NULL 代表"未知",它不等于任何值,甚至不等于它自己。所以判断是否为 NULL 不能用 = NULL,必须用 IS NULL

问题二:统计结果总是少那么几条

还是刚才那个需求,现在你想统计有邮箱的用户有多少:

sql 复制代码
SELECT COUNT(*) FROM user WHERE email != '';

等等,这样写对吗?不对! 因为 NULL 值不会被 != 条件包含进去。正确写法应该是:

sql 复制代码
SELECT COUNT(*) FROM user WHERE email IS NOT NULL AND email != '';

每次写这种条件都得记着排除NULL,似乎挺烦的。

问题三:函数计算错误

想统计一下用户平均年龄:

sql 复制代码
SELECT AVG(age) FROM user;

结果发现结果比实际小。为啥? 因为 AVG、SUM 这些聚合函数会忽略 NULL 值。如果你的表里很多用户的 age 是 NULL,那平均值就只计算有年龄的用户,结果当然不准。

更坑的是字符串拼接:

sql 复制代码
SELECT CONCAT(username, '的邮箱是', email) FROM user;

只要 email 是 NULL,整个结果就变成 NULL 了。想显示邮箱为空都不行。

问题四:程序里报错防不胜防

假设你用 Java 的 MyBatis 查询用户信息:

java 复制代码
User user = userMapper.findById(1L);
String email = user.getEmail();
if (email.equals("")) {
    // 做点啥...
}

这里会直接空指针异常!因为 email 是 NULL,不能调用 equals 方法。 你可能会说:"我知道 NULL 会空指针,我加判断不就行了?"

可以,但你想想,每个字段取出来都可能 NULL,每个地方都得加判空,代码写出来全是 if else,看着不难受吗?

问题五:索引失效,查询变慢

NULL 值在索引中的处理方式比较特殊。虽然现在 MySQL 对 NULL 的索引优化做得比以前好了,但依然存在一些问题:

sql 复制代码
SELECT * FROM user WHERE email = 'xxx@qq.com';

这条语句如果 email 有很多 NULL 值,MySQL扫描索引时会跳过这些NULL吗?不一定。某些情况下,NULL 值会让索引的选择性变差,查询优化器可能选择全表扫描。

那应该怎么设计?

一个原则:字段尽量设置为 NOT NULL,并给定默认值

上面的表应该改成这样:

sql 复制代码
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含义。

但这种字段应该是少数,大部分字段完全可以用空字符串或0代替NULL。

一句话总结:很多系统变复杂,并不是因为功能多,而是因为数据状态太多。而滥用 NULL,往往就是问题的开始。

📌往期推荐

《为什么 PUT 和 DELETE 请求在大厂中逐渐被弃用?》

《Java 双层 for 循环太慢?优化方案来了》

《几行代码就能实现的效果,为什么很多前端写不出来?》

《Java 开发千万别给布尔变量加 is 前缀!很容易背锅》

相关推荐
追逐时光者1 天前
一个基于 .NET Core + Vue3 构建的开源全栈平台 Admin 系统
后端·.net
成为大佬先秃头1 天前
数据库连接池:Druid
数据库·mysql·druid
程序员飞哥1 天前
90后大龄程序员失业4个月终于上岸了
后端·面试·程序员
zs宝来了1 天前
Playwright 自动发布 CSDN 的完整实践
java
吴声子夜歌1 天前
TypeScript——基础类型(三)
java·linux·typescript
GetcharZp1 天前
Git 命令行太痛苦?这款 75k Star 的神级工具,让你告别“合并冲突”恐惧症!
后端
晓华-warm1 天前
Warm-Flow 1.8.5 正式发布:超时自动审批、暂存功能来了!
数据库
Victor3561 天前
MongoDB(69)如何进行增量备份?
后端
Victor3561 天前
MongoDB(70)如何使用副本集进行备份?
后端