一千个不用 Null 的理由

简介: 港真,Null 貌似在哪里都是个头疼的问题,比如 Java 里让人头疼的 NullPointerException,为了避免猝不及防的空指针异常,千百年来程序猿们不得不在代码里小心翼翼的各种 if 判断,麻烦而又臃肿,为此 java8 引入了 Optional 来避免这一问题。

港真,Null 貌似在哪里都是个头疼的问题,比如 Java 里让人头疼的 NullPointerException,为了避免猝不及防的空指针异常,千百年来程序猿们不得不在代码里小心翼翼的各种 if 判断,麻烦而又臃肿,为此 java8 引入了 Optional 来避免这一问题。

下面咱们要聊的是 MySQL 里的 null,在大量的 MySQL 优化文章和书籍里都提到了字段尽可能用NOT NULL,而不是NULL,除非特殊情况。但却都只给结论不说明原因,犹如鸡汤不给勺子一样,让不少初学者对这个结论半信半疑或者云里雾里。本文今天就详细的剖析下使用 Null 的原因,并给出一些不用 Null 的理由。

1、NULL 为什么这么多人用?

NULL是创建数据表时默认的,初级或不知情的或怕麻烦的程序员不会注意这点。
很多人员都以为not null 需要更多空间,其实这不是重点。

重点是很多程序员觉得NULL在开发中不用去判断插入数据,写sql语句的时候更方便快捷。

2、是不是以讹传讹?

MySQL 官网文档:

NULL columns require additional space in the rowto record whether their values are NULL. For MyISAM tables, each NULL columntakes one bit extra, rounded up to the nearest byte.

Mysql难以优化引用可空列查询,它会使索引、索引统计和值更加复杂。可空列需要更多的存储空间,还需要mysql内部进行特殊处理。可空列被索引后,每条记录都需要一个额外的字节,还能导致MYisam 中固定大小的索引变成可变大小的索引。

—— 出自《高性能mysql第二版》

照此分析,还真不是以讹传讹,这是有理论依据和出处的。

3、给我一个不用 Null 的理由?

 ●  所有使用NULL值的情况,都可以通过一个有意义的值的表示,这样有利于代码的可读性和可维护性,并能从约束上增强业务数据的规范性。
 ●  NULL值到非NULL的更新无法做到原地更新,更容易发生索引分裂,从而影响性能。

注意:但把NULL列改为NOT NULL带来的性能提示很小,除非确定它带来了问题,否则不要把它当成优先的优化措施,最重要的是使用的列的类型的适当性。

 ●  NULL值在timestamp类型下容易出问题,特别是没有启用参数explicit_defaults_for_timestamp
 ●  NOT IN、!= 等负向条件查询在有 NULL 值的情况下返回永远为空结果,查询容易出错

举例:


create table table_2 (
`id` INT (11) NOT NULL,
user_name varchar(20) NOT NULL
)


create table table_3 (
`id` INT (11) NOT NULL,
user_name varchar(20)
)

insert into table_2 values (4,"zhaoliu_2_1"),(2,"lisi_2_1"),(3,"wangmazi_2_1"),(1,"zhangsan_2"),(2,"lisi_2_2"),(4,"zhaoliu_2_2"),(3,"wangmazi_2_2")

insert into table_3 values (1,"zhaoliu_2_1"),(2, null)

-- 1、NOT IN子查询在有NULL值的情况下返回永远为空结果,查询容易出错
select user_name from table_2 where user_name not in (select user_name from table_3 where id!=1)

mysql root@10.48.186.32:t_test_zz5431> select user_name from table_2 where user_name not
-> in (select user_name from table_3 where id!=1);
+-------------+
| user_name |
|-------------|
+-------------+
0 rows in set
Time: 0.008s
mysql root@10.48.186.32:t_test_zz5431>

