找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4682

积分

0

好友

641

主题
发表于 5 天前 | 查看: 22| 回复: 0

来源:blog.csdn.net/qq_30549099/article/details/107395521

我们常听到一种说法:“使用了NULL值的列将会使索引失效”。但如果你实际测试过,会发现IS NULL其实是可以用到索引的,所以这个说法存在漏洞。

理解 NULL 约束

Null is a special constraint of columns. The columns in table will be added null constrain if you do not define the column with “not null” key words explicitly when creating the table.Many programmers like to define columns by default because of the conveniences(reducing the judgement code of nullibility) what consequently cause some uncertainty of query and poor performance of database.

NULL值是对列的一种特殊约束。在创建新表时,如果没有明确使用not null关键字声明某一列,MySQL会默认为我们添加上NULL约束。

一些开发者在创建数据表时,为了省事,直接使用MySQL的默认推荐设置(即允许字段使用NULL值)。这种做法容易在使用NULL的场景中导致不确定的查询结果,并可能引起数据库性能的下降。

NULL 的本质与操作符

Null is null means it is not anything at all,we cannot think of null is equal to ‘’ and they are totally different.
MySQL provides three operators to handle null value:“IS NULL”,“IS NOT NULL”,"<=>" and a function ifnull().
IS NULL: It returns true,if the column value is null.
IS NOT NULL: It returns true,if the columns value is not null.
<=>: It’s a compare operator similar with “=” but not the same.It returns true even for the two null values.
(eg. null <=> null is legal) IFNULL(): Specify two input parameters,if the first is null value then returns the second one.
It’s similar with Oracle’s NVL() function.

NULL并不意味着什么都没有,要注意 NULL''(空字符串)是两个完全不同的值。在MySQL中,专门用于操作NULL值的操作符主要有三个:

  • IS NULL
  • IS NOT NULL
  • <=>: 太空船操作符。它和=很像,select NULL<=>NULL会返回true,但select NULL=NULL会返回false
  • IFNULL(): 一个函数。用法可自行查阅。

示例

Null never returns true when comparing with any other values except null with “<=>”.

NULL通过任何操作符(除了<=>)与其它值比较,结果都会是NULL

 (root@localhost mysql3306.sock)[zlm]>create table test_null(
     -> id int not null,
     -> name varchar(10)
     -> );
 Query OK, 0 rows affected (0.02 sec)

 (root@localhost mysql3306.sock)[zlm]>insert into test_null values(1,'zlm');
 Query OK, 1 row affected (0.00 sec)

 (root@localhost mysql3306.sock)[zlm]>insert into test_null values(2,null);
 Query OK, 1 row affected (0.00 sec)

 (root@localhost mysql3306.sock)[zlm]>select * from test_null;
+----+------+
| id | name |
+----+------+
|  1 | zlm  |
|  2 | NULL |
+----+------+
2 rows in set (0.00 sec)
 // -------------------------------------->这个很有代表性<----------------------
 (root@localhost mysql3306.sock)[zlm]>select * from test_null where name=null;
 Empty set (0.00 sec)

 (root@localhost mysql3306.sock)[zlm]>select * from test_null where name is null;
+----+------+
| id | name |
+----+------+
|  2 | NULL |
+----+------+
1 row in set (0.00 sec)

 (root@localhost mysql3306.sock)[zlm]>select * from test_null where name is not null;
+----+------+
| id | name |
+----+------+
|  1 | zlm  |
+----+------+
1 row in set (0.00 sec)

 (root@localhost mysql3306.sock)[zlm]>select * from test_null where null=null;
 Empty set (0.00 sec)

 (root@localhost mysql3306.sock)[zlm]>select * from test_null where null<>null;
 Empty set (0.00 sec)

 (root@localhost mysql3306.sock)[zlm]>select * from test_null where null<=>null;
+----+------+
| id | name |
+----+------+
|  1 | zlm  |
|  2 | NULL |
+----+------+
2 rows in set (0.00 sec)
 //null<=>null always return true,it's equal to "where 1=1".

Null means “a missing and unknown value”.Let’s see details below.

NULL代表一个不确定的、未知的值。即使是两个NULL,它们也不一定相等。

 (root@localhost mysql3306.sock)[zlm]>SELECT 0 IS NULL, 0 IS NOT NULL, '' IS NULL, '' IS NOT NULL;
