MySQL自增列ID的面试题--AUTO_INCREMENT

MySQL自增列ID的面试题--AUTO_INCREMENT


在一张表中,里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把MySQL重启,再Insert一条记录,
这条记录的ID是18还是15?
在MySQL 8.0之前,对于MyISAM和InnoDB的表,因为存储引擎对于自增列的实现机制不同,ID值也会有所不同,
对于InnoDB存储引擎的表,ID是按照max(id)+1的算法来计算的。在MySQL 8.0之后,InnoDB的自增列信息写入了共享表空间中,
所以服务重启之后,还是可以继续追溯这个自增列的ID变化情况的。
所以,对于MyISAM表来说,若数据库重启后,则ID值为18;
对于InnoDB表来说,若数据库重启后,则对于MySQL 8.0来说,ID值为18,
对于MySQL 8.0之前的数据库来说,则数据库重启后,ID值为15。
MySQL [(none)]> use lhrdb;
Database changed
MySQL [lhrdb]> select @@version;
+-----------+
| @@version |
+-----------+
| 5.7.29    |
+-----------+
1 row in set (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> create table test_innodb(id int primary key auto_increment,name varchar(30)) engine=innodb;
Query OK, 0 rows affected (0.01 sec)
MySQL [lhrdb]> create table test_myisam(id int primary key auto_increment,name varchar(30)) engine=myisam;
Query OK, 0 rows affected (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> insert into test_innodb(name) values('aa'),('bb'),('cc');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0
MySQL [lhrdb]> insert into test_myisam(name) values('aa'),('bb'),('cc');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0
MySQL [lhrdb]>
MySQL [lhrdb]> insert into test_innodb(id,name) values(5,'ee');
Query OK, 1 row affected (0.00 sec)
MySQL [lhrdb]> insert into test_myisam(id,name) values(5,'ee');
Query OK, 1 row affected (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> show create table test_innodb \G
*************************** 1. row ***************************
       Table: test_innodb
Create Table: CREATE TABLE `test_innodb` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
MySQL [lhrdb]> show create table test_myisam \G
*************************** 1. row ***************************
       Table: test_myisam
Create Table: CREATE TABLE `test_myisam` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> delete from test_innodb where id=5;
Query OK, 1 row affected (0.00 sec)
MySQL [lhrdb]> delete from test_myisam where id=5;
Query OK, 1 row affected (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> show create table test_innodb \G
*************************** 1. row ***************************
       Table: test_innodb
Create Table: CREATE TABLE `test_innodb` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
MySQL [lhrdb]> show create table test_myisam \G
*************************** 1. row ***************************
       Table: test_myisam
Create Table: CREATE TABLE `test_myisam` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
MySQL [lhrdb]> select @@version;
ERROR 2013 (HY000): Lost connection to MySQL server during query
MySQL [lhrdb]> select @@version;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    2
Current database: lhrdb
+-----------+
| @@version |
+-----------+
| 5.7.29    |
+-----------+
1 row in set (0.40 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> show create table test_innodb \G
*************************** 1. row ***************************
       Table: test_innodb
Create Table: CREATE TABLE `test_innodb` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
1 row in set (0.01 sec)
MySQL [lhrdb]> show create table test_myisam \G
*************************** 1. row ***************************
       Table: test_myisam
Create Table: CREATE TABLE `test_myisam` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]>
MySQL [lhrdb]> insert into test_innodb(name) values('ee');
Query OK, 1 row affected (0.01 sec)
MySQL [lhrdb]> insert into test_myisam(name) values('ee');
Query OK, 1 row affected (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> select * from test_innodb;
+----+------+
| id | name |
+----+------+
|  1 | aa   |
|  2 | bb   |
|  3 | cc   |
|  4 | ee   |
+----+------+
4 rows in set (0.00 sec)
MySQL [lhrdb]> select * from test_myisam;
+----+------+
| id | name |
+----+------+
|  1 | aa   |
|  2 | bb   |
|  3 | cc   |
|  6 | ee   |
+----+------+
4 rows in set (0.00 sec)
MySQL [lhrdb]> select @@version;
+-----------+
| @@version |
+-----------+
| 8.0.19    |
+-----------+
1 row in set (0.00 sec)
MySQL [(none)]> use lhrdb;
Database changed
MySQL [lhrdb]> create table test_innodb(id int primary key auto_increment,name varchar(30)) engine=innodb;
Query OK, 0 rows affected (0.03 sec)
MySQL [lhrdb]> create table test_myisam(id int primary key auto_increment,name varchar(30)) engine=myisam;
Query OK, 0 rows affected (0.01 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> insert into test_innodb(name) values('aa'),('bb'),('cc');
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0
MySQL [lhrdb]> insert into test_myisam(name) values('aa'),('bb'),('cc');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0
MySQL [lhrdb]>
MySQL [lhrdb]> insert into test_innodb(id,name) values(5,'ee');
Query OK, 1 row affected (0.01 sec)
MySQL [lhrdb]> insert into test_myisam(id,name) values(5,'ee');
Query OK, 1 row affected (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> show create table test_innodb \G
*************************** 1. row ***************************
       Table: test_innodb
Create Table: CREATE TABLE `test_innodb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(30) 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)
MySQL [lhrdb]> show create table test_myisam \G
*************************** 1. row ***************************
       Table: test_myisam
Create Table: CREATE TABLE `test_myisam` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> delete from test_innodb where id=5;
Query OK, 1 row affected (0.01 sec)
MySQL [lhrdb]> delete from test_myisam where id=5;
Query OK, 1 row affected (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> show create table test_innodb \G
*************************** 1. row ***************************
       Table: test_innodb
Create Table: CREATE TABLE `test_innodb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(30) 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)
MySQL [lhrdb]> show create table test_myisam \G
*************************** 1. row ***************************
       Table: test_myisam
Create Table: CREATE TABLE `test_myisam` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
MySQL [lhrdb]> select @@version;
ERROR 2013 (HY000): Lost connection to MySQL server during query
MySQL [lhrdb]> select @@version;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    8
Current database: lhrdb
+-----------+
| @@version |
+-----------+
| 8.0.19    |
+-----------+
1 row in set (0.05 sec)
MySQL [lhrdb]>
MySQL [lhrdb]> show create table test_innodb \G
*************************** 1. row ***************************
       Table: test_innodb
Create Table: CREATE TABLE `test_innodb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.02 sec)
MySQL [lhrdb]> show create table test_myisam \G
*************************** 1. row ***************************
       Table: test_myisam
Create Table: CREATE TABLE `test_myisam` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]>
MySQL [lhrdb]> insert into test_innodb(name) values('ee');
Query OK, 1 row affected (0.01 sec)
MySQL [lhrdb]> insert into test_myisam(name) values('ee');
Query OK, 1 row affected (0.00 sec)
MySQL [lhrdb]>
MySQL [lhrdb]>
MySQL [lhrdb]> select * from test_innodb;
+----+------+
| id | name |
+----+------+
|  1 | aa   |
|  2 | bb   |
|  3 | cc   |
|  6 | ee   |
+----+------+
4 rows in set (0.00 sec)
MySQL [lhrdb]> select * from test_myisam;
+----+------+
| id | name |
+----+------+
|  1 | aa   |
|  2 | bb   |
|  3 | cc   |
|  6 | ee   |
+----+------+
4 rows in set (0.00 sec)




https://mp.weixin.qq.com/s?__biz=MjM5ODEzNDA4OA==&mid=2650315359&idx=1&sn=61801f8b806f6ec7ff1a3a433ccf2f70&chksm=bec3527889b4db6ec3cc1e2d970c58d787cc34dca6d7d7dd2b1bbb3f914382e7f86a01a366a7&mpshare=1&scene=1&srcid=&sharer_sharetime=1581380653038&sharer_shareid=056c2fdf5dd5650130a6a3cea2b1cc1b&key=541e2b75854761481f047acde19e89d4db35200f528a7c21a58c7f265662e6f515306bdc9cdc4978a2a1c86d2997355e0372fdeed0f35dc4701da38219d92088c5b7aec026687f311367cfcd77363fa9&ascene=1&uin=MTk5MDM4ODY5&devicetype=Windows+10+x64&version=62090072&lang=zh_CN&exportkey=A1Mzq6smdQcOiGaHXYmuATE%3D&pass_ticket=ughIvtE%2BmsBr9uYKXlb%2B%2FwQ0SieoXj2IPkRlWrcyuiI%3D





在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 的视图语句,也是可以对列值溢出进行有效判断的。 



为什么 MySQL 的自增主键不单调也不连续

来自公众号: 真没什么逻辑

为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面留言。

当我们在使用关系型数据库时,主键(Primary Key)是无法避开的概念,主键的作用就是充当记录的标识符,我们能够通过标识符在一张表中定位到唯一的记录,作者在  为什么总是需要无意义的 ID 曾经介绍过为什么不应该使用有意义的字段来充当唯一标识符,感兴趣的读者可以了解一下。

在关系型数据库中,我们会选择记录中多个字段的最小子集作为该记录 在表中的唯一标识符[^1],根据关系型数据库对主键的定义,我们既可以选择单个列作为主键,也可以选择多个列作为主键,但是主键在整个记录中必须存在并且唯一。最常见的方式当然是使用 MySQL 默认的自增 ID 作为主键,虽然使用其他策略设置的主键也是合法的,但是不是通用的以及推荐的做法。

图 1 - MySQL 的主键

MySQL 中默认的  AUTO_INCREMENT 属性在多数情况下可以保证主键的连续性,我们通过  show create table 命令可以在表的定义中能够看到  AUTO_INCREMENT 属性的当前值,当我们向当前表中插入数据时,它会使用该属性的值作为插入记录的主键,而每次获取该值也都会将它加一。


CREATE 
TABLE 
`trades` (

   `id`  bigint( 20NOT  NULL AUTO_INCREMENT,
  ...
   `created_at`  timestamp  NULL  DEFAULT  NULL,
  PRIMARY  KEY ( `id`),
ENGINE= InnoDB AUTO_INCREMENT= 17130  DEFAULT  CHARSET=utf8mb4

在很多开发者的认知中,MySQL 的主键都应该是单调递增的,但是在我们与 MySQL 打交道的过程中会遇到两个问题,首先是记录的主键并不连续,其次是可能会创建多个主键相同的记录,我们将从以下的两个角度回答 MySQL 不单调和不连续的原因:

  • 较早版本的 MySQL 将  AUTO_INCREMENT 存储在内存中,实例重启后会根据表中的数据重新设置该值;
  • 获取  AUTO_INCREMENT 时不会使用事务锁,并发的插入事务可能出现部分字段冲突导致插入失败;

需要注意的是,我们在这篇文章中讨论的是 MySQL 中最常见的 InnoDB 存储引擎,MyISAM 等其他引擎提供的  AUTO_INCREMENT 实现原理不在本文的讨论范围中。

删除记录

AUTO_INCREMENT 属性虽然在 MySQL 中十分常见,但是在较早的 MySQL 版本中,它的实现还比较简陋,InnoDB 引擎会在内存中存储一个整数表示下一个被分配到的 ID,当客户端向表中插入数据时会获取  AUTO_INCREMENT 值并将其加一。

图 2 - AUTO_INCREMENT 的使用

因为该值存储在内存中,所以在每次 MySQL 实例重新启动后,当客户端第一次向  table_name 表中插入记录时,MySQL 会使用如下所示的 SQL 语句查找当前表中  id 的最大值,将其加一后作为待插入记录的主键,并作为当前表中  AUTO_INCREMENT 计数器的初始值[^2]。

SELECT MAX(ai_col) FROM table_name FOR UPDATE;

如果让作者实现  AUTO_INCREMENT,在最开始也会使用这种方法。不过这种实现虽然非常简单,但是如果 使用者不严格遵循关系型数据库的设计规范,就会出现如下所示的数据不一致的问题:

图 3 - 5.7 版本之前的 AUTO_INCMRENT

因为重启了 MySQL 的实例,所以内存中的  AUTO_INCREMENT 计数器会被重置成表中的最大值,当我们再向表中插入新的  trades 记录时会重新使用  10 作为主键,主键也就不是单调的了。在新的  trades 记录插入之后, executions 表中的记录就错误的引用了新的  trades,这其实是一个比较严重的错误。

然而这也不完全是 MySQL 的问题,如果我们严格遵循关系型数据库的设计规范,使用外键处理不同表之间的联系,就可以避免上述问题,因为当前  trades 记录仍然有外部的引用,所以外键会禁止  trades 记录的删除,不过多数公司内部的 DBA 都不推荐或者禁止使用外键,所以确实存在出现这种问题的可能。

然而在 MySQL 8.0 中, AUTO_INCREMENT 计数器的初始化行为发生了改变,每次计数器的变化都会写入到系统的重做日志(Redo log)并在每个检查点存储在引擎私有的系统表中[^3]。

In MySQL 8.0, this behavior is changed. The current maximum auto-increment counter value is written to the redo log each time it changes and is saved to an engine-private system table on each checkpoint. These changes make the current maximum auto-increment counter value persistent across server restarts.

当 MySQL 服务被重启或者处于崩溃恢复时,它可以从持久化的检查点和重做日志中恢复出最新的  AUTO_INCREMENT 计数器,避免出现不单调的主键也解决了这里提到的问题。

并发事务

为了提高事务的吞吐量,MySQL 可以处理并发执行的多个事务,但是如果并发执行多个插入新记录的 SQL 语句,可能会导致主键的不连续。如下图所示,事务 1 向数据库中插入  id = 10 的记录,事务 2 向数据库中插入  id = 11 和  id = 12 的两条记录:

图 4 - 并发事务的执行

不过如果在最后事务 1 由于插入的记录发生了唯一键冲突导致了回滚,而事务 2 没有发生错误而正常提交,在这时我们会发现当前表中的主键出现了不连续的现象,后续新插入的数据也不再会使用  10 作为记录的主键。

图 5 - 不连续的主键

这个现象背后的原因也很简单,虽然在获取  AUTO_INCREMENT 时会加锁,但是该锁是语句锁,它的目的是保证  AUTO_INCREMENT 的获取不会导致线程竞争,而不是保证 MySQL 中主键的连续[^4]。

上述行为是由 InnoDB 存储引擎提供的  innodb_autoinc_lock_mode 配置控制的,该配置决定了获取  AUTO_INCREMENT 计时器时需要先得到的锁,该配置存在三种不同的模式,分别是传统模式(Traditional)、连续模式(Consecutive)和交叉模式(Interleaved)[^5],其中 MySQL 使用连续模式作为默认的锁模式:

  • 传统模式  innodb_autoinc_lock_mode = 0
    • 在包含  AUTO_INCREMENT 属性的表中插入数据时, 所有的  INSERT 语句都会获取 表级别的  AUTO_INCREMENT 锁,该锁会在当前语句执行后释放;
  • 连续模式  innodb_autoinc_lock_mode = 1
    • INSERT ... SELECTREPLACE ... SELECT  以及  LOAD DATA 等批量的插入操作需要获取 表级别的  AUTO_INCREMENT 锁,该锁会在当前语句执行后释放;
    • 简单的插入语句(预先知道插入多少条记录的语句)只需要获取获取  AUTO_INCREMENT 计数器的互斥锁并在获取主键后直接释放,不需要等待当前语句执行完成;
  • 交叉模式  innodb_autoinc_lock_mode = 2
    • 所有的插入语句都不需要获取 表级别的  AUTO_INCREMENT 锁,但是当多个语句插入的数据行数不确定时,可能存在分配相同主键的风险;

这三种模式都不能解决 MySQL 自增主键不连续的问题,想要解决这个问题的终极方案是串行执行所有包含插入操作的事务,也就是使用数据库的最高隔离级别 —— 可串行化(Serialiable)。当然直接修改数据库的隔离级别相对来说有些简单粗暴,基于 MySQL 或者其他存储系统实现完全串行的插入也可以保证主键 在插入时的连续,但是仍然不能避免删除数据导致的不连续。

总结

早期 MySQL 的主键既不是单调的,也不是连续的,这些都是在当时工程上做出的一些选择,如果严格地按照关系型数据库的设计规范,MySQL 最初的设计造成问题的概率也比较低,只有当被删除的主键被外部系统引用时才会影响数据的一致性,但是今天使用方式的不同却增加出错的可能性,而 MySQL 也在 8.0 中持久化了  AUTO_INCREMENT 以避免该问题的出现。

MySQL 中不连续的主键又是一个 工程设计向性能低头的例子,牺牲主键的连续性来支持数据的并发插入,最终提高了 MySQL 服务的吞吐量,作者在几年前刚刚使用 MySQL 时就遇到过这个问题,但是当时并没有深究背后的原因,今天重新理解该问题背后的设计决策也是个非常有趣的过程。我们在这里简单总结一下本文的内容,重新回到今天的问题 — 为什么 MySQL 的自增主键不单调也不连续:

  • MySQL 5.7 版本之前在内存中存储  AUTO_INCREMENT 计数器,实例重启后会根据表中的数据重新设置,在删除记录后重启就可能出现重复的主键,该问题在 8.0 版本使用重做日志解决,保证了主键的单调性;
  • MySQL 插入数据获取  AUTO_INCREMENT 时不会使用事务锁,而是会使用互斥锁,并发的插入事务可能出现部分字段冲突导致插入失败,想要保证主键的连续需要串行地执行插入语句;






About Me

........................................................................................................................

● 本文作者:小麦苗,部分内容整理自网络,若有侵权请联系小麦苗删除

● 本文在itpub、博客园、CSDN和个人微 信公众号( DB宝)上有同步更新

● 本文itpub地址: http://blog.itpub.net/26736162

● 本文博客园地址: http://www.cnblogs.com/lhrbest

● 本文CSDN地址: https://blog.csdn.net/lihuarongaini

● 本文pdf版、个人简介及小麦苗云盘地址: http://blog.itpub.net/26736162/viewspace-1624453/

● 数据库笔试面试题库及解答: http://blog.itpub.net/26736162/viewspace-2134706/

● DBA宝典今日头条号地址: http://www.toutiao.com/c/user/6401772890/#mid=1564638659405826

........................................................................................................................

● QQ群号: 230161599 、618766405

● 微 信群:可加我微 信,我拉大家进群,非诚勿扰

● 联系我请加QQ好友 646634621 ,注明添加缘由

● 于 2020-04-01 06:00 ~ 2020-04-30 24:00 在西安完成

● 最新修改时间:2020-04-01 06:00 ~ 2020-04-30 24:00

● 文章内容来源于小麦苗的学习笔记,部分整理自网络,若有侵权或不当之处还请谅解

● 版权所有,欢迎分享本文,转载请保留出处

........................................................................................................................

小麦苗的微店https://weidian.com/s/793741433?wfr=c&ifr=shopdetail

小麦苗出版的数据库类丛书http://blog.itpub.net/26736162/viewspace-2142121/

小麦苗OCP、OCM、高可用网络班http://blog.itpub.net/26736162/viewspace-2148098/

小麦苗腾讯课堂主页https://lhr.ke.qq.com/

........................................................................................................................

使用 微 信客户端扫描下面的二维码来关注小麦苗的微 信公众号( DB宝)及QQ群(DBA宝典)、添加小麦苗微 信, 学习最实用的数据库技术。

........................................................................................................................

欢迎与我联系

 

 





请使用浏览器的分享功能分享到微信等