参考Mysql是怎么运行的,并结合实际的工作经验对mysql的知识进行总结。

Mysql架构

从整体上来说,Mysql也算是一个C/S架构。以我们平时使用的聊天工具为例(如qq、微信、钉钉等),它其实是由两部分组成的,一部分是客户端程序,一部分是服务器程序。客户端可以有多种形式,比如手机版,PC版,每个客户端都有一个唯一的id, 比如我们注册的手机号或者用户名,另一方面,各聊天工具的提供商有一个服务端程序,我们平时使用其实都是用客户端来和这个服务端通讯。比如用户A给用户B发了一条消息的过程其实是这样的:
1、消息被客户端包装了一下,添加了发送者和接收者信息,然后从A的客户端传送给服务端程序;
2、服务端程序从消息里获取到它的发送者和接收者,根据消息的接收者信息把这条消息送达到B的客户端,B的客户端里就显示出A给他发了一条消息。

Mysql的使用过程和这个类似,它的服务器程序直接和底层的存储的数据打交道,然后可以有很多客户端程序连接到这个服务器程序,发送增删改查的请求,然后服务器就响应这些请求,根据请求返回数据。和聊天工具一样,MySQL的每个客户端也需要用户名密码才能登录,登录之后才能给服务器发请求来操作某些数据。我们日常使用MySQL的情景一般是这样的:

  • 启动MySQL服务器程序。
  • 启动MySQL客户端程序并连接到服务器程序。
  • 在客户端程序中输入一些命令语句作为请求发送到服务器程序,服务器程序收到这些请求后,会根据请求的内容来操作具体的数据并向客户端返回操作结果。

我们的MySQL服务器程序和客户端程序本质上都算是计算机上一个进程,这个代表着MySQL服务器程序的进程也被称为MySQL数据库实例,简称数据库实例。

每个进程都有一个唯一的编号,称为进程ID,英文名叫PID,这个编号是我们在启动程序的时候由操作系统随机分配的,操作系统会保证在某一时刻同一台机器上的进程号不重复。如果进程被关掉了,那么操作系统会把这个进程号回收,之后可能会重新分配给其他的进程使用。当我们下一次再次启动相同的程序事分配的就很可能就是另一个进程号。每个进程都有一个名称,这个名称是编写程序的人自定义的,比如启动MySQL服务端的默认名称为mysqld,而我们常用的MySQL客户端的默认名称为mysql。

Mysql的安装

Mysql常用的安装方法有两种:源码安装和二进制安装。不论使用上述两者的哪种安装方式,我们都需要注意MySQL实际的安装目录。
注意:MySQL的大部分安装包都包含了服务器程序和客户端程序,不过在Linux下使用RPM包时会有单独的服务器RPM包和客户端RPM包,需要分别安装。而我们在某些服务器上可以只安装客服端用于连接测试。

其次,目前 MySQL 可以运行在各种各样的操作系统上,如常见的Windows、Mac OS、Linux。不同的操作系统在使用上会有一些差别。
在Windows 操作系统上的安装目录:
C:\Program Files\MySQL\MySQL Server 5.7
而Linux(Centos7)操作系统上的安装目录:
/usr/local/mysql/
下边我们以这两种操作系统为例进行说明,实际使用中注意自己的安装目录。

可执行文件

在 MySQL 的安装目录下有一个特别特别重要的 bin 目录,这个目录下存放着许多可执行文件,以 centos7 操作系统为例,这个 bin 目录的绝对路径就是(在我的机器上):
/usr/local/mysql/bin