-- 2、单列索引不存null值,复合索引不存全为null的值,如果列允许为null,可能会得到“不符合预期”的结果集
-- 如果name允许为null,索引不存储null值,结果集中不会包含这些记录。所以,请使用not null约束以及默认值。
select * from table_3 where name != 'zhaoliu_2_1'

-- 3、如果在两个字段进行拼接:比如题号+分数,首先要各字段进行非null判断,否则只要任意一个字段为空都会造成拼接的结果为null。
select CONCAT("1",null) from dual; -- 执行结果为null。

-- 4、如果有 Null column 存在的情况下,count(Null column)需要格外注意,null 值不会参与统计。
mysql root@10.48.186.32:t_test_zz5431> select * from table_3;
+------+-------------+
| id | user_name |
|------+-------------|
| 1 | zhaoliu_2_1 |
| 2 | <null> |
| 21 | zhaoliu_2_1 |
| 22 | <null> |
+------+-------------+
4 rows in set
Time: 0.007s
mysql root@10.48.186.32:t_test_zz5431> select count(user_name) from table_3;
+--------------------+
| count(user_name) |
|--------------------|
| 2 |
+--------------------+
1 row in set
Time: 0.007s

-- 5、注意 Null 字段的判断方式, = null 将会得到错误的结果。
mysql root@localhost:cygwin> create index IDX_test on table_3 (user_name);
Query OK, 0 rows affected
Time: 0.040s
mysql root@localhost:cygwin> select * from table_3 where user_name is null\G
***************************[ 1. row ]***************************
id | 2
user_name | None

1 row in set
Time: 0.002s
mysql root@localhost:cygwin> select * from table_3 where user_name = null\G

0 rows in set
Time: 0.002s
mysql root@localhost:cygwin> desc select * from table_3 where user_name = 'zhaoliu_2_1'\G
***************************[ 1. row ]***************************
id | 1
select_type | SIMPLE
table | table_3
type | ref
possible_keys | IDX_test
key | IDX_test
key_len | 23
ref | const
rows | 1
Extra | Using where

1 row in set
Time: 0.006s
mysql root@localhost:cygwin> desc select * from table_3 where user_name = null\G
***************************[ 1. row ]***************************
id | 1
select_type | SIMPLE
table | None
type | None
possible_keys | None
key | None
key_len | None
ref | None
rows | None
Extra | Impossible WHERE noticed after reading const tables

1 row in set
Time: 0.002s
mysql root@localhost:cygwin> desc select * from table_3 where user_name is null\G
***************************[ 1. row ]***************************
id | 1
select_type | SIMPLE
table | table_3
type | ref
possible_keys | IDX_test
key | IDX_test
key_len | 23
ref | const
rows | 1
Extra | Using where

1 row in set
Time: 0.002s
mysql root@localhost:cygwin>

 ●  Null 列需要更多的存储空间:需要一个额外字节作为判断是否为 NULL 的标志位

举例:


alter table table_3 add index idx_user_name (user_name);
alter table table_2 add index idx_user_name (user_name);
explain select * from table_2 where user_name='zhaoliu_2_1';
explain select * from table_3 where user_name='zhaoliu_2_1';

7f920b9733872fd7709ad076d361a3f3a2231576

可以看到同样的 varchar(20) 长度,table_2 要比 table_3 索引长度大,这是因为:

两张表的字符集不一样,且字段一个为 NULL 一个非 NULL。

873ecfbc9d8dbce876a87ddd2bc94babe972bbba

key_len 的计算规则和三个因素有关:数据类型、字符编码、是否为 NULL

 ●  key_len 62 == 20*3(utf8 3字节) + 2 (存储 varchar 变长字符长度 2字节,定长字段无需额外的字节)
 ●  key_len 83 == 20*4(utf8mb4 4字节) + 1 (是否为 Null 的标识) + 2 (存储 varchar 变长字符长度 2字节,定长字段无需额外的字节)

