暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

为什么不建议给MySQL设置NUll值?

一安未来 2023-09-06
117

大家好,我是一安~

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

下面咱们要聊的是 MySQL
里的 NULL
,在大量的 MySQL
优化文章和书籍里都提到了字段尽可能用NOT NULL
,而不是 NULL
,除非特殊情况。

案例数据

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

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统计丢失

count
除了主键字段外,会有两种情况:

  • 字段为NULL
    ,执行时,判断到有可能是NULL
    ,但还要把值取出来再判断下,不是NULL
    的进行累加
  • 字段为NOT NULL
    ,执行时,逐行从记录里边读出这个字段,判断不是NULL
    ,才进行累加

说明:MySQL
对于count
做了专门的优化,跟字段不同的是并不是把所有带了*
的值取出来,而是指定了count(*)
肯定不是NULL
,只需要按行累加即可

使用表达式数据丢失

MySQL
中,NULL
是一个特殊的值,表示缺少值或未知值。NULL
不等于任何值,包括自身。因此,当你使用不等于操作符进行比较时,NULL
值将不会与'kaka'
相等,它们也不会被包含在查询结果中

要解决这个问题,只能再加一个条件就是把字段值为NULL
的条件(or isnull(name)
)再单独处理一下

空指针

CREATE TABLE user_order (
 id INT PRIMARY KEY auto_increment,
 num int
) ENGINE='innodb';

insert into user_order(num) values(3),(6),(6),(NULL);

NULL
表示缺少值或未知值,它在计算中被视为未定义的值。当使用 SUM()
函数时,MySQL
将无法确定 NULL
值的具体数值,因此结果被设置为 NULL

如果希望在计算总和时将 NULL
值视为 0,可以使用 COALESCE()
函数来将 NULL
值转换为 0(SUM(COALESCE(num, 0))
),然后再进行求和计算

NULL值过滤

对存在NULL
值的字段使用表达式进行过滤,正确用法应该是is null
或者 is not null

补充说明

当可为NULL
的列被索引时,每个索引记录需要一个额外的字节,在MyISAM
里甚至还可能导致固定大小的索引(例如只有一个整数列的索引)变成可变大小的索引。

  1. 对于索引字段,如果它们不是NOT NULL,则每个字段加1个字节。
  2. 对于定长字段:
    • tinyint占1个字节
    • int占4个字节
    • bitint占8个字节
    • date占3个字节
    • datetime占5个字节
    • char(n)占n个字符
  3. 对于变长字段:
    • varchar(n)占n个字符+2个字节
  4. 根据不同的字符集,一个字符占用的字节数不同:
    • latin1编码,每个字符占用1个字节
    • gbk编码,每个字符占用2个字节
    • utf8编码,每个字符占用3个字节
    • utf8mb4编码,每个字符占用4个字节

如我们计算一下在utf8mb4编码下,通过EXPLAIN
命令查看key_len
的值:

字段:phone varchar(20) DEFAULT NULL COMMENT '手机号'
条件:where phone='xxx'

在utf8mb4编码下,每个字符占用4个字节。因此,对于字段phone
,我们需要计算20 * 4 + 2 + 1

首先,计算20 * 4
,即20个字符乘以每个字符占用的字节数4,得到80个字节。

接下来,加上2个字节,这是变长字段(varchar)的额外开销。

最后,加上1个字节,这是因为在查询中使用了该字段作为索引字段。

将这些值相加,得到83个字节,即key_len=83

请注意,这是在utf8mb4编码下的计算结果。如果使用其他编码(如utf8或gbk),计算方法会有所不同。


如果这篇文章对你有所帮助,或者有所启发的话,帮忙 分享、收藏、点赞、在看,你的支持就是我坚持下去的最大动力!

SpringBoot 这样做参数校验才足够优雅,彻底告别 if-else


常用正则表达式合集,这一篇就够了(含案例,建议收藏)


面试官:在 Java 中 new 一个对象的流程是怎样的?彻底被问懵了。。

文章转载自一安未来,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论