Windows 中的可执行文件与 centos7 中的类似,不过都是以 .exe 为扩展名的。这些可执行文件都是与服务器程序和客户端程序相关的,后边我们会用到一些常用的可执行文件,现在先看看执行这些文件的方式。
以 centos7 系统为例来看看如何启动这些可执行文件( Windows 中的操作是类似的)

  • 使用可执行文件的相对/绝对路径,假设我们现在所处的工作目录是 MySQL 的安装目录,也就是 /usr/local/mysql ,我们想启动 bin 目录下的 mysqld 这个可执行文件,可以使用相对路径来启动:./bin/mysqld
    或者直接输入 mysqld 的绝对路径也可以:
    /usr/local/mysql/bin/mysqld
  • 将该 bin 目录的路径加入到环境变量 PATH 中
    这和我们很多其他服务一样(如java),将路径加入到环境变量PATH中方便使用,方法是在文件/etc/profile 添加如下行:
    export PATH=/usr/local/mysql/bin:$PATH
    然后source /etc/profile生效即可。

这样现在不论我们所处的工作目录是什么,都可以直接输入可执行文件的名字就可以启动它,比如这样:
mysqld
就可以使用了。

启动Mysql服务

Linux操作系统启动Mysql服务

在各种Linux系统中用来启动 MySQL 服务器程序的可执行文件有很多,大多在 MySQL 安装目录的 bin 目录下,下面进行说明。

mysqld

mysqld 这个可执行文件就代表着 MySQL 服务器程序,运行这个可执行文件可用于启动mysqld服务,但是我们不常用。

mysqld_safe

mysqld_safe 是一个启动脚本,它间接调用 mysqld ,并顺便启动了另外一个监控进程,这个监控进程在服务端挂了的时候,可以用于重启它。另外,使用 mysqld_safe 启动服务器程序时,它会将服务器程序的出错信息和其他诊断信息重定向到某个文件中,产生出错日志,这样我们可以通过相应的日志进行排错。

mysql.server

mysql.server 默认位于support-files目录下,也是一个启动脚本,但它默认没有执行权限,它会间接的调用 mysqld_safe ,在调用 mysql.server 时在后边指定 start参数就可以启动服务器程序了,如:
sh ./support-files/mysql.server start
此外,我们还可以使用 mysql.server 命令来关闭正在运行的服务器程序,将 start 参数改成 stop 即可:
sh ./support-files/mysql.server stop

mysqld_multi

其实我们一台计算机上也可以运行多个服务器实例,也就是运行多个 MySQL 服务端。mysql_multi 可执行文件可以对每一个服务端的启动或停止进行监控。这个命令实际使用中不多。

Windows操作系统启动Mysql服务

Windows 里没有像Linux系统中那么多的启动脚本,但是也提供了手动启动和以服务的形式启动这两种方式,下面进行说明。

mysqld

Windows下在 MySQL 安装目录下的 bin 目录下有一个 mysqld 可执行文件,通过cmd在命令行里输入 mysqld ,或者直接双击运行它就可以启动 MySQL 服务器程序。

以服务的方式运行服务器程序

大家对Windows下的服务可能不太熟悉,我们在windows10 下开始菜单的搜索栏内输入service就明白了。默认情况下,开机默认启动都有很多的服务。而如果我们都想要长时间的运行某个程序,并且需要开机自启,一般我们都会把它注册为一个 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 服务端后,就可以使用客户端来连接使用了, bin 目录下有许多客户端程序,如常用的mysqladmin 、 mysqldump 、 mysqlcheck 等等。启动这些可执行文件时一般需要添加一些额外的参数,如下:
mysql -h主机名 -u用户名 -p密码
参数说明:

  • -h:指定服务端所在计算机的域名或者IP地址,如果省略这个参数,默认是连接本机上的服务端,当然本机也可以使用 localhost 或者 127.0.0.1进行连接 。也可以写作 --host=主机名的形式。
  • -u:指定用户名。也可以写作 --user=用户名的形式。
  • -p :指定密码。也可以写作 --password=密码的形式。|
    注意:像 h、u、p 这样名称只有一个英文字母的参数称为短格式的参数,使用时前边需要加单短划线,而如果要使用host、user、password 这样完整的英文单词的参数称为长格式的参数,使用时前边需要加双短划线。
    下面执行下边这个可执行文件(用户名密码按你的实际情况填写),就可以通过 MySQL 客户端连接到服务端。
[root@k8s-m2 mysql]# mysql -hlocalhost -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.30 Source distribution

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

