MySQL的客户端/服务器架构
以我们平时使用的微信为例,它是由两部分组成的,一部分是客户端程序,一部分是服务器程序。
客户端可能有很多种形式,比如:手机 APP,电脑软件或者是网页版微信,每个客户端都有一个唯一的用户名,就是你的微信号,另一方面,腾讯公司在他们的机房里运行着一个服务器软件,我们平时操作微信其实都是用客户端来和这个服务器来打交道。
比如狗哥用微信给猫爷发了一条消息的过程其实是这样的:
- 消息被客户端包装了一下,添加了发送者和接收者信息,然后从狗哥的微信客户端传送给微信服务器
- 微信服务器从消息里获取到消息的发送者和接收者,根据消息的接收者信息把这条消息送达到猫爷的微信客户端,猫爷的微信客户端里就显示出狗哥给他发的消息
MySQL 的使用过程跟这个是一样的,MySQL 的服务器程序直接和我们存储的数据打交道,然后可以有好多客户端程序连接到这个服务器程序,发送增删改查的请求,然后服务器就响应这些请求,从而操作它维护的数据。
和微信一样, MySQL 的每个客户端都需要提供用户名密码才能登录,登录之后才能给服务器发请求来操作某些数据。
我们日常使用 MySQL 的情景一般是这样的:
- 启动 MySQL 服务器程序
- 启动 MySQL 客户端程序并连接到服务器程序
- 在客户端程序中输入一些命令语句作为请求发送到服务器程序,服务器程序收到这些请求后,会根据请求的
内容来操作具体的数据并向客户端返回操作结果
每一个运行着的程序也被称为一个 进程 。
我们的 MySQL 服务器程序和客户端程序本质上都算是计算机上的一个 进程 ,这个代表着 MySQL 服务器程序的进程也被称为 MySQL 数据库实例,简称 数据库实例。
每个进程都有一个唯一的编号,称为 进程ID ,英文名叫 PID ,这个编号是在我们启动程序的时候由操作系统随
机分配的,操作系统会保证在某一时刻同一台机器上的进程号不重复。
每个进程都有一个名称,这个名称是编写程序的人自己定义的,比如我们启动的 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 命令来关闭正在运行的服务器程序,就像这样:mysql.server stop
mysqld_multi
其实我们一台计算机上也可以运行多个服务器实例,也就是运行多个 MySQL 服务器进程。 mysql_multi 可执行文
件可以对每一个服务器进程的启动或停止进行监控。
这个命令的使用比较复杂。
Windows里启动服务器程序
Windows 里没有像类 UNIX 系统中那么多的启动脚本,但是也提供了:手动启动和以服务的形式启动这两种方式。
mysqld
在命令行里输入 mysqld ,或者直接双击运行它就算启动 MySQL 服务器程序了。
以服务的方式运行服务器程序
首先看什么是 Windows 服务?
如果无论是谁正在使用这台计算机,我们都需要长时间的运行某个程序,而且需要在计算机启动的时候便启动它,一般我们都会把它注册为一个 Windows 服务 ,操作系统会帮我们管理它。
把某个程序注册为 Windows 服务的方式如下:"完整的可执行文件路径" --install [-manual] [服务名]
其中的 -manual 可以省略,加上的话表示:在 Windows 系统启动的时候不自动启动该服务,否则会自动启动。
服务名 也可以省略,默认的服务名就是 MySQL 。
在把 mysqld 注册为 Windows 服务之后,就可以通过 net start MySQL
命令来启动 MySQL 服务器程序,
通过 net stop MySQL
命令来关闭 MySQL 服务器程序了
启动MySQL客户端程序
bin 目录下有许多客户端程序,比方说:mysqladmin 、 mysqldump 、 mysqlcheck 等。
这里重点关注的是可执行文件 mysql ,这个可执行文件可以让我们和服务器程序进程交互,也就是发送请求,
接收服务器的处理结果。
启动这个可执行文件时一般需要一些参数,格式:mysql -h主机名 -u用户名 -p密码
各个参数的意义如下:
- | | -h |表示:服务器进程所在计算机的域名或者 IP 地址,如果服务器进程就运行在本机的话,可以省略这个参数,或者填 localhost 或者 127.0.0.1 。也可以写作
--host=主机名
的形式 - | | -u |表示:用户名。也可以写作
--user=用户名
的形式 - | | -p |表示:密码。也可以写作
--password=密码
的形式
小贴士:
- 像 h、u、p 这样名称只有一个英文字母的参数称为:短形式的参数,使用时前边需要加单短划线
- 像 host、user、password 这样大于一个英文字母的参数称为:长形式的参数,使用时前边需要加双短划线
连接注意事项
- -p 和密码值之间不能有空白字符,其他参数名之间可以有空白字符,如下:
mysql -h localhost -u root -p123456
- 如果你使用的是类 UNIX 系统,并且省略 -u 参数后,会把你登陆操作系统的用户名当作 MySQL 的用户名去
处理 - 对于 Windows 系统来说,默认的用户名是 ODBC ,你可以通过设置环境变量 USER 来添加一个默认用户名
客户端 & 服务器连接的过程
运行着的服务器程序和客户端程序本质上都是计算机上的一个进程,所以客户端进程向服务器进程发送请求并得到回复的过程本质上是一个进程间通信的过程。
MySQL 支持下边三种客户端进程和服务器进程的通信方式。TCP/IP
命名管道和共享内存
Unix域套接字文件
服务器处理客户端请求
不论客户端进程和服务器进程采用哪种方式进行通信,实现的效果都是:客户端进程向服务器进程发送一段文本(MySQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)。
那服务器进程对客户端进程发送的请求做了什么处理,才能产生最后的处理结果呢?客户端可以向服务器发送增删改查各类请求,下图以比较复杂的查询请求为例展示大致的过程。
从图中可以看出,服务器程序处理来自客户端的查询请求大致需要经过三个部分,分别是:连接管理、解析与优化、存储引擎。
下边来详细看一下这三个部分都干了什么。
连接管理
客户端进程可以采用上边介绍的 TCP / IP、命名管道或共享内存、Unix 域套接字 这几种方式之一来与服务器进程建立连接。
每当有一个客户端进程连接到服务器进程时,服务器进程都会创建一个线程来专门处理与这个客户端的交互,当该客户端退出时会与服务器断开连接,服务器并不会立即把与该客户端交互的线程销毁掉,而是把它缓存起来,在另一个新的客户端再进行连接时,把这个缓存的线程分配给该新客户端。
这样就起到了不频繁创建和销毁线程的效果,从而节省开销。
从这一点大家也能看出, MySQL 服务器会为每一个连接进来的客户端分配一个线程,但是线程分配的太多会严重影响系统性能,所以也需要限制一下可以同时连接到服务器的客户端数量。
在客户端程序发起连接的时候,需要携带主机信息、用户名、密码,服务器程序会对客户端程序提供的这些信息
进行认证,如果认证失败,服务器程序会拒绝连接。
另外,如果客户端程序和服务器程序不运行在一台计算机上,我们还可以使用 SSL (安全套接字)的网络连接进行通信,来保证数据传输的安全性。
当连接建立后,与该客户端关联的服务器线程会一直等待客户端发送请求, MySQL 服务器接收到的请求只是一个文本消息,该文本消息还要经过各种处理。
解析与优化
到现在为止,MySQL 服务器已经获得了文本形式的请求,接着还要经过许多处理,其中几个重要的部分是:查询缓存、语法解析、语法优化。
查询缓存
MySQL 服务器程序处理查询请求会把已经处理过的查询请求和结果缓存起来起来,如果下次有一模一样的查询请求过来,直接从缓存中查找结果,不用再去底层的表中查找了。
这个查询缓存可以在不同客户端之间共享,也就是说:如果客户端 A 刚查询了一个语句,而客户端 B 之后发送了同样的查询请求,那么客户端 B 的这次查询就可以直接使用查询缓存中的数据。
不会被缓存的情况
但是,如果两个查询请求在任何字符上的不同(例如:空格、注释、大小写),都会导致缓存不会命中。另外,如果查询请求中包含某些系统函数、用户自定义变量和函数、一些系统表,如 mysql 、information_schema、 performance_schema 数据库中的表,那这个请求就不会被缓存。
以某些系统函数举例,可能同样函数的两次调用会产生不一样的结果,比如函数 now,如果在一个查询请求中调用了这个函数,那即使查询请求的文本信息都一样,不同时间的两次查询也应该得到不同的结果,如果在第一次查询时就缓存了,那第二次查询时使用第一次查询的结果就是错误的!
缓存失效的情况
不过既然是缓存,那就有它缓存失效的时候。
MySQL 的缓存系统会监测涉及到的每张表,只要该表的结构或者数据被修改,如对该表使用了 insert、update、truncate table、alter table、trop table、trop database
语句,那使用该表的所有高速缓存查询都将变为无效并从高速缓存中删除!
小贴士: 虽然查询缓存有时可以提升系统性能,但也不得不因维护这块缓存而造成一些开销, 比如每次都要去查询缓存中检索,查询请求处理完需要更新查询缓存,维护该查询缓存对应的内存区域。 从 MySQL 5.7.20 开始,不推荐使用查询缓存,并在 MySQL 8.0 中删除。
语法解析
如果查询缓存没有命中,接下来就需要进入正式的查询阶段了。
因为客户端程序发送过来的请求只是一段文本,所以 MySQL 服务器程序首先要对这段文本做分析,判断请求的语法是否正确,然后从文本中将要查询的表、各种查询条件都提取出来放到 MySQL 服务器内部使用的一些数据结构上来。
小贴士: 这个从指定的文本中提取出我们需要的信息本质上算是一个编译过程,涉及:词法解析、语法分析、语义分析等阶段
语法优化
语法解析之后,服务器程序获得到了需要的信息,比如:要查询的列是哪些,表是哪个,搜索条件是什么等。
但光有这些是不够的,因为我们写的 MySQL 语句执行起来效率可能并不是很高,MySQL 的优化程序会对我们的语句做一些优化,如:外连接转换为内连接、表达式简化、子查询转为连接等。
优化的结果就是生成一个执行计划,这个执行计划表明了应该使用哪些索引进行查询,表之间的连接顺序是怎么样的。
我们可以使用 explain 语句来查看某个语句的执行计划。
存储引擎
截止到服务器程序完成了查询优化为止,还没有真正的去访问真实的数据表,
MySQL 服务器把数据的存储和提取操作都封装到了一个叫:存储引擎 的模块里。
我们知道:表是由一行一行的记录组成的,但这只是一个逻辑上的概念,物理上如何表示记录,怎么从表中读取数据,怎么把数据写入具体的物理存储器上,这都是存储引擎负责的事情。
存储引擎的功能就是:接收上层传下来的指令,然后对表中的数据进行提取或写入操作。
为了实现不同的功能,MySQL 提供了各式各样的存储引擎,不同存储引擎管理的表具体的存储结构可能不同,采用的存取算法也可能不同。
为了管理方便,人们把 连接管理、查询缓存、语法解析、查询优化 这些并不涉及真实数据存储的功能划分为 MySQL server 的功能,把真实存取数据的功能划分为存储引擎的功能。
各种存储引擎向上边的 MySQL server 层提供统一的调用接口(也就是存储引擎 API),包含几十个底层函数,像 “读取索引第一条内容”、”读取索引下一条内容”、”插入记录” 等。
所以在 MySQL server 完成了查询优化后,只需按照生成的执行计划调用底层存储引擎提供的 API,获取到数据后返回给客户端就好了。
MySQL的存储引擎
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
命令查看当前服务器程序支持的存储引擎,
调用效果如下:
- Support 列表示:该存储引擎是否可用。default 值代表是当前服务器程序的默认存储引擎
- Comment 列是:对该存储引擎的一个描述
- Transactions 列表示:该存储引擎是否支持事务处理
- XA 列表示:该存储引擎是否支持分布式事务
- Savepoints 列表示:该存储引擎是否支持部分事务回滚
设置表的存储引擎
我们可以为不同的表设置不同的存储引擎。
创建表时指定存储引擎
创建表的语句没有指定表的存储引擎,那会使用默认的存储引擎 InnoDB(默认的存储引擎可以修改)。
如果想显式的指定表的存储引擎,那可以这么写:
create table 表名(
建表语句;
) engine = 存储引擎名称;
修改表的存储引擎
如果表已经建好,可以使用下边这个语句修改表的存储引擎:alter table 表名 engine = 存储引擎名称;