《MySQL是怎样运行的》这本书最大的优点是,每一章的篇幅都是三四十页,不会出现啃不动的情况。

用作者的原话来说:章节内容经过精心编排,尊重用户的认知规律,甚至推敲了很久各种概念的出场顺序对用户体验的影响。

强力推荐。

一、初识MySQL

1 MySQL的日常使用场景

1. 启动服务器程序。

2. 启动客户端程序并连接到服务器。

3. 在客户端程序中输入命令语句,并将其作为请求发送给服务器程序。

4. 服务器程序收到请求,根据请求内容操作具体的数据,并将结果返回给客户端。

1.1 启动MySQL服务器程序

mysqld

mysql.server start / mysql.server stop

1.2 启动MySQL客户端程序

mysql -h主机名 -u用户名 -p密码 / quit/exit

1.3 客户端与服务器连接

TCP/IP连接:使用 IP地址+端口号。

默认最大连接数:151。

1.4 服务器处理客户端请求

1. 连接管理。

2. 解析与优化。

查询缓存;

语法解析;

查询优化。

3. 存储引擎。

二、系统变量

1 查看系统变量

SHOW [GLOBAL|SESSION] VARIABLES [LIKE 匹配的模式];

SHOW [GLOBAL|SESSION] STATUS [LIKE 匹配的模式];

2 设置系统变量

2.1 通过启动选项设置

2.1.1 在命令行上使用选项

长形式和短形式。

2.1.2 配置文件中使用选项

扫描固定的几个路径,如 /etc/my.cnf、/etc/mysql/my.cnf、$MYSQL_HOME/my.cnf 等。

2.2 服务器程序运行中设置

SET [GLOBAL|SESSION] 系统变量名 = 值;

三、字符集和比较规则

MySQL有4个级别的字符集和比较规则,分别是服务器级别、数据库级别、表级别、列级别。

1 MySQL中字符集的应用

常用字符集:ASCII、GBK、UTF-8。

1) 客户端发送的请求字节序列是采用哪种字符集进行编码的?

一般情况下,客户端编码请求字符串时使用的字符集与操作系统当前使用的字符集一致。

2) 服务器接收到请求字节序列后会认为它是采用哪种字符集进行编码的?

由系统变量 character_set_client 指定。

3) 服务器在运行过程中会把请求的字节序列转换为以哪种字符集编码的字节序列?

由系统变量 character_set_connection 指定。

与之配套的系统变量 collation_connection 表示采用的比较规则。

4) 服务器在向客户端返回字节序列时是采用哪种字符集进行编码的?

由系统变量 character_set_results 指定。

5) 客户端在收到响应字节序列后是怎么把它们写到黑窗口中的?

如果没有特殊设置的话,一般用操作系统当前使用的字符集。

在启动MySQL客户端时,可以使用 –default-character-set= 启动选项设置客户端的默认字符集和服务器的 character_set_results。

2 比较规则

比较规则通常用来比较字符串的大小以及对某些字符串进行排序,所以有时候也称为排序规则。

四、InnoDB记录存储结构

1 InnoDB页

MySQL按页读写,页的大小默认为16KB。

2 InnoDB行格式

2.1 COMPACT

一条完整的记录可以被分为记录的额外信息和记录的真实数据两部分。

1) 额外信息:变长字段长度列表、NULL值列表、记录头信息。

变长字段长度列表:1~2字节。

NULL值列表,每一位表示一个可为空的字段,占用整数个字节。

记录头信息:固定5字节。

2) 真实数据:隐藏列、真实列。

隐藏列:row_id、trx_id、roll_pointer。

真实列:很真实。

InnoDB主键生成策略:

优先使用用户自定义的主键作为主键;如果用户没有定义主键,则选取一个不允许存储NULL值的UNIQUE键作为主键;如果表中连不允许存储NULL值的UNIQUE键都没有定义,则InnoDB会为表默认添加一个名为row_id的隐藏列作为主键。

2.2 REDUNDANT、DYNAMIC、COMPRESSED

知道有这些格式就行了。

2.3 溢出列

知道如果一条记录的某个列中存储的数据占用的字节数非常多时,该列可能成为溢出列就行了。

五、InnoDB数据页结构

一个数据页(官方名称:索引页)可以被大致划分为以下7个部分:

1) File Header:表示页的一些通用信息,如页号、页的类型、页的校验和、前后双指针,占固定的38字节。