最后一行的 mysql> 是一个客户端的提示符,之后客户端发送给服务器的命令都需要写在这个提示符后边。
如果我们想断开客户端与服务器的连接并且关闭客户端的话,可以在 mysql> 提示符后输入下边任意一个命令:
1 . quit
2 . exit
3 . \q
4. ctrl+d (常用快捷键)

最后MySQL是可以多线程连接的,可以通过多个mysql客户端程序同时连接,互不影响。

注意事项

  • 实际工作中安全起见不要通过在同一行命令中直接输入密码,避免其他无关人员看到。
  • 如果非要在一行命令中显式的把密码输出来,那 -p 和密码值之间不能有空白字符(其他参数名之间可以有空白字符)
  • mysql 的各个参数的顺序可以随意书写
  • 如果服务器和客户端安装在同一台机器上, -h 参数可以省略
  • 如果使用的是Linux系统,并且省略 -u 参数后,会把你登陆操作系统的用户名当作 MySQL 的用户名去登录。对于Windows 系统来说,默认的用户名是 ODBC ,可以通过设置环境变量 USER 来添加一个默认用户名。

客户端与服务器连接的过程

运行着的服务端和客户端本质上是计算机上的一个进程,所以客户端向服务端发送请求并得到所需数据的过程本质上是一个进程间通信的过程。 MySQL 支持下边三种客户端和服务端的通信方式。

TCP/IP

在实际使用中,数据库服务端和客户端一般都运行在不同的主机中,它们之间需要通过网络来进行通讯。MySQL 采用 TCP 作为服务器和客户端之间的网络通信协议。在网络环境下,每台计算机都有一个唯一的 IP地址 ,如果某个进程有需要采用 TCP 协议进行网络通信方面的需求,可以向操作系统申请一个 端口号 ,这是一个整数值,它的取值范围是 0~65535 。这样在网络中的其他进程就可以通过 IP地址 + 端口号 的方式来与这个进程连接,这样进程之间就可以通过网络进行通信了。
MySQL 服务器启动的时候会默认监听3306 端口,之后就在这个端口号上等待客户端进程进行连接。实际使用中,为安全起见,我们可以根据需求设定运行端口。当然如果不是使用的默认端口,客户端在连接时需要通过-P指定。格式如下:
mysql -h127.0.0.1 -uroot -P3307 -p

命名管道和共享内存

如果使用的是Windows系统,那么客户端和服务端之间可以考虑使用命名管道或共享内存进行通信。不过启用这些通信方式的时候需要在启动服务器程序和客户端程序时添加一些额外参数:

  • 使用命名管道来进行进程间通信
    需要在启动服务器程序的命令中加上 --enable-named-pipe 参数,然后在启动客户端程序的命令中加入 --pipe 或者 --protocol=pipe 参数。
  • 使用共享内存来进行进程间通信
    需要在启动服务器程序的命令中加上 --shared-memory 参数,在成功启动服务器后, 共享内存便成为本地客户端程序的默认连接方式,不过我们也可以在启动客户端程序的命令中加入 --protocol=memory 参数来显式的指定使用共享内存进行通信。
    不过需要注意的是,使用共享内存的方式进行通信的服务端和客户端必须在同一台 Windows主机上。

Linux套接字文件

如果我们的服务端和客户端都在同一台操作系统为Linux 的机器上的话,我们可以使用 Linux套接字文件来进行进程间通信。如果我们在启动客户端程序的时候指定的主机名为 localhost ,或者指定了 --protocol=socket 的启动参数,那服务端和客户端之间就可以通过Linux套接字文件来进行通信。
MySQL 服务器程序默认监听的 Linux套接字文件路径为 /tmp/mysql.sock ,客户端程序也默认连接到这个Linux套接字文件。我们也可以改变这个默认路径,可以在启动服务器程序时显式指定 socket 参数,如下:
mysqld --socket=/tmp/a.txt
这样服务器启动后便会监听 /tmp/a.txt 。在服务器改变了默认的 Linux套接字文件后,如果户端程序想通过Linux套接字文件进行通信的话,也需要显式的指定连接到的Linux套接字文件路径,就像这样:
mysql -hlocalhost -uroot --socket=/tmp/a.txt -p
这样该客户端和服务端就可以通过路径为 /tmp/a.txt 的Linux套接字文件进行通信。

