由一个关于auto_increment的奇怪案例看MySQL8的一些改进优化

在MySQL8上偶然发现一个比较奇怪的问题,在使用alter table tab auto_increment=N修改表的自增初始值时,information_schema.tables这个表的auto_increment列并没有同步更新:

mysql> show create table test.incr_test\g
+-----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table     | Create Table                                                                                                                                                                  |
+-----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| incr_test | CREATE TABLE `incr_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+-----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select auto_increment from information_schema.tables where table_schema='test' and table_name='incr_test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|            101 |
+----------------+
1 row in set (0.00 sec)

mysql> alter table test.incr_test auto_increment=120;
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> select auto_increment from information_schema.tables where table_schema='test' and table_name='incr_test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|            101 |
+----------------+
1 row in set (0.00 sec)

首先想到的是,数据在buffer没提交?于是重启一下MySQL,auto_increment列仍是没有变化:

[root@linux-base data]# service mysql restart
Shutting down MySQL...                                     [  OK  ]
Starting MySQL..                                           [  OK  ]
[root@linux-base data]# mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.12 MySQL Community Server - GPL

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select auto_increment from information_schema.tables where table_schema='test' and table_name='incr_test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|            101 |
+----------------+
1 row in set (0.01 sec)

auto_increment还是没有更新为120,难道是MySQL8把这个数据记录到其他系统表了?在information_schema,sys,mysql等schema里找了半天也没发现什么端倪。于是跑去MySQL5.7版本的去测试了一下,发现当用alter table修改了auto_increment,tables表的auto_increment列是会同步更新的。奇怪了,难道是BUG?
一番折腾之后,发现MySQL8做了下面的一些骚操作:
在5.7及以前,alter table修改的auto_increment值确实是记录到tables表的auto_increment列的,但是有一个缺点是,tables表的引擎是memory,也就是说,如果MySQL重启了,这个表记录的auto_increment值就丢失了。例如tab表当前的auto_increment值是30,使用alter table将其修改为50,那么tables表的auto_increment列确实是记录显示了50,但如果tab没有插入任何数据,并重启了MySQL(不管是正常还是异常重启),下一次运行时,tab表的auto_increment属性就会回退到30,同时tables表的auto_increment列显示的也是30。
那么既然tables是memory引擎表,重启之后理应没有数据才对,为什么auto_increment列还显示30呢?原因是MySQL重启后,通过select max(col) from tab for update的方式,来选出当前的最大值并赋值给auto_increment。
那么到了8.0以后,有什么变化呢?auto_increment的值会记录到redo log里了。什么概念,redo log大哥出手,就意味着下次重启还能把值要回来啊,不再随着重启打水漂。
除了redo log,在系统表中也会记录auto_increment的信息,例如tables表。那么,在插入新行时,递增列应该参考redo log还是系统表呢?而且从上面的例子可以看到,tables记录根本就不准确。其实答案就一句话,哪个是最新的,就用哪个。
那么回到系统表的问题上,既然要在重启后提供参考,总得靠谱一点吧?以前那么老实,到新版就变调皮了啊。看一下8.0以前的tables表定义:

mysql> show create table tables\G
*************************** 1. row ***************************
       Table: TABLES
Create Table: CREATE TEMPORARY TABLE `TABLES` (
  `TABLE_CATALOG` varchar(512) NOT NULL DEFAULT '',
  `TABLE_SCHEMA` varchar(64) NOT NULL DEFAULT '',
  `TABLE_NAME` varchar(64) NOT NULL DEFAULT '',
  `TABLE_TYPE` varchar(64) NOT NULL DEFAULT '',
  `ENGINE` varchar(64) DEFAULT NULL,
  `VERSION` bigint(21) unsigned DEFAULT NULL,
  `ROW_FORMAT` varchar(10) DEFAULT NULL,
  `TABLE_ROWS` bigint(21) unsigned DEFAULT NULL,
  `AVG_ROW_LENGTH` bigint(21) unsigned DEFAULT NULL,
  `DATA_LENGTH` bigint(21) unsigned DEFAULT NULL,
  `MAX_DATA_LENGTH` bigint(21) unsigned DEFAULT NULL,
  `INDEX_LENGTH` bigint(21) unsigned DEFAULT NULL,
  `DATA_FREE` bigint(21) unsigned DEFAULT NULL,
  `AUTO_INCREMENT` bigint(21) unsigned DEFAULT NULL,
  `CREATE_TIME` datetime DEFAULT NULL,
  `UPDATE_TIME` datetime DEFAULT NULL,
  `CHECK_TIME` datetime DEFAULT NULL,
  `TABLE_COLLATION` varchar(32) DEFAULT NULL,
  `CHECKSUM` bigint(21) unsigned DEFAULT NULL,
  `CREATE_OPTIONS` varchar(255) DEFAULT NULL,
  `TABLE_COMMENT` varchar(2048) NOT NULL DEFAULT ''
) ENGINE=MEMORY DEFAULT CHARSET=utf8

8.0以后的tables表定义:


mysql> show create table tables\G
*************************** 1. row ***************************
                View: TABLES
         Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`mysql.infoschema`@`localhost` SQL SECURITY DEFINER VIEW `TABLES` AS select `cat`.`name` AS `TABLE_CATALOG`,`sch`.`name` AS `TABLE_SCHEMA`,`tbl`.`name` AS `TABLE_NAME`,`tbl`.`type` AS `TABLE_TYPE`,if((`tbl`.`type` = 'BASE TABLE'),`tbl`.`engine`,NULL) AS `ENGINE`,if((`tbl`.`type` = 'VIEW'),NULL,10) AS `VERSION`,`tbl`.`row_format` AS `ROW_FORMAT`,internal_table_rows(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`table_rows`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `TABLE_ROWS`,internal_avg_row_length(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`avg_row_length`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `AVG_ROW_LENGTH`,internal_data_length(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`data_length`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `DATA_LENGTH`,internal_max_data_length(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`max_data_length`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `MAX_DATA_LENGTH`,internal_index_length(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`index_length`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `INDEX_LENGTH`,internal_data_free(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`data_free`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `DATA_FREE`,internal_auto_increment(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`auto_increment`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0),`tbl`.`se_private_data`) AS `AUTO_INCREMENT`,`tbl`.`created` AS `CREATE_TIME`,internal_update_time(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(cast(`stat`.`update_time` as unsigned),0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `UPDATE_TIME`,internal_check_time(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(cast(`stat`.`check_time` as unsigned),0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `CHECK_TIME`,`col`.`name` AS `TABLE_COLLATION`,internal_checksum(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`checksum`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `CHECKSUM`,if((`tbl`.`type` = 'VIEW'),NULL,get_dd_create_options(`tbl`.`options`,if((ifnull(`tbl`.`partition_expression`,'NOT_PART_TBL') = 'NOT_PART_TBL'),0,1))) AS `CREATE_OPTIONS`,internal_get_comment_or_error(`sch`.`name`,`tbl`.`name`,`tbl`.`type`,`tbl`.`options`,`tbl`.`comment`) AS `TABLE_COMMENT` from (((((`mysql`.`tables` `tbl` join `mysql`.`schemata` `sch` on((`tbl`.`schema_id` = `sch`.`id`))) join `mysql`.`catalogs` `cat` on((`cat`.`id` = `sch`.`catalog_id`))) left join `mysql`.`collations` `col` on((`tbl`.`collation_id` = `col`.`id`))) left join `mysql`.`tablespaces` `ts` on((`tbl`.`tablespace_id` = `ts`.`id`))) left join `mysql`.`table_stats` `stat` on(((`tbl`.`name` = `stat`.`table_name`) and (`sch`.`name` = `stat`.`schema_name`)))) where (can_access_table(`sch`.`name`,`tbl`.`name`) and is_visible_dd_object(`tbl`.`hidden`))
character_set_client: utf8
collation_connection: utf8_general_ci

可以发现原来tables不再是memory表了,这家伙整容了,在8.0以后变成视图了。再仔细看一下,auto_increment列引用自mysql.table_stats表的auto_increment列。到这里已经茅塞顿开了,马上执行一下analyze,不出意外tables表的auto_increment列就变成最新的120了:

mysql> select auto_increment from information_schema.tables where table_schema='test' and table_name='incr_test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|            101 |
+----------------+
1 row in set (0.01 sec)

mysql> analyze table test.incr_test;
+----------------+---------+----------+----------+
| Table          | Op      | Msg_type | Msg_text |
+----------------+---------+----------+----------+
| test.incr_test | analyze | status   | OK       |
+----------------+---------+----------+----------+
1 row in set (10.10 sec)

mysql> select auto_increment from information_schema.tables where table_schema='test' and table_name='incr_test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|            120 |
+----------------+
1 row in set (0.00 sec)

由此可以看出,MySQL8以后,auto_increment方面有了改进的地方,当然不止这里所涉及的,同时在数据字典和系统表方面也做出了一些变化,由表转变为视图,越来越像Oracle了。MySQL自从被Oracle收购以后,真是入乡随俗啊。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的MySQL教务系统案例: 首先,我们需要创建一个数据库,命名为"edu_system",并创建以下几个表: 1. 学生表(student): ``` CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `gender` varchar(10) NOT NULL, `age` int(11) NOT NULL, `major` varchar(50) NOT NULL, `grade` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` 2. 教师表(teacher): ``` CREATE TABLE `teacher` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `gender` varchar(10) NOT NULL, `age` int(11) NOT NULL, `title` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` 3. 课程表(course): ``` CREATE TABLE `course` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `credit` int(11) NOT NULL, `teacher_id` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `teacher_id` (`teacher_id`), CONSTRAINT `course_ibfk_1` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` 4. 选课表(course_selection): ``` CREATE TABLE `course_selection` ( `id` int(11) NOT NULL AUTO_INCREMENT, `student_id` int(11) NOT NULL, `course_id` int(11) NOT NULL, `score` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `student_id` (`student_id`), KEY `course_id` (`course_id`), CONSTRAINT `course_selection_ibfk_1` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `course_selection_ibfk_2` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` 以上是创建表的SQL语句,接下来我们来插入一些数据: 1. 学生表插入数据: ``` INSERT INTO `student` (`name`, `gender`, `age`, `major`, `grade`) VALUES ('张三', '男', 20, '计算机科学', 2018), ('李四', '女', 21, '软件工程', 2017), ('王五', '男', 19, '信息安全', 2019), ('赵六', '女', 22, '数据科学', 2016); ``` 2. 教师表插入数据: ``` INSERT INTO `teacher` (`name`, `gender`, `age`, `title`) VALUES ('张老师', '男', 35, '教授'), ('李老师', '女', 30, '副教授'), ('王老师', '男', 40, '教授'), ('赵老师', '女', 38, '副教授'); ``` 3. 课程表插入数据: ``` INSERT INTO `course` (`name`, `credit`, `teacher_id`) VALUES ('数据库', 3, 1), ('编译原理', 4, 2), ('操作系统', 4, 3), ('计算机网络', 3, 4); ``` 4. 选课表插入数据: ``` INSERT INTO `course_selection` (`student_id`, `course_id`, `score`) VALUES (1, 1, 90), (2, 1, 85), (3, 3, 80), (4, 2, 88), (1, 4, 95), (2, 2, 92), (3, 1, 86), (4, 3, 78); ``` 以上是简单的MySQL教务系统案例,主要包括学生表、教师表、课程表和选课表,可以根据需要进行扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值