删除行对MySQL序列有这么多影响?

共 5472字,需浏览 11分钟

 ·

2020-08-18 10:18

墨墨导读:MySQL序列概述为了达到标识的目的,许多应用程序需要生成唯一编号,比如:商品编号、交易流水号等。


一、MySQL序列概述


为了达到标识的目的,许多应用程序需要生成唯一编号,比如:商品编号、交易流水号等。MySQL数据库同样能够支持这样的需求场景,AUTO_INCREMENT就是为MySQL实现序列的方式,它会自动生成序列编号。但是它的使用是有要求的,比如:

  • 每个表只能有一个列具备AUTO_INCREMENT属性,并且为整数型

  • AUTO_INCREMENT列不能包含NULL值(MySQL会自动设置为NOT NULL)

  • AUTO_INCREMENT列上必选要有索引,常见为primary key和unique index


备注:由于存储引擎的不同对于序列的定义和使用存在差异,本文以innodb引擎作为讲解,具体差异区别,可参考后期分享:《浅析MySQL存储引擎序列属性》。


二、场景演示


设置为AUTO_INCREMENT属性后,每一次插入数据都会向前增加一位数,但是如果删除行后,序列会怎么样呢?

mysql> CREATE TABLE animals (    ->      id MEDIUMINT NOT NULL AUTO_INCREMENT,    ->      name CHAR(30) NOT NULL,    ->      PRIMARY KEY (id)    -> );Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO animals (name) VALUES -> ('dog'),('cat'),('penguin'), -> ('lax'),('whale'),('ostrich');Query OK, 6 rows affected (0.01 sec)Records: 6 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM animals;+----+---------+| id | name |+----+---------+| 1 | dog || 2 | cat || 3 | penguin || 4 | lax || 5 | whale || 6 | ostrich |+----+---------+6 rows in set (0.00 sec)

对于动物编号来说,序列的作用确实很好用,但是当删除某行数据后,序列会发生什么情况呢?

Query OK, 3 rows affected (0.02 sec)
mysql> SELECT * FROM animals;+----+---------+| id | name |+----+---------+| 1 | dog || 3 | penguin || 5 | whale |+----+---------+3 rows in set (0.00 sec)

现在序列(id)发生了断层。
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM animals;+----+---------+| id | name |+----+---------+| 1 | dog || 3 | penguin || 5 | whale || 7 | Horse |+----+---------+4 rows in set (0.00 sec)
mysql> INSERT INTO animals (name) VALUES ('Kangaroo');Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM animals;+----+----------+| id | name |+----+----------+| 1 | dog || 3 | penguin || 5 | whale || 7 | Horse || 8 | Kangaroo |+----+----------+5 rows in set (0.00 sec)

在插入新数据后,原来被删除的序列已经不再重复使用了,而下一个序列为未使用的最小整数。删除当前行对于下一次序列的分配,没有影响。


对于每次数据进行插入,都会从AUTO_INCREMENT列中获取最大值,在进行偏移量增加。如默认的偏移量为1。

+--------------------------+-------+| Variable_name            | Value |+--------------------------+-------+| auto_increment_increment | 1     |+--------------------------+-------+1 row in set (0.02 sec)
mysql> show variables like 'auto_increment_offset';+-----------------------+-------+| Variable_name | Value |+-----------------------+-------+| auto_increment_offset | 1 |+-----------------------+-------+1 row in set (0.00 sec)

说明:对于最大值获取,不是简单使用max函数,这样并不准确,因为在并行事务中,可能会有其他会话进行插入更改,因此当前会话max值并不是准确的,同时还会存在自增长字段的值之间发生冲突,所以MySQL会调用LAST_INSERT_ID(),返回最新AUTO_INCREMENT最大值。


Query OK, 1 row affected (0.00 sec)
mysql> select last_insert_id();+------------------+| last_insert_id() |+------------------+| 9 |+------------------+1 row in set (0.00 sec)
mysql> INSERT INTO animals (name) VALUES ('DEFG');Query OK, 1 row affected (0.01 sec)
mysql> select * from animals where id =last_insert_id();+----+------+| id | name |+----+------+| 10 | DEFG |+----+------+1 row in set (0.00 sec)