服务器处理客户端请求

不论客户端和服务端是采用哪种方式进行通信,最后实现的效果都是:客户端向服务端发送一段文本(MySQL语句),服务端处理后再向客户端发送一段文本(处理结果)。在这过程中服务端对客户端发送的请求做了哪些处理呢?客户端可以向服务器发送增删改查各类请求,我们这里以比较复杂的查询请求为例来画个图展示一下大致的过程:

mysql 可以带参数生成视图吗_MySQL


从上图我们可以看出,服务器程序处理来自客户端的查询请求大致需要经过三个部分,分别是连接管理 、解析与优化 、存储引擎 。下边我们来分别看看这三个步骤的具体作用。

连接管理

客户端可以采用我们上边介绍的 TCP/IP 、命名管道或共享内存 、LInux套接字这几种方式之一来与服务端建立连接,每当有一个客户端连接到服务端时,服务端都会创建一个线程来专门处理与这个客户端的交互,当该客户端退出时会与服务器断开连接,服务器并不会立即把与该客户端交互的线程销毁掉,而是把它缓存起来,在另一个新的客户端再进行连接时,把这个缓存的线程分配给该新客户端。这样就起到了不频繁创建和销毁线程的效果,从而节省开销。从这一点大家也能看出, 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 语句来查看某个语句的执行计划,后面的查询优化会相详细分享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 。在mysql5.7中InnoDB 是 MySQL 默认的存储引擎,后续会详细分享这个存储引擎的各种功能。

关于存储引擎的一些操作

查看当前服务器程序支持的存储引擎

我们可以用下边这个命令来查看当前服务器程序支持的存储引擎:
SHOW ENGINES;

mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| MRG_MYISAM         | YES     | Collection of identical MyISAM 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         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | 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         |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)

mysql>

说明:

  • Support 列表示该存储引擎是否可用, DEFAULT 值代表是当前服务器程序的默认存储引擎。 - Comment 列是对存储引擎的一个描述。
  • Transactions 列代表该存储引擎是否支持事务处理。
  • XA 列代表着该存储引擎是否支持分布式事务。
  • Savepoints 代表着该列是否支持部分事务回滚。

设置表的存储引擎

存储引擎是负责对表中的数据进行提取和写入工作的,可以为不同的表设置不同的存储引擎,也就是说不同的表可以有不同的物理存储结构,不同的提取和写入方式。

创建表时指定存储引擎

我们之前创建表的语句都没有指定表的存储引擎,那就会使用默认的存储引擎 InnoDB (当然这个默认的存储引擎也是可以修改的)。如果我们想显式的指定一下表的存储引擎,那可以这么写:
CREATE TABLE 表名(
建表语句;
) ENGINE = 存储引擎名称;
比如我们想创建一个存储引擎为 MyISAM 的表可以这么写:

mysql> create database test;
Query OK, 1 row affected (0.00 sec)

mysql> use test
Database changed
mysql> create table  myisam_demo_table(i int) engine=myisam;
Query OK, 0 rows affected (0.05 sec)

mysql>

修改表的存储引擎

如果表已经建好了,我们也可以使用下边这个语句来修改表的存储引擎:
ALTER TABLE 表名 ENGINE = 存储引擎名称;
比如我们修改一下myisam_demo_table表的存储引擎:

mysql> alter table myisam_demo_table engine=innodb;
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table myisam_demo_table;
+-------------------+----------------------------------------------------------------------------------------------------+
| Table             | Create Table                                                                                       |
+-------------------+----------------------------------------------------------------------------------------------------+
| myisam_demo_table | CREATE TABLE `myisam_demo_table` (
  `i` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------------------+----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

通过上面查看myisam_demo_table的表结构,可以看到该表的存储引擎已经改为 InnoDB 了。