第四十一期:一道经典的MySQL面试题,答案出现三次反转

前几天偶然看到大家在讨论一道面试题,而且答案也不够统一,我感觉蛮有意思,在此就做一个解读,整个过程中确实会有几处反转。

作者:杨建荣的学习笔记来源:今日头条

前几天偶然看到大家在讨论一道面试题,而且答案也不够统一,我感觉蛮有意思,在此就做一个解读,整个过程中确实会有几处反转。

我们先来看下题目:

一张表,里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把MySQL重启,再Insert一条记录,这条记录的ID是18还是15.

和后面的一些题目整体来看,难度不大,都是一些看起来很基础的问题,但是这道题目引起了我的注意,因为这道题目的背景过于开放,所以答案也是不固定的,而这也是我们在技术学习中需要保持的严谨态度。

首先这道题整体来看,想表达的是对于MySQL中自增列的理解。

按照我们常规理解的逻辑,ID自增,应该是18,按照这个逻辑怎么都不应该是15吧?

但是这个答案对吗?显然不是,我们进入第一轮反转。

确实,对于自增列的问题,这个是MySQL里面饱受诟病的老问题了。如果节点重启,会从数据列中按照max(id)+1的方式来处理,在多环境历史数据归档的情况下,如果主库重启,很可能会出现数据不一致的情况,记得在MySQL bug中很多人留言,说十多年前的老问题了,怎么还不解决。

而在OpenWorld上面Percona CEO Peter也再次提到了这个问题。

我认真查了一下这个bug的历史,巧合的是,这个问题是Peter在十几年前提出的,时光荏苒,一直没有修复。

好的,按照MySQL bug的思路来理解,答案应该是15了。

但是这个答案对吗?显然不是,我们进入第二轮反转。

这个题目的背景是不够清晰的,这个表的存储引擎没有说是InnoDB还是MyISAM,所以存在不确定性,这么说的意义在于,自增列的信息在MyISAM和InnoDB中的维护逻辑是不大一样的,在MyISAM中是存储持久化在文件中的,当数据库重启之后,是可以通过持久化的信息持续对ID进行自增的,而InnoDB的自增列信息既不在.frm文件,也不在.ibd文件中,所以在此启动的时候会按照max(id)+1的算法进行修复。

所以如果是MyISAM,则答案应该是18,而如果是InnoDB,则答案是15。

我们可以综合对比,用一个小的测试来模拟复现,我们选择的是MySQL 5.7环境。

为了对比明显,我们创建两张表test_innodb和test_myisam,分别对应InnoDB和MyISAM存储引擎,来做同样的操作,看看重启后的差异情况。

>>create table test_innodb(id int primary key auto_increment,name varchar(30)) 
engine=innodb;
>>create table test_myisam(id int primary key auto_increment,name varchar(30)) engine=myisam; 

插入几行数据,查看数据:

>>insert into test_innodb(name) values('aa'),('bb'),('cc');Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0 
>>insert into test_myisam(name) values('aa'),('bb'),('cc'); Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0 

查看两张表的数据情况,数据是完全一样。

>>select *from test_innodb;+----+------+| id | name |+----+------+| 1 | aa || 2| bb || 3 | cc |+----+------+3 rows in set (0.00 sec) 
>>select *from test_myisam;+----+------+| id | name |+----+------+| 1 | aa || 2| bb || 3 | cc |+----+------+3 rows in set (0.00 sec)

 

在1,2,3的基础上,我们继续插入值为5,跳过id值为4。

>>insert into test_innodb(id,name) values(5,'ee');Query OK, 1 row affected (0.00 sec) 
>>insert into test_myisam(id,name) values(5,'ee'); Query OK, 1 row affected (0.00 sec) 

此时查看test_innodb自增列已经开始增长,值为6。

>>show create table test_innodb\G CREATE TABLE `test_innodb` 
( `id` int(11) NOT AUTO_INCREMENT, `name` varchar(30) DEFAULT , PRIMARY KEY (`id`)) ENGINE=InnoDB 
AUTO_INCREMENT=6 DEFAULT CHARSET=utf81 row in set (0.00 sec) 