2) Page Header:表示数据页的一些专有信息,占固定的56字节。

3) Infimum + Supremum:两个虚拟的伪记录,占固定的26字节。

4) User Records:真正存储我们插入的记录,大小不固定。

5) Free Space:页中尚未使用的部分,大小不固定。

6) Page Direcotry:页中某些记录的相对位置,大小不固定。

7) File Trailer:用于检验页是否完整,占固定的8字节。

1 File Header 和 File Trailer

文件头和文件尾,每个页都有。

2 User Records

User Records是真正存储我们插入的记录的地方。

回顾一下记录的结构:变长字段长度列表 + NULL值列表 + 记录头信息 + 隐藏列 + 真实列。

其中记录头信息是此时关注的部分。

记录头信息包括:

1) 预留位。

2) deleted_flag:删除标识,所有被删除掉的记录会组成一个垃圾链表,该链表占用的空间为可重用空间。

3) min_rec_flag:此时不重要。

4) n_owned:此时不重要,见Page Directory。

5) heap_no:表示当前记录在堆中的位置。

6) record_type:表示当前记录的类型,一共有4种类型:普通记录、Infimun记录、Supremum记录和一种此时不重要的类型(目录项记录)。

7) next_record:下一条记录相对于当前记录偏移量,通过next_record将记录按照主键从小到大连接成了一个单向链表。

3 Infimum + Supremum

两条特殊的记录,Infimum表示最小的记录,Supremum表示最大的记录。

是链表前后的两个虚拟节点。

4 Free Space

Free Space是页内尚未使用的空间。

在Page Header中使用一个指针分隔了User Records和Free Space。

5 Page Directory

为优化查询,将页内的所有正常记录(包括Infimum和Supremum,不包括已删除记录)划分成了多个组,将每个组的最后一条记录在页面中的地址偏移量单独提出来,存储在Page Directory中,称为槽(Slot),每个槽占用2字节。

每个组的最后一条记录的n_owned属性表示该组内共有几条记录。

每个组中记录的条数范围是4~8条之间(除Infimun所在组只能有1条,Supremum所在组范围是1~8条)。

在一个数据页中查找指定主键值的记录的过程为:

1. 通过二分法确定该记录所在分组对应的槽,然后找到该槽所在分组中主键值最小的那条记录(最小:因为是单向链表)。

2. 通过记录的next_record属性遍历该槽所在的组中的各条记录。

6 Page Header

Page Header中记录的一部分状态信息(不完整):

1) PAGE_N_DIR_SLOTS:Page Directory中的槽数量。

2) PAGE_HEAP_TOP:Free Space的最小地址。

3) PAGE_N_HEAP:本页堆中记录的数量(包括Infimum和Supremum以及已删除记录)。

4) PAGE_FREE:垃圾链表头节点的位置(在页面中的偏移量)。

5) PAGE_GARBAGE:垃圾链表占用的字节数。

6) PAGE_LAST_INSERT:最后插入记录的位置。

六、B+树索引

1 没有索引时进行查找

每个数据页中的记录通过next_record属性形成单向链表,数据页之间通过File Header中的FIL_PAGE_PREV和FIL_PAGE_NEXT形成双向链表。

遍历所有数据页、所有记录。

2 聚簇索引

聚簇索引是在创建表时,根据主键自动生成的索引。

2.1 记录在数据页之间也是有序的

下一个数据页中的用户记录的主键值必须大于上一个页中用户记录的主键值。

如果在已满的数据页的中间插入记录,会导致页分裂,本页中的一些记录移动到新创建的页中,造成性能损耗。为了尽量避免这种情况发生,一般会控制新插入记录的主键值依次递增。

2.2 给所有的页创建一个目录项

重新分配一个页来专门存储目录项。

1) 记录头信息中的record_type:

0:普通的用户记录。

1:目录项记录。

2:Infimum记录。

3:Supremum记录。

2) 目录项记录只包括(页的第一条记录的)主键值和页号两个列。

2.3 B+树

当存储目录项记录的页的容量已满,仍有新的目录项记录需要存储,就再生成一个更高级的目录,形成多级目录。

所有数据页组成一棵B+树。

2.4 聚簇索引

上文中的B+树本身就是一个索引,它有两个特点:

1) 使用记录主键值的大小进行记录和页的排序。

2) 叶子节点存储的是完整的用户记录。

我们把具有这两个特点的B+树称为聚簇索引,“索引即数据,数据即索引”。