所以说索引字段最好不要为NULL,因为NULL会使索引、索引统计和值更加复杂,并且需要额外一个字节的存储空间。基于以上这些理由和原因,我想咱们不用 Null 的理由应该是够了 :)


原文发布时间为:2018-11-18

本文作者:leejun2005

本文来自云栖社区合作伙伴“数据与算法之美”,了解相关信息可以关注“数据与算法之美”。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
安全 关系型数据库 MySQL
Docker 进阶 发布自己的Docker镜像 将镜像上传到阿里云和Docker Hub
上传自己制作的docker镜像的方法,将docker镜像上传到Docker Hub,阿里云的两种方法,以及上传镜像时可能遇见的问题和解决办法,每一步的详解和上传镜像时的规范。
10590 0
Docker 进阶 发布自己的Docker镜像 将镜像上传到阿里云和Docker Hub
|
存储 Java 编译器
JVM-不同jdk版本静态变量存储位置
JVM-不同jdk版本静态变量存储位置
|
人工智能 安全 数据可视化
数字孪生 + 区块链:MyEMS 引领能源管理技术融合新趋势
MyEMS融合数字孪生与区块链技术,打造可信、透明、高效的能源管理新范式。通过实时镜像、智能预测与数据上链,实现能耗可追溯、碳排可验证、交易可信任,推动能源管理迈向智能化与价值化新时代。(238字)
561 1
|
JSON Java 测试技术
面向初学者的JMeter实战手册:从环境搭建到组件解析
Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具,用于对软件做压力测试。JMeter 最初被设计用于 Web 应用测试,但后来扩展到了其他测试领域,可用于测试静态和动态资源,如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库和 FTP 服务器等等。JMeter 可对服务器、网络或对象模拟巨大的负载,在不同压力类别下测试它们的强度和分析整体性能。另外,JMeter 能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证程序是否返回了期望结果。
1399 0
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
7494 117
|
关系型数据库 MySQL Java
天天使用MySQL,你知道MySQL数据库能抗多少压力吗?附(真实案例)
天天使用MySQL,你知道MySQL数据库能抗多少压力吗?附(真实案例)
3436 0
|
算法
二分查找法的时间复杂度
【10月更文挑战第9天】
1689 57
|
Ubuntu 应用服务中间件 nginx
docker入门-快速学会docker
本文介绍了Docker的基本概念,包括镜像、容器、tar文件、Dockerfile和仓库,并通过实际操作演示了如何使用Docker。从拉取Nginx镜像、运行容器、修改容器内容、保存容器为新镜像,到使用Dockerfile构建自定义镜像,最后讲解了如何保存和恢复镜像。文中还推荐了一个在线实践平台Play with Docker,方便读者快速上手Docker。
1298 5
docker入门-快速学会docker
秒杀圣经:10Wqps秒杀,16大架构绝招,一文帮你秒变架构师 (2)
高并发下的秒杀系统设计是一个复杂的挑战,涉及多个关键技术点。40岁老架构师尼恩在其读者交流群中分享了16个关键架构要点,帮助解决高并发下的秒杀问题,如每秒上万次下单请求的处理、超卖问题的解决等。这些要点包括业务架构设计、流量控制、异步处理、缓存策略、限流熔断、分布式锁、消息队列、数据一致性、存储架构等多个方面。尼恩还提供了详细的实战案例和代码示例,帮助读者全面理解和掌握秒杀系统的架构设计。此外,他还分享了《尼恩Java面试宝典》等资源,帮助读者在面试中脱颖而出。如果你对高并发秒杀系统感兴趣,可以关注尼恩的技术自由圈,获取更多详细资料。
秒杀圣经:10Wqps秒杀,16大架构绝招,一文帮你秒变架构师 (2)
|
前端开发 Java 开发工具
【Java】轻松解决支付宝沙箱问题。
本文详细介绍了如何使用Java实现支付宝沙箱支付功能,涵盖从环境搭建到代码实现的全过程
1630 1