MySQL的列非空和自增属性

2024年 1月 23日 98.4k 0

MySQL 里字段的属性很多的,对性能来说,影响也是可大可小的,技术社群的这篇文章《第03期:列非空与自增》就对列的非空和自增属性进行了探究,值得我们学习。

NULL值相关的历史文章如下所示,

《MySQL唯一索引和NULL空值之间的关系》

《Oracle唯一索引和NULL空值之间的关系》

《NULL判断对SQL的影响》

一、NULL NOT NULLNULL 对外部程序来说,具体为不知道、不确切的、无法表述的值。所以在很多家公司的开发规范里都明确规定了,必须为 NOT NULL。其实用到 NULL 的场景都可以转换为有意义的字符或者数值,一是有利用数据的易读性以及后期的易维护性;二是降低 SQL 语句的编写难度。

关于 NULL 的特性如下,

1. 参与 NULL 字段拼接的结果都为 NULL,预期的可能会有差异预想把字段 r1 做个拼接,再插入到新的表 t3 里,结果 t3 表的记录全为 NULL,跟预期不符。

  1. mysql> show create table t1G

  2. *************************** 1. row ***************************

  3. Table: t1

  4. Create Table: CREATE TABLE `t1` (

  5. `r1` varchar(10) DEFAULT NULL

  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

  7. 1 row in set (0.00 sec)

  8. mysql> show create table t2G

  9. *************************** 1. row ***************************

  10. Table: t2

  11. Create Table: CREATE TABLE `t2` (

  12. `r1` varchar(10) NOT NULL

  13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

  14. 1 row in set (0.00 sec)

  15. mysql> create table t3 like t1;

  16. Query OK, 0 rows affected (0.04 sec)

  17. mysql> insert into t3 select concat(r1,'database') from t1 limit 2;

  18. Query OK, 2 rows affected (0.02 sec)

  19. Records: 2 Duplicates: 0 Warnings: 0

  20. mysql> select * from t3;

  21. +------+

  22. | r1 |

  23. +------+

  24. | NULL |

  25. | NULL |

  26. +------+

  27. 2 rows in set (0.00 sec)

那正确的方法如下,对 NULL 用函数 IFNULL 特殊处理。

  1. mysql> insert into t3 select concat(ifnull(r1,''),'database') from t1 limit 2;

  2. Query OK, 2 rows affected (0.01 sec)

  3. Records: 2 Duplicates: 0 Warnings: 0

  4. mysql> select * from t3;

  5. +----------+

  6. | r1 |

  7. +----------+

  8. | database |

  9. | database |

  10. +----------+

  11. 2 rows in set (0.00 sec)

2. 对于包含 NULL 列的求 COUNT 值也不准确

t1 和 t2 的记录数是一样的,但是字段 r1 包含了 NULL,这导致结果忽略了这些值。

  1. mysql> select count(r1) as rc from t1;

  2. +-------+

  3. | rc |

  4. +-------+

  5. | 16390 |

  6. +-------+

  7. 1 row in set (0.01 sec)

  8. mysql> select count(r1) as rc from t2;

  9. +-------+

  10. | rc |

  11. +-------+

  12. | 32768 |

  13. +-------+

  14. 1 row in set (0.03 sec)

这时候我们可能想到了,正确的方法是用 NULL 相关函数处理,

  1. mysql> select count(ifnull(r1,'')) as rc from t1;

  2. +-------+

  3. | rc |

  4. +-------+

  5. | 32768 |

  6. +-------+

  7. 1 row in set (0.03 sec)

或者是直接用 COUNT(*) 包含了所有可能的值

  1. mysql> select count(*) as rc from t1;

  2. +-------+

  3. | rc |

  4. +-------+

  5. | 32768 |

  6. +-------+

  7. 1 row in set (0.02 sec)

当然了不仅仅是 COUNT,除了 NULL 相关的函数,大部分对 NULL 处理都不友好。所以其实从上面两点来看,NULL 的处理都得特殊处理,额外增加了编写 SQL 的难度。

3. 包含 NULL 的索引列

对包含 NULL 列建立索引,比不包含的 NULL 的字段,要多占用一个 BIT 位来存储。示例key_len 分别为 43 和 42,t1 比 t2 多了一个字节,

  1. mysql> pager grep -i 'key_len'

  2. PAGER set to 'grep -i 'key_len''

  3. mysql> explain select * from t1 where r1 = ''G

  4. key_len: 43

  5. 1 row in set, 1 warning (0.00 sec)

  6. mysql> explain select * from t2 where r1 = ''G

  7. key_len: 42

  8. 1 row in set, 1 warning (0.00 sec)

4. 各存储引擎相关的对 NULL 的处理

在 MySQL 8.0 发布后,仅有 InnoDB、MyISAM 和 Memory 支持对包含 NULL 列的索引,其他引擎不支持。比如 NDB。
二、AUTO_INCREMENT列的自增属性,一般用来设置整数列根据一定步长逐步增长的值,类似于其它数据库的序列。不过这里的“序列”是基于特定一张表的。关于自增属性的相关特性如下:

1. 控制自增属性性能的变量:innodb_autoinc_lock_mode

innodb_autoinc_lock_mode=0

代表传统模式,也就是说,在对有自增属性的字段插入记录时,会持续持有一个表级别的自增锁,直到语句执行结束为止。比如说下面两条语句,SQL 1 在执行期间,一直持有对表 f1 的表级自增锁,接下来 SQL 2 执行时锁超时。

innodb_autoinc_lock_mode=1

代表连续模式,和传统模式差不多,不同的点在于对于简单的插入语句,比如 SQL 2,只在分配新的 ID 过程中持有一个轻量级的互斥锁(线程级别,而不是事务级别),而不是直到语句结束才释放的表锁。

  1. -- SQL 1

  2. mysql> insert into f1(c2) select rpad(uuid(),100,uuid()) from t1;

  3. Query OK, 16777216 rows affected (3 min 35.92 sec)

  4. Records: 16777216 Duplicates: 0 Warnings: 0

  5. -- SQL 2

  6. mysql> insert into f1(c2) select 'database';

  7. ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

innodb_autoinc_lock_mode=2

代表交错模式。这个模式下放弃了自增表锁,产生的值会不连续。不过这是性能最高的模式,多条插入语句可以并发执行。MySQL 8.0 默认就是交错模式。

  1. -- SQL 1

  2. mysql> insert into f1(c2) select rpad(uuid(),100,uuid()) from t1;

  3. Query OK, 16777216 rows affected (3 min 35.92 sec)

  4. Records: 16777216 Duplicates: 0 Warnings: 0

  5. -- SQL 2

  6. mysql> insert into f1(c2) select 'sql2';

  7. Query OK, 1 row affected (0.02 sec)

  8. Records: 1 Duplicates: 0 Warnings: 0

那针对复制安全来说,以上三种模式,0 和 1 对语句级别安全,也就是产生的二进制日志复制到任何其他机器都数据都一致;2 对于语句级别不安全;三种模式对二进制日志格式为行的都安全。

2. 控制自增属性的步长以及偏移量

一般用在主主复制架构或者多源复制架构里,主动规避主键冲突。

auto_increment_increment 控制步长auto_increment_offset 控制偏移量

3. 对于要立刻获取插入值的需求

就是说要获取一张表任何时候的最大值,应该时刻执行以下 SQL 3 ,而不是 SQL 2。SQL 2 里的函数 last_insert_id() 只获取上一条语句最开始的 ID,只适合简单的 INSERT。

  1. -- SQL 1

  2. mysql> insert into f1(c2) values ('xx1'),('xx2'),('xx3');

  3. Query OK, 3 rows affected (0.01 sec)

  4. Records: 3 Duplicates: 0 Warnings: 0

  5. -- SQL 2

  6. mysql> select last_insert_id() as last_insert_id;

  7. +----------------+

  8. | last_insert_id |

  9. +----------------+

  10. | 1 |

  11. +----------------+

  12. 1 row in set (0.00 sec)

  13. -- SQL 3

  14. mysql> select max(c1) as last_insert_id from f1;

  15. +----------------+

  16. | last_insert_id |

  17. +----------------+

  18. | 3 |

  19. +----------------+

  20. 1 row in set (0.00 sec)

  21. -- SQL 4

  22. mysql> select * from f1;

  23. +----+------+

  24. | c1 | c2 |

  25. +----+------+

  26. | 1 | xx1 |

  27. | 2 | xx2 |

  28. | 3 | xx3 |

  29. +----+------+

  30. 3 rows in set (0.00 sec)

4. 自增列溢出现象

自增属性的列如果到了此列数据类型的最大值,会发生值溢出。比如变更表 f1 的自增属性列为 tinyint。SQL 2 显式插入最大值 127, SQL 3 就报错了。所以这点上建议提前规划好自增列的字段类型,提前了解上限值。

  1. mysql> drop table f1;

  2. Query OK, 0 rows affected (0.04 sec)

  3. mysql> create table f1(c1 tinyint auto_increment primary key);

  4. Query OK, 0 rows affected (0.05 sec)

  5. -- SQL 1

  6. mysql> insert into f1 values (127);

  7. Query OK, 1 row affected (0.01 sec)

  8. -- SQL 2

  9. mysql> select * from f1;

  10. +-----+

  11. | c1 |

  12. +-----+

  13. | 127 |

  14. +-----+

  15. 1 row in set (0.00 sec)

  16. -- SQL 3

  17. mysql> insert into f1 select null;

  18. ERROR 1062 (23000): Duplicate entry '127' for key 'PRIMARY'

5. 自增列也可以显式插入有符号的值

  1. mysql> insert into f1 values (-10),(-20),(-30);

  2. Query OK, 3 rows affected (0.01 sec)

  3. Records: 3 Duplicates: 0 Warnings: 0

  4. mysql> select * from f1;

  5. +-----+

  6. | c1 |

  7. +-----+

  8. | -30 |

  9. | -20 |

  10. | -10 |

  11. | 127 |

  12. +-----+

  13. 4 rows in set (0.00 sec)

那针对这样的,建议在请求到达数据库前规避掉。
因此,本文讲了一个是字段是否应该为NOT NULL,应该很清楚了,字段最好是NOT NULL;另外一个是字段的自增属性,关于性能与用法的相关例子,希望对大家有帮助。

如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"和"在看",或者直接转发pyq,

近期更新的文章:《如何将印象笔记文件转换成无需授权的md文件?》《MySQL的DDL成本为何高?》
《What is SQL?》《为什么很少用钢材质制作公路车?》《从《持续发力 纵深推进》了解中国足球的现状》
近期的热文:《推荐一篇Oracle RAC Cache Fusion的经典论文》
《"红警"游戏开源代码带给我们的震撼》
文章分类和索引:《公众号1300篇文章分类和索引》

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论