+-----------+---------------+------------+----------------+
| 0 IS NULL | 0 IS NOT NULL | '' IS NULL | '' IS NOT NULL |
+-----------+---------------+------------+----------------+
|         0 |             1 |          0 |              1 |
+-----------+---------------+------------+----------------+
1 row in set (0.00 sec)

//It‘s not equal to zero number or vacant string.
//In MySQL,0 means fasle,1 means true.

 (root@localhost mysql3306.sock)[zlm]>SELECT 1 = NULL, 1 <> NULL, 1 < NULL, 1 > NULL;
+----------+-----------+----------+----------+
| 1 = NULL | 1 <> NULL | 1 < NULL | 1 > NULL |
+----------+-----------+----------+----------+
|     NULL |      NULL |     NULL |     NULL |
+----------+-----------+----------+----------+
1 row in set (0.00 sec)

//It cannot be compared with number.
//In MySQL,null means false,too.

It truns null as a result if any expression contains null value.

任何有返回值的表达式中,只要有NULL参与运算,最终结果都会是NULL

 (root@localhost mysql3306.sock)[zlm]>select ifnull(null,'First is null'),ifnull(null+10,'First is null'),ifnull(concat('abc',null),'First is null');
+------------------------------+---------------------------------+--------------------------------------------+
| ifnull(null,'First is null') | ifnull(null+10,'First is null') | ifnull(concat('abc',null),'First is null') |
+------------------------------+---------------------------------+--------------------------------------------+
| First is null                | First is null                   | First is null                              |
+------------------------------+---------------------------------+--------------------------------------------+
1 row in set (0.00 sec)

   //null value needs to be disposed with ifnull() function,what usually causes sql statement more complex.
   //As we all know,MySQL does not support funcion index.Therefore,indexes on the column may not be used.That‘s really worse.

It’s diffrent when using count(*) & count(null column).

使用count(*)count(null column) 的结果是不同的:count(null column) <= count(*)

 (root@localhost mysql3306.sock)[zlm]>select count(*),count(name) from test_null;
+----------+-------------+
| count(*) | count(name) |
+----------+-------------+
|        2 |           1 |
+----------+-------------+
1 row in set (0.00 sec)

 //count(*) returns all rows ignore the null while count(name) returns the non-null rows in column “name”.
 // This will also leads to uncertainty if someone is unaware of the details above.
 如果使用者对`NULL`属性不熟悉,很容易统计出错误的结果。

When using distinct,group by,order by,all null values are considered as the same value.

虽然select NULL=NULL的结果为false,但是在使用distinctgroup byorder by时,所有的NULL值又会被认为是相同的。

 (root@localhost mysql3306.sock)[zlm]>insert into test_null values(3,null);
 Query OK, 1 row affected (0.00 sec)

 (root@localhost mysql3306.sock)[zlm]>select distinct name from test_null;
+------+
| name |
+------+
| zlm  |
| NULL |
+------+
2 rows in set (0.00 sec)

 //Two rows of null value returned one and the result became two.

 (root@localhost mysql3306.sock)[zlm]>select name from test_null group by name;
+------+
| name |
+------+
| NULL |
| zlm  |
+------+
2 rows in set (0.00 sec)

 //Two rows of null value were put into the same group.
 //By default,group by will also sort the result(null row showed first).

 (root@localhost mysql3306.sock)[zlm]>select id,name from test_null order by name;
+----+------+
| id | name |
+----+------+
|  2 | NULL |
|  3 | NULL |
|  1 | zlm  |
+----+------+
3 rows in set (0.00 sec)

 //Three rows were sorted(two null rows showed first).

关于索引的真相

MySQL supports to use index on column which contains null value(what’s different from oracle).

MySQL支持在含有NULL值的列上使用索引,这一点与Oracle不同。这纠正了我们常听到的“列上含有NULL会使索引失效”的说法。严格来说,这句话对于MySQL是不准确的。

 (root@localhost mysql3306.sock)[sysbench]>show tables;