2.5 根节点不动

一个B+树索引的根节点自创建之日起便不会再移动。凡是InnoDB存储引擎需要用到这个索引时,都会从那个固定的地方取出根节点的页号。

当根节点中的可用空间用完时继续插入记录,会将根节点中的所有记录复制到一个新分配的页中,然后对这个新页进行页分裂操作,得到另一个新页。根节点升级为存储目录项记录的页。

3 二级索引

如果我们想以主键以外别的列作为搜索条件,可以多建几棵B+树,即二级索引。

3.1 与聚簇索引的区别

二级索引与聚簇索引的区别在于:

1) 使用记录的某一列进行记录和页的排序。

2) 叶子节点存储的是索引列+主键这两个列的值。

3) 目录项记录中不再是主键+页号,而变成了索引列+主键+页号。

这里加主键是因为索引列有重复的可能。

3.2 回表

通过二级索引携带的主键信息到聚簇索引中重新定位完整的用户记录的过程称为回表。

二级索引的“二级”就是这么来的。

4 联合索引

本质上也是二级索引,只不过是同时为多个列建立索引。

先按照其中一个列进行排序,在此基础上再按照第二个列进行排序,依此类推。

每个节点存储的是所有索引列的值+主键值。

七、B+树索引的使用

1 索引的代价

1.1 空间上的代价

每建立一个索引,都要为它建立一棵B+树。

1.2 时间上的代价

1) 每当对表中的数据进行增删改查操作时,都需要修改各个B+树索引。

2) 在执行查询语句前,首先要生成一个执行计划。在生成执行计划时需要计算使用不同索引执行查询时所需的成本,如果创建了太多索引,可能会导致成本分析过程耗时太多,从而影响查询语句的执行性能。

2 使用索引执行查询

所有搜索条件都可以生成一个扫描区间。

在使用某个索引执行查询时,关键的问题就是通过搜索条件找出合适的扫描区间,然后再到对应的B+树中扫描索引值在这些扫描区间的记录。

2.1 索引用于搜索

最左前缀原则。

2.2 索引用于排序

1) 排序列必须来自同一个索引。

2) 排序列必须满足最左前缀原则。

3) ASC、DESC不能混用。

4) 排序列必须以单独列名出现,不能参与计算或函数。

2.3 索引用于分组

跟排序差不多。

2.4 回表的代价

需要执行回表操作的记录越多,使用二级索引进行查询的性能也就越低。

频繁地执行回表操作时,由于要读取很多id值并不连续的聚簇索引记录,而且这些聚簇索引记录分布在不同的数据页中,会造成大量的随机IO。

大量的随机IO,还不如全表扫描,全表扫描至少能保证每个内存页只加载一次,随机IO就不一定了。

一般情况下,可以给查询语句指定LIMIT子句来限制查询返回的记录数,这可能会让查询优化器倾向于选择使用二级索引+回表的方式进行查询。

3 使用索引的正确姿势

1) 只为用于搜索、排序或分组的列创建索引。

2) 尽量只为重复率低的列创建索引。

3) 索引列的类型尽量小,比如能用INT就不要使用BIGINT。

4) 可以为列前缀建立索引。

5) 推荐使用覆盖索引,即在查询列表中只包含索引列,这样可以告别回表操作。

6) 让索引列以列名的形式在搜索条件中单独出现,不要让它参与计算。

7) 设置插入记录时主键依次递增。

8) 避免索引重复。

八、MySQL的数据目录

1 数据目录

所谓数据目录,就是在文件系统中,MySQL用来存放数据文件的路径。

可以使用命令

SHOW VARIABLES LIKE ‘datadir’;

进行查看。

2 数据目录下的文件

2.1 数据库的属性文件

每创建一个数据库,MySQL会帮我们在数据目录下创建一个与数据库名同名的子目录,并在此子目录下创建一个名为db.opt的文件,这个文件中包含了该数据库的一些属性,比如该数据库的字符集和比较规则。

2.2 表结构文件

表结构指的是该表的名称是啥、表里面有多少列、每个列的数据类型是啥、有啥约束条件和索引、用的是啥字符集和比较规则等各种信息。为了保存这些信息,InnoDB在数据目录下对应的数据库子目录中创建了一个专门用于描述表结构的文件:表名.frm。

2.3 表空间文件

InnoDB是使用页为基本单位来管理存储空间的,为了更好地管理页,MySQL提出了表空间的概念。

