MySQL的客户端/服务器架构
MySQL的客户端/服务器架构
MySQL
的服务器程序直接和我们存储的数据打交道,然后可以有好多客户端程序连接到这个服务器程序,发送增删改查的请求,然后服务器就响应这些请求,从而操作它维护的数据。我们日常使用MySQL
的情景一般是这样的:
- 启动
MySQL
服务器程序。 - 启动
MySQL
客户端程序并连接到服务器程序。 - 在客户端程序中输入一些命令语句作为请求发送到服务器程序,服务器程序收到这些请求后,会根据请求的内容来操作具体的数据并向客户端返回操作结果。
我们的MySQL
服务器程序和客户端程序本质上都算是计算机上的一个进程
,这个代表着MySQL
服务器程序的进程也被称为MySQL数据库实例
,简称数据库实例
。
每个进程都有一个唯一的编号,称为进程ID
,英文名叫PID
,这个编号是在我们启动程序的时候由操作系统随机分配的,操作系统会保证在某一时刻同一台机器上的进程号不重复。比如你打开了计算机中的QQ程序,那么操作系统会为它分配一个唯一的进程号,如果你把这个程序关掉了,那操作系统就会把这个进程号回收,之后可能会重新分配给别的进程。当我们下一次再启动 QQ程序的时候分配的就可能是另一个编号。
每个进程都有一个名称,这个名称是编写程序的人自己定义的,比如我们启动的MySQL
服务器进程的默认名称为mysqld
, 而我们常用的MySQL
客户端进程的默认名称为mysql
。
启动MySQL服务器程序
UNIX里启动服务器程序
在类UNIX
系统中用来启动MySQL
服务器程序的可执行文件有很多,大多在MySQL
安装目录的bin
目录下,我们一起来瞅瞅。
mysqld
mysqld
这个可执行文件就代表着MySQL
服务器程序,运行这个可执行文件就可以直接启动一个服务器进程。但这个命令不常用,我们继续往下看更牛逼的启动命令。
mysqld_safe
mysqld_safe
是一个启动脚本,它会间接的调用mysqld
,而且还顺便启动了另外一个监控进程,这个监控进程在服务器进程挂了的时候,可以帮助重启它。另外,使用mysqld_safe
启动服务器程序时,它会将服务器程序的出错信息和其他诊断信息重定向到某个文件中,产生出错日志,这样可以方便我们找出发生错误的原因。
mysql.server
mysql.server
也是一个启动脚本,它会间接的调用mysqld_safe
,在调用mysql.server
时在后边指定start
参数就可以启动服务器程序了,就像这样:
mysql.server start
需要注意的是,这个 mysql.server 文件其实是一个链接文件,它的实际文件是 ../support-files/mysql.server。我使用的macOS
操作系统会帮我们在bin
目录下自动创建一个指向实际文件的链接文件,如果你的操作系统没有帮你自动创建这个链接文件,那就自己创建一个呗~ 别告诉我你不会创建链接文件,上网搜搜呗~
另外,我们还可以使用mysql.server
命令来关闭正在运行的服务器程序,只要把start
参数换成stop
就好了:
mysql.server stop
mysqld_multi
其实我们一台计算机上也可以运行多个服务器实例,也就是运行多个MySQL
服务器进程。mysql_multi
可执行文件可以对每一个服务器进程的启动或停止进行监控。这个命令的使用比较复杂,本书主要是为了讲清楚MySQL
服务器和客户端运行的过程,不会对启动多个服务器程序进行过多唠叨。
Windows里启动服务器程序
Windows
里没有像类UNIX
系统中那么多的启动脚本,但是也提供了手动启动和以服务的形式启动这两种方式,下边我们详细看。
mysqld
同样的,在MySQL
安装目录下的bin
目录下有一个mysqld
可执行文件,在命令行里输入mysqld
,或者直接双击运行它就算启动了MySQL
服务器程序了。
以服务的方式运行服务器程序
首先看看啥是个Windows
服务?如果无论是谁正在使用这台计算机,我们都需要长时间的运行某个程序,而且需要在计算机启动的时候便启动它,一般我们都会把它注册为一个Windows 服务
,操作系统会帮我们管理它。把某个程序注册为Windows
服务的方式挺简单,如下:
"完整的可执行文件路径" --install [-manual] [服务名]
其中的-manual
可以省略,加上它的话表示在Windows
系统启动的时候不自动启动该服务,否则会自动启动。服务名
也可以省略,默认的服务名就是MySQL
。比如我的Windows
计算机上mysqld
的完整路径是:
C:\Program Files\MySQL\MySQL Server 5.7\bin\mysqld
所以如果我们想把它注册为服务的话可以在命令行里这么写:
"C:\Program Files\MySQL\MySQL Server 5.7\bin\mysqld" --install
在把mysqld
注册为Windows
服务之后,我们就可以通过下边这个命令来启动MySQL
服务器程序了:
net start MySQL
当然,如果你喜欢图形界面的话,你可以通过Windows
的服务管理器通过用鼠标点点点的方式来启动和停止服务(作为一个程序猿,还是用黑框框吧~)。
关闭这个服务也非常简单,只要把上边的start
换成stop
就行了,就像这样:
net stop MySQL
客户端与服务器连接的过程
我们现在已经知道如何启动MySQL
的服务器程序,以及如何启动客户端程序来连接到这个服务器程序。运行着的服务器程序和客户端程序本质上都是计算机上的一个进程,所以客户端进程向服务器进程发送请求并得到回复的过程本质上是一个进程间通信的过程!MySQL
支持下边三种客户端进程和服务器进程的通信方式。
TCP/IP
真实环境中,数据库服务器进程和客户端进程可能运行在不同的主机中,它们之间必须通过网络来进行通讯。MySQL
采用TCP
作为服务器和客户端之间的网络通信协议。在网络环境下,每台计算机都有一个唯一的IP地址
,如果某个进程有需要采用TCP
协议进行网络通信方面的需求,可以向操作系统申请一个端口号
,这是一个整数值,它的取值范围是0~65535
。这样在网络中的其他进程就可以通过IP地址 + 端口号
的方式来与这个进程连接,这样进程之间就可以通过网络进行通信了。
MySQL
服务器启动的时候会默认申请3306
端口号,之后就在这个端口号上等待客户端进程进行连接,用书面一点的话来说,MySQL
服务器会默认监听3306
端口。
小贴士:
TCP/IP
网络体系结构是现在通用的一种网络体系结构,其中的TCP
和IP
是体系结构中两个非常重要的网络协议,如果你并不知道协议是什么,或者并不知道网络是什么,那恐怕兄弟你来错地方了,找本计算机网络的书去瞅瞅吧!
如果3306
端口号已经被别的进程占用了或者我们单纯的想自定义该数据库实例监听的端口号,那我们可以在启动服务器程序的命令行里添加-P
参数来明确指定一下端口号,比如这样:
mysqld -P3307
这样MySQL
服务器在启动时就会去监听我们指定的端口号3307
。
如果客户端进程想要使用TCP/IP
网络来连接到服务器进程,比如我们在使用mysql
来启动客户端程序时,在-h
参数后必须跟随IP地址
来作为需要连接的服务器进程所在主机的主机名,如果客户端进程和服务器进程在一台计算机中的话,我们可以使用127.0.0.1
来代表本机的IP地址
。另外,如果服务器进程监听的端口号不是默认的3306
,我们也可以在使用mysql
启动客户端程序时使用-P
参数(大写的P
,小写的p
是用来指定密码的)来指定需要连接到的端口号。比如我们现在已经在本机启动了服务器进程,监听的端口号为3307
,那我们启动客户端程序时可以这样写:
mysql -h127.0.0.1 -uroot -P3307 -p
命名管道和共享内存
如果你是一个Windows
用户,那么客户端进程和服务器进程之间可以考虑使用命名管道
或共享内存
进行通信。不过启用这些通信方式的时候需要在启动服务器程序和客户端程序时添加一些参数:
-
使用
命名管道
来进行进程间通信需要在启动服务器程序的命令中加上
--enable-named-pipe
参数,然后在启动客户端程序的命令中加入--pipe
或者--protocol=pipe
参数。 -
使用
共享内存
来进行进程间通信需要在启动服务器程序的命令中加上
--shared-memory
参数,在成功启动服务器后,共享内存
便成为本地客户端程序的默认连接方式,不过我们也可以在启动客户端程序的命令中加入--protocol=memory
参数来显式的指定使用共享内存进行通信。不过需要注意的是,使用
共享内存
的方式进行通信的服务器进程和客户端进程必须在同一台Windows
主机中。
小贴士:
命名管道和共享内存是Windows操作系统中的两种进程间通信方式,如果你没听过的话也不用纠结,并不妨碍我们介绍MySQL的知识~
Unix域套接字文件
如果我们的服务器进程和客户端进程都运行在同一台操作系统为类Unix
的机器上的话,我们可以使用Unix域套接字文件
来进行进程间通信。如果我们在启动客户端程序的时候指定的主机名为localhost
,或者指定了--protocol=socket
的启动参数,那服务器程序和客户端程序之间就可以通过Unix
域套接字文件来进行通信了。MySQL
服务器程序默认监听的Unix
域套接字文件路径为/tmp/mysql.sock
,客户端程序也默认连接到这个Unix
域套接字文件。如果我们想改变这个默认路径,可以在启动服务器程序时指定socket
参数,就像这样:
mysqld --socket=/tmp/a.txt
这样服务器启动后便会监听/tmp/a.txt
。在服务器改变了默认的UNIX
域套接字文件后,如果客户端程序想通过UNIX
域套接字文件进行通信的话,也需要显式的指定连接到的UNIX
域套接字文件路径,就像这样:
mysql -hlocalhost -uroot --socket=/tmp/a.txt -p
这样该客户端进程和服务器进程就可以通过路径为/tmp/a.txt
的Unix
域套接字文件进行通信了。
服务器处理客户端请求
其实不论客户端进程和服务器进程是采用哪种方式进行通信,最后实现的效果都是:客户端进程向服务器进程发送一段文本(MySQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)。那服务器进程对客户端进程发送的请求做了什么处理,才能产生最后的处理结果呢?客户端可以向服务器发送增删改查各类请求,我们这里以比较复杂的查询请求为例来画个图展示一下大致的过程:
从图中我们可以看出,服务器程序处理来自客户端的查询请求大致需要经过三个部分,分别是连接管理
、解析与优化
、存储引擎
。下边我们来详细看一下这三个部分都干了什么。
连接管理
客户端进程可以采用我们上边介绍的TCP/IP
、命名管道或共享内存
、Unix域套接字
这几种方式之一来与服务器进程建立连接,每当有一个客户端进程连接到服务器进程时,服务器进程都会创建一个线程来专门处理与这个客户端的交互,当该客户端退出时会与服务器断开连接,服务器并不会立即把与该客户端交互的线程销毁掉,而是把它缓存起来,在另一个新的客户端再进行连接时,把这个缓存的线程分配给该新客户端。这样就起到了不频繁创建和销毁线程的效果,从而节省开销。从这一点大家也能看出,MySQL
服务器会为每一个连接进来的客户端分配一个线程,但是线程分配的太多了会严重影响系统性能,所以我们也需要限制一下可以同时连接到服务器的客户端数量,至于怎么限制我们后边再说哈~
在客户端程序发起连接的时候,需要携带主机信息、用户名、密码,服务器程序会对客户端程序提供的这些信息进行认证,如果认证失败,服务器程序会拒绝连接。另外,如果客户端程序和服务器程序不运行在一台计算机上,我们还可以采用使用了SSL
(安全套接字)的网络连接进行通信,来保证数据传输的安全性。
当连接建立后,与该客户端关联的服务器线程会一直等待客户端发送过来的请求,MySQL
服务器接收到的请求只是一个文本消息,该文本消息还要经过各种处理,预知后事如何,继续往下看哈~
解析与优化
到现在为止,MySQL
服务器已经获得了文本形式的请求,接着 还要经过九九八十一难的处理,其中的几个比较重要的部分分别是查询缓存
、语法解析
和查询优化
,下边我们详细来看。
查询缓存
MySQL
服务器程序处理查询请求的过程会把刚刚处理过的查询请求和结果缓存
起来,如果下一次有一模一样的请求过来,直接从缓存中查找结果就好了,就不用再傻呵呵的去底层的表中查找了。这个查询缓存可以在不同客户端之间共享,也就是说如果客户端A刚刚查询了一个语句,而客户端B之后发送了同样的查询请求,那么客户端B的这次查询就可以直接使用查询缓存中的数据。
当然,MySQL
服务器并没有人聪明,如果两个查询请求在任何字符上的不同(例如:空格、注释、大小写),都会导致缓存不会命中。另外,如果查询请求中包含某些系统函数、用户自定义变量和函数、一些系统表,如 mysql 、information_schema、 performance_schema 数据库中的表,那这个请求就不会被缓存。
以某些系统函数举例,可能同样的函数的两次调用会产生不一样的结果,比如函数NOW
,每次调用都会产生最新的当前时间,如果在一个查询请求中调用了这个函数,那即使查询请求的文本信息都一样,那不同时间的两次查询也应该得到不同的结果,如果在第一次查询时就缓存了,那第二次查询的时候直接使用第一次查询的结果就是错误的!
不过既然是缓存,那就有它缓存失效的时候。MySQL的缓存系统会监测涉及到的每张表,只要该表的结构或者数据被修改,如对该表使用了INSERT
、 UPDATE
、DELETE
、TRUNCATE TABLE
、ALTER TABLE
、DROP TABLE
或 DROP DATABASE
语句,那使用该表的所有高速缓存查询都将变为无效并从高速缓存中删除!
小贴士:
虽然查询缓存有时可以提升系统性能,但也不得不因维护这块缓存而造成一些开销,比如每次都要去查询缓存中检索,查询请求处理完需要更新查询缓存,维护该查询缓存对应的内存区域。从MySQL 5.7.20开始,不推荐使用查询缓存,并在MySQL 8.0中删除。
语法解析
如果查询缓存没有命中,接下来就需要进入正式的查询阶段了。因为客户端程序发送过来的请求只是一段文本而已,所以MySQL
服务器程序首先要对这段文本做分析,判断请求的语法是否正确,然后从文本中将要查询的表、各种查询条件都提取出来放到MySQL
服务器内部使用的一些数据结构上来。
小贴士:
这个从指定的文本中提取出我们需要的信息本质上算是一个编译过程,涉及词法解析、语法分析、语义分析等阶段,这些问题不属于我们讨论的范畴,大家只要了解在处理请求的过程中需要这个步骤就好了。
查询优化
语法解析之后,服务器程序获得到了需要的信息,比如要查询的列是哪些,表是哪个,搜索条件是什么等等,但光有这些是不够的,因为我们写的MySQL
语句执行起来效率可能并不是很高,MySQL
的优化程序会对我们的语句做一些优化,如外连接转换为内连接、表达式简化、子查询转为连接吧啦吧啦的一堆东西。优化的结果就是生成一个执行计划,这个执行计划表明了应该使用哪些索引进行查询,表之间的连接顺序是啥样的。我们可以使用EXPLAIN
语句来查看某个语句的执行计划。
存储引擎
截止到服务器程序完成了查询优化为止,还没有真正的去访问真实的数据表,MySQL
服务器把数据的存储和提取操作都封装到了一个叫存储引擎
的模块里。我们知道表
是由一行一行的记录组成的,但这只是一个逻辑上的概念,物理上如何表示记录,怎么从表中读取数据,怎么把数据写入具体的物理存储器上,这都是存储引擎
负责的事情。为了实现不同的功能,MySQL
提供了各式各样的存储引擎
,不同存储引擎
管理的表具体的存储结构可能不同,采用的存取算法也可能不同。
小贴士:
为什么叫
引擎
呢?因为这个名字更拉风~ 其实这个存储引擎以前叫做表处理器
,后来可能人们觉得太土,就改成了存储引擎
的叫法,它的功能就是接收上层传下来的指令,然后对表中的数据进行提取或写入操作。
为了管理方便,把连接管理
、查询缓存
、语法解析
、查询优化
这些并不涉及真实数据存储的功能划分为MySQL server
的功能,把真实存取数据的功能划分为存储引擎
的功能。\
各种不同的存储引擎向上边的MySQL server
层提供统一的调用接口(也就是存储引擎API),包含了几十个底层函数,像"读取索引第一条内容"、"读取索引下一条内容"、"插入记录"等等。
所以在MySQL server
完成了查询优化后,只需按照生成的执行计划调用底层存储引擎提供的API,获取到数据后返回给客户端就好了。
常用存储引擎
MySQL
支持非常多种存储引擎,我这先列举一些:
存储引擎 | 描述 |
---|---|
ARCHIVE |
用于数据存档(行被插入后不能再修改) |
BLACKHOLE |
丢弃写操作,读操作会返回空内容 |
CSV |
在存储数据时,以逗号分隔各个数据项 |
FEDERATED |
用来访问远程表 |
InnoDB |
具备外键支持功能的事务存储引擎 |
MEMORY |
置于内存的表 |
MERGE |
用来管理多个MyISAM表构成的表集合 |
MyISAM |
主要的非事务处理存储引擎 |
NDB |
MySQL集群专用存储引擎 |
其实我们最常用的就是InnoDB
和MyISAM
,有时会提一下Memory
。其中InnoDB
是MySQL
默认的存储引擎,我们之后会详细唠叨这个存储引擎的各种功能,现在先看一下一些存储引擎对于某些功能的支持情况:
Feature | MyISAM | Memory | InnoDB | Archive | NDB |
---|---|---|---|---|---|
B-tree indexes | yes | yes | yes | no | no |
Backup/point-in-time recovery | yes | yes | yes | yes | yes |
Cluster database support | no | no | no | no | yes |
Clustered indexes | no | no | yes | no | no |
Compressed data | yes | no | yes | yes | no |
Data caches | no | N/A | yes | no | yes |
Encrypted data | yes | yes | yes | yes | yes |
Foreign key support | no | no | yes | no | yes |
Full-text search indexes | yes | no | yes | no | no |
Geospatial data type support | yes | no | yes | yes | yes |
Geospatial indexing support | yes | no | yes | no | no |
Hash indexes | no | yes | no | no | yes |
Index caches | yes | N/A | yes | no | yes |
Locking granularity | Table | Table | Row | Row | Row |
MVCC | no | no | yes | no | no |
Query cache support | yes | yes | yes | yes | yes |
Replication support | yes | Limited | yes | yes | yes |
Storage limits | 256TB | RAM | 64TB | None | 384EB |
T-tree indexes | no | no | no | no | yes |
Transactions | no | no | yes | no | yes |
Update statistics for data dictionary | yes | yes | yes | yes | yes |
关于存储引擎的一些操作
查看当前服务器程序支持的存储引擎
我们可以用下边这个命令来查看当前服务器程序支持的存储引擎:
SHOW ENGINES;
来看一下调用效果:
mysql> SHOW ENGINES;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
| MyISAM | YES | MyISAM storage engine | NO | NO | NO |
| CSV | YES | CSV storage engine | NO | NO | NO |
| ARCHIVE | YES | Archive storage engine | NO | NO | NO |
| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
| FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)
mysql>
其中的Support
列表示该存储引擎是否可用,DEFAULT
值代表是当前服务器程序的默认存储引擎。Comment
列是对存储引擎的一个描述,英文的,将就着看吧。Transactions
列代表该存储引擎是否支持事务处理。XA
列代表着该存储引擎是否支持分布式事务。Savepoints
代表着该存储引擎是否支持部分事务回滚。
设置表的存储引擎
我们前边说过,存储引擎是负责对表中的数据进行提取和写入工作的,我们可以为不同的表设置不同的存储引擎,也就是说不同的表可以有不同的物理存储结构,不同的提取和写入方式。
创建表时指定存储引擎
我们之前创建表的语句都没有指定表的存储引擎,那就会使用默认的存储引擎InnoDB
(当然这个默认的存储引擎也是可以修改的,我们在后边的章节中再说怎么改)。如果我们想显式的指定一下表的存储引擎,那可以这么写:
CREATE TABLE 表名(
建表语句;
) ENGINE = 存储引擎名称;
比如我们想创建一个存储引擎为MyISAM
的表可以这么写:
mysql> CREATE TABLE engine_demo_table(
-> i int
-> ) ENGINE = MyISAM;
Query OK, 0 rows affected (0.02 sec)
mysql>
修改表的存储引擎
如果表已经建好了,我们也可以使用下边这个语句来修改表的存储引擎:
ALTER TABLE 表名 ENGINE = 存储引擎名称;
比如我们修改一下engine_demo_table
表的存储引擎:
mysql> ALTER TABLE engine_demo_table ENGINE = InnoDB;
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql>
这时我们再查看一下engine_demo_table
的表结构:
mysql> SHOW CREATE TABLE engine_demo_table\G
*************************** 1. row ***************************
Table: engine_demo_table
Create Table: CREATE TABLE `engine_demo_table` (
`i` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)
mysql>
可以看到该表的存储引擎已经改为InnoDB
了。