+--------------------+
| Tables_in_sysbench |
+--------------------+
| sbtest1            |
| sbtest10           |
| sbtest2            |
| sbtest3            |
| sbtest4            |
| sbtest5            |
| sbtest6            |
| sbtest7            |
| sbtest8            |
| sbtest9            |
+--------------------+
10 rows in set (0.00 sec)

 (root@localhost mysql3306.sock)[sysbench]>show create table sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` int(11) NOT NULL DEFAULT ‘0’,
  `c` char(120) NOT NULL DEFAULT ‘’,
  `pad` char(60) NOT NULL DEFAULT ‘’,
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

 (root@localhost mysql3306.sock)[sysbench]>alter table sbtest1 modify k int null,modify c char(120) null,modify pad char(60) null;
Query OK, 0 rows affected (4.14 sec)
Records: 0  Duplicates: 0  Warnings: 0

 (root@localhost mysql3306.sock)[sysbench]>insert into sbtest1 values(100001,null,null,null);
Query OK, 1 row affected (0.00 sec)

 (root@localhost mysql3306.sock)[sysbench]>explain select id,k from sbtest1 where id=100001;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | sbtest1 | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

 (root@localhost mysql3306.sock)[sysbench]>explain select id,k from sbtest1 where k is null;
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+--------------------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra                    |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+--------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | ref  | k_1           | k_1  | 5       | const |    1 |   100.00 | Using where; Using index |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

//In the first query,the newly added row is retrieved(检索) by primary key.
//In the second query,the newly added row is retrieved by secondary key “k_1“
// It has been proved that indexes can be used on the columns which contain null value.
  通过`explain` 可以看到 mysql支持含有`NULL`值的列上使用索引
//column “k“ is int datatype which occupies 4 bytes,but the value of “key_len“ turn out to be 5.
   // what‘s happed?Because null value needs 1 byte to store the null flag in the rows.

以下是一个额外的测试例子,进一步证明了 IS NULLIS NOT NULL 都可以利用索引:

mysql> select * from test_1;
+-----------+------+------+
| name      | code | id   |
+-----------+------+------+
| gaoyi     | wo   |    1 |
| gaoyi     | w    |    2 |
| chuzhong  | wo   |    3 |
| chuzhong  | w    |    4 |
| xiaoxue   | dd   |    5 |
| xiaoxue   | dfdf |    6 |
| sujianhui | su   |   99 |
| sujianhui | NULL |   99 |
+-----------+------+------+
8 rows in set (0.00 sec)

mysql> explain select * from test_1 where code is NULL;
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
| id | select_type | table  | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | test_1 | NULL       | ref  | index_code    | index_code | 161     | const |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from test_1 where code is not NULL;
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test_1 | NULL       | range | index_code    | index_code | 161     | NULL |    7 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from test_1 where code=‘dd‘;
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
| id | select_type | table  | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | test_1 | NULL       | ref  | index_code    | index_code | 161     | const |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from test_1 where code like “dd%“;
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test_1 | NULL       | range | index_code    | index_code | 161     | NULL |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

总结

null value always leads to many uncertainties when disposing sql statement.It may cause bad performance accidentally.

在列中使用NULL值容易引发不可控的问题,有时还会严重拖慢系统性能。

例如:

  • 影响统计函数结果:对含有NULL值的列进行统计计算(如count()max()min()),结果可能不符合期望。NULL值不会被计入。
  • 干扰排序、分组、去重:在使用distinctgroup byorder by时,所有NULL会被视为相等,可能导致错误的排序和分组结果。
  • 增加代码复杂度:为了处理NULL带来的潜在问题,我们常常需要在SQL中使用IFNULL()等函数来确保结果可控,这使得SQL语句和业务逻辑变得更加复杂。
  • 额外的存储开销NULL值并非占用原有字段的空间来存储实际内容,而是需要额外的一个字节来标记该字段为NULL(就像一个额外的标志位)。

As these above drawbacks,it’s not recommended to define columns with default null. We recommand to define “not null” on all columns and use zero number & vacant string to substitute relevant data type of null.

鉴于以上这些缺点,不建议将NULL作为列的默认值。推荐的做法是在所有列上明确地定义NOT NULL约束,并使用0(针对数值类型)或''空字符串(针对字符类型)等有意义的值来替代NULL,以提升数据的一致性和查询的可预测性。这类数据库设计与优化的实践经验,也常在像云栈社区这样的技术论坛中被深入探讨。




上一篇:Claude Code源码泄露事件复盘:从npm失误到GitHub最快增长的开源项目
下一篇:大白话拆解AI系统:LLM、Agent、多智能体、MCP等核心概念全梳理
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-7 21:32 , Processed in 0.788208 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表