千万别给数据库字段加默认值 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 前缀!很容易背锅》

相关推荐
刘胡子大叔20 分钟前
SQL 脚本的导入顺序
数据库·sql
代码什么用42 分钟前
Spring基础使用
java·后端·spring
代码什么用44 分钟前
Spring对IoC的实现
数据库·spring
Su米苏1 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
明月_清风1 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
码云数智-园园1 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁1 小时前
Java 异常体系详解:从字节码原理到最佳实践
java
蜗牛互联网2 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端
禾小西2 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨2 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言