表空间是一个抽象的概念,它可以对应文件系统上一个或多个真实文件,每一个表空间可以被划分为很多个页,表数据就存放在某个表空间下的某些页中。

1) 系统表空间。

在默认情况下,InnoDB会在数据目录下创建一个名为ibdata1、大小为12MB的文件,这个文件就是对应的系统表空间在文件系统上的表示。

ibdata1文件是自扩展文件,当不够用的时候会自己增加文件大小。

从MySQL 5.5.7到MySQL 5.6.5之间的各个版本中,表中的数据都会被默认存储到系统表空间。

2) 独立表空间。

MySQL 5.6.6之后的版本中,InnoDB不再默认把各个表的数据存储到系统表空间中,而是为每一个表建立一个独立表空间,也就是说,我们创建了多少个表,就有多少个独立表空间。

独立表空间对应的文件名:表名.ibd。

3) 其他类型的表空间略。

九、InnoDB的表空间

表空间的逻辑划分:段、页。

表空间的物理划分:组、区、页。

1 表空间的逻辑划分:段、页

为什么我没有把区放到逻辑划分当中?我觉得页对应了B+树索引中的节点,段对应了B+树索引中的叶子节点和非叶子节点,而区没有这样的对应关系,只是用来管理表空间的工具。

1.1 段 segment

MySQL引入段的概念,是为了对B+树的叶子节点和非叶子节点进行区别对待,毕竟我们在使用B+树执行查询时,只需要扫描叶子节点的记录。

一个索引会生成两个段:一个叶子节点段和一个非叶子节点段。

2 表空间的物理划分:组、区、页

大家可以把表空间想象成被切分为许多个页的池子,当想为某个表插入一条记录的时候,就从池子中捞出一个对应的页把数据写进去。

但表空间中的页实在是太多了,为了更好地管理这些页面,MySQL又将表空间划分成了区和组。

1) 对于16KB的页来说,连续的64个页就是一个区,占用1MB空间大小。

2) 每256个区被划分为一组,所有的组组成了表空间。

2.1 区 extent

如果由数据页组成的双向链表中相邻的两个页的物理位置不连续,对于传统的机械硬盘来说,需要重新定位磁头位置,也就是会产生随机I/O,这样会影响磁盘的性能。所以我们应该尽量让页面链表中相邻的页的物理位置也相邻,这样在扫描叶子节点中大量的记录时才可以使用顺序I/O。

所以才引入了区的概念。在表中的数据量很大时,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位进行分配。甚至在表中的数据非常非常多的时候,可以一次性分配多个连续的区。

2.1.1 碎片区 fragment

段是以区为单位申请存储空间的,然而,以完整的区为单位分配给某个段时,对于数据量较小的表来说太浪费了。于是MySQL提出了碎片区的概念,碎片区中的页可以用于不同的目的,比如有些页属于段A,有些页属于页B,有些页不属于任何段。

2.1.2 区的分类

在了解了碎片区的概念后,我们可以将表空间中的区分为4种类型,也可以称为区的4种状态:

1) FREE:空闲的区。

2) FREE_FRAG:有剩余空闲页面的碎片区。

3) FULL_FRAG:没有剩余空闲页面的碎片区。

4) FSEG:附属于某个段的区。

2.2 对段的补充

在刚开始向表中插入数据时,段是从某个碎片区以单个页面为单位来分配存储空间的。

当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间(原先占用的碎片区页面并不会被复制到新申请的完整的区中)。

2.3 组

MySQL使用一些特殊的页,以组为单位对表空间中所有的区进行管理。

1) 表空间的第一个组的第一个区的前三个页的类型是固定的,分别为:FSP_HDR、IBUF_BITMAP、INODE。

2) 后面每个组的第一个区的前两个页的类型固定为:XDES、IBUF_BITMAP。

这里先不对上面这些特殊类型的页展开介绍(后面会介绍),为了接下来的内容,这里要讲的是,在FSP_HDR和XDES两种类型的页中,记录了当前组的每个区的区描述信息XDES Entry。

3 将区通通连起来

3.1 区描述信息 XDES Entry

XDES Entry结构有40个字节,大致分为4个部分:

1) Segment ID:该区所在的段的唯一编号,只对已经被分配给某个段的区有用。

2) List Node:两个指针,可以将若干个XDES Entry结构串连成双向链表。

每个指针是一个页号和一个偏移量。

