删除行对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·

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

我们一起愉快的玩耍!



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

浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报