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

相关推荐
YangYang9YangYan8 小时前
2026 电商数据运营校招 JD 梳理|岗位任务、技能与面试考点汇总
大数据·数据库·数据分析
Sun 32858 小时前
读懂集中式主备的集群状态与节点角色
数据库·opengauss·集群·节点角色·磐维数据库·运行状态
StevenLdh8 小时前
第四期 · 云端同步与冲突仲裁:离线可写、多端一致的工程解法
java·微信小程序
事已至此先睡覺吧8 小时前
第三篇:Java 流程控制详解:条件判断、循环与跳转语句
java·开发语言
毕业设计7038 小时前
(免费领源码) SpringBoot 游戏交易平台17600-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
java·spring boot·mysql·决策树·mybatis·idea·推荐算法
一 乐8 小时前
自习室座位预约管理系统|基于springboot + vue自习室座位预约管理系统(源码+数据库+文档)
java·vue.js·spring boot
行百里er8 小时前
轻量级 Spring 监测工具——Spring Insight 发布了
spring boot·后端·监控
努力努力再努力wz8 小时前
【Redis进阶系列】:从主从复制到 Sentinel,一文建立故障检测、Leader 选举与 Failover 的完整心智模型
数据库·redis·缓存
此时不提桶,更待何时8 小时前
02-02-B-AQS与JUC锁面试与生产事故实战
java
达梦数据8 小时前
DMDRS搭建部署简介与运行环境
数据库