3) State:区的状态。即前面提到的区的分类。

4) Page State Bitmap:页状态位映射(直译)。

给区中所有的页每页分配两个bit,一个bit标记页是否为空闲,另一个bit还没有用到。

3.2 XDES Entry链表

我们怎么知道表空间中哪些区的状态是FREE,哪些区的状态是FREE_FRAG,哪些区的状态是FULL_FRAG呢?

将状态为FREE、FREE_FRAG、FULL_FRAG的区分别通过XDES Entry中的List Node连接成链表。

FSEG状态的区比较特殊,MySQL为每个段对应的FSEG区维护了3个列表:

1) FREE链表:所有页面都是空闲页面的区组成的链表。

2) NOT_FULL链表:仍有空闲页面的区组成的链表。

3) FULL链表:已经没有空闲页面的区组成的链表。

我们来捋一捋向某个段中插入数据时,申请新页面的过程。

1. 当段中数据较少时,首先会查看表空间中是否有状态为FREE_FRAG的区。

如果找到了,就从该区中取一个零散页把数据插进去;

如果没有找到,就到表空间中申请一个状态为FREE的区,把该区的状态变为FREE_FRAG,然后从中取一个零散页把数据插进去;

不同的段不断地从FREE_FRAG区中取零散页,直到其中没有空闲页面时,把该区的状态变为FULL_FRAG。

2. 当段中的数据已经占满32个零散的页后,直接申请完整的区来插入数据,该区的状态为FSEG。

假如一张表有一个聚簇索引和一个二级索引,这个表共有4个段,每个段维护3个链表,再加上直属于表空间的3个链表,整个独立表空间共需要维护15个链表(一张表对应一个独立表空间)。

至于每个链表的基节点在哪,请关注段描述信息INODE Entry。

3.3 段描述信息 INODE Entry

INODE Entry的结构为:

1) Segment ID:段的唯一编号。

2) NOT_NULL_N_USED:在NOT_NULL链表中已经使用了多少个页面。

3) 3个List Base Node:段的3个链表的基节点。

4) Magic Numer:魔法值,标记这个INODE Entry是否已经被初始化。

如果这个魔法值为97937874,表明该INODE Entry已经初始化。

5) Fragment Array Entry:段的零散页面的的页号,共32个。

4 组中特殊类型的页

4.1 FSP_HDR类型

表空间的第一个组的第一个区的第一个页的类型固定为FSP_HDR。

一个完整的FSP_HDR类型的页面由5部分组成:

1) File Header:文件头。

2) File Space Header:表空间头,记录表空间的一些整体属性信息。

3) XDES Entry:区描述信息,前面已详细介绍。

4) Empty Space:尚未使用的空间。

5) File Trailer:文件尾。

单拎File Space Header出来说一说,File Space Header包含了以下信息(不完整):

1) Space ID:表空间的ID。

2) Size:当前表空间拥有的页面数。

3) FRAG_N_USED:在FREE_FRAG链表中已经使用的页面数量。

4) 表空间的3个XDES Entry链表的基节点。

4.2 XDES类型

表空间的第一个组后面的每个组的第一个区的第一个页的类型固定为XDES。

与FSP_HDR类型的页相比,除了没有File Space Header部分之外,其余的部分是一样的。

4.3 IBUF_BITMAP类型

每个分组的第一个区的第二个页的类型固定为IBUF_BITMAP,记录一些有关Change Buffer的东西。

对于UPDATE和DELETE操作来说,会带来许多随机I/O,所以MySQL引入了一种称为Change Buffer的结构。在修改二级索引页面时,如果该页面尚未被加载到内存中,就将修改先暂时缓存到Change Buffer中,之后服务器空闲或者其他什么原因导致对应页面从磁盘上加载到内存中时,再将修改合并到对应页面。

4.4 INODE类型

表空间的第一个组的第一个区的第三个页的类型固定为INODE,用于存储INODE Entry。

其结构为:

1) File Header:文件头。

2) List Node for INODE Page List:两个指针,将所有的INODE页连成双向链表。

3) INODE Entry:段描述信息,前面已详细介绍。

4) Empty Space:尚未使用的空间。

5) File Trailer:文件尾。

5 对INDEX页的补充

INDEX页就是前几章讲的普通数据页。

INDEX页的Page Header中,记录了B+树叶子节点段的头部信息和非叶子节点段的头部信息(仅在B+树的根页中记录)。

