为什么不建议给MySQL设置Null值?《死磕MySQL系列 十八》
大家好,我是咔咔 不期速成,日拱一卒
之前ElasticSearch系列文章中提到了如何处理空值,若为Null则会直接报错,因为在ElasticSearch中当字段值为null时、空数组、null值数组时,会将其视为该字段没有值,最终还是需要使用exists或者null_value来处理空值
大多数ElasticSearch的数据都来自于各类数据库,这里暂且只针对于MySQL,各个开源软件中都默认兼容各种Null值,空数组等等
若从根源上截断就可以省很多事,直到现在很多开发小伙伴还是坚韧不拔的给字段的默认值还是Null
本期就来聊一聊为什么不建议给字段的默认值设置为Null
本期环境为:MySQL8.0.26

一、案例数据
创建表user
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`age` tinyint(4) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
添加数据,共计10条数据,有两条数据的name值为Null
INSERT INTO `user` (`name`, `age`) VALUES ('kaka', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('niuniu', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('yangyang', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('dandan', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('liuliu', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('yanyan', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('leilie', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('yao', 26);
INSERT INTO `user` (`name`, `age`) VALUES (NULL, 26);
INSERT INTO `user` (`name`, `age`) VALUES (NULL, 26);
一、count数据丢失
在这期 MySQL统计总数就用count,别花里胡哨的《死磕MySQL系列 十》 文章中,已经对count的使用说的非常明白了。
那借着这个案例,来分析一下为什么数据会丢失,先看结果
select count(*) as num1 ,count(name) as num2 from user;

使用count字段名时出现了数据丢失,很明显是因为主键ID9、10这两条记录的name值为空造成的。
为什么会出现这种情况?
当count除了主键字段外,会有两种情况:
一种是字段为null,执行时,判断到有可能是null,但还要把值取出来再判断下,不是null的进行累加
另一种是字段为not null,执行时,逐行从记录里边读出这个字段,判断不是null,才进行累加
此时,咱们遇到的问题是name字段的值存在了null值,所以会走第一种情况,不进行统计null值
为什么建议大家都使用count(*)?
MySQL对于count做了专门的优化,跟字段不同的是并不是把所有带了*的值取出来,而是指定了count(*)肯定不是null,只需要按行累加即可
MySQL团队对count(*)做了什么优化?
MySQL系列文章至今已经更新了第十八期了,你有没有猜到原因呢?
现在你应该知道主键索引结构中叶子节点存储的是整行数据,而普通索引叶子节点存储的是主键ID
那对于普通索引来说肯定会比主键索引小,因为对于MySQL来说,不管遍历哪个索引结果都一样,所以优化器会主动去找到那颗最小的树进行遍历。
在逻辑正确的前提下,尽量减少访问数据量,是数据库系统设计通用法则之一。
最后给大家留一个问题,为什么Innodb存储引擎不跟Myisam存储一样存储一个count值呢?
如果不知道的话,可以看上文提到的count文章
二、为distinct打抱不平
在开发工作中使用Distinct进行去重的场景十分的少,大多数情况都是使用group by完成的
select distinct name from user;
可以看到此时的数据依然是正确的,对Null值做了去重的操作

为什么要说这个,因为咔咔在其它的平台上看到过有人这么使用count(distinct name,mobile),然后说是统计出来的数据不准确。
这种用法依然是count(字段)的用法,distinct本身是会对Null进行去重,去重后依然是需要判断name的值不为null时,才会进行累计。
所以,不要把锅甩给distinct
三、使用表达式数据丢失
在一些值为null时,使用表达式会造成数据的不一致,接下来一起看下
select * from user where name != 'kaka';

这跟我们的预期结果不大一致,预期是想返回id2~10的数据
当然,这个问题也不是无解,MySQL同样也提供了方法
要解决这个问题,只能再加一个条件就是把字段值为null的再单独处理一下

四、空指针问题
如果一个列存在null值,使用MySQL的聚合函数后返回结果是null,而并非是0,就会造成程序执行时的指针异常
CREATE TABLE user_order (
id INT PRIMARY KEY auto_increment,
num int
) ENGINE='innodb';
insert into user_order(num) values(3),(6),(6),(NULL);
创建用户订单数量表,并插入4条数据,接下来演示一下产生的问题
select sum(num) from goods where id>4;

可以看到当字段为null时,使用聚合函数返回值就是null,并非是0,那么这个问题要怎么处理呢?
同样MySQL也给大家提供了对应函数,就是ifnull
select ifnull(sum(num), 0) from goods where id>4;

五、这是在难为谁?
当一个字段的值存在null值,若要进行null值查询时,必须要使用isnull或者ifnull进行匹配查询,又或者使用is null,is not null。
而常用的表达式就不能再进行使用了,有工作经验的还好的,要是新人的话会很难受。
接下来看几个新人经常犯的错误
错误一
对存在null值的字段使用表达式进行过滤,正确用法应该是is null 或者 is not null
select * from user where name<>null;

错误二
依然是使用表达式,同样可以使用isnull

六、总结
说了这么多也都感觉到了字段设置为null的麻烦之处,不过幸好的是MySQL对使用is null、isnull()等依然可以使用上索引。
咔咔目前所在的公司存在大量字段默认值就是null,于是代码中就大量存储ifnull、is null、is not null等代码。
一般字段数值类型的默认值就给成0,字符串的给个空也行,千万不要给null了哈!
推荐阅读
闯祸了,生成环境执行了DDL操作《死磕MySQL系列 十四》
MySQL对JOIN做了那些不为人知的优化《死磕MySQL系列 十七》
“
坚持学习、坚持写作、坚持分享是咔咔从业以来所秉持的信念。愿文章在偌大的互联网上能给你带来一点帮助,我是咔咔,下期见。
”
为什么不建议给MySQL设置Null值?《死磕MySQL系列 十八》的更多相关文章
- MySQL的NULL值处理
我们已经知道MySQL使用 SQL SELECT 命令及 WHERE 子句来读取数据表中的数据,但是当提供的查询条件字段为 NULL 时,该命令可能就无法正常工作. 为了处理这种情况,MySQL提供了 ...
- Mysql 排序null值 排序问题分析
mysql中null值的排序问题分析 如下表t_user: name age zhangsan 1 lisi NULL wangwu 2 www.2cto.com 执行一下sql: S ...
- 如何选择普通索引和唯一索引《死磕MySQL系列 五》
系列文章 一.原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 二.一生挚友redo log.binlog<死磕MySQL系列 二> 三.MySQL强 ...
- 为什么不让用join?《死磕MySQL系列 十六》
大家好,我是咔咔 不期速成,日拱一卒 在平时开发工作中join的使用频率是非常高的,很多SQL优化博文也让把子查询改为join从而提升性能,但部分公司的DBA又不让用,那么使用join到底有什么问题呢 ...
- 一生挚友redo log、binlog《死磕MySQL系列 二》
系列文章 原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 一生挚友redo log.binlog<死磕MySQL系列 二> 前言 咔咔闲谈 上期根据 ...
- S 锁与 X 锁的爱恨情仇《死磕MySQL系列 四》
系列文章 一.原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 二.一生挚友redo log.binlog<死磕MySQL系列 二> 三.MySQL强 ...
- MySQL统计总数就用count(*),别花里胡哨的《死磕MySQL系列 十》
有一个问题是这样的统计数据总数用count(*).count(主键ID).count(字段).count(1)那个效率高. 先说结论,不用那么花里胡哨遇到统计总数全部使用count(*). 但是有很多 ...
- 为什么MySQL字符串不加引号索引失效?《死磕MySQL系列 十一》
群里一个小伙伴在问为什么MySQL字符串不加单引号会导致索引失效,这个问题估计很多人都知道答案.没错,是因为MySQL内部进行了隐式转换. 本期文章就聊聊什么是隐式转换,为什么会发生隐式转换. 系列文 ...
- 闯祸了,生成环境执行了DDL操作《死磕MySQL系列 十四》
由于业务随着时间不停的改变,起初的表结构设计已经满足不了如今的需求,这时你是不是想那就加字段呗!加字段也是个艺术活,接下来由本文的主人咔咔给你吹. 试想一下这个场景 事务A在执行一个非常大的查询 事务 ...
随机推荐
- 如何实现数组与List的相互转换?在 Queue 中 poll()和 remove()有什么区别?哪些集合类是线程安全的?
如何实现数组与List的相互转换? List转数组:toArray(arraylist.size()方法 数组转List:Arrays的asList(a)方法 /** * 〈一句话功能简述〉; * 〈 ...
- Java 进程和线程
进程和线程 在并发编程中,有两个基本的执行单元:进程和线程.在Java编程语言中,通常并发编程主要与线程有关.但是进程也很重要. 计算机系统通常具有许多活动的进程和线程.即使在只有一个执行核心,因此在 ...
- mac-brew
brew search [TEXT|/REGEX/] 搜索软件 brew (info|home|options) [FORMULA...] 查询软件信息 brew install FORMULA... ...
- BMZCTF simple_pop
simple_pop 打开题目得到源码 这边是php伪协议的考点,需要去读取useless.php 解码获得源码 <?php class Modifier { protected $var; p ...
- JAVA中内存分配的问题
JAVA中内存分配的问题 1. 有这样一种说法,如今争锋于IT战场的两大势力,MS一族偏重于底层实现,Java一族偏重于系统架构.说法根据无从考证,但从两大势力各自的社区力量和图书市场已有佳作不难看出 ...
- python-正整数的因子展开式
[题目描述]编写程序,输出一个给定正整数x(x>1)的质因子展开式. [输入格式]请在一行中输入整数x的值. [输出格式]对每一组输入的x,按以下格式输出x的质因子展开式(假如x的质因子分别为a ...
- 引用nodejs的url模块实现url路由功能
我们在本地创建服务器之后需要写不同的后缀名来访问同一个站点的不同页面,如果不实现路由功能.则每次访问localhost:3000 不论后面写什么 比如localhost:3000/index.loc ...
- Tsunami: A Learned Multi-dimensional Index for Correlated Data and Skewed Workloads 论文解读(VLDB 2021)
Tsunami: A Learned Multi-dimensional Index for Correlated Data and Skewed Workloads 论文解读(VLDB 2021) ...
- python并发——生产者消费者信号量实现
介绍 写扫描器的时候,需要让资产扫描结果一出来(生产者),另外一边就会开个线程去运行漏洞扫描(消费者). 但是又不能让结果没出来,另外一边消费者就开始干活了. 代码 # *coding:UTF-8 * ...
- IDEA小技巧:Debug条件断点
今天给大家分享一个IDEA调试过程中的一个小技巧. 先来说说场景,你有没有碰到类似的情况,一个循环结构里,中间某一个情况可能会出错.比如下面的代码结果中,可能执行到第27次的时候,会出现问题. for ...