【MySQL】索引

本期我们好好唠唠索引


目录

一、索引的概念

二、索引的重要性

三、对于索引的理解

3.1 MySQL与磁盘交互的基本单位page

3.2 MySQL中的数据交互过程

3.3 索引建立的过程

3.3.1 page的存储形式

3.3.2 B+树的形成

3.4 为什么不用其他数据结构来建立索引

四、聚簇索引和非聚簇索引

五、 辅助(普通)索引

六、索引操作

6.1 查看表中存在的索引

6.2 创建索引

6.2.1 创建主键索引

6.2.2 创建唯一索引

6.2.3 创建普通索引

6.2.4 创建复合索引

6.2.5 创建全文索引

6.2.6 创建索引的原则

6.3 删除索引

6.3.1 删除主键索引

6.3.2 删除其他类型的索引 


一、索引的概念

索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书籍中,索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。

常见索引分有:

主键索引(primary key)

唯一索引(unique)

普通索引(index)

全文索引(fulltext)--解决中子文索引问题。

二、索引的重要性

索引可以提高数据库的性能,是物美价廉的东西了。不用加内存,不用改程序,不用调sql,只要执行正确的 create index ,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以它的价值,在于提高一个海量数据的检索速度。

光说概念太抽象了,下面我们来直观的感受一下索引:

先整一个海量表,在查询的时候看看没有索引有什么问题:

drop database if exists `bit_index`;
create database if not exists `bit_index` default character set utf8;
use `bit_index`;-- 构建一个8000000条记录的数据
-- 构建的海量表数据需要有差异性,所以使用存储过程来创建, 拷贝下面代码就可以了,暂时不用理解-- 产生随机字符串
delimiter $$
create function rand_string(n INT)
returns varchar(255)
begin
declare chars_str varchar(100) default
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
declare return_str varchar(255) default '';
declare i int default 0;
while i < n do
set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));
set i = i + 1;
end while;
return return_str;
end $$
delimiter ;-- 产生随机数字
delimiter $$
create function rand_num( )
returns int(5)
begin
declare i int default 0;
set i = floor(10+rand()*500);
return i;
end $$
delimiter ;-- 创建存储过程,向雇员表添加海量数据
delimiter $$
create procedure insert_emp(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i + 1;
insert into EMP values ((start+i)
,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());
until i = max_num
end repeat;
commit;
end $$
delimiter ;-- 雇员表
CREATE TABLE `EMP` (`empno` int(6) unsigned zerofill NOT NULL COMMENT '雇员编号',`ename` varchar(10) DEFAULT NULL COMMENT '雇员姓名',`job` varchar(9) DEFAULT NULL COMMENT '雇员职位',`mgr` int(4) unsigned zerofill DEFAULT NULL COMMENT '雇员领导编号',`hiredate` datetime DEFAULT NULL COMMENT '雇佣时间',`sal` decimal(7,2) DEFAULT NULL COMMENT '工资月薪',`comm` decimal(7,2) DEFAULT NULL COMMENT '奖金',`deptno` int(2) unsigned zerofill DEFAULT NULL COMMENT '部门编号'
);-- 执行存储过程,添加8000000条记录
call insert_emp(100001, 8000000);

上面的代码数据量较大,需要向数据库中插入800w条数据,实操时需要耐心等待一下

插入过程结束后,我们稍微看一下该表中的数据:

现在有一个要求:查询员工编号为998877的员工

我们直接使用普通的select查询,可以发现单单查这一条记录就耗费了4.92s。在实际项目中,如果放在公网中,假如同时有 1000个人并发查询,那很可能就死机。

下面演示一遍将empno构建索引后再来查询:

我们可以看到构建索引后,使用select查询相同的数据耗费的时间缩短了好几个数量级,由此可见索引在实际运用中是多么的重要

三、对于索引的理解

3.1 MySQL与磁盘交互的基本单位page

那索引底层原理到底是个啥,下面来详细跟大家来说道说道:

说到索引之前,我们要弄清楚MySQL的底层数据存取原理。我们都知道MySQL下存取数据依靠的存储引擎,下面就以InnoDB存储引擎讲解:

MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO(Input/Output,也就是输入和输出。它是指程序和运行时数据在内存中与外设之间进行数据交换的过程)场景,所以,为了提高基本的IO效率, MySQL进行IO的基本单位是 16KB:

即, MySQL 和磁盘进行数据交互的基本单位是 16KB 。这个基本数据单元,在MySQL这里叫做page

3.2 MySQL中的数据交互过程

MySQL中的数据文件,是以page为单位保存在磁盘当中的。

MySQL的CURD操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。

而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中。 所以在特定时间内,数据一定是磁盘中有,内存中也有。

后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page。

为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称 为Buffer Pool的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。

下面是MySQL进行数据交互的模拟图:

 为何更高的效率,一定要尽可能的减少系统和磁盘IO的次数

3.3 索引建立的过程

为了更好的演示,我们先建立一个表:

create table if not exists user (
id int primary key,
age int not null,
name varchar(16) not null
);

在这里先声明一点:MySQL会为表设置的主键自动建立索引

再向该表中插入一下数据:

insert into user values(3, 18, '张三'),(4, 16, '李四'),(2, 26, '王五'),(5, 36, '赵六'),(1, 56, '钱七');

插入数据时我们可以看到id这一列的数据时无序的,但是由于系统自动会根据主键来建立索引,会将插入的数据按id列排序:

为什么被系统设置的索引的列会自动排序呢?

这是为了提高按主键查找时的效率

3.3.1 page的存储形式

下面我们来问一下大家:select这表中的数据,先要将所有要打印的数据从磁盘调到page上进行存储之后再输出到屏幕上,那这些数据在page中是如何进行存储的呢?

来看下面的示意图:

我们可以看到page是一个双向链表的存储形式

不同的 Page ,在MySQL中,都是16KB ,使用 prev 和 next 构成双向链表

因为有主键的问题, MySQL 会默认按照主键给我们的数据进行排序,从上面的Page内数据记录可以看出,数据是有序且彼此关联的

通过上面的分析,我们知道,上面page的中,只有一个功能,就是在查询某条数据的时候直接将其内部的数据加载到内存中,以减少硬盘IO次数,从而提高性能。但是,我们也可以看到,现在的page的数据内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据。 如果有1千万条数据,一定需要多个Page来保存1千万条数据,多个Page彼此使用双链表链接起 来,而且每个Page内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。这效率也太低了:

为了解决这个问题,我们是不是可以像图书一样,向这些page中插入目录来提升查找效率?

当然可以:

 在单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的Page来保存新的数据,然后通过指针的方式,将所有的Page组织起来:

需要注意,上面的图,是理想结构,大家也知道,目前要保证整体有序,那么新插入的数据,不一定会在新Page上面,这里仅仅做演示。 这样,我们就可以通过多个Page遍历,Page内部通过目录来快速定位数据。

3.3.2 B+树的形成

可是,貌似上面有目录的形式也有效率问题,在Page之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的IO,需要将每一个Page加载到内存,进行线性检测。这样就显得我们之前的Page内部的目录,有点杯水车薪了。

那么如何解决呢?其实就是按我们之前的思路,给Page也带上目录。 使用一个page充当目录项来指向某一个page,而这个目录项存放的就是将要指向的page中存放的最小数据的键值和指向子page的指针。 和page内目录不同的地方在于,这种目录管理的级别是page,而page内目录管理的级别是数据:

那如果数据量更庞大呢?在上面继续加目录就可以了:

 但是在这里要注意了:作为路径的中间节点就不会有前后指针来指向相邻的节点了,但是最底层的叶子节点还是有前后指针的,这样可以方便范围查找

这就是传说中的B+树啊!没错,至此,我们已经给我们的表user构建完了主键索引。 随便找一个id=?我们发现,现在查找的Page数一定减少了,也就意味着IO次数减少了,那么效率也就提高了。

3.4 为什么不用其他数据结构来建立索引

链表?线性遍历,效率太低

二叉搜索树?退化问题,可能退化成为线性结构

AVL &&红黑树?虽然是平衡或者近似平衡,但是毕竟是二叉结构,相比较多阶B+,意味着树整体过高,大家都是自顶向下找,层高越高,意味着系统与硬盘更多的IO交互

Hash?官方的索引实现方式中, MySQL 是支持HASH的,不过 InnoDB 和 MyISAM 并不支持,Hash跟进其算法特征,决定了虽然有时候也很快(O(1)),不过,在面对范围查找就明显不行。

B树?B+树节点不存储data,这样一个节点就可以存储更多的key,可以使得树更矮,所以IO操作次数更少,而且B+树叶子节点相连,更便于进行范围查找

 综上所述,索引的本质就是B+树结构!

四、聚簇索引和非聚簇索引

MyISAM引擎同样使用B+树作为索引结果,但叶节点的data域存放的是数据记录的地址,并不存储数据。下图为 MyISAM 表的主索引, Col1 为主键。

MyISAM这种数据与索引数据分离的索引方案,叫做非聚簇索引

而上面介绍的InnoDB这种数据与索引数据在一起索引方案,叫做聚簇索引

我们用实例来感受一下非聚簇索引与聚簇索引区别:

先建立一个存储引擎为innodb的表:

建完后查看一下该数据库下的目录: 

可以看到t1表只有两个文件.frm是表结构文件,.ibd存储的是索引建立的B+树结构和数据

再建立一个存储引擎为MyISAM的表:

可以看到t2表有三个文件.frm是表结构文件,.MYD存储的是用户数据,.MYI索引建立的B+树结构

五、 辅助(普通)索引

当然, MySQL 除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以叫做辅助(普通)索引

对于 MyISAM,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复。

对于InnoDB,建立辅助(普通)索引中叶子节点并没有数据,而只有对应记录的key值。

为什么不给叶子节点也附上数据呢?原因就是InnoDB在主键索引中已经存有数据了,如果再给叶子节点也附上数据就太浪费空间了。

所以在InnoDB存储引擎下,通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询

六、索引操作

6.1 查看表中存在的索引

show index from 表名;

用上述语句可以查询表中存在的索引

例如:

6.2 创建索引

6.2.1 创建主键索引

创建主键索引我们不需要另外操作,只要表中有主键(建表时声明主键,追加主键等一系列操作,系统都会帮我们建立索引),系统就会自动对主键这一列创建索引

6.2.2 创建唯一索引

唯一索引和主键索引一样,创建唯一索引我们不需要另外操作,只要表中有唯一键(建表时声明唯一键,追加唯一键等一系列操作),系统就会自动对唯一键这一列创建索引

但是在这里说明一下唯一索引和普通索引一样,没有什么区别

例如,我们现在向t1表中追加一个唯一键:

6.2.3 创建普通索引

第一种方式:在表的定义最后,使用index关键字指定某列为索引

create table user(id int primary key,name varchar(20),email varchar(30),index(name) 
);

第二种方式: 创建完表以后使用index关键字指定某列为普通索引

create table user(id int primary key, name varchar(20), email varchar(30));
alter table user add index(name);

第三种方式:使用index关键字创建一个索引名为idx_name(索引名可自定义,这里只是举例)的索引

create table user(id int primary key, name varchar(20), email varchar(30));
create index idx_name on user(name);

6.2.4 创建复合索引

我们创建索引不仅仅只能用一列数据来作为我们的索引B+树的key值,我们可以使用多列组合来创建一个索引,这种索引被称为复合索引

例如,我们向下面这张表中创建一个复合索引: 

咦?这里怎么显示有三个索引?这表中不应该就一个主键索引和一个复合索引吗?

不用担心,2.row和3.row实际是同一个索引,索引名都为name

那这个复合索引有什么用呢?

在我们查name列的数据时,如果我们只为了找到相对应数据的email信息,这时由于name和email构成复合索引,其索引在B+树中找到的key值里面存有name数据对应的email信息,就不需要再回到主键索引中做回表查询了,直接返回key值中的email数据即可,这个过程被称为索引覆盖,可以提高查询效率。

所以复合索引一般用于多个列联系比较密切的场景中,比如:拿名字找电话号码、拿电话号码找人名。

但是要注意了,复合索引有最左匹配原则,即查询数据时优先匹配创建复合索引时最左边的列数据

例如在上面这个例子中,我们可以拿单独name列中的数据来找相对应的email数据,但是不能单独拿email列中的数据来找对应的name数据

6.2.5 创建全文索引

当对文章字段或有大量文字的字段进行查询是否包含关键语句时,会使用到全文索引。MySQL提供全文索引机制,但是有要求,要求表的存储引擎必须是MyISAM,而且默认的全文索引支持英文,不支持中文。如果对中文进行全文检索,可以使用sphinx的中文版(coreseek)。

创建全文索引时我们会用到关键字FULLTEXT

下面我们来举例说明:

先来创建一个表:

CREATE TABLE articles (id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,title VARCHAR(200),body TEXT,FULLTEXT (title,body)
)engine=MyISAM;

再来添加点数据:

INSERT INTO articles (title,body) VALUES
('MySQL Tutorial','DBMS stands for DataBase ...'),
('How To Use MySQL Well','After you went through a ...'),
('Optimizing MySQL','In this tutorial we will show ...'),
('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
('MySQL vs. YourSQL','In the following database comparison ...'),
('MySQL Security','When configured properly, MySQL ...');

再来看看该表中有什么样的索引:

可以看到有一个主键索引和一个全文索引title

现在我们来使用select查一下有没有database数据:

用上面的语句虽然查询出数据,但是没有使用到全文索引

怎么知道查询语句有没有用索引呢,我们可以使用explain工具来语句执行时的状态;

要想使用全文索引进行查询,需要用到matchagainst关键字:

我们再来用explain来查看一下该语句的执行状态:

可以看到此语句用到了全文索引

但需要注意的是,全文索引的底层结构并非B+树,而是倒排索引和辅助表,这里就不再过多深入

6.2.6 创建索引的原则

比较频繁作为查询条件的字段应该创建索引

唯一性太差的字段不适合单独创建索引,例如:性别

即使频繁作为查询条件,但更新非常频繁的字段不适合作创建索引

不会出现在where子句中的字段不该创建索引

6.3 删除索引

6.3.1 删除主键索引

alter table 表名 drop primary key;

就是删除表中的主键

6.3.2 删除其他类型的索引 

alter table 表名 drop index 索引名;

或者

drop index 索引名 on 表名;

本文知识点较多,如有纰漏还请各位大佬指正~

更多MySQL技能请看:http://t.csdn.cn/W9dQl

博主努力更新中~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/98096.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python tkinter Notebook标签添加关闭按钮元素,及左侧添加存储状态提示图标案例,类似Notepad++页面

效果图展示 粉色框是当前页面&#xff0c;橙色框是鼠标经过&#xff0c;红色框是按下按钮&#xff0c;灰色按钮是其他页面的效果&#xff1b; 存储标识可以用来识别页面是否存储&#xff1a;例如当前页面已经保存用蓝色&#xff0c;未保存用红色&#xff0c;其他页面已经保存用…

【力扣】42. 接雨水 <模拟、双指针、单调栈>

【力扣】42. 接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 目录 【力扣】42. 接雨水题解暴力双指针单调栈 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&…

数据结构<树和二叉树>顺序表存储二叉树实现堆排

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

c语言实现MD5算法

MD5加密 文章目录 MD5加密MD5介绍应用场景代码分析 &#xff08;基于qt5.14.2&#xff09;测试记录 MD5介绍 1。 一种单向加密算法&#xff0c;即对明文加密&#xff0c;而不能通过密文得到明文。对原数据的任何改动&#xff0c;哪怕是1字节&#xff0c;得到的MD5值都有很大的区…

vue路由及打包部署

vue路由&#xff08;前端路由&#xff09;&#xff1a;URL中的hash&#xff08;#号&#xff09;与组件之间的对应关系。 一、安装vue路由 npm install vue-router3.5.1 二、定义路由表 路由表主要记录hash&#xff08;#号&#xff09;与组件之间的对应关系。主要定义在route…

FPGA:uart原理+tx发送模块+rx接收模块

文章目录 一、串口通信二、UART通信三、tx发送模块四、rx模块接收 一、串口通信 处理器与外部设备通信的两种方式&#xff1a; 串行通信&#xff1a; 指数据的各个位使用多条数据线同时进行传输。 并行通信&#xff1a; 将数据分成一位一位的形式在一条数据线上逐个传输。 串…

SQL Injection

SQL Injection 就是通过把恶意的sql命令插入web表单递交给服务器&#xff0c;或者输入域名或页面请求的查询字符串递交到服务器&#xff0c;达到欺骗服务器&#xff0c;让服务器执行这些恶意的sql命令&#xff0c;从而让攻击者&#xff0c;可以绕过一些机制&#xff0c;达到直…

sql server安装报错 合成活动模板库(ATL) 失败

错误 “合成活动模板库(ATL) 规则失败“ 解决办法&#xff1a; 进入SQL Server 2008R2安装包目录找到文件&#xff1a;sqlsupport_msi&#xff0c;安装此文件之后&#xff0c;再安装SQL Server&#xff0c;便可解决该问题。C:\SQL Server 2008R2\new\SQL Server 2008R2\2052_CH…

Java虚拟机(JVM):虚拟机栈溢出

一、概念 Java虚拟机栈溢出&#xff08;Java Virtual Machine Stack Overflow&#xff09;是指在Java程序中&#xff0c;当线程调用的方法层级过深&#xff0c;导致栈空间溢出的情况。 Java虚拟机栈是每个线程私有的&#xff0c;用于存储方法的调用和局部变量的内存空间。每当…

如何学习专业的学术用语01

问题的提出——凭啥人家写的词汇这么专业 做法一 做法二&#xff1a;做一个专业数据库 专门做教育技术类的

Android Ble蓝牙App(六)请求MTU与显示设备信息

前言 在上一篇文章中已经了解了数据操作的方式&#xff0c;而数据交互的字节长度取决于我们手机与蓝牙设备的最大支持长度。 目录 Ble蓝牙App&#xff08;一&#xff09;扫描Ble蓝牙App&#xff08;二&#xff09;连接与发现服务Ble蓝牙App&#xff08;三&#xff09;特性和属…

unity 之 Vector 数据类型

文章目录 Vector 1Vector 2Vector 3Vector 4 Vector 1 在Unity中&#xff0c;Vector1 并不是一个常见的向量类型。 如果您需要表示标量&#xff08;单个值&#xff09;或者只需要一维的数据&#xff0c;通常会直接使用浮点数&#xff08;float&#xff09;或整数&#xff08;in…

Linux命令200例:tail用来显示文件的末尾内容(常用)

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &…

【ARM】Day4 点亮LED灯

1. 思维导图 2. 自己编写代码实现三盏灯点亮 .text .global _start _start: /**********LED1&#xff0c;LED2,LED3点灯:PE10,PF10,PE8**************/ RCC_INIT:使能GPIOE组/GPIOF组控制器,通过RXCC_MP_AHB4ENSETR设置第[5:4]位写1,地址:0x50000A28[5:4]1ldr r0,0x50000A28 …

【深入探究人工智能】:常见机器学习算法总结

文章目录 1、前言1.1 机器学习算法的两步骤1.2 机器学习算法分类 2、逻辑回归算法2.1 逻辑函数2.2 逻辑回归可以用于多类分类2.3 逻辑回归中的系数 3、线性回归算法3.1 线性回归的假设3.2 确定线性回归模型的拟合优度3.3线性回归中的异常值处理 4、支持向量机&#xff08;SVM&a…

〔012〕Stable Diffusion 之 中文提示词自动翻译插件 篇

✨ 目录 🎈 翻译插件🎈 下载谷歌翻译🎈 谷歌翻译使用方法🎈 谷歌翻译使用效果🎈 翻译插件 在插件列表中搜索 Prompt Translator可以看到有2个插件选项:一个是基于谷歌翻译 〔推荐〕、一个基于百度和deepl翻译推荐使用谷歌翻译,因为是离线翻译,所以使用时不用在乎是…

Java之SpringCloud Alibaba【四】【微服务 Sentinel服务熔断】

Java之SpringCloud Alibaba【四】【微服务 Sentinel服务熔断】 一、分布式系统遇到的问题1、服务挂掉的一些原因 二、解决方案三、Sentinel&#xff1a;分布式系统的流量防卫兵1、Sentinel是什么2、Sentinel和Hystrix对比3、Sentinel快速开发4、通过注解的方式来控流5、启动Sen…

vue3 基础知识

vue3创建一个项目 PS D:\code> npm init vuelatestVue.js - The Progressive JavaScript Framework√ Add TypeScript? ... No / Yes √ Add JSX Support? ... No / Yes √ Add Vue Router for Single Page Application development? ... No / Yes √ Add Pinia for sta…

电商项目part04 微服务拆分

微服务架构拆分 微服务介绍 英文:https://martinfowler.com/articles/microservices.html 中文:http://blog.cuicc.com/blog/2015/07/22/microservices 微服务拆分时机 如下场景是否需要进行微服务拆分&#xff1f; 代码维护困难&#xff0c;几百人同时开发一个模块&…

小白的Node.js学习笔记大全---不定期更新

Node.js是什么 Node. js 是一个基于 Chrome v8 引擎的服务器端 JavaScript 运行环境Node. js 是一个事件驱动、非阻塞式I/O 的模型&#xff0c;轻量而又高效Node. js 的包管理器 npm 是全球最大的开源库生态系统 特性 单一线程 Node.js 沿用了 JavaScript 单一线程的执行特…