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

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

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

问题背景

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

问题复现

8.0版本

  1. root@localhost:mysql8006.sock [wjq]>show create table replace_uniq1\G;
  2. *************************** 1. row ***************************
  3. Table: replace_uniq1
  4. Create Table: CREATE TABLE `replace_uniq1` (
  5. `id` int(11) NOT NULL AUTO_INCREMENT,
  6. `content` varchar(10) DEFAULT NULL,
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
  9. 1 row in set (0.00 sec)
  10.  
  11. root@localhost:mysql8006.sock [wjq]>select * from replace_uniq1;
  12. +----+---------+
  13. | id | content |
  14. +----+---------+
  15. | 1 | aa |
  16. | 2 | b |
  17. | 3 | c |
  18. | 4 | d |
  19. | 5 | ee |
  20. +----+---------+
  21. 5 rows in set (0.00 sec)
  1. root@localhost:mysql8006.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
  2. +----------------+
  3. | AUTO_INCREMENT |
  4. +----------------+
  5. | 6 |
  6. +----------------+
  7. 1 row in set (0.00 sec)
  8.  
  9. root@localhost:mysql8006.sock [wjq]>alter table replace_uniq1 auto_increment=10;
  10. Query OK, 0 rows affected (0.02 sec)
  11. Records: 0 Duplicates: 0 Warnings: 0
  12.  
  13. root@localhost:mysql8006.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
  14. +----------------+
  15. | AUTO_INCREMENT |
  16. +----------------+
  17. | 6 |
  18. +----------------+
  19. 1 row in set (0.00 sec)

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

  1. root@localhost:mysql8006.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
  2. ERROR 2006 (HY000): MySQL server has gone away
  3. No connection. Trying to reconnect...
  4. ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql8006.sock' (2)
  5. ERROR:
  6. Can't connect to the server
  7.  
  8. root@not_connected:not_connected [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
  9. No connection. Trying to reconnect...
  10. Connection id: 8
  11. Current database: wjq
  12.  
  13. +----------------+
  14. | AUTO_INCREMENT |
  15. +----------------+
  16. | 6 |
  17. +----------------+
  18. 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版本实例

  1. root@localhost:mysql3306.sock [wjq]>show create table replace_uniq1\G;
  2. *************************** 1. row ***************************
  3. Table: replace_uniq1
  4. Create Table: CREATE TABLE `replace_uniq1` (
  5. `id` int(11) NOT NULL AUTO_INCREMENT,
  6. `content` varchar(10) DEFAULT NULL,
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
  9. 1 row in set (0.00 sec)
  10.  
  11.  
  12. root@localhost:mysql3306.sock [wjq]>select * from replace_uniq1;
  13. +----+---------+
  14. | id | content |
  15. +----+---------+
  16. | 1 | aa |
  17. | 2 | b |
  18. | 3 | c |
  19. | 4 | d |
  20. | 5 | ee |
  21. +----+---------+
  22. 5 rows in set (0.00 sec)
  1. root@localhost:mysql3306.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
  2. +----------------+
  3. | auto_increment |
  4. +----------------+
  5. | 6 |
  6. +----------------+
  7. 1 row in set (0.00 sec)
  8.  
  9.  
  10. root@localhost:mysql3306.sock [wjq]>alter table replace_uniq1 auto_increment=10;
  11. Query OK, 0 rows affected (0.01 sec)
  12. Records: 0 Duplicates: 0 Warnings: 0
  13.  
  14. root@localhost:mysql3306.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
  15. +----------------+
  16. | auto_increment |
  17. +----------------+
  18. | 10 |
  19. +----------------+
  20. 1 row in set (0.00 sec)
  1. root@localhost:mysql3306.sock [wjq]>select * from replace_uniq1;
  2. +----+---------+
  3. | id | content |
  4. +----+---------+
  5. | 1 | aa |
  6. | 2 | b |
  7. | 3 | c |
  8. | 4 | d |
  9. | 5 | ee |
  10. +----+---------+
  11. 5 rows in set (0.00 sec)
  12.  
  13. root@localhost:mysql3306.sock [wjq]>insert into replace_uniq1(content) values('f');
  14. Query OK, 1 row affected (0.01 sec)
  15.  
  16. root@localhost:mysql3306.sock [wjq]>select * from replace_uniq1;
  17. +----+---------+
  18. | id | content |
  19. +----+---------+
  20. | 1 | aa |
  21. | 2 | b |
  22. | 3 | c |
  23. | 4 | d |
  24. | 5 | ee |
  25. | 10 | f |
  26. +----+---------+
  27. 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版本

  1. root@localhost:mysql3306.sock [wjq]>show create table information_schema.tables\G;
  2. *************************** 1. row ***************************
  3. Table: TABLES
  4. Create Table: CREATE TEMPORARY TABLE `TABLES` (
  5. `TABLE_CATALOG` varchar(512) NOT NULL DEFAULT '',
  6. `TABLE_SCHEMA` varchar(64) NOT NULL DEFAULT '',
  7. `TABLE_NAME` varchar(64) NOT NULL DEFAULT '',
  8. `TABLE_TYPE` varchar(64) NOT NULL DEFAULT '',
  9. `ENGINE` varchar(64) DEFAULT NULL,
  10. `VERSION` bigint(21) unsigned DEFAULT NULL,
  11. `ROW_FORMAT` varchar(10) DEFAULT NULL,
  12. `TABLE_ROWS` bigint(21) unsigned DEFAULT NULL,
  13. `AVG_ROW_LENGTH` bigint(21) unsigned DEFAULT NULL,
  14. `DATA_LENGTH` bigint(21) unsigned DEFAULT NULL,
  15. `MAX_DATA_LENGTH` bigint(21) unsigned DEFAULT NULL,
  16. `INDEX_LENGTH` bigint(21) unsigned DEFAULT NULL,
  17. `DATA_FREE` bigint(21) unsigned DEFAULT NULL,
  18. `AUTO_INCREMENT` bigint(21) unsigned DEFAULT NULL,
  19. `CREATE_TIME` datetime DEFAULT NULL,
  20. `UPDATE_TIME` datetime DEFAULT NULL,
  21. `CHECK_TIME` datetime DEFAULT NULL,
  22. `TABLE_COLLATION` varchar(32) DEFAULT NULL,
  23. `CHECKSUM` bigint(21) unsigned DEFAULT NULL,
  24. `CREATE_OPTIONS` varchar(255) DEFAULT NULL,
  25. `TABLE_COMMENT` varchar(2048) NOT NULL DEFAULT ''
  26. ) ENGINE=MEMORY DEFAULT CHARSET=utf8
  27. 1 row in set (0.00 sec)
  28.  

8.0版本

  1. root@localhost:mysql8006.sock [wjq]>show create table information_schema.tables\G;
  2. *************************** 1. row ***************************
  3. View: TABLES
  4. 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`))
  5. character_set_client: utf8
  6. collation_connection: utf8_general_ci
  7. 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了:

  1. root@localhost:mysql8006.sock [wjq]>analyze table replace_uniq1;
  2. +-------------------+---------+----------+----------+
  3. | Table | Op | Msg_type | Msg_text |
  4. +-------------------+---------+----------+----------+
  5. | wjq.replace_uniq1 | analyze | status | OK |
  6. +-------------------+---------+----------+----------+
  7. 1 row in set (0.01 sec)
  8.  
  9. root@localhost:mysql8006.sock [wjq]>select auto_increment from information_schema.tables where table_schema='wjq' and table_name='replace_uniq1';
  10. +----------------+
  11. | AUTO_INCREMENT |
  12. +----------------+
  13. | 10 |
  14. +----------------+
  15. 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,您需要填写昵称和邮箱!

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