一道经典的MySQL面试题,答案被反转了三次会有几次反转。先看题目:一张ID自增主键的表。插入17条记录后,删除第15、16、17条记录,重启MySQL,再插入一条记录。这条记录你的ID是18号还是15号?整体来说,下面的题都不难,而且都是看似基础的题,但是这个题引起了我的注意,因为这个题的背景太开放了,所以答案也是不固定的,这也是严谨的我们在技术学习中需要保持的态度。首先,从整体上看这道题,我想表达的是我对MySQL自增列的理解。按照我们常规理解的逻辑,ID自增应该是18。按照这个逻辑,应该不是15吧?但这个答案正确吗?显然不是,我们进入第一轮逆转。确实,对于自增列的问题,这是MySQL中一直被诟病的老问题。如果节点重启,则从数据列中按照max(id)+1方法进行处理。在多环境历史数据归档的情况下,如果重启主库,可能会出现数据不一致的情况。记得在MySQLbug很多人留言,说十几年前的老问题还没解决。在OpenWorld上,PerconaCEOPeter也再次提到了这个问题。我仔细检查了这个错误的历史。巧合的是,这个问题在十多年前就被彼得提出来了。时间飞逝,它还没有被修复。好吧,按照MySQLbug的思路,答案应该是15。但是这个答案正确吗?显然不是,我们进入第二轮逆转。这个题目的背景还不够清楚。这个表的存储引擎没有说是InnoDB还是MyISAM,所以有不确定性。这句话的意思是MyISAM和InnoDB中自增列信息的维护逻辑是不一样的,在MyISAM中是存储在文件中持久化的。数据库重启后,ID可以通过持久化信息不断递增,而InnoDB的自增列信息既不在.frm文件中,也不在.frm文件中。ibd文件,所以这里启动的时候会按照max(id)+1的算法进行修复。所以如果是MyISAM,答案应该是18,如果是InnoDB,答案是15,我们可以综合比较,用小测试来模拟复现。我们选择了MySQL5.7环境。为了比较明显,我们创建了两张表test_innodb和test_myisam,分别对应InnoDB和MyISAM存储引擎,做同样的操作,重启后看区别。>>createtabletest_innodb(idintprimarykeyauto_increment,namevarchar(30))engine=innodb;>>createtabletest_myisam(idintprimarykeyauto_increment,namevarchar(30))engine=myisam;插入几行数据,查看数据:>>insertintotest_innodb(name)values('aa'),('bb'),('cc');QueryOK,3rowsaffected(0.00sec)Records:3Duplicates:0Warnings:0>>insertintotest_myisam(name)values('aa'),('bb'),('cc');QueryOK,3rowsaffected(0.00sec)Records:3Duplicates:0Warnings:0查看两个表的数据,数据为一模一样。>>select*fromtest_innodb;+----+------+|id|name|+----+------+|1|aa||2|bb||3|cc|+----+-----+3rowsinset(0.00sec)>>select*fromtest_myisam;+----+-----+|id|name|+----+------+|1|aa||2|bb||3|cc|+----+-----+3rowsinset(0.00sec)在1,2,3的基础上,我们继续插入值为5并跳过id值为4。>>insertintotest_innodb(id,name)values(5,'ee');QueryOK,1rowaffected(0.00sec)>>insertintotest_myisam(id,name)values(5,'ee');QueryOK,1rowaffected(0.00sec)在这个time查看test_innodb的自增列已经开始增长,值为6。`id`))ENGINE=InnoDBAUTO_INCREMENT=6DEFAULTCHARSET=utf81rowinset(0.00sec)deleteid=5Record>>deletefromtest_innodbwhereid=5;QueryOK,1rowaffected(0.01sec)删除记录后自增列不变。>>showcreatetabletest_innodb\GCREATETABLE`test_innodb`(`id`int(11)NOTAUTO_INCREMENT,`name`varchar(30)DEFAULT,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=6DEFAULTCHARSET=utf81rowinset(0.00sec)相同test_myisam相同测试,结果完全一样,这里略过log。我们停止数据库>>shutdown;QueryOK,0rowsaffected(0.00sec)重启数据库#mysqld_safe--defaults-file=/data/mysql_5723/my.cnf&这时候test_innodb和test_myisam的自增列的区别开始出现。MyISAM存储引擎的表test_myisam的自增列保持6不变。>>showcreatetabletest_myisam\GCREATETABLE`test_myisam`(`id`int(11)NOTAUTO_INCREMENT,`name`varchar(30)DEFAULT,PRIMARYKEY(`id`))ENGINE=MyISAMAUTO_INCREMENT=6DEFAULTCHARSET=utf81rowinset(0.00sec)和InnoDB存储引擎表test_innodb的自增列变成了4.>>showcreatetabletest_innodb\G*********************************1.row****************************表:test_innodbCreateTable:创建表`test_innodb`(`id`int(11)NOTAUTO_INCREMENT,`name`varchar(30)DEFAULT,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=4DEFAULTCHARSET=utf8我们继续插入一条数据,id列保持自增。>>insertintotest_innodb(name)values('ee');QueryOK,1rowaffected(0.00sec)>>insertintotest_myisam(name)values('ee');QueryOK,1rowaffected(0.00sec)可以看到两者的id列表已经分道扬镳。>>select*fromtest_innodb;+----+------+|id|name|+----+------+|1|aa||2|bb||3|cc||4|ee|+----+-----+4rowsinset(0.00sec)>>select*fromtest_myisam;+----+------+|id|name|+----+-----+|1|aa||2|bb||3|cc||6|ee|+----+-----+4rowsinset(0.00sec)总结:对于MyISAM和InnoDB表,由于存储引擎对自增列的实现机制不同,ID值也可能不同。对于InnoDB存储引擎表,ID是按照max(id)+1的算法计算的。但这个答案对吗?显然不是,因为还是不够严谨,我们进入第三轮反转。这个问题不够严谨,因为技术在逐渐发展。MySQL8.0中已经回答了这个问题。对于InnoDB的自增列信息,断电会直接丢失,可能会造成级联从库之间的数据不同步。问题,而MySQL8.0以后,这个信息被写入了共享表空间,所以重启服务后,仍然可以追踪到自增列的ID变化。限于篇幅,因为测试日志都大同小异,我直接给出测试后的日志。这是数据库重启后自增的列。可以看到test_innodb和test_myisam的自增列是完全一样的。mysql>showcreatetabletest_myisam\G******************************1.row****************************表:test_myisamCreateTable:创建表`test_myisam`(`id`int(11)NOTAUTO_INCREMENT,`name`varchar(30)DEFAULT,PRIMARYKEY(`id`))引擎=MyISAMAUTO_INCREMENT=6DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci1rowinset(0.00sec)mysql>showcreatetabletest_innodb\G*******************************1.row***************************表:test_innodbCreateTable:创建表`test_innodb`(`id`int(11)NOTAUTO_INCREMENT,`name`varchar(30)DEFAULT,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=6DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci1rowinset(0.00sec)总结一下:MySQL8.0之前:1)如果是MyISAM表,重启数据库后,id值为182)如果是InnoDB表,重启数据库后ID值为15。从MySQL8.0开始,1)如果是MyISAM表,重启数据库后ID值为182。)如果是一张InnoDB表,数据后ID值为18基地重启。是的,对于ID自增列,在MySQL5.7中可以使用sysschema进行有效监控,查看schema_auto_increment_columns视图可以有效判断列值溢出。更难能可贵的是,如果MySQL版本在5.7以下,虽然没有sysschema特性,但是可以复用MySQL5.7中schema_auto_increment_columns的视图语句,也可以有效判断列值溢出。
