成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

MySQL主鍵自增值為什么有“空洞”?

數據庫 MySQL
本文在測試 insert、insert ignore、replace into 三種數據插入方式的時候,發現插入數據的時候在表內存在帶有“唯一特性”的值重復的情況下三種語句的處理方式。最終發現了MySQL主鍵自增值“空洞”了

一.場景準備

測試場景為MySQL 8.0:

  • 主鍵重復場景
  • 唯一鍵重復場景

1、建表,包含主鍵及唯一約束

CREATE TABLE t1(
id int(11) NOT NULL auto_increment,
c1 varchar(64) DEFAULT NULL,
c2 int(11) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_c1 (c1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2、寫入初始測試數據

insert into t1 (c1,c2) values ('a',1),('b',2),('c',3);

mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | a | 1 |
| 2 | b | 2 |
| 3 | c | 3 |
+----+------+------+
3 rows in set (0.00 sec)

二.開始測試

insert into

# 測試主鍵重復
mysql> insert into t1 values (1,'aaa', 111);
ERROR 1062 (23000): Duplicate entry '1' for key 't1.PRIMARY'

# 測試唯一鍵重復
mysql> insert into t1 (c1,c2) values('a', 4);
ERROR 1062 (23000): Duplicate entry 'a' for key 't1.uk_c1'

insert ignore into

insert方式插入數據在處理過程中發生主鍵傳統等錯誤時候,語句會被終止,并告知錯誤的原因。而使用insert ignore的方式進行數據插入,則會忽略插入錯誤的行繼續插入沒有問題的行記錄,最終以warning進行提示。

# 測試主鍵重復
mysql> insert ignore into t1 values (1,'aaa', 111);
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+------------------------------------------+
| Level | Code | Message |
+---------+------+------------------------------------------+
| Warning | 1062 | Duplicate entry '1' for key 't1.PRIMARY' |
+---------+------+------------------------------------------+
1 row in set (0.01 sec)

# 測試唯一鍵重復
mysql> insert ignore into t1 (c1,c2) values('a', 4);
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1062 | Duplicate entry 'a' for key 't1.uk_c1' |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)

在測試過程中驚奇地發現測試表中的主鍵自增列發生了改變,經過之前的操作已經變成了7:

mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int NOT NULL AUTO_INCREMENT,
`c1` varchar(64) DEFAULT NULL,
`c2` int DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3
1 row in set (0.00 sec)
# 可是表的行數據并沒有增加
mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | a | 1 |
| 2 | b | 2 |
| 3 | c | 3 |
+----+------+------+
3 rows in set (0.00 sec)
# 新寫入一條數據后,自增 id 變成 7
mysql> insert into t1 (c1,c2) values('d', 4);
Query OK, 1 row affected (0.00 sec)
mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | a | 1 |
| 2 | b | 2 |
| 3 | c | 3 |
| 7 | d | 4 |
+----+------+------+
4 rows in set (0.00 sec)

replace into

最后,replace into的方式導致如果插入數據是原值的情況,然后主鍵沖突,就對該主鍵的內容進行替換,如果唯一鍵沖突,唯一值所在行就會刪除,重新插入新的行,如果都不沖突則正常插入數據。

# 測試主鍵重復
mysql> replace into t1 values (1,'aaa', 111);
Query OK, 2 rows affected (0.00 sec)

mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | aaa | 111 |
| 2 | b | 2 |
| 3 | c | 3 |
| 7 | d | 4 |
+----+------+------+
4 rows in set (0.00 sec)

# 測試唯一鍵重復
mysql> replace into t1 (c1,c2) values('b', 4);
Query OK, 2 rows affected (0.01 sec)

mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | aaa | 111 |
| 3 | c | 3 |
| 7 | d | 4 |
| 8 | b | 4 |
+----+------+------+
4 rows in set (0.00 sec)

上文測試了三種插入數據的方式,可是測試過程中發現插入失敗的時候,自增列的自增值居然變大了。

三.問題分析

為了更好地理解,首先讓我們具體認識一下 AUTO_INCREMENT 屬性在不同的存儲引擎當中,其自增值的保存策略有所不同:

  • MyISAM引擎的自增值是保存在數據文件中的。
  • InnoDB引擎的自增值,其實是保存在了內存里,并且到了MySQL 8.0版本后,將自增值的變更記錄在了redo log中,當MySQL發生重啟的時候依靠redo log恢復重啟之前的自增值。在此前,現在該表的自增值是7,重啟后又變成4了。

可是理解了這個并不能馬上理解現在的這個問題,我們知道當數據進行數據插入的時候,如果插入的數據中自增列不指定其值的時候,該列就會以當前自增值作為其值,如果指定其值就會插入指定的值,當然也有滿足唯一的原則,同時插入指定值大于自增值時,自增值也會隨之改變。而自增值使用的算法是以 auto_increment_offset 參數決定開始,以 auto_increment_increment 決定步長來實現的,默認情況都是1:

mysql> select @@auto_increment_offset;
+-------------------------+
| @@auto_increment_offset |
+-------------------------+
| 1 |
+-------------------------+
1 row in set (0.00 sec)

mysql> select @@auto_increment_increment;
+----------------------------+
| @@auto_increment_increment |
+----------------------------+
| 1 |
+----------------------------+
1 row in set (0.00 sec)

那么,為什么會出現插入數據未成功,自增值卻變大了的情況呢?原因很簡單,用插入數據的流程來進行分析:

MySQL主鍵自增值為什么有“空洞”?

因為自增值的保存是在插入數據真正執行前完成的,因此就會出現這種問題了。

這個時候有人就會想了,可以把 AUTO_INCREMENT 值改回去嗎?簡單測試一下:

mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int NOT NULL AUTO_INCREMENT,
`c1` varchar(64) DEFAULT NULL,
`c2` int DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3
1 row in set (0.00 sec)
mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | a | 1 |
| 3 | c | 3 |
| 7 | d | 4 |
| 8 | b | 4 |
+----+------+------+
4 rows in set (0.00 sec)
# 自增值修改為15
mysql> alter table t1 auto_increment = 15;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
# 修改成功
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int NOT NULL AUTO_INCREMENT,
`c1` varchar(64) DEFAULT NULL,
`c2` int DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb3
1 row in set (0.00 sec)
# 未插入任何值,修改回去,修改成功
mysql> alter table t1 auto_increment = 9;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int NOT NULL AUTO_INCREMENT,
`c1` varchar(64) DEFAULT NULL,
`c2` int DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3
1 row in set (0.00 sec)
# 修改回自增中間的值
mysql> alter table t1 auto_increment = 5;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
# 不會報錯但無法修改
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int NOT NULL AUTO_INCREMENT,
`c1` varchar(64) DEFAULT NULL,
`c2` int DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3
1 row in set (0.00 sec)

顯然,如果自增值往大的方向修改是沒有問題的,但如果往小的修改就要看目前數據庫插入的值是否會將修改后的自增值“卡”在中間,如果出現這種情況是沒辦法改回去的,原因顯而易見,自增屬性與主鍵配套使用,如果現在表里id=4和id=6之間差了個5的值,將自增值改回5,當插入數據時,自增值就會插入5的值并且把自增值加1,問題就出現了,此時自增值再進行插入就違背了唯一的原則了

四.問題拓展

在生產環境中還存在很多類似的問題,如:

# 目前的插入值為8,自增值為9
mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | a | 1 |
| 3 | c | 3 |
| 7 | d | 4 |
| 8 | b | 4 |
+----+------+------+
4 rows in set (0.00 sec)
# 插入數據相當于(9,'t1', 1)
mysql> insert into t1 values (null,'t1', 1);
Query OK, 1 row affected (0.00 sec)
# 開啟事務
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
# 插入數據相當于(10,'t2', 2)
mysql> insert into t1 values (null,'t2', 2);
Query OK, 1 row affected (0.00 sec)
# 事務回滾
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
# 插入數據相當于(11,'t3', 3)
mysql> insert into t1 values (null,'t3', 3);
Query OK, 1 row affected (0.01 sec)

mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | a | 1 |
| 3 | c | 3 |
| 7 | d | 4 |
| 8 | b | 4 |
| 9 | t1 | 1 |
| 11 | t3 | 3 |
+----+------+------+
6 rows in set (0.00 sec)

在插入過程中,開啟了一個事務,在插入的時候發生了事務的回滾,當回滾后再次插入數據,發現自增值又出現了“空洞”,那么問題又來了,為什么在插入數據的時候發生了回滾,數據回滾了,自增值卻沒有回滾呢?為了更直觀,繼續測試,假設有兩個事務。

測試前數據:

mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | a | 1 |
| 3 | c | 3 |
| 7 | d | 4 |
| 8 | b | 4 |
| 9 | t1 | 1 |
| 11 | t3 | 3 |
| 13 | t4 | 3 |
+----+------+------+
7 rows in set (0.00 sec)

進行測試:

session1

session2

begin;


insert into t1 values (null,'s1', 1);

begin;

insert into t1 values (null,'s2', 2);

commit;


rollback;


測試后數據:

mysql> select * from t1;
+----+------+------+
| id | c1 | c2 |
+----+------+------+
| 1 | a | 1 |
| 3 | c | 3 |
| 7 | d | 4 |
| 8 | b | 4 |
| 9 | t1 | 1 |
| 11 | t3 | 3 |
| 13 | t4 | 3 |
| 15 | s2 | 2 |
+----+------+------+
8 rows in set (0.00 sec)

發現還是“空洞”了,而且此時答案也十分清楚了,在不同事務在進行寫入操作的時候申請自增值,為了避免兩個事務申請到相同的自增值,所以需要對其加鎖,按照一定順序進行申請自增值。根據前面的例子來看:

  • 首先兩個session都開啟了事務,session1前面的是id=14的自增值,session2則申請到id=15的自增值
  • 接著當session2插入成功后提交了事務,而此時,session1插入成功或出現插入失敗時進行了事務回滾

此時就出現了前面說到的問題了,沒辦法回滾,回滾就會出現自增值“卡”在中間的情況了,以后有機會再繼續聊聊自增鎖的問題。?

責任編輯:華軒 來源: GreatSQL社區
相關推薦

2020-04-21 15:59:50

MySQL自增主鍵數據庫

2020-05-06 15:02:58

MySQL數據庫技術

2023-10-24 15:27:33

Mysql自增主鍵

2023-12-26 01:09:28

MySQL存儲釋放鎖

2024-05-29 09:05:17

2022-12-27 08:39:54

MySQL主鍵索引

2020-05-11 10:48:01

技術資訊

2009-09-24 13:49:31

Hibernate自增

2023-01-12 09:01:01

MongoDBMySQL

2020-08-31 11:20:53

MySQLuuidid

2021-09-28 17:48:20

MySQL主鍵索引

2023-09-20 14:54:17

MySQL

2021-01-26 21:00:24

SSL證書網絡安全加密

2022-06-14 08:01:43

數據庫MySQL

2022-12-06 09:00:11

MySQL自增主鍵查詢

2024-06-07 10:14:23

2024-12-25 15:32:29

2022-06-07 08:39:35

RPCHTTP

2010-06-04 11:15:23

MySQL自增主鍵

2022-05-12 15:20:17

Wi-Fi 6無線網絡
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中文字幕精品一区 | 亚洲国产成人精品一区二区 | 97视频免费 | 亚洲一区二区三区在线播放 | 欧美激情久久久久久 | 国产免费一区二区 | 色伊人网| 超碰在线人人 | 亚洲精品欧美 | 一区二区免费看 | 国产精品福利在线观看 | 免费在线精品视频 | 一区二区三区观看视频 | 99精品久久久国产一区二区三 | 国产一区二区在线免费播放 | 亚洲欧美日韩精品久久亚洲区 | 日日干夜夜草 | 国产欧美一区二区三区日本久久久 | 久久黄色网 | 亚洲精品久久久一区二区三区 | 在线观看国产 | 国产成人精品视频在线观看 | 国产婷婷综合 | 国产一区二区三区网站 | 亚洲伊人久久综合 | 在线视频国产一区 | 久久久精品网 | 亚洲情综合五月天 | 一区二区三区视频 | 99re66在线观看精品热 | 久久不卡区 | 久久久精品亚洲 | 久久精品国产一区二区电影 | 国产精品久久久久久久午夜片 | 在线亚洲免费 | 国产精品一区视频 | 国产三区视频在线观看 | 超碰在线亚洲 | 高清欧美性猛交xxxx黑人猛交 | www.99热| 97成人精品 |