6 系统表空间

系统表空间的前3个页面的类型和独立表空间是一致的,但是页号3~7的页面是系统表空间特有的。

1) 页号3:SYS类型,存储Change Buffer的头部信息。

2) 页号4:INDEX类型,存储Change Buffer的根页面。

3) 页号5:TRX_SYS类型,存储事务系统的相关信息。

4) 页号6:SYS类型,存储第一个回滚段的信息。

5) 页号7:SYS类型,存储数据字典头部信息。

可以关注下InnoDB数据字典的使用,这里不再展开介绍。

整活儿:五分钟读完

张艺谋谈《满江红》创作:“悬疑+喜剧”是自己一次的全新尝试_bilibili

我原来想做五分钟读完MySQL,是不是很大胆?五分钟读完MySQL,就那个MySQL,就完全是MySQL,五分钟读完。

我最早跟同学们说的时候,跟所有同学说的都是五分钟读完,哇,所有同学 卢兄、王兄他们所有人都很期待,朱兄他们很期待,五分钟读完,一直说的是五分钟读完,我一直在做五分钟读完的准备,所以当时跟同学们说的时候,我让你们五分钟读完,或者六七分钟,不到十分钟,他们说为什么,我说很简单,整本书22章,每一章我用50个字概括,1000多字五分钟读完,你们每天读两遍,黎明读一遍,黄昏读一遍,你们每天读两遍,你们读十天,二十遍,背得滚瓜烂熟,五分钟读完啊,MySQL就学完了,滚瓜烂熟,学十天就够了,然后前边的两个月编辑,编辑,疯狂地编辑,我说只要你们十天,咱就学完MySQL,哇,所有同学觉得太厉害了,就挑战性太大了,一点儿都不能偷懒啊。

但是后来我还是放弃了,你知道为什么?我研究了大量五分钟读完的文章,也有一些十分钟读完的文章,还有尤其是最近这两年,有一些实验性的五分钟读完的文章,大厂员工写的我都看过,我专门看,研究,我就一直下不了决心,其实在《Spring揭秘》的时候,我就想写一篇五分钟读完,后来也没有做,你知道最大问题是什么?当然这是一个专业问题,简单地说最大问题就是深度,就是深度,因为一个大脑功能啊深度没有办法,对一个广泛流行的,对于一个开源的,免费的一个数据库来说,那么好用的一个数据库,深度达不到一定,五分钟读完的深度达不到,深度达不到,因为你不能欻欻欻欻行文是这样子,不可能,你从这里到这里,中间的知识点就没了,第一是深度达不到,无论怎么样,我想来想去,因为我算个内行,我不写我就知道深度达不到,深度实现不了,第二个,同学学习的成就感会损失一半以上,一个同学的成就感会损失一半以上,每一个同学会损失一半以上的成就感,是无形的,或者是在几个人交流当中,学习就没意思了,啊学习就没意思了。

最后我差不多一个多月的思想斗争,那是个重大决定,一个个同学都是按照五分钟读完期待的,等着看呢,后来说放弃,我自己决定放弃,一个人做的决定,一个人的思考,一个多月以后我放弃了,我第一个电话打给卢兄,我说我放弃做五分钟读完,“啊?”卢兄就不能接受,他已经完全沉浸到五分钟读完当中去了,就想得已经好得,就喜欢得不得了,说“怎么可能?”就过来说服我,我说你不用跟我说,你不太清楚,反正五分钟读完(做不到),(卢兄)说“怎么可能放弃?你能做到”,就开始给我忽悠,我放弃了,然后跟王兄说放弃,王兄说我都准备好在吃饭的时候看了这那的,所有人,大家都觉得太遗憾了,然后跟朱兄说,最有意思跟朱兄说,跟朱兄说不做五分钟读完,“哎呀,哎呀呀”,觉得好像就没劲了,跟杨巨佬说不能五分钟读完,“哎呀”,好像就不想学了,哈哈开玩笑啊开玩笑,就所有人都沮丧而失落,我看到大家的反应,我也很难过。

我后来还是放弃了,所以写文章是一个学习,对于一个追求实用性的文章来说,它是破坏性的,它凸显了五分钟读完的风格又能怎么样?你丢掉了深度,牺牲掉了学习的成就感,孰重孰轻啊?所以我在学习,我在学习做一个博主的思考,这就是我的遗憾,但也许是我的一个清醒,或者学习的心得。