删除id=5的记录

delete from test_innodb where id=5;Query OK, 1 row affected (0.01 sec) 

删除记录之后,自增列还是保持不变。

show create table test_innodb\G CREATE TABLE 
`test_innodb` ( `id` int(11) NOT AUTO_INCREMENT, `name` varchar(30) DEFAULT , 
PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf81 row in set (0.00 sec) 

同理test_myisam也做同样的测试,结果是完全一样的,在此略过日志。

我们停止数据库

>>shutdown;Query OK, 0 rows affected (0.00 sec) 

重启数据库

#mysqld_safe --defaults-file=/data/mysql_5723/my.cnf & 

此时查看test_innodb和test_myisam的自增列就开始出现差异了。

MyISAM存储引擎的表test_myisam的自增列还是不变,为6。

show create table test_myisam\G CREATE TABLE `test_myisam` ( `id` int(11) NOT AUTO_INCREMENT, 
`name` varchar(30) DEFAULT , PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=6DEFAULT CHARSET=utf81 row in set (0.00 sec) 

而InnoDB存储引擎的表test_innodb的自增列却变为了4。

>>show create table test_innodb\G *************************** 
1. row *************************** Table: test_innodbCreate Table:CREATE TABLE `test_innodb`( `id` int(11) NOT AUTO_INCREMENT, `name` varchar(30) DEFAULT , 
PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 

我们继续插入一条数据,保持id列自增。

>>insert into test_innodb(name) values('ee');Query OK, 1 row affected (0.00 sec) 
>>insert into test_myisam(name) values('ee'); Query OK, 1 row affected (0.00 sec) 

可以看到两张表的id列已经分道扬镳了。

>>select *from test_innodb; +----+------+| id | name |+----+------+| 1 | aa || 2 | bb || 3 | cc || 4 | ee |+----+------+4 rows in set (0.00 sec) 
>>select *from test_myisam; +----+------+| id | name |+----+------+| 1 | aa || 2 | bb || 3 | cc || 6 | ee |+----+------+4 rows in set (0.00 sec) 

小结:对于MyISAM和InnoDB的表,因为存储引擎对于自增列的实现机制不同,ID值也可能会有所不同,对于InnoDB存储引擎的表,ID是按照max(id)+1的算法来计算的。

但是这个答案对吗?显然不是,因为还是不够严谨,我们进入第三轮反转。

这个问题不够严谨是因为技术是逐步发展的,这个问题在MySQL 8.0中有了答案,对于InnoDB的自增列信息,如果断电之后会直接丢失,很可能造成级联从库间的数据同步出现问题,而在MySQL 8.0之后,这个信息写入了共享表空间中,所以服务重启之后,还是可以继续追溯这个自增列的ID变化情况的。

限于篇幅,因为测试日志是很相似的,我就直接给出测试后的日志,这是在数据库重启之后的自增列情况,可以看到test_innodb和test_myisam的自增列是完全一样的。

mysql> show create table test_myisam\G*************************** 
1. row *************************** Table: test_myisamCreate Table: 
CREATE TABLE `test_myisam`( `id` int(11) NOT AUTO_INCREMENT, `name` varchar(30) DEFAULT , PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci1 
row in set (0.00 sec) 
mysql> show create table test_innodb\G *************************** 
1. row *************************** Table: test_innodbCreate Table: CREATE TABLE `test_innodb` 
( `id` int(11) NOT AUTO_INCREMENT, `name` varchar(30) DEFAULT , PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=
6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci1 row in set (0.00 sec) 

我们做一个小结:

在MySQL 8.0之前:

1)如果是MyISAM表,则数据库重启后,ID值为18

2)如果是InnoDB表,则数据库重启后,ID值为15

在MySQL 8.0开始,

1)如果是MyISAM表,则数据库重启后,ID值为18

2)如果是InnoDB表,则数据库重启后,ID值为18

此处需要补充的是,对于ID自增列,在MySQL 5.7中可以使用sys schema来进行有效监控了,可以查看视图schema_auto_increment_columns 来进行列值溢出的有效判断。

更难能可贵的是,如果是MySQL 5.7版本以下,虽然没有sys schema特性,但是可以复用MySQL 5.7中的schema_auto_increment_columns 的视图语句,也是可以对列值溢出进行有效判断的。

阅读目录(置顶)(长期更新计算机领域知识)https://blog.csdn.net/weixin_43392489/article/details/102380691

阅读目录(置顶)(长期更新计算机领域知识)https://blog.csdn.net/weixin_43392489/article/details/102380882

阅读目录(置顶)(长期科技领域知识)https://blog.csdn.net/weixin_43392489/article/details/102600114

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/424778.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java中资源获取

安装java sudo apt-get install default-jdk 获取doc $dpkg -l | grep jdk ii default-jdk 2:1.8-56ubuntu2 amd64 Standard Java or Java compatible Development Kit ii default-jdk-headless …

LinkedList专题3

148. Sort List 问题:要求时间度O(NlogN)O(NlogN),空间复杂度是常数,实现数组排序。 思路:之前做过linkedList的插入排序,时间复杂度应该是O(n2)O(n2)。时间复杂度控制在nlogn 一定有二分。二分我只在数组版的list中…

java面试题1 牛客:A派生出子类B,B派生出子类C,并且在java源代码中有如下声明:

懵逼树上懵逼果,懵逼树下你和我 第一题 [单选题] A派生出子类B,B派生出子类C,并且在java源代码中有如下声明: 1 2 3 A a0new A(); A a1new B(); A a2new C(); 以哪个说法是正确的 A第1行,第2行和第3行的声明都是正…

[Leetcode][第81题][JAVA][N皇后问题][回溯算法]

【问题描述】[困难] 【解答思路】 1. 主副对角线列 标记 复杂度 import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List;public class Solution {private int n;// 记录某一列是否放置了皇后private boolean[] col;// 记…

编码解码错误

python读取文件时提示UnicodeDecodeError错误: "UnicodeDecodeError: gbk codec cant decode byte 0x80 in position 205: illegal multibyte sequence" “UnicodeDecodeError: cp950 codes cant decode byte oxc3 in position 25402: illegal multibyte …

java面试题2 牛客:定义类中成员变量时不可能用到的修饰是

懵逼树上懵逼果,懵逼树下你和我 第二题 定义类中成员变量时不可能用到的修饰是() A final B void C protected D static 一看到这道题,有点慌了,直接选c,很显然,我又错了,难题…

[Leetcode][第257题][JAVA][二叉树的所有路径][BFS][DFS]

【问题描述】[简单] 【解答思路】 1. DFS 时间复杂度&#xff1a;O(N^2) 空间复杂度&#xff1a;O(N^2) class Solution {public List<String> binaryTreePaths(TreeNode root) {List<String> paths new ArrayList<String>();constructPaths(root, "…

Depth-first Search深度优先搜索专题1

104. Maximum Depth of Binary Tree 思路&#xff1a;顺着树的一个分支一直数层数直到叶子节点。DFS的思路。这个题目可以练习的是递归转迭代。 代码 695. Max Area of Island 思路&#xff1a;遇到点i,j;如果grid[i][j]没有被访问过&#xff0c;并且等于1&#xff0c;那么…

作用域和数组

作用域 就是一对大括号{} 作用域的的特点在作用域内部声明的变量,只能在这个作用域使用 数组 array 一组类型相同的数(值)1.数组一旦定义好长度就不能改变了2.数组访问摸个数据的方式 数组以下标(index)的方式获取数据 数组的声明 定义10个学员的成绩/两只之间以,隔开…

java面试题3 牛客:下面有关jdbc statement的说法错误的是

下面有关jdbc statement的说法错误的是&#xff1f; A JDBC提供了Statement、PreparedStatement 和 CallableStatement三种方式来执行查询语句&#xff0c; 其中 Statement 用于通用查询&#xff0c; PreparedStatement 用于执行参数化查询&#xff0c;而 CallableStatement则…

Depth-first Search深度优先搜索专题2

199. Binary Tree Right Side View 思路&#xff1a;想要得到树的每一层最右侧元素值&#xff0c;用BFS最方便。先访问左节点再访问右节点&#xff0c;最后访问的一个值就是留下的值。 想要DFS的思路也可以。只是一定要访问所有节点。 代码 491 Increasing Subsequences …

[Leetcode][第60题][JAVA][第k个排列][回溯][DFS][剪枝]

【问题描述】[中等] 【解答思路】 1. 回溯搜索算法 剪枝 &#xff0c;直接来到叶子结点 时间复杂度&#xff1a;O(N^2) 空间复杂度&#xff1a;O(N) import java.util.Arrays;public class Solution {/*** 记录数字是否使用过*/private boolean[] used;/*** 阶乘数组*/priv…

java面试题4 牛客:运行下列代码,运行结果为

第四题&#xff1a;运行下列代码&#xff0c;运行结果为&#xff08;&#xff09; class A {public A() {System.out.println("class A");}{ System.out.println("Im A class"); }static { System.out.println("class A static"); }}public cla…

蓝桥杯-每日刷题-026

奖金提成 一、题目要求 题目描述 企业发放的奖金根据利润提成。 利润I低于100000元的奖金可提10%&#xff1b; 利润高于100000元&#xff0c;低于200000元的&#xff08;100000<I<200000&#xff09;时,低于100000元的部分按10%提成&#xff0c;高于100000元的部分&…

优秀技术人的管理陷阱

写在前面 几乎每一位做软件开发的技术人&#xff0c;都听过类似这样的话&#xff0c;“30 岁以后&#xff0c;身体比不过 20 几岁的年轻人&#xff0c;需要转型做管理了”。这句话理解起来是没有问题的。的确年龄越大身体就越比不过自己 20 多岁的时候&#xff0c;拼体力不够&a…

机器学习中的数学--数学知识复习

机器学习 机器学习三个部分&#xff1a;编程能力数学统计知识业务知识 机器学习分类 1 监督学习&#xff1a;例如分类、房价预测 2 无监督学习&#xff1a;例如聚类 3 强化学习&#xff1a;例如动态系统、机器人控制系统 机器学习算法 是否连续无监督有监督连续聚类 &am…

【数据结构与算法】【算法思想】拓扑排序

一、拓扑排序 拓扑排序是基于依赖关系的节点&#xff0c;根据依赖关系而生成的序列。节点和依赖关系往往要生成有向无环图。类似的问题有&#xff1a;穿衣服裤子的先后关系&#xff0c;生成穿衣序列/专业课程与前置课程形成的课程学习序列/代码编译依赖关系形成的编译顺序序列…

java面试题5 牛客:下列关于JavaBean的说法正确的是:

下列关于JavaBean的说法正确的是&#xff1a; A:Java文件与Bean所定义的类名可以不同&#xff0c;但一定要注意区分字母的大小写 B:在JSP文件中引用Bean&#xff0c;其实就是用语句 C&#xff1a;被引用的Bean文件的文件名后缀为.java D:Bean文件放在任何目录下都可以被引用…

Depth-first Search深度优先搜索专题3

473. Matchsticks to Square 思路&#xff1a;有n根长度不一的火柴&#xff0c;这些火柴可以拼接在一起&#xff0c;但不能被折断。这些火柴能够围城一个正方形吗&#xff1f;每个火柴可以并且必须使用一次。分析得到每个边的长度应该是所有火柴长度和的1/4。接下来就是将火柴…

mysql数据库之事务与存储过程

事务 什么是事务&#xff1f; 事务是指一些SQL语句的集合&#xff0c;这些语句同时执行成功完成某项功能 事务的CAID特性&#xff1a; 原子性&#xff1a;一个事务的执行是整体性的&#xff0c;要么内部所有语句都执行成功&#xff0c;要么一个都别想成功 一致性&#xff1a;事…