• 欢迎访问DBA的辛酸事儿,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站
  • 欢迎大家关注博主公众号:DBA的辛酸事儿
  • 博文中若有错误的地方,请大家指正,大家的指正是我前进的动力

MySQL 8.0新特性: 持久化自增列

MySQL SEian.G 5年前 (2019-10-26) 1850次浏览 已收录 0个评论
文章目录[隐藏]

问题背景

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

问题复现

8.0版本

root@localhost:mysql8006.sock [wjq]>show create table replace_uniq1\G;
*************************** 1. row ***************************
       Table: replace_uniq1
Create Table: CREATE TABLE `replace_uniq1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

root@localhost:mysql8006.sock [wjq]>select * from replace_uniq1;
+----+---------+
| id | content |
+----+---------+
|  1 | aa      |
|  2 | b       |
|  3 | c       |
|  4 | d       |
|  5 | ee      |
+----+---------+
5 rows in set (0.00 sec)
root@localhost:mysql8006.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              6 |
+----------------+
1 row in set (0.00 sec)

root@localhost:mysql8006.sock [wjq]>alter table replace_uniq1 auto_increment=10;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost:mysql8006.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              6 |
+----------------+
1 row in set (0.00 sec)

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

root@localhost:mysql8006.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql8006.sock' (2)
ERROR: 
Can't connect to the server

root@not_connected:not_connected [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
No connection. Trying to reconnect...
Connection id:    8
Current database: wjq

+----------------+
| AUTO_INCREMENT |
+----------------+
|              6 |
+----------------+
1 row in set (0.00 sec)

auto_increment还是没有更新为10,难道是MySQL8.0把这个数据记录到其他系统表了?在information_schema,sys,mysql等schema里没发现什么。

于是跑去MySQL5.7版本的去测试了一下,发现当用alter table修改了auto_increment,tables表的auto_increment列是会同步更新的。奇怪了,难道是BUG?

5.7版本实例

root@localhost:mysql3306.sock [wjq]>show create table replace_uniq1\G;
*************************** 1. row ***************************
       Table: replace_uniq1
Create Table: CREATE TABLE `replace_uniq1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)


root@localhost:mysql3306.sock [wjq]>select * from replace_uniq1;
+----+---------+
| id | content |
+----+---------+
|  1 | aa      |
|  2 | b       |
|  3 | c       |
|  4 | d       |
|  5 | ee      |
+----+---------+
5 rows in set (0.00 sec)
root@localhost:mysql3306.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
+----------------+
| auto_increment |
+----------------+
|              6 |
+----------------+
1 row in set (0.00 sec)


root@localhost:mysql3306.sock [wjq]>alter table replace_uniq1 auto_increment=10;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost:mysql3306.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
+----------------+
| auto_increment |
+----------------+
|             10 |
+----------------+
1 row in set (0.00 sec)
root@localhost:mysql3306.sock [wjq]>select * from replace_uniq1;
+----+---------+
| id | content |
+----+---------+
|  1 | aa      |
|  2 | b       |
|  3 | c       |
|  4 | d       |
|  5 | ee      |
+----+---------+
5 rows in set (0.00 sec)

root@localhost:mysql3306.sock [wjq]>insert into replace_uniq1(content) values('f');
Query OK, 1 row affected (0.01 sec)

root@localhost:mysql3306.sock [wjq]>select * from replace_uniq1;
+----+---------+
| id | content |
+----+---------+
|  1 | aa      |
|  2 | b       |
|  3 | c       |
|  4 | d       |
|  5 | ee      |
| 10 | f       |
+----+---------+
6 rows in set (0.00 sec)

一番折腾之后,发现MySQL 8.0对自增列出了如下的优化:

在5.7及以前,alter table修改的auto_increment值确实是记录到tables表的auto_increment列的,但是有一个缺点是,tables表的引擎是memory,也就是说,如果MySQL重启了,这个表记录的auto_increment值就丢失了。例如表当前的auto_increment值是30,使用alter table将其修改为50,那么tables表的auto_increment列确实是记录显示了50,但如果表没有插入任何数据,并重启了MySQL(不管是正常还是异常重启),下一次运行时,表的auto_increment属性就会回退到30,同时tables表的auto_increment列显示的也是30。

那么既然tables是memory引擎表,重启之后理应没有数据才对,为什么auto_increment列还显示30呢?

原因是MySQL重启后,通过select max(col) from tab for update的方式,来选出当前的最大值并赋值给auto_increment。

那么到了8.0以后,有什么变化呢?

将自增主键的计数器持久化到redo log中。每次计数器发生改变,都会将其写入到redo log中。如果数据库发生重启,InnoDB会根据redo log中的计数器信息来初始化其内存值。为了尽量减小对系统性能的影响,计数器写入到redo log中,并不会马上刷新。

除了redo log,在系统表中也会记录auto_increment的信息,例如mysql.tables表。那么,在插入新行时,递增列应该参考redo log还是系统表呢?

而且从上面的例子可以看到,tables记录根本就不准确。其实答案就一句话,哪个是最新的,就用哪个。

那么回到系统表的问题上,既然要在重启后提供参考,总得靠谱一点吧?看一下8.0之前和之后版本对mysql.tables表定义:

5.7版本

root@localhost:mysql3306.sock [wjq]>show create table information_schema.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
1 row in set (0.00 sec)

8.0版本

root@localhost:mysql8006.sock [wjq]>show create table information_schema.tables\G;
*************************** 1. row ***************************
                View: TABLES
         Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`mysql.infoschema`@`localhost` SQL SECURITY DEFINER VIEW `information_schema`.`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
1 row in set (0.00 sec)

可以发现8.0之后,mysql.tables不再是memory表了,在8.0以后变成视图了。再仔细看一下,auto_increment列引用自mysql.table_stats表的auto_increment列。

到这里已经茅塞顿开了,马上执行一下analyze,不出意外mysql.tables表的auto_increment列就变成最新的10了:

root@localhost:mysql8006.sock [wjq]>analyze table replace_uniq1;           
+-------------------+---------+----------+----------+
| Table             | Op      | Msg_type | Msg_text |
+-------------------+---------+----------+----------+
| wjq.replace_uniq1 | analyze | status   | OK       |
+-------------------+---------+----------+----------+
1 row in set (0.01 sec)

root@localhost:mysql8006.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
+----------------+
| AUTO_INCREMENT |
+----------------+
|             10 |
+----------------+
1 row in set (0.01 sec)

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

参考文章

https://yq.aliyun.com/articles/60885

https://blog.csdn.net/weixin_39004901/article/details/84346093

 

MySQL 8.0新特性: 持久化自增列


如果您觉得本站对你有帮助,那么可以收藏和推荐本站,帮助本站更好地发展,在此谢过各位网友的支持。
转载请注明原文链接:MySQL 8.0新特性: 持久化自增列
喜欢 (6)
SEian.G
关于作者:
用心去记录工作,用心去感受生活,用心去学着成长;座右铭:苦练七十二变,笑对八十一难
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址