对于并行事务,AUTO_INCREMENT 计数会怎么分配呢?


下面做个案例测试:


会话1:

mysql> set autocommit=0    -> ;Query OK, 0 rows affected (0.00 sec)
mysql> select * from animals;+----+----------+| id | name |+----+----------+| 1 | dog || 3 | penguin || 5 | whale || 7 | Horse || 8 | Kangaroo || 9 | ABC || 10 | DEFG |+----+----------+7 rows in set (0.00 sec)
mysql> INSERT INTO animals (name) VALUES ('LISAT1');Query OK, 1 row affected (0.00 sec)
mysql> select * from animals;+----+----------+| id | name |+----+----------+| 1 | dog || 3 | penguin || 5 | whale || 7 | Horse || 8 | Kangaroo || 9 | ABC || 10 | DEFG || 11 | LISAT1 |+----+----------+8 rows in set (0.01 sec)

会话2:

mysql> set autocommit=0    -> ;Query OK, 0 rows affected (0.00 sec)
mysql> select * from animals;+----+----------+| id | name |+----+----------+| 1 | dog || 3 | penguin || 5 | whale || 7 | Horse || 8 | Kangaroo || 9 | ABC || 10 | DEFG |+----+----------+7 rows in set (0.00 sec)
mysql> INSERT INTO animals (name) VALUES ('LISAT2');Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO animals (name) VALUES ('LISAT3');Query OK, 1 row affected (0.00 sec)
mysql>mysql> select * from animals;+----+----------+| id | name |+----+----------+| 1 | dog || 3 | penguin || 5 | whale || 7 | Horse || 8 | Kangaroo || 9 | ABC || 10 | DEFG || 12 | LISAT2 || 13 | LISAT3 |+----+----------+9 rows in set (0.00 sec)

会话1:
mysql> rollback;Query OK, 0 rows affected (0.00 sec)

会话2:

mysql> commit;Query OK, 0 rows affected (0.00 sec)
mysql> select * from animals;+----+----------+| id | name |+----+----------+| 1 | dog || 3 | penguin || 5 | whale || 7 | Horse || 8 | Kangaroo || 9 | ABC || 10 | DEFG || 12 | LISAT2 || 13 | LISAT3 |+----+----------+9 rows in set (0.00 sec)

可以从上面的测试看出,当会话1持有该序列后,会对该序列占有语句锁,会话2重新申请下一个序列,因此出现了序列不连续情况,这样的目的其实也是为了避免线程冲突,性能优先。


在高效使用AUTO_INCREMENT列时,有几项注意事项:

  • 自增序列的目的是得到一系列的正整数序列,,因此不支持非正数使用。

  • 可以将AUTO_INCREMENT列定义为UNSIGED类型,创建主键 UNSIGNED 和 AUTO_INCREMENT 连用 表示从0开始自增 (由0开始自增,所以第一个自增的id为 1 ) 但可以增加的范围为,不加 UNSIGNED 的两倍

  • 使用truncate table来清除某个表的内容,可以将该表的序列重置为1开始。

1. 人人都能看懂的 6 种限流实现方案!

2. 一个空格引发的“惨案“

3大型网站架构演化发展历程

4Java语言“坑爹”排行榜TOP 10

5. 我是一个Java类(附带精彩吐槽)

6. 看完这篇Redis缓存三大问题,保你能和面试官互扯

7. 程序员必知的 89 个操作系统核心概念

8. 深入理解 MySQL:快速学会分析SQL执行效率

9. API 接口设计规范

10. Spring Boot 面试,一个问题就干趴下了!



扫码二维码关注我


·end·

—如果本文有帮助,请分享到朋友圈吧—

我们一起愉快的玩耍!



你点的每个赞,我都认真当成了喜欢

浏览 31
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