- Linux下MySQL的安装与使用
- 字符集的相关操作
- SQL大小写规范 - (了解)
- mysql 的数据目录
- 用户与权限管理
- 逻辑架构
- 存储引擎
- 索引的数据结构
- 索引的创建与设计原则
- 性能分析工具的使用
- 索引优化与查询优化
一、Linux下MySQL的安装与使用
1. 安装前说明
- 安装并启动好两台虚拟机: CentOS 7
- 安装有 Xshell 和 Xftp 等访问CentOS系统的工具
- CentOS6和CentOS7在MySQL的使用中的区别
- 防火墙:6是iptables,7是firewalld
- 启动服务的命令:6是service,7是systemctl
1.2 查看是否安装过MySQL
- 如果你是用rpm安装, 检查一下RPM PACKAGE:
rpm -qa | grep -i mysql # -i 忽略大小写
- 检查mysql service:
systemctl status mysqld.service
- 如果存在mysql-libs的旧版本包,显示如下:
- 如果不存在mysql-lib的版本,显示如下:
1.3 MySQL的卸载
1.关闭 mysql 服务
systemctl stop mysqld.service
2.查看当前 mysql 安装状况
rpm -qa | grep -i mysql
或
yum list installed | grep mysql
3.卸载上述命令查询出的已安装程序
- 查找相关文件
find / -name mysql
- 删除上述命令查找出的相关文件
rm -rf xxx
5.删除 my.cnf
rm -rf /etc/my.cnf
2.MySQL的Linux版安装
2.1 MySQL的4大版本
MySQL Community Server 社区版本
,开源免费,自由下载,但不提供官方技术支持,适用于大多数普通用户。
MySQL Enterprise Edition 企业版本
,需付费,不能在线下载,可以试用30天。提供了更多的功能和更完备的技术支持,更适合于对数据库的功能和可靠性要求较高的企业客户。
MySQL Cluster 集群版
,开源免费。用于架设集群服务器,可将几个MySQL Server封装成一个Server。需要在社区版或企业版的基础上使用。
MySQL Cluster CGE 高级集群版
,需付费。
- 截止目前,官方最新版本为 。此前,8.0.0 在 2016.9.12日就发布了。
- 本课程中主要使用 8.0.25 版本 。同时为了更好的说明MySQL8.0新特性,还会安装 MySQL5.7 版本,作为对比。
此外,官方还提供了 MySQL Workbench (GUITOOL)一款专为MySQL设计的 ER/数据库建模工具 。它是著名的数据库设计工具DBDesigner4的继任者。MySQLWorkbench又分为两个版本,分别是 社区版
(MySQL Workbench OSS)、 商用版 (MySQL WorkbenchSE)。
2.2 下载MySQL指定版本
1.下载地址
2.打开官网,点击DOWNLOADS
然后,点击 MySQL Community(GPL) Downloads
3.点击 MySQL Community Server
4.在General Availability(GA) Releases中选择适合的版本
如果安装Windows 系统下MySQL ,推荐下载 MSI安装程序 ;点击 Go to Download Page 进行下载即可
Windows下的MySQL安装有两种安装程序
mysql-installer-web-community-8.0.25.0.msi
下载程序大小:2.4M;安装时需要联网安装组件
mysql-installer-community-8.0.25.0.msi
下载程序大小:435.7M;安装时离线安装即可。推荐
。
5.Linux系统下安装MySQL的几种方式
5.1 Linux系统下安装软件的常用三种方式:
方式1:rpm命令
使用rpm命令安装扩展名为".rpm"的软件包。
.rpm包的一般格式:
方式2:yum命令
需联网,从 互联网
获取 的yum源,直接使用yum命令安装。
方式3:编译安装源码包
针对 tar.gz
这样的压缩格式,要用tar命令来解压;如果是其它压缩格式,就使用其它命令。
5.2Linux系统下安装MySQL,官方给出多种安装方式
安装方式 | 特点 |
---|---|
rpm | 安装简单,灵活性差,无法灵活选择版本、升级 |
rpm repository | 安装包极小,版本安装简单灵活,升级方便,需要联网安装 |
通用二进制包 | 安装比较复杂,灵活性高,平台通用性好 |
源码包 | 安装最复杂,时间长,参数设置灵活,性能好 |
- 这里不能直接选择CentOS 7系统的版本,所以选择与之对应的
Red Hat Enterprise Linux
https://downloads.mysql.com/archives/community/
直接点Download下载RPM Bundle全量包。包括了所有下面的组件。不需要一个一个下载了。
6.下载的tar包,用压缩工具打开
- 解压后rpm安装包 (红框为抽取出来的安装包)
2.3 CentOS7下检查MySQL依赖
1.检查/tmp临时目录权限(必不可少)
由于mysql安装过程中,会通过mysql用户在/tmp目录下新建tmp_db文件,所以请给/tmp较大的权限。执 行 :
chmod -R 777 /tmp
2.安装前,检查依赖
rpm -qa|grep libaio
- 如果存在libaio包如下:
rpm -qa|grep net-tools
- 如果存在net-tools包如下:
rpm -qa|grep net-tools
- 如果不存在需要到centos安装盘里进行rpm安装。安装linux如果带图形化界面,这些都是安装好 的。
2.4 CentOS7下MySQL安装过程
1.将安装程序拷贝到/opt目录下
在mysql的安装文件目录下执行:(必须按照顺序执行)
rpm -ivh mysql-community-common-8.0.25-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-plugins-8.0.25-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-8.0.25-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-8.0.25-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-8.0.25-1.el7.x86_64.rpm
- 注意: 如在检查工作时,没有检查mysql依赖环境在安装mysql-community-server会报错
rpm
是Redhat Package Manage缩写,通过RPM的管理,用户可以把源代码包装成以rpm为扩展名的文件形式,易于安装-i
,–install 安装软件包-v
, --verbose 提供更多的详细信息输出-h
, --hash 软件包安装的时候列出哈希标记 (和 -v 一起使用效果更好),展示进度条
2.安装过程截图
安装过程中可能的报错信息:
一个命令:
yum remove mysql-libs
解决,清除之前安装过的依赖即可
**
3.查看MySQL版本
执行如下命令,如果成功表示安装mysql成功。类似java -version如果打出版本等信息
mysql --version
或
mysqladmin --version
执行如下命令,查看是否安装成功。需要增加 -i 不用去区分大小写,否则搜索不到。
rpm -qa|grep -i mysql
4.服务的初始化
为了保证数据库目录与文件的所有者为 mysql 登录用户,如果你是以 root 身份运行 mysql 服务,需要执行下面的命令初始化:
mysqld --initialize --user=mysql
说明: --initialize 选项默认以“安全”模式来初始化,则会为 root 用户生成一个密码并将 该密码标记为过期
,登录后你需要设置一个新的密码。生成的 临时密码
会往日志中记录一份。
查看密码:
cat /var/log/mysqld.log
root@localhost: 后面就是初始化的密码
5.启动MySQL,查看状态
#加不加.service后缀都可以
启动:systemctl start mysqld.service
关闭:systemctl stop mysqld.service
重启:systemctl restart mysqld.service
查看状态:systemctl status mysqld.service
mysqld 这个可执行文件就代表着 MySQL 服务器程序,运行这个可执行文件就可以直接启动一个服务器进程。
查看进程:
ps -ef | grep -i mysql
6.查看MySQL服务是否自启动
systemctl list-unit-files|grep mysqld.service
默认是enabled。
- 如不是enabled可以运行如下命令设置自启动
systemctl enable mysqld.service
- 如果希望不进行自启动,运行如下命令设置
systemctl disable mysqld.service
7. 首次登录
通过
mysql -hlocalhost -P3306 -uroot -p
进行登录,在Enter password:录入初始化密码
7.1修改密码
- 因为初始化密码默认是过期的,所以查看数据库会报错
- 修改密码:
ALTER USER ‘root’@‘localhost’ IDENTIFIED BY ‘new_password’;
- 5.7版本之后(不含5.7),mysql加入了全新的密码安全机制。设置新密码太简单会报错。
- 改为更复杂的密码规则之后,设置成功,可以正常使用数据库了
7.2 设置远程登录
在用SQLyog或Navicat中配置远程连接Mysql数据库时遇到如下报错信息,这是由于Mysql配置了不支持远 程连接引起的。
- 在远程机器上使用ping ip地址 保证网络畅通
- 在远程机器上使用telnet命令 保证端口号开放 访问
telnet ip地址 端口号
注:telnet不能使用说明windows没有设置(百度)
- 关闭防火墙或开放端口
方式一:关闭防火墙
CentOS6 :service iptables stop
CentOS7:
systemctl start firewalld.service
systemctl status firewalld.service
systemctl stop firewalld.service
#设置开机启用防火墙
systemctl enable firewalld.service
#设置开机禁用防火墙
systemctl disable firewalld.service
方式二:开放端口
查看开放的端口号: firewall-cmd --list-all
设置开放的端口号:
firewall-cmd --add-service=http --permanent
firewall-cmd --add-port=3306/tcp --permanent
重启防火墙:
firewall-cmd --reload
- 4.Linux下修改配置
在Linux系统MySQL下测试:
use mysql;
select Host,User from user;
修改Host为通配符%
Host列指定了允许用户登录所使用的IP,比如user=root Host=192.168.1.1。这里的意思就是说root用户只能通过192.168.1.1的客户端去访问。 user=root Host=localhost,表示只能通过本机客户端去访问。而 % 是个 通配符 ,如果Host=192.168.1.%,那么就表示只要是IP地址前缀为“192.168.1.”的客户端都可以连接。如果 Host=% ,表示所有IP都有连接权限。
注意:在生产环境下不能为了省事将host设置为%,这样做会存在安全问题,具体的设置可以根据生产 环境的IP进行设置。
update user set host = '%' where user ='root';
Host设置了“%”后便可以允许远程访问。
Host修改完成后记得执行flush privileges使配置立即生效:
flush privileges;
测试:
如果是MySQL5.7
版本,接下来就可以使用SQLyog或者Navicat成功连接至MySQL了。
如果是MySQL8
版本,连接时还会出现如下问题
配置新连接报错:错误号码 2058,分析是 mysql 密码加密方法变了。
解决方法
:Linux下 mysql -u root -p 登录你的 mysql 数据库,然后 执行这条SQL:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'abc123';
然后在重新配置SQLyog的连接,则可连接成功了,OK。
二、字符集的相关操作
在MySQL 8.0版本之前,默认字符集为
atin1
,utf8字符集指向的是utf8mb3
。网站开发人员在数据库设计的时候往往会将编码修改为utf8字符集。如果遗忘修改默认的编码,就会出现乱码的问题。从MySQL 8.0开始,数据库的默认编码将改为utf8mb4
,从而避免上述乱码的问题。
MySQL有4个级别的字符集和比较规则,分别是:
- 服务器级别
- 数据库级别
- 表级别
- 列级别
1.查看默认使用的字符集
show variables like ‘character%’;
#或者
show variables like ‘%char%’;
character_set_server: 服 务 器 级 别 的 字 符 集
character_set_database: 当 前 数 据 库 的 字 符 集
character_set_client: 服 务 器 解 码 请 求 时 使 用 的 字 符 集
character_set_connection:服务器处理请求时会把请求字符串从character_set_client转为character_set_connection
character_set_results:服务器向客户端返回数据时使用的字符集
2.修改字符集
2.1.服务器级别
在MySQL5.7或之前的版本中,在文件最后加上中文字符集配置:(重启)
vim /etc/my.cnf
character_set_server=utf8
2.2 数据库级别
我们在创建和修改数据库的时候可以指定该数据库的字符集和比较规则,具体语法如下:
CREATE DATABASE
数据库名
[[DEFAULT
]CHARACTER
SET
字符集名称]
[[DEFAULT
]COLLATE
比较规则名称];
ALTER
DATABASE
数据库名
[[DEFAULT
]CHARACTER
SET
字符集名称]
[[DEFAULT
]COLLATE
比较规则名称];
示例如下:
alter database dbtest1 character set ‘utf8’;
注:但是原库、原表的设定不会发生变化,参数修改只对新建的数据库生效。
2.3 表级别
我们也可以在创建和修改表的时候指定表的字符集和比较规则,语法如下:
CREATE TABLE
表名 (列的信息)
[[DEFAULT
]CHARACTER
SET
字符集名称]
[[DEFAULT
]COLLATE
比较规则名称];
ALTER
TABLE
表名
[[DEFAULT
]CHARACTER
SET
字符集名称]
[[DEFAULT
]COLLATE
比较规则名称];
示例如下:
修改已创建 数据表 的字符集
alter table t_emp convert to character set ‘utf8’;
注意:但是原有的数据如果是用非’utf8’编码的话,数据本身编码不会发生改变。已有数据需要导 出或删除,然后重新插入。
2.4 列级别
对于存储字符串的列,同一个表中的不同的列也可以有不同的字符集和比较规则。我们在创建和修改列 定义的时候可以指定该列的字符集和比较规则,语法如下:
CREATE
TABLE
表名(
列名 字符串类型 [CHARACTER
SET
字符集名称] [COLLATE
比较规则名称],
其他列…
);
2.5 小结
我们介绍的这4个级别字符集和比较规则的联系如下:
- 如果 创建或修改列 时没有显式的指定字符集和比较规则,则该列 默认用表的 字符集和比较规则
- 如果 创建表时 没有显式的指定字符集和比较规则,则该表 默认用数据库的 字符集和比较规则
- 如果 创建数据库时 没有显式的指定字符集和比较规则,则该数据库 默认用服务器的 字符集和比较规则
3.字符集与比较规则(了解)
3.1 utf8 与 utf8mb4
utf8 字符集表示 一个字符 需要使用 1~4 个字节,但是我们常用的一些字符使用1~3个字节就可以表示了。而字符集表示一个字符所用的最大字节长度,在某些方面会影响系统的存储和性能,所以设计MySQL的设计者偷偷的定义了两个概念:
- utf8mb3 :阉割过的 utf8 字符集,只使用1~3个字节表示字符。
- utf8mb4 :正宗的 utf8 字符集,使用1~4个字节表示字符。
3.2 比较规则
MySQL版本一共支持41种字符集,其中的Default collation 列表示这种字符集中一种默认的比较规则,里面包含着该比较规则主要作用于哪种语言,比如utf8_polish_ci 表示以波兰语的规则比较, utf8_spanish_ci 是以西班牙语的规则比较, utf8_general_ci 是一种通用的比较规则。
后缀表示该比较规则是否区分语言中的重音、大小写。具体如下:
后缀 | 英文释义 | 描述 |
---|---|---|
_ai | accent insensitive | 不区分重音 |
_as | accent sensitive | 区分重音 |
_ci | case insensitive | 不区分大小写 |
_cs | case sensitive | 区分大小写 |
_bin | binary | 以二进制方式比较 |
3.3 常用操作
#查看GBK字符集的比较规则
SHOW
COLLATION
LIKE
‘gbk%’;#查看UTF-8字符集的比较规则
SHOW
COLLATION
LIKE
‘utf8%’;
#查看服务器的字符集和比较规则
SHOW
VARIABLES
LIKE
‘%_server’;#查看数据库的字符集和比较规则
SHOW
VARIABLES
LIKE
‘%_database’;#查看具体数据库的字符集
SHOW
CREATE
DATABASE
dbtest1;#修改具体数据库的字符集
ALTER
DATABASE
dbtest1DEFAULT
CHARACTER
SET
‘utf8’COLLATE
‘utf8_general_ci’;
#查看表的字符集
show
create
table
employees;
#查看表的比较规则
show
table
status
from
atguigudblike
‘employees’;
三、SQL 大小写规范 - (了解)
在 SQL 中,关键字和函数名是不用区分字母大小写的,比如 SELECT、WHERE、ORDER、GROUP BY 等关键字,以及 ABS、MOD、ROUND、MAX 等函数名。
不过在 SQL 中,你还是要确定大小写的规范,因为在 Linux 和 Windows 环境下,你可能会遇到不同的大小写问题。 windows系统默认大小写不敏感 ,但是 linux系统是大小写敏感的 。
通过如下命令查看:
SHOW VARIABLES LIKE
‘%lower_case_table_names%’
lower_case_table_names参数值的设置:
默认为0,大小写敏感 。
设置1,大小写不敏感。创建的表,数据库都是以小写形式存放在磁盘上,对于sql语句都是转 换为小写对表和数据库进行查找。
设置2,创建的表和数据库依据语句上格式存放,凡是查找都是转换为小写进行。
MySQL在Linux下数据库名、表名、列名、别名大小写规则是这样的: 1、数据库名、表名、表的别名、变量名是严格区分大小写的 2、关键字、函数名称在 SQL 中不区分大小写; 3、列名(或字段名)与列的别名(或字段别名)在所有的情况下均是忽略大小写的; MySQL在Windows的环境下全部不区分大小写 |
1. Linux下大小写规则设置
当想设置为大小写不敏感时,要在 my.cnf 这个配置文件 [mysqld] 中加入
lower_case_table_names=1 ,然后重启服务器。
- 但是要在重启数据库实例之前就需要将原来的数据库和表转换为小写,否则将找不到数据库名。
此参数适用于MySQL5.7
。在MySQL 8下禁止
在重新启动 MySQL 服务时将lower_case_table_names 设置成不同于初始化 MySQL 服务时设置的lower_case_table_names 值。如果非要将MySQL8设置为大小写不敏感,具体步骤为:
1、停止MySQL服务
2、删除数据目录,即删除 /var/lib/mysql 目录
3、在MySQL配置文件( /etc/my.cnf )中添加 lower_case_table_names=1
4、启动MySQL服务
四、mysql的数据目录
1.MySQL8的主要目录结构
1.1 数据库文件的存放路径
MySQL数据库文件的存放路径:/var/lib/mysql/
mysql> show variables like ‘datadir’;
±--------------±----------------+
| Variable_name | Value |
±--------------±----------------+
| datadir | /var/lib/mysql/ |
±--------------±----------------+
1 row in set (0.04 sec)
从结果中可以看出,在我的计算机上MySQL的数据目录就是 /var/lib/mysql/ 。
1.2 相关命令目录
相关命令目录:/usr/bin(mysqladmin、mysqlbinlog、mysqldump等命令)和/usr/sbin。
1.3 配置文件目录
配置文件目录:/usr/share/mysql-8.0(命令及配置文件),/etc/mysql(如my.cnf)
2.数据库和文件系统的关系
2.1 查看默认数据库
查看一下在我的计算机上当前有哪些数据库:
mysql> SHOW DATABASES;
可以看到有4个数据库是属于MySQL自带的系统数据库。
- mysql
MySQL 系统自带的核心数据库,它存储了MySQL的用户账户和权限信息,一些存储过程、事件的定义信息,一些运行过程中产生的日志信息,一些帮助信息以及时区信息等。 - information_schema
MySQL 系统自带的数据库,这个数据库保存着MySQL服务器 维护的所有其他数据库的信息 ,比如有哪些表、哪些视图、哪些触发器、哪些列、哪些索引。这些信息并不是真实的用户数据,而是一些 描述性信息,有时候也称之为 元数据 。在系统数据库 information_schema 中提供了一些以innodb_sys 开头的表,用于表示内部系统表。 - performance_schema
MySQL 系统自带的数据库,这个数据库里主要保存MySQL服务器运行过程中的一些状态信息,可以用来 监控 MySQL 服务的各类性能指标 。包括统计最近执行了哪些语句,在执行过程的每个阶段都花费了多长时间,内存的使用情况等信息。 - sys
MySQL 系统自带的数据库,这个数据库主要是通过 视图 的形式把 information_schema 和
performance_schema 结合起来,帮助系统管理员和开发人员监控 MySQL 的技术性能。
3.表在文件系统中的表示
举例: 数据库a , 表b 。
1、如果表b采用 InnoDB ,data\a中会产生1个或者2个文件:
- b.frm : 描述表结构文件,字段长度等
- 如果采用 系统表空间 模式的,数据信息和索引信息都存储在 ibdata1 中
- 如果采用 独立表空间 存储模式,data\a中还会产生 b.ibd 文件(存储数据信息和索引信息) 此外:
① MySQL5.7 中会在data/a的目录下生成 db.opt 文件用于保存数据库的相关配置。比如:字符集、比较规则。而MySQL8.0不再提供db.opt文件。
② MySQL8.0中不再单独提供b.frm,而是合并在b.ibd文件中。
2、如果表b采用 MyISAM ,data\a中会产生3个文件:
- MySQL5.7 中: b.frm :描述表结构文件,字段长度等。
- MySQL8.0 中 b.xxxx.sdi:描述表结构文件,字段长度等
- b.MYD(MYData):数据信息文件,存储数据信息(如果采用独立表存储模式)
- b.MYI(MYIndex):存放索引信息文件
五、用户与权限管理
1.用户管理
1.1 登录MySQL服务器
启动MySQL服务后,可以通过mysql命令来登录MySQL服务器,命令如下:
mysql –h hostname|hostIP –P port –u username –p DatabaseName –e “SQL语句”
举例:
mysql -uroot -p -hlocalhost -P3306 mysql -e “select host,user from user”
下面详细介绍命令中的参数:
- -h参数 :后面接主机名或者主机IP,hostname为主机,hostIP为主机IP。
- -P参数 : 后面接MySQL服务的端口,通过该参数连接到指定的端口。MySQL服务的默认端口是3306, 不使用该参数时自动连接到3306端口,port为连接的端口号。
- -u参数 :后面接用户名,username为用户名。
- -p参数 : 会提示输入密码
- DatabaseName参数 :指明登录到哪一个数据库中。如果没有该参数,就会直接登录到MySQL数据库中,然后可以使用USE命令来选择数据库。
- -e参数 :后面可以直接加SQL语句。登录MySQL服务器以后即可执行这个SQL语句,然后退出MySQL 服务器。
1.2 创建用户
CREATE USER语句的基本语法形式如下:
CREATE
USER
用户名 [IDENTIFIED BY ‘密码’][,用户名 [IDENTIFIED
BY
‘密码’]];
举例:
CREATE USER zhang3 IDENTIFIED BY ‘123123’; # 默认host是 %
CREATE USER 'zhang3 '@‘localhost’ IDENTIFIED BY ‘123123’;
- 用户名参数表示新建用户的账户,由用户(user)和 主机名(Host) 构成;
- “[ ]”表示可选,也就是说,可以指定用户登录时需要密码验证,也可以不指定密码验证,这样用户可以直接登录。不过,不指定密码的方式不安全,不推荐使用。如果指定密码值,这里需要使用IDENTIFIED BY指定明文密码值。
- CREATE USER语句可以同时创建多个用户。
1.3 修改用户
修改用户名:
UPDATE
mysql.userSET
USER
=‘li4’WHERE
USER
=‘wang5’;
FLUSH
PRIVILEGES
;
1.4 删除用户
方式1:使用DROP方式删除(推荐)
使用DROP USER语句来删除用户时,必须用于DROP USER权限。DROP USER语句的基本语法形式如下:
DROP
USER
user[,user]…;
举例:
DROP USER li4 ; # 默认删除host为%的用户
DROP USER ‘kangshifu’@‘localhost’;
方式2:使用DELETE方式删除
DELETE
FROM
mysql.userWHERE
Host
=’hostname’AND
User
=’username’;
FLUSH
PRIVILEGES
;
举例:DELETE FROM mysql.user WHERE Host=‘localhost’ AND User=‘Emily’;
FLUSH PRIVILEGES;
注意:不推荐通过 DELETE FROM USER u WHERE USER='li4' 进行删除,系统会有残留信息保留。而drop user命令会删除用户以及对应的权限,执行命令后你会发现mysql.user表和mysql.db表的相应记录都消失了。 |
1.5 设置当前用户密码
旧的写法如下 :
#修改
当前
用户的密码:(MySQL5.7测试有效)
SET
PASSWORD
=PASSWORD
(‘123456’);
这里介绍 推荐的写法 :
- 使用
ALTER USER
命令来修改当前用户密码 基本语法如下:
ALTER
USER
USER()
IDENTIFIED
BY
new_password’;
- 使用
SET
语句来修改当前用户密码 基本语法如下:
SET
PASSWORD
=‘new_password’;
该语句会自动将密码加密后再赋给当前用户。
1.6 修改其它用户密码
- **使用
ALTER
语句来修改普通用户的密码 ** 基本语法如下:
ALTER
USER
user
[IDENTIFIED
BY
‘新密码’] [,user[IDENTIFIED BY ‘新密码’]]…;
举例:
ALTER USER ‘lisi’ IDENTIFIED BY ‘1234’
- 使用
SET
命令来修改普通用户的密码 基本语法如下:
SET
PASSWORD
FOR
‘username’@
‘hostname’=‘new_password’;
- 使用
UPDATE
语句修改普通用户的密码(不推荐
)
UPDATE MySQL.user SET authentication_string=PASSWORD(“123456”) WHERE User = “username” AND Host = “hostname”;
1.7 MySQL8密码管理(了解)
1.密码过期策略
- 在MySQL中,数据库管理员可以 手动设置 账号密码过期,也可以建立一个 自动 密码过期策略。
- 过期策略可以是 全局的 ,也可以为 每个账号 设置单独的过期策略。
ALTER
USER
user
PASSWORD
EXPIRE;
练习:
ALTER USER ‘kangshifu’@‘localhost’ PASSWORD EXPIRE;
方式①:使用SQL语句更改该变量的值并持久化
·SET· PERSIST default_password_lifetime = 180; # 建立全局策略,设置密码每隔180天过期
方式②:配置文件my.cnf中进行维护
[mysqld]
default_password_lifetime=180 #建立全局策略,设置密码每隔180天过期
手动设置指定时间过期方式2:单独设置
每个账号既可延用全局密码过期策略,也可单独设置策略。在 CREATE USER 和 ALTER USER 语句上加入 PASSWORD EXPIRE 选项可实现单独设置策略。下面是一些语句示例。
#设置kangshifu账号密码每90天过期:
CREATE
USER
‘kangshifu’@‘localhost’PASSWORD
EXPIREINTERVAL
90 DAY;
ALTER
USER
‘kangshifu’@‘localhost’PASSWORD
EXPIREINTERVAL
90 DAY;
#设置密码永不过期:
CREATE
USER
‘kangshifu’@‘localhost’PASSWORD
EXPIRE NEVER;
ALTER
USER
‘kangshifu’@‘localhost’PASSWORD
EXPIRE >NEVER;
#延用全局密码过期策略:
CREATE
USER
‘kangshifu’@‘localhost’PASSWORD
EXPIREDEFAULT
;
ALTER
USER
‘kangshifu’@‘localhost’PASSWORD
EXPIRE >DEFAULT
;
2.密码重用策略
手动设置密码重用方式1:全局
方式①:使用SQL
SET
PERSIST password_history = 6; #设置不能选> 择最近使用过的6个密码
SET
PERSIST password_reuse_interval = 365; #设置不能选择最近一年内的密码
方式②:my.cnf配置文件
[mysqld]
password_history=6 #设置不能选择最近使用过的6个密码
password_reuse_interval=365 #设置不能选择最近一年内的密码
手动设置密码重用方式2:单独设置
#不能使用最近5个密码:
CREATE
USER
‘kangshifu’@‘localhost’PASSWORD
HISTORY 5;
ALTER
USER
‘kangshifu’@‘localhost’PASSWORD
HISTORY 5;
#不能使用最近365天内的密码:
CREATE
USER
‘kangshifu’@‘localhost’PASSWORD
REUSEINTERVAL
365 DAY;
ALTER
USER
‘kangshifu’@‘localhost’PASSWORD
REUSEINTERVAL
365 DAY;
#既不能使用最近5个密码,也不能使用365天内的密码
CREATE
USER
‘kangshifu’@‘localhost’
PASSWORD
HISTORY 5
PASSWORD
REUSEINTERVAL
365 DAY;
ALTER
USER
‘kangshifu’@‘localhost’
PASSWORD
HISTORY 5
PASSWORD
REUSEINTERVAL
365 DAY;
2.权限管理
2.1权限列表
MySQL到底都有哪些权限呢?
mysql>
show privileges
;
- CREATE 和 DROP 权限 ,可以创建新的数据库和表,或删除(移掉)已有的数据库和表。如果将MySQL数据库中的DROP权限授予某用户,用户就可以删除MySQL访问权限保存的数据库。
- SELECT、INSERT、UPDATE和DELETE 权限 允许在一个数据库现有的表上实施操作。
- SELECT权限 只有在它们真正从一个表中检索行时才被用到。
- INDEX权限 允许创建或删除索引,INDEX适用于已有的表。如果具有某个表的CREATE权限,就可以在CREATE TABLE语句中包括索引定义。
- ALTER权限 可以使用ALTER TABLE来更改表的结构和重新命名表。
- CREATE ROUTINE权限 用来创建保存的程序(函数和程序),ALTER ROUTINE权限用来更改和删除保存的程序, EXECUTE权限 用来执行保存的程序。
- GRANT权限 允许授权给其他用户,可用于数据库、表和保存的程序。
- FILE权限 使用户可以使用LOAD DATA INFILE和SELECT … INTO OUTFILE语句读或写服务器上的文件,任何被授予FILE权限的用户都能读或写MySQL服务器上的任何文件(说明用户可以读任何数据库目录下的文件,因为服务 器可以访问这些文件)。
2.2授予权限的原则
权限控制主要是出于安全因素,因此需要遵循以下几个 经验原则 :
- 只授予能 满足需要的最小权限 ,防止用户干坏事。比如用户只是需要查询,那就只给select权限就可以了,不要给用户赋予update、insert或者delete权限。
- 创建用户的时候 限制用户的登录主机 ,一般是限制成指定IP或者内网IP段。
- 为每个用户 设置满足密码复杂度的密码 。
- 定期清理不需要的用户 ,回收权限或者删除用户。
2.3授予权限
给用户授权的方式有 2 种,分别是通过把 角色赋予用户给用户授权 和 直接用户授权。用户是数据库的
使用者,我们可以通过给用户授予访问数据库中资源的权限,来控制使用者对数据库的访问,消除安全 隐患。
授权命令:
#该权限如果发现没有该用户,则会直接新建一个用户。
GRANT
权限1,权限2,…权限nON
数据库名称.表名称 TO 用户名@用户地址 [IDENTIFIEDBY
‘密码口令’];
举例:
给li4用户用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。
GRANT SELECT,INSERT,DELETE,UPDATE ON
atguigudb.*TO
li4@localhost ;
授予通过网络方式登录的joe用户 ,对所有库所有表的全部权限,密码设为123。注意这里唯独不包括grant的权限
GRANT ALL PRIVILEGES ON
*.*TO
joe@’%’ IDENTIFIED BY ‘123’;
ALL PRIVILEGES
:表示所有权限,你也可以使用SELECT,UPDATE 等权限。
ON
: 用来指定权限针对那些库和表
*.*
:前面的 *
用来指定数据库名,后面的 *
用来指定表名。这里的表示所有的表。
TO
: 表示将权限赋予某个用户
li4@localhost
:表示li4用户,@后面接限制的主机,可以是IP、IP段、域名以及%,%表示任何地方。注意:这里的百分号不包括本地,以前碰到过给某个用户设置了%允许任何地方登陆,但是在本地登陆不了,这个和版本有关系,遇到这个问题再加一个localhost的用户就可以了。
IDENTIFIED BY
:指定用户的登录密码
- 如果需要赋予包括GRANT的权限,添加参数
WITH GRANT OPTION
这个选项即可,表示该用户可以将自己拥有的权限授权给别人。经常有人在创建操作用户的时候不指定 WITH GRANT OPTION 选项导致后来该用户不能使用grant 命令创建用户或者给其他用户授权 - 可以使用GRANT重复给用户添加权限,
权限叠加
,比如你先给一个用户添加一个select权限,然后又给用户添加一个insert 权限,那么就该给用户就同时拥有了SELECT和INSERT 权限
注意:不推荐通过 我们在开发应用的时候,经常会遇到一种需求,就是要根据用户的不同,对数据进行横向和纵向的 分组。 ● 所谓横向的分组,就是指用户可以接触到的数据的范围,比如可以看到哪些表的数据; ● 所谓纵向的分组,就是指用户对接触到的数据能访问到什么程度,比如能看、能改,甚至是 删除。 |
2.4查看权限
● 查看当前用户权限
SHOW GRANTS
;
#或
SHOW GRANTS FOR CURRENT_USER
;
#或
SHOW GRANTS FOR CURRENT_USER()
;
● 查看某用户的全局权限
SHOW
GRANTS
FOR
‘user’@‘主机地址’ ;
2.5收回权限
收回权限就是取消已经赋予用户的某些权限。收回用户不必要的权限可以在一定程度上保证系统的安全 性。 MySQL中使用 REVOKE 语句 取消用户的某些权限。使用REVOKE收回权限之后,用户账户的记录将从db、host、tables_priv和columns_priv表中删除,但是用户账户记录仍然在user表中保存(删除user表中 的账户记录使用DROP USER语句)。
注意:在将用户账户从user表删除之前,应该收回相应用户的所有权限。
- 收回权限命令
REVOKE
权限1,权限2,…权限n ON 数据库名称.表名称 FROM 用户名@用户地址;
举例:
#收回全库全表的所有权限
REVOKE ALL PRIVILEGES ON
*.*FROM
joe@’%’;
#收回mysql库下的所有表的插删改查权限
REVOKE SELECT,INSERT,UPDATE,DELETE ON
mysql.*FROM
joe@localhost;
注意:须用户重新登录后才能生效
总结 有一些程序员喜欢使用ROOT超级用户来访问数据库,完全把 权限控制 放在 应用层面实现 |
3.权限表
3.1 USER
表
user表是MySQL中最重要的一个权限表, 记录用户账号和权限信息 ,有49个字段。如下图:
这些字段可以分成4类,分别是范围列(或用户列)、权限列、安全列和资源控制列。
1.范围列(或用户列)
- host : 表示连接类型
%
表示所有远程通过 TCP方式的连接
IP 地址
如 (192.168.1.2、127.0.0.1) 通过制定ip地址进行的TCP方式的连接
机器名
通过制定网络中的机器名进行的TCP方式的连接
::1
IPv6的本地ip地址,等同于IPv4的 127.0.0.1
localhost
本地方式通过命令行方式的连接 ,比如mysql -u xxx -p xxx 方式的连接。
- user : 表示用户名,同一用户通过不同方式链接的权限是不一样的。
- password : 密码
● 所有密码串通过 password(明文字符串) 生成的密文字符串。MySQL 8.0 在用户管理方面增加了
角色管理,默认的密码加密方式也做了调整,由之前的 SHA1 改为了 SHA2 ,不可逆 。同时
加上 MySQL 5.7 的禁用用户和用户过期的功能,MySQL 在用户管理方面的功能和安全性都较之
前版本大大的增强了
● mysql 5.7 及之后版本的密码保存到 authentication_string 字段中不再使用password 字
段。
2.权限列
- Grant_priv字段 : 表示是否拥有GRANT权限
- Shutdown_priv字段 : 表示是否拥有停止MySQL服务的权限
- Super_priv字段: 表示是否拥有超级权限
- Execute_priv字段 :表示是否拥有EXECUTE权限。拥有EXECUTE权限,可以执行存储过程和函数。
- Select_priv , Insert_priv等 : 为该用户所拥有的权限。
3.安全列
安全列只有6个字段,其中两个是ssl相关的(ssl_type、ssl_cipher),用于 加密 ;两个是x509相关的(x509_issuer、x509_subject),用于标识用户 ;另外两个Plugin字段用于 验证用户身份 的插件,该字段不能为空。如果该字段为空,服务器就使用内建授权验证机制验证用户身份。
4.资源控制列
资源控制列的字段用来 限制用户使用的资源 ,包含4个字段,分别为:
①max_questions,用户每小时允许执行的查询操作次数; ②max_updates,用户每小时允许执行的更新操作次数;
③max_connections,用户每小时允许执行的连接操作次数; ④max_user_connections,用户允许同时建立的连接次数。
#查看字段
DESC
mysql.user;
#查看用户, 以列的方式显示数据:
SELECT * FROM mysql.user \G;
#查询特定字段:
SELECT host,user,authentication_string,select_priv,insert_priv,drop_priv FROM mysql.user;
3.2 db
表
使用DESCRIBE查看db表的基本结构:
DESCRIBE
mysql.db;
- 用户列
db表用户列有3个字段,分别是Host、User、Db。这3个字段分别表示主机名、用户名和数据库
名。表示从某个主机连接某个用户对某个数据库的操作权限,这3个字段的组合构成了db表的主键。 - 权限列
Create_routine_priv和Alter_routine_priv这两个字段决定用户是否具有创建和修改存储过程的权限。
3.3 tables_priv
表和columns_priv
表
tables_priv表用来对表设置操作权限,columns_priv表用来对表的某一列设置权限 。tables_priv表和columns_priv表的结构分别如图:
desc
mysql.tables_priv;
desc
mysql.columns_priv;
tables_priv表有8个字段,分别是Host、Db、User、Table_name、Grantor、Timestamp、Table_priv和 Column_priv,各个字段说明如下:
- Host 、 Db 、 User 和 Table_name 四个字段分别表示主机名、数据库名、用户名和表名。
- Grantor表示修改该记录的用户。
- Timestamp表示修改该记录的时间。
- Table_priv 表示对象的操作权限。包括Select、Insert、Update、Delete、Create、Drop、Grant、 References、Index和Alter。
- Column_priv字段表示对表中的列的操作权限,包括Select、Insert、Update和References。
3.4 procs_priv
表
procs_priv表可以对 存储过程和存储函数设置操作权限 ,表结构如图:
desc
mysql.procs_priv;
4.访问控制(了解)
1连接核实阶段
当用户试图连接MySQL服务器时,服务器基于用户的身份以及用户是否能提供正确的密码验证身份来确 定接受或者拒绝连接。即客户端用户会在连接请求中提供用户名、主机地址、用户密码,MySQL服务器 接收到用户请求后,会使用user表中的host、user和authentication_string这3个字段匹配客户端提供信 息。
服务器只有在user表记录的Host和User字段匹配客户端主机名和用户名,并且提供正确的密码时才接受 连接。如果连接核实没有通过,服务器就完全拒绝访问;否则,服务器接受连接,然后进入阶段2等待 用户请求。
2请求核实阶段
一旦建立了连接,服务器就进入了访问控制的阶段2,也就是请求核实阶段。对此连接上进来的每个请 求,服务器检查该请求要执行什么操作、是否有足够的权限来执行它,这正是需要授权表中的权限列发 挥作用的地方。这些权限可以来自user、db、table_priv和column_priv表。
确认权限时,MySQL首先 检查user表 ,如果指定的权限没有在user表中被授予,那么MySQL就会继续检查db表 ,db表是下一安全层级,其中的权限限定于数据库层级,在该层级的SELECT权限允许用户查看指定数据库的所有表中的数据;如果在该层级没有找到限定的权限,则MySQL继续 检查tables_priv表 以及 columns_priv表 ,如果所有权限表都检查完毕,但还是没有找到允许的权限操作,MySQL将 返回错误信息 ,用户请求的操作不能执行,操作失败。
提示: MySQL通过向下层级的顺序(从user表到columns_priv表)检查权限表,但并不是所有的权限都要执行该过程。例如,一个用户登录到MySQL服务器之后只执行对MySQL的管理操作,此时只 涉及管理权限,因此MySQL只检查user表。另外,如果请求的权限操作不被允许,MySQL也不会继 续检查下一层级的表。 |
5.角色管理
5.1 角色的理解
引入角色的目的是 方便管理拥有相同权限的用户 。恰当的权限设定,可以确保数据的安全性,这是至关重要的。
5.2 创建角色
创建角色使用 CREATE ROLE
语句,语法如下:
CREATE
ROLE ‘role_name’[@‘host_name’] [,‘role_name’[@‘host_name’]]…
角色名称的命名规则和用户名类似。如果 host_name省略,默认为%,role_name不可省略 不可为空。
举例
练习:我们现在需要创建一个经理的角色,就可以用下面的代码:
CREATE
ROLE ‘manager’@‘localhost’;
5.3 给角色赋予权限
创建角色之后,默认这个角色是没有任何权限的,我们需要给角色授权。给角色授权的语法结构是:
GRANT privileges ON table_name TO
‘role_name’[@‘host_name’];
上述语句中privileges代表权限的名称,多个权限以逗号隔开。可使用SHOW语句查询权限名称,图11-43 列出了部分权限列表。
SHOW PRIVILEGES
\G;
举例:
练习1:我们现在想给经理角色授予商品信息表、盘点表和应付账款表的只读权限,就可以用下面的代码 来实现:
GRANT SELECT ON
demo.settlementTO
‘manager’;
GRANT SELECT ON
demo.goodsmasterTO
‘manager’;
GRANT SELECT ON
demo.invcountTO
‘manager’;
5.4 查看角色的权限
赋予角色权限之后,我们可以通过 SHOW GRANTS 语句,来查看权限是否创建成功了:
SHOW GRANTS FOR
‘manager’;
±------------------------------------------------------+
| Grants for manager@% |
±------------------------------------------------------+
| GRANT USAGE ON . TOmanager
@%
|
| GRANT SELECT ONdemo
.goodsmaster
TOmanager
@%
|
| GRANT SELECT ONdemo
.invcount
TOmanager
@%
|
| GRANT SELECT ONdemo
.settlement
TOmanager
@%
|
±------------------------------------------------------+
只要你创建了一个角色,系统就会自动给你一个“ USAGE ” 权限,意思是 连接登录数据库的权限 。代码的最后三行代表了我们给角色“manager”赋予的权限,也就是对商品信息表、盘点表和应付账款表的只读权 限。
结果显示,库管角色拥有商品信息表的只读权限和盘点表的增删改查权限。
5.5 回收角色的权限
角色授权后,可以对角色的权限进行维护,对权限进行添加或撤销。添加权限使用GRANT语句,与角色 授权相同。撤销角色或角色权限使用REVOKE语句。
修改了角色的权限,会影响拥有该角色的账户的权限。
撤销角色权限的SQL语法如下:
REVOKE privileges ON
tablenameFROM
‘rolename’;
举例
练习1:撤销school_write角色的权限。 (
1)使用如下语句撤销school_write角色的权限。
REVOKE INSERT, UPDATE, DELETE ON
school.*FROM
‘school_write’;
(2)撤销后使用SHOW语句查看school_write对应的权限,语句如下。
SHOW GRANTS FOR
‘school_write’;
5.6 删除角色
当我们需要对业务重新整合的时候,可能就需要对之前创建的角色进行清理,删除一些不会再使用的角 色。删除角色的操作很简单,你只要掌握语法结构就行了。
DROP
ROLE role [,role2]…
注意, 如果你删除了角色,那么用户也就失去了通过这个角色所获得的所有权限。
举例
练习:执行如下SQL删除角色school_read。
DROP
ROLE ‘school_read’;
5.7 给用户赋予角色
角色创建并授权后,要赋给用户并处于 激活状态 才能发挥作用。给用户添加角色可使用GRANT语句,语法形式如下:
GRANT
role [,role2,…]TO user
[,user2,…];
在上述语句中,role代表角色,user代表用户。可将多个角色同时赋予多个用户,用逗号隔开即可。
举例
练习:给kangshifu用户添加角色school_read权限。
(1)使用GRANT语句给kangshifu添加school_read权限,SQL语句如下。
GRANT
‘school_read’TO
‘kangshifu’@‘localhost’;
(2)添加完成后使用SHOW语句查看是否添加成功,SQL语句如下。
SHOW GRANTS FOR
‘kangshifu’@‘localhost’
(3)使用kangshifu用户登录,然后查询当前角色,如果角色未激活,结果将显示NONE。SQL语句如下。
SELECT
CURRENT_ROLE();
5.8 激活角色
方式1:使用set default role 命令激活角色
举例:
SET DEFAULT
ROLEALL TO
‘kangshifu’@‘localhost’;
使用 SET DEFAULT ROLE
为下面4个用户默认激活所有已拥有的角色如下:
SET DEFAULT ROLE ALL TO
‘dev1’@‘localhost’,
‘read_user1’@‘localhost’,
‘read_user2’@‘localhost’,
‘rw_user1’@‘localhost’;
方式2:将activate_all_roles_on_login设置为ON
- 默认情况:
mysql>
show variables like
‘activate_all_roles_on_login’;
±----------------------------±------+
| Variable_name | Value |
±----------------------------±------+
| activate_all_roles_on_login | OFF |
±----------------------------±------+
1 row in set (0.00 sec)
- 设置:
SET GLOBAL
activate_all_roles_on_login=ON;
这条 SQL 语句的意思是,对 所有角色永久激活 。运行这条语句之后,用户才真正拥有了赋予角色的所有权限。
5.9 撤销用户的角色
撤销用户角色的SQL语法如下:
REVOKE
roleFROM user
;
举例
练习:撤销kangshifu用户的school_read角色。
(1)撤销的SQL语句如下:
REVOKE
‘school_read’FROM
‘kangshifu’@‘localhost’;
(2)撤销后,执行如下查询语句,查看kangshifu用户的角色信息
SHOW GRANTS FOR
‘kangshifu’@‘localhost’;
执行发现,用户kangshifu之前的school_read角色已被撤销。
5.10 设置强制角色(mandatory role)
强制角色是给每个创建账户得默认角色,不需要手动设置,强制角色无法被 REVOKE
或者 DROP
方式1:服务启动前设置
[mysqld]
mandatory_roles=‘role1,role2@localhost,r3@%.atguigu.com’
方式2:运行时设置
SET PERSIST mandatory_roles = ‘role1,role2@localhost,r3@%.example.com’; #系统重启后仍然有效
SET GLOBAL mandatory_roles = ‘role1,role2@localhost,r3@%.example.com’; #系统重启后失效
六、逻辑架构
1.逻辑架构剖析
那服务器进程对客户端进程发送的请求做了什么处理,才能产生最后的处理结果呢?这里以查询请求为 例展示:
1.1 第1层:连接层 - Connectors
系统(客户端)访问 MySQL 服务器前,做的第一件事就是 TCP 建立
经过三次握手建立连接成功后, MySQL 服务器对 TCP 传输过来的账号密码做身份认证、权限获取。
- 用户名或密码不对,会收到一个Access denied for user错误,客户端程序结束执行
- 用户名密码认证通过,会从权限表查出账号拥有的权限与连接关联,之后的权限判断逻辑,都将依 赖于此时读到的权限
TCP连接连接收到请求后,必须要分配给一个线程专门与这个客户端的交互。所以还会有个线程池,去走后面的流程。每一个连接从线程池中获取线程,省去了创建和销毁线程的开销。
1.2 第2层:服务层
- SQL Interface: SQL接口
● 接收用户的SQL命令,并且返回用户需要查询的结果。比如SELECT … FROM就是调用SQL Interface
● MySQL支持DML(数据操作语言)、DDL(数据定义语言)、存储过程、视图、触发器、自定义函数等多种SQL语言接口
- Parser: 解析器
● 在解析器中对 SQL 语句进行语法分析、语义分析。将SQL语句分解成数据结构,并将这个结构传递到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。如果在分解构成中遇到错误,那么就说明这个SQL语句是不合理的。
● 在SQL命令传递到解析器的时候会被解析器验证和解析,并为其创建 语法树 ,并根据数据字典丰富查询语法树,会 验证该客户端是否具有执行该查询的权限 。创建好语法树后,MySQL还会对SQl查询进行语法上的优化,进行查询重写。
- Optimizer: 查询优化器
● SQL语句在语法解析之后、查询之前会使用查询优化器确定 SQL 语句的执行路径,生成一个执行计划
● 这个执行计划表明应该 使用哪些索引 进行查询(全表检索还是使用索引检索),表之间的连接顺序如何,最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将 查询结果返回给用户。
● 它使用 “ 选取-投影-连接 ”策略进行查询。例如:
SELECT id,name FROM student WHERE gender = ‘女’;
这个SELECT查询先根据WHERE语句进行 选取 ,而不是将表全部查询出来以后再进行gender过滤。 这个SELECT查询先根据id和name进行属性 投影 ,而不是将属性全部取出以后再进行过滤,将这两个查询条件 连接 起来生成最终查询结果。
- Caches & Buffers: 查询缓存组件
● MySQL内部维持着一些Cache和Buffer,比如Query Cache用来缓存一条SELECT语句的执行结
果,如果能够在其中找到对应的查询结果,那么就不必再进行查询解析、优化和执行的整个过 程了,直接将结果反馈给客户端。
● 这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等 。
● 这个查询缓存可以在 不同客户端之间共享 。
● 从MySQL 5.7.20开始,不推荐使用查询缓存,并在 MySQL 8.0中删除 。
1.3 第3层:引擎层
插件式存储引擎层( Storage Engines), 真正的负责了MySQL中数据的存储和提取,对物理服务器级别维护的底层数据执行操作 ,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。
MySQL 8.0.25默认支持的存储引擎如下:
1.6 存储层 - (不算mysql的)
所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在 文件系统 上,以 文件 的方式存在的,并完成与存储引擎的交互。当然有些存储引擎比如InnoDB,也支持不使用文件系统直接管理裸设备,但现代文件系统的实现使得这样做没有必要了。在文件系统之下,可以使用本地磁盘,可以使用 DAS、NAS、SAN等各种存储系统。
1.4 小结
MySQL架构图本节开篇所示。下面为了熟悉SQL执行流程方便,我们可以简化如下:
简化为三层结构: 1.连接层:客户端和服务器端建立连接,客户端发送 SQL 至服务器端; 2.SQL 层(服务层):对 SQL 语句进行查询处理;与数据库文件的存储方式无关; 3.存储引擎层:与数据库文件打交道,负责数据的存储和读取。 |
2.SQL执行流程
2.1 MySQL 中的 SQL执行流程
MySQL的查询流程:
- 查询缓存: Server 如果在查询缓存中发现了这条 SQL 语句,就会直接将结果返回给客户端;如果没有,就进入到解析器阶段。需要说明的是,因为查询缓存往往效率不高,所以在 MySQL8.0 之后就抛弃了这个功能。
大多数情况查询缓存就是个鸡肋,为什么呢?
SELECT employee_id,last_name FROM employees WHERE employee_id = 101
查询缓存是提前把查询结果缓存起来,这样下次不需要执行就可以直接拿到结果。需要说明的是,在MySQL 中的查询缓存,不是缓存查询计划,而是查询对应的结果。这就意味着查询匹配的 鲁棒性大大降低 ,只有 相同的查询操作才会命中查询缓存 。两个查询请求在任何字符上的不同(例如:空格、注释、大小写),都会导致缓存不会命中。因此 MySQL 的 查询缓存命中率不高 。
同时,如果查询请求中包含某些系统函数、用户自定义变量和函数、一些系统表,如 mysql 、information_schema、 performance_schema 数据库中的表,那这个请求就不会被缓存。以某些系统函数举例,可能同样的函数的两次调用会产生不一样的结果,比如函数 NOW ,每次调用都会产生最新的当前时间,如果在一个查询请求中调用了这个函数,那即使查询请求的文本信息都一样,那不同时间的两次 查询也应该得到不同的结果,如果在第一次查询时就缓存了,那第二次查询的时候直接使用第一次查询 的结果就是错误的!
此外,既然是缓存,那就有它 缓存失效的时候 。MySQL的缓存系统会监测涉及到的每张表,只要该表的
结构或者数据被修改,如对该表使用了 INSERT 、UPDATE 、DELETE、TRUNCATE、ALTER、TABLE 、 DROP 、TABLE 或 DROP DATABASE 语句,那使用该表的所有高速缓存查询都将变为无效并从高速缓存中删除!对于更新压力大的数据库 来说,查询缓存的命中率会非常低。
2.解析器 :在解析器中对 SQL 语句进行语法分析、语义分析。
分析器先做“ 词法分析 ”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。 MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语
句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。
接着,要做“ 语法分析 ”。根据词法分析的结果,语法分析器(比如:Bison)会根据语法规则,判断你输入的这个 SQL 语句是否 满足 MySQL 语法 。
select department_id,job_id,avg(salary) from employees group by department_id;
如果SQL语句正确,则会生成一个这样的语法树:
3.优化器 :在优化器中会确定 SQL 语句的执行路径,比如是根据 全表检索 ,还是根据 索引检索 等。举例:如下语句是执行两个表的 join:
select
*from
test1join
test2using
(ID)
where
test1.name=‘zhangwei’and
test2.name=‘mysql高级课程’;
方案1:可以先从表 test1 里面取出 name='zhangwei'的记录的 ID 值,再根据 ID 值关联到表 test2,再判断 test2 里面 name的值是否等于 'mysql高级课程'。 方案2:可以先从表 test2 里面取出 name='mysql高级课程' 的记录的 ID 值,再根据 ID 值关联到 test1, 再判断 test1 里面 name的值是否等于 zhangwei。 这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。优化 器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。 |
4.执行器:
截止到现在,还没有真正去读写真实的表,仅仅只是产出了一个执行计划。于是就进入了 执行器阶段 。
在执行之前需要判断该用户是否 具备权限 。如果没有,就会返回权限错误。如果具备权限,就执行 SQL
查询并返回结果。在 MySQL8.0 以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存。
select * from test where id=1;
调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是1,如果不是则跳过,如果是则将这行存在结果集中; 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。 |
2.2 MySQL8中SQL执行原理
1.确认profiling 是否开启
mysql>
select
@@profiling;
#profiling=0 代表关闭,我们需要把 profiling 打开,即设置为 1
mysql>show variables like
‘profiling’;
mysql> set profiling=1;
2.多次执行相同SQL查询
然后我们执行一个 SQL 查询(你可以执行任何一个 SQL 查询):
mysql>
select
*from
employees;
3.查看profiles
查看当前会话所产生的所有 profiles:
mysql>
show profiles;
# 显示最近的几次查询
4.查看profile
mysql>
show profile
;
当然你也可以查询指定的 Query ID,比如:
mysql>show profile for query
7;
查询 SQL 的执行时间结果和上面是一样的。此外,还可以查询更丰富的内容:
mysql>
show profile
cpu,block iofor query
6;
2.3 MySQL5.7中SQL执行原理
上述操作在MySQL5.7中测试,发现前后两次相同的sql语句,执行的查询过程仍然是相同的。不是会使用缓存吗?这里我们需要 显式开启查询缓存模式 。在MySQL5.7中如下设置:
1.配置文件中开启查询缓存
在 /etc/my.cnf 中新增一行:
query_cache_type=1
2.重启mysql服务
systemctl restart mysqld
3.开启查询执行计划
由于重启过服务,需要重新执行如下指令,开启profiling。
mysql> set profiling=1;
4.执行语句两次:
mysql> select * from locations;
mysql> select * from locations;
5.查看profiles
6.查看profile
显示执行计划,查看两条SQL程序的执行步骤:
mysql>
show profile for query
1[2];
结论不言而喻。执行编号2时,比执行编号1时少了很多信息,从截图中可以看出查询语句直接从缓存中 获取数据。
2.4 SQL语法顺序
随着Mysql版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。
需求:查询每个部门年龄高于20岁的人数且高于20岁人数不能少于2人,显示人数最多的第一名部门信息下面是经常出现的查询顺序:
2.5 Oracle中的SQL执行流程(了解)
Oracle 中采用了 共享池
来判断 SQL 语句是否存在缓存和执行计划,通过这一步骤我们可以知道应该采用硬解析还是软解析。
我们先来看下 SQL 在 Oracle 中的执行过程:
从上面这张图中可以看出,SQL 语句在 Oracle 中经历了以下的几个步骤。
1.语法检查:检查 SQL 拼写是否正确,如果不正确,Oracle 会报语法错误。
2.语义检查:检查 SQL 中的访问对象是否存在。比如我们在写 SELECT 语句的时候,列名写错了,系统就会提示错误。语法检查和语义检查的作用是保证 SQL 语句没有错误。
3.权限检查:看用户是否具备访问该数据的权限。
4.共享池检查:共享池(Shared Pool)是一块内存池,最主要的作用是缓存 SQL 语句和该语句的执行计划。Oracle 通过检查共享池是否存在 SQL 语句的执行计划,来判断进行软解析,还是硬解析。那软解析和硬解析又该怎么理解呢?
在共享池中,Oracle 首先对 SQL 语句进行 Hash 运算 ,然后根据 Hash 值在库缓存(Library Cache)中查找,如果 存在 SQL 语句的执行计划 ,就直接拿来执行,直接进入“执行器”的环节,这就是 软解析 。
如果没有找到 SQL 语句和执行计划,Oracle 就需要创建解析树进行解析,生成执行计划,进入“优化器” 这个步骤,这就是 硬解析
5.优化器:优化器中就是要进行硬解析,也就是决定怎么做,比如创建解析树,生成执行计划。
6.执行器:当有了解析树和执行计划之后,就知道了 SQL 该怎么被执行,这样就可以在执行器中执行语句了。
共享池是 Oracle 中的术语,包括了库缓存,数据字典缓冲区等。我们上面已经讲到了库缓存区,它主要缓存 SQL 语句和执行计划。而 数据字典缓冲区 存储的是 Oracle 中的对象定义,比如表、视图、索引等对象。当对 SQL 语句进行解析的时候,如果需要相关的数据,会从数据字典缓冲区中提取。
库缓存 这一个步骤,决定了 SQL 语句是否需要进行硬解析。为了提升 SQL 的执行效率,我们应该尽量避免硬解析,因为在 SQL 的执行过程中,创建解析树,生成执行计划是很消耗资源的。
你可能会问,如何避免硬解析,尽量使用软解析呢?在 Oracle 中, 绑定变量 是它的一大特色。绑定变量就是在 SQL 语句中使用变量,通过不同的变量取值来改变 SQL 的执行结果。这样做的好处是能 提升软解析的可能性 ,不足之处在于可能会导致生成的执行计划不够优化,因此是否需要绑定变量还需要视情况而定。举个例子,我们可以使用下面的查询语句:
SQL> select * from player where player_id = 10001;
你也可以使用绑定变量,如:
SQL> select * from player where player_id = :player_id;
这两个查询语句的效率在 Oracle 中是完全不同的。如果你在查询 player_id = 10001 之后,还会查询10002、10003 之类的数据,那么每一次查询都会创建一个新的查询解析。而第二种方式使用了绑定变量,那么在第一次查询之后,在共享池中就会存在这类查询的执行计划,也就是软解析。
因此,我们可以通过使用绑定变量来减少硬解析,减少 Oracle 的解析工作量。但是这种方式也有缺点, 使用动态 SQL 的方式,因为参数不同,会导致 SQL 的执行效率不同,同时 SQL 优化也会比较困难。
Oracle的架构图:
小结:
Oracle 和 MySQL 在进行 SQL 的查询上面有软件实现层面的差异。Oracle 提出了共享池的概念,通过共享池来判断是进行软解析,还是硬解析。
3.数据库缓冲池(buffer pool)
InnoDB 存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页 面(包括读页面、写页面、创建新页面等操作)。而磁盘 I/O 需要消耗的时间很多,而在内存中进行操作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS 会申请 占用内存来作为数据缓冲池 ,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访问。
这样做的好处是可以让磁盘活动最小化,从而 减少与磁盘直接进行 I/O 的时间 。要知道,这种策略对提升 SQL 语句的查询性能来说至关重要。如果索引的数据在缓冲池里,那么访问的成本就会降低很多。
3.1 缓冲池 vs 查询缓存
缓冲池和查询缓存是一个东西吗?不是。
1.缓冲池(Buffer Pool)
首先我们需要了解在 InnoDB 存储引擎中,缓冲池都包括了哪些。
在 InnoDB 存储引擎中有一部分数据会放到内存中,缓冲池则占了这部分内存的大部分,它用来存储各种数据的缓存,如下图所示:
从图中,你能看到 InnoDB 缓冲池包括了数据页、索引页、插入缓冲、锁信息、自适应 Hash 和数据字典信息等。
缓存池的重要性:
缓存原则:
“ 位置 * 频次 ” 这个原则,可以帮我们对 I/O 访问效率进行优化。
首先,位置决定效率,提供缓冲池就是为了在内存中可以直接访问数据。
其次,频次决定优先级顺序。因为缓冲池的大小是有限的,比如磁盘有 200G,但是内存只有 16G,缓冲池大小只有 1G,就无法将所有数据都加载到缓冲池里,这时就涉及到优先级顺序,会优先对使用频次高的热数据进行加载
缓冲池的预读特性:
了解了缓冲池的作用之后,我们还需了解缓冲池的另一个特性:预读
缓冲池的作用就是提升I/O效率,而我们进行读取数据的时候存在一个“局部性原理”,也就是说我们使用了一些数据,大概率还会使用它周围的一些数据 ,因此采用 “预读”的机制提前加载,可以减少未来可能得磁盘I/O操作。
2.查询缓存
那么什么是查询缓存呢?
查询缓存是提前把 查询结果缓存 起来,这样下次不需要执行就可以直接拿到结果。需要说明的是,在MySQL 中的查询缓存,不是缓存查询计划,而是查询对应的结果。因为命中条件苛刻,而且只要数据表发生变化,查询缓存就会失效,因此命中率低。
3.2 缓冲池如何读取数据
缓冲池管理器会尽量将经常使用的数据保存起来,在数据库进行页面读操作的时候,首先会判断该页面 是否在缓冲池中,如果存在就直接读取,如果不存在,就会通过内存或磁盘将页面存放到缓冲池中再进 行读取。
缓存在数据库中的结构和作用如下图所示:
如果我们执行 SQL 语句的时候更新了缓存池中的数据,那么这些数据会马上同步到磁盘上吗?
3.3 查看/设置缓冲池的大小
如果你使用的是 InnoDB 存储引擎,可以通过查看 innodb_buffer_pool_size 变量来查看缓冲池的大
小。命令如下:
show variables like
‘innodb_buffer_pool_size’;
你能看到此时 InnoDB 的缓冲池大小只有 134217728/1024/1024=128MB。我们可以修改缓冲池大小,比如改为256MB,方法如下:
set global innodb_buffer_pool_size = 268435456;
或者:
vim /etc/my.cnf
[server]
innodb_buffer_pool_size = 268435456
3.4 多个Buffer Pool实例
[server]
innodb_buffer_pool_instances = 26
这样就表明我们要创建2个 Buffer Pool 实例。
我们看下如何查看缓冲池的个数,使用命令:
show variables like
‘innodb_buffer_pool_instances’;
那每个 Buffer Pool 实例实际占多少内存空间呢?其实使用这个公式算出来的:
innodb_buffer_pool_size/innodb_buffer_pool_instances
不过也不是说Buffer Pool 实例创建的越多越好,分别 管理各个 Buffer Pool 也是需要性能呢开销的,InnoDB规定: 当innodb_buffer_pool_size 的值小于1G的时候设置多个实例是无效的,InnoDB会默认把innodb_buffer_pool_instances的值修改为1.而我们鼓励在Buffer Pool 大于或等于1G的时候设置多个Buffer Pool 实例. |
七、存储引擎
1.查看存储引擎
查看mysql提供什么存储引擎:
show engines;
show engines \G;
显式如下:
*************************** 1. row *************************** Engine: InnoDB
Support: DEFAULT
Comment: Supports transactions, row-level locking, and foreign keys Transactions: YES
XA: YES
Savepoints: YES
*************************** 2. row *************************** Engine: MRG_MYISAM
Support: YES
Comment: Collection of identical MyISAM tables Transactions: NO
XA: NO
Savepoints: NO
*************************** 3. row *************************** Engine: MEMORY
Support: YES
Comment: Hash based, stored in memory, useful for temporary tables Transactions: NO
XA: NO
Savepoints: NO
*************************** 4. row *************************** Engine: BLACKHOLE
Support: YES
Comment: /dev/null storage engine (anything you write to it disappears) Transactions: NO
XA: NO
Savepoints: NO
*************************** 5. row *************************** Engine: MyISAM
Support: YES
Comment: MyISAM storage engine Transactions: NO
XA: NO
Savepoints: NO
*************************** 6. row *************************** Engine: CSV
Support: YES
Comment: CSV storage engine Transactions: NO
XA: NO
Savepoints: NO
*************************** 7. row *************************** Engine: ARCHIVE
Support: YES
Comment: Archive storage engine Transactions: NO
XA: NO
Savepoints: NO
*************************** 8. row *************************** Engine: PERFORMANCE_SCHEMA
Support: YES
Comment: Performance Schema Transactions: NO
XA: NO
Savepoints: NO
*************************** 9. row *************************** Engine: FEDERATED
Support: NO
Comment: Federated MySQL storage engine Transactions: NULL
XA: NULL
Savepoints: NULL
2.设置系统默认的存储引擎
- 查看默认的存储引擎:
show variables like
‘%storage_engine%’;
#或
SELECT
@@default_storage_engine;
- 修改默认的存储引擎
如果在创建表的语句中没有显式指定表的存储引擎的话,那就会默认使用 InnoDB 作为表的存储引擎。如果我们想改变表的默认存储引擎的话,可以这样写启动服务器的命令行:
SET DEFAULT_STORAGE_ENGINE=MyISAM;
#或者
修改 my.cnf 文件:
default-storage-engine=MyISAM
#重启服务
systemctl restart mysqld.service
3.设置表的存储引擎
3.1 创建表时指定存储引擎
CREATE TABLE
表名(
建表语句;
)ENGINE
= 存储引擎名称;
3.2 修改表的存储引擎
如果表已经建好了,我们也可以使用下边这个语句来修改表的存储引擎:
ALTER TABLE
表名ENGINE
= 存储引擎名称;
举例:
比如我们修改一下 engine_demo_table 表的存储引擎:
ALTER TABLE
engine_demo_tableENGINE
= InnoDB;
这时我们再查看一下 engine_demo_table 的表结构:
mysql>
SHOW CREATE TABLE
engine_demo_table\G
*************************** 1. row *************************** Table: engine_demo_table
Create Table: CREATE TABLEengine_demo_table
(
i
int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)
4.引擎介绍
4.1 InnoDB 引擎 - 重点
具备外键支持功能的事务存储引擎
- MySQL从3.23.34a开始就包含InnoDB存储引擎。 大于等于5.5之后,默认采用InnoDB引擎
- InnoDB是MySQL的 默认事务型引擎 ,它被设计用来处理大量的短期(short-lived)事务。可以确保事务的完整提交(Commit)和回滚(Rollback)。
- 除了增加和查询外,还需要更新、删除操作,那么,应优先选择InnoDB存储引擎
- 除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。
- 数据文件结构 (见第四章小结)
- InnoDB是 为处理巨大数据量的最大性能设计
- 对比MyISAM的存储引擎, InnoDB写的处理效率差一些 ,并且会占用更多的磁盘空间以保存数据和索引。
- MyISAM只缓存索引,不缓存真实数据;InnoDB不仅缓存索引还要缓存真实数据,对内存要求较高 ,而且内存大小对性能有决定性的影响。
4.2 MyISAM 引擎 - 重点
主要的非事务处理存储引擎
- MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM 不支持事务、行级锁、外键 ,有一个毫无疑问的缺陷就是 崩溃后无法安全恢复 。
- 5.5之前默认的存储引擎
- 优势是访问的 速度快 ,对事务完整性没有要求或者以SELECT、INSERT为主的应用
- 针对数据统计有额外的常数存储。故而 count(*) 的查询效率很高
- 数据文件结构 (见第四章小结)
- 应用场景:只读应用或者以读为主的业务
4.3 Archive 引擎
用于数据存档
特征 | 支持 |
---|---|
B树索引 | × |
备份/时间点恢复 (在服务器中实现,而不是在存储引擎中) | √ |
集群数据库支持 | × |
聚集索引 | × |
压缩数据 | √ |
数据缓存 | × |
加密数据(加密功能在服务器中实现) | √ |
外键支持 | × |
全文检索索引 | × |
地理空间数据类型支持 | √ |
地理空间索引支持 | × |
哈希索引 | × |
索引缓存 | × |
锁粒度 | 行锁 |
MVCC | × |
存储限制 | 没有任何限制 |
交易 | × |
更新数据字典的统计信 | √ |
4.4 Blackhole 引擎
丢弃写操作,读操作会返回空内容
4.5 CSV 引擎
存储数据时,以逗号分隔各个数据项
使用案例如下:
mysql>
CREATE TABLE
test (i INTNOT
NULL, c CHAR(10)NOT
NULL)ENGINE
= CSV;
创建CSV表还会创建相应的 元文件 ,用于 存储表的状态 和 表中存在的行数 。此文件的名称与表的名称相
同,后缀为 。如图所示
如果检查 test.CSV 通过执行上述语句创建的数据库目录中的文件,其内容使用Notepad++打开如下:
“1”,“record one”
“2”,“record two”
4.6 Memory 引擎
置于内存的表
Memory采用的逻辑介质是 内存 , 响应速度很快 ,但是当mysqld守护进程崩溃的时候数据会丢失 。另外,要求存储的数据是数据长度不变的格式,比如,Blob和Text类型的数据不可用(长度不固定的)。
- Memory同时支持哈希(HASH)索引 和 B+树索引
- Memory表至少比MyISAM表要 快一个数量级 。
- MEMORY 表的大小是受到限制 的。表的大小主要取决于两个参数,分别是 max_rows 和 max_heap_table_size 。其中,max_rows可以在创建表时指定;max_heap_table_size的大小默认为16MB,可以按需要进行扩大。
- 数据文件与索引文件分开存储。
- 缺点:其数据易丢失,生命周期短。基于这个缺陷,选择MEMORY存储引擎时需要特别小心。
使用Memory存储引擎的场景:
1.目标数据比较小 ,而且非常 频繁的进行访问 ,在内存中存放数据,如果太大的数据会造成内存溢出
出 。可以通过参数 max_heap_table_size 控制Memory表的大小,限制Memory表的最大的大小。
2.如果 数据是临时的 ,而且 必须立即可用 得到,那么就可以放在内存中。
3.存储在Memory表中的数据如果突然间 丢失的话也没有太大的关系
4.7 Federated 引擎
访问远程表
Federated引擎是访问其他MySQL服务器的一个 代理 ,尽管该引擎看起来提供了一种很好的
器的灵活性 ,但也经常带来问题,因此 默认是禁用的 。
4.8 Merge引擎
管理多个MyISAM表构成的表集合
4.9 NDB引擎
MySQL集群专用存储引擎
也叫做 NDB Cluster 存储引擎,主要用于 MySQL Cluster 分布式集群 环境,类似于 Oracle 的 RAC 集群。
4.10 引擎对比
特点 | MyISAM | InnoDB | MEMORY | MERGE | NDB |
---|---|---|---|---|---|
存储限制 | 有 | 64TB | 有 | 没有 | 有 |
事务安全 | 支持 | ||||
锁机制 | 表锁,即使操作一条记录也会锁住整个 表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作 | 表锁 | 表锁 | 行锁 |
B树索引 | 支持 | 支持 | 支持 | 支持 | 支持 |
哈希索引 | 支持 | 支持 | |||
全文索引 | 支持 | ||||
集群索引 | 支持 | ||||
数据缓存 | 支持 | 支持 | 支持 | ||
索引缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 | 支持 | 支持 | 支持 |
数据可压缩 | 支持 | ||||
空间使用 | 低 | 高 | N/A | 低 | 低 |
内存使用 | 低 | 高 | 中等 | 低 | 高 |
批量插入的速度 | 高 | 低 | 高 | 高 | 高 |
支持外键 | 支持 |
5.MyISAM和InnoDB
很多人对 InnoDB 和 MyISAM 的取舍存在疑问,到底选择哪个比较好呢?
MySQL5.5之前的默认存储引擎是MyISAM,5.5之后改为了InnoDB。
对比项 | MyISAM | InnoDB |
---|---|---|
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
自带系统表使用 | Y | N |
关注点 | 性能:节省资源、消耗少、简单业务 | 事务:并发写、事务、更大资源 |
默认安装 | Y | Y |
默认使用 | N | Y |
MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的。小结两种引擎中索引的区别:
- 在InnoDB存储引擎中,我们只需要根据主键值对
聚簇索引
进行一次查找就能找到对应的记录,而在MyISAM
中却需要进行一次回表
操作,意味着MyISAM中建立的索引相当于全部都是二级索引
。 - InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是
分离的
,索引文件仅保存数据记录的地址。 - InnoDB的非聚簇索引data域存储相应记录
主键的值
,而MyISAM索引记录的是地址
。换句话说,
InnoDB的所有非聚簇索引都引用主键作为data域。 - MyISAM的回表操作是十分
快速
的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。 - InnoDB要求表
必须有主键 ( MyISAM可以没有 )
。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。
6. 阿里巴巴、淘宝用哪个(了解)
- Percona 为 MySQL 数据库服务器进行了改进,在功能和性能上较 MySQL 有很显著的提升。
- 该版本提升了在高负载情况下的 InnoDB 的性能、为 DBA 提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为。
- 该公司新建了一款存储引擎叫 完全可以替代 Innodb ,并且在性能和并发上做得更好
- 阿里巴巴大部分mysql数据库其实使用的percona的原型加以修改。
7.课外补充
7.1 InnoDB表的优势
InnoDB存储引擎在实际应用中拥有诸多优势,比如操作便利、提高了数据库的性能、维护成本低等。如果由于硬件或软件的原因导致服务器崩溃,那么在重启服务器之后不需要进行额外的操作。InnoDB崩溃恢复功能自动将之前提交的内容定型,然后撤销没有提交的进程,重启之后继续从崩溃点开始执行。
InnoDB存储引擎在主内存中维护缓冲池,高频率使用的数据将在内存中直接被处理。这种缓存方式应用于多种信息,加速了处理进程。
在专用服务器上,物理内存中高达80%的部分被应用于缓冲池。如果需要将数据插入不同的表中,可以设置外键加强数据的完整性。更新或者删除数据,关联数据将会被自动更新或删除。如果试图将数据插入从表,但在主表中没有对应的数据,插入的数据将被自动移除。如果磁盘或内存中的数据出现崩溃,在使用脏数据之前,校验和机制会发出警告。当每个表的主键都设置合理时,与这些列有关的操作会被自动优化。插入、更新和删除操作通过做改变缓冲自动机制进行优化。InnoDB不仅支持当前读写,也会缓冲改变的数据到数据流磁盘 。
InnoDB的性能优势不只存在于长时运行查询的大型表。在同一列多次被查询时,自适应哈希索引会提高查询的速度。使用InnoDB可以压缩表和相关的索引,可以 在不影响性能和可用性的情况下创建或删除索 引 。对于大型文本和BLOB数据,使用动态行形式,这种存储布局更高效。通过查询 INFORMATION_SCHEMA库中的表可以监控存储引擎的内部工作。在同一个语句中,InnoDB表可以与其他存储引擎表混用。即使有些操作系统限制文件大小为2GB,InnoDB仍然可以处理。 当处理大数据量时,InnoDB兼顾CPU,以达到最大性能 。
7.2 InnoDB和ACID模型
ACID模型是一系列数据库设计规则,这些规则着重强调可靠性,而可靠性对于商业数据和任务关键型应用非常重要。MySQL包含类似InnoDB存储引擎的组件,与ACID模型紧密相连,这样出现意外时,数据不会崩溃,结果不会失真。如果依赖ACID模型,可以不使用一致性检查和崩溃恢复机制。如果拥有额外的软件保护,极可靠的硬件或者应用可以容忍一小部分的数据丢失和不一致,可以将MySQL设置调整为只依赖部分ACID特性,以达到更高的性能。下面讲解InnoDB存储引擎与ACID模型相同作用的四个方面。
1. 原子方面 ACID的原子方面主要涉及InnoDB事务,与MySQL相关的特性主要包括:
- 自动提交设置
- COMMIT语句
- ROLLBACK语句
- 操作INFORMATION_SCHEMA库中的表数据。
2. 一致性方面 2.ACID模型的一致性主要涉及保护数据不崩溃的内部InnoDB处理过程,与MySQL相关的特性主要包括:
- InnoDB双写缓存
- InnoDB崩溃恢复。
3. 隔离方面 隔离是应用于事务的级别,与MySQL相关的特性主要包括:
- 自动提交设置。
- SET ISOLATION LEVEL语句
- InnoDB锁的低级别信息。
4.耐久性方面 4.ACID模型的耐久性主要涉及与硬件配置相互影响的MySQL软件特性。由于硬件复杂多样化,耐久性方面没有具体的规则可循。与MySQL相关的特性有:
- InnoDB双写缓存,通过innodb_doublewrite配置项配置。
- 配置项innodb_flush_log_at_trx_commit。
- 配置项sync_binlog。
- 配置项innodb_file_per_table。
- 存储设备的写入缓存。
- 存储设备的备用电池缓存。
- 运行MySQL的操作系统。
- 持续的电力供应。
- 备份策略。
- 对分布式或托管的应用,最主要的在于硬件设备的地点以及网络情况。
7.3 InnoDB架构
- 缓冲池 缓冲池是主内存中的一部分空间,用来缓存已使用的表和索引数据。缓冲池使得经常被使用的数据能够直接在内存中获得,从而提高速度。
- 更改缓存 2.更改缓存是一个特殊的数据结构,当受影响的索引页不在缓存中时,更改缓存会缓存辅助索引页的更改。索引页被其他读取操作时会加载到缓存池,缓存的更改内容就会被合并。不同于集群索
引,辅助索引并非独一无二的。当系统大部分闲置时,清除操作会定期运行,将更新的索引页刷入磁 盘。更新缓存合并期间,可能会大大降低查询的性能。在内存中,更新缓存占用一部分InnoDB缓冲池。在磁盘中,更新缓存是系统表空间的一部分。更新缓存的数据类型由innodb_change_buffering配置项管理。 - 自适应哈希索引 自适应哈希索引将负载和足够的内存结合起来,使得InnoDB像内存数据库一样运行,不需要降低事务上的性能或可靠性。这个特性通过innodb_adaptive_hash_index选项配置,或者通过-- skip-innodb_adaptive_hash_index命令行在服务启动时关闭。
- 重做日志缓存 重做日志缓存存放要放入重做日志的数据。重做日志缓存大小通过 innodb_log_buffer_size配置项配置。重做日志缓存会定期地将日志文件刷入磁盘。大型的重做日志缓存使得大型事务能够正常运行而不需要写入磁盘。
- 系统表空间 系统表空间包括InnoDB数据字典、双写缓存、更新缓存和撤销日志,同时也包括表和索引数据。多表共享,系统表空间被视为共享表空间。
- 双写缓存 双写缓存位于系统表空间中,用于写入从缓存池刷新的数据页。只有在刷新并写入双写缓存后,InnoDB才会将数据页写入合适的位置。
- 撤销日志 7.撤销日志是一系列与事务相关的撤销记录的集合,包含如何撤销事务最近的更改。如果其他事务要查询原始数据,可以从撤销日志记录中追溯未更改的数据。撤销日志存在于撤销日志片段中,这些片段包含于回滚片段中。
- 每个表一个文件的表空间 每个表一个文件的表空间是指每个单独的表空间创建在自身的数据文件中,而不是系统表空间中。这个功能通过innodb_file_per_table配置项开启。每个表空间由一个单独的.ibd数据文件代表,该文件默认被创建在数据库目录中
- 通用表空间 使用CREATE TABLESPACE语法创建共享的InnoDB表空间。通用表空间可以创建在MySQL数据目录之外能够管理多个表并支持所有行格式的表。
- 撤销表空间 10.撤销表空间由一个或多个包含撤销日志的文件组成。撤销表空间的数量由 innodb_undo_tablespaces配置项配置。
- 临时表空间 11.用户创建的临时表空间和基于磁盘的内部临时表都创建于临时表空间。 innodb_temp_data_file_path配置项定义了相关的路径、名称、大小和属性。如果该值为空,默认会在 innodb_data_home_dir变量指定的目录下创建一个自动扩展的数据文件。
- 重做日志 重做日志是基于磁盘的数据结构,在崩溃恢复期间使用,用来纠正数据。正常操作期间,重做日志会将请求数据进行编码,这些请求会改变InnoDB表数据。遇到意外崩溃后,未完成的更改会自动在初始化期间重新进行。
八、索引的数据结构
1.为什么使用索引
假如给数据使用 二叉树 这样的数据结构进行存储,如下图所示
2.索引及其优缺点
2.1 索引概述
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
索引的本质: 索引是数据结构。你可以简单理解为“排好序的快速查找数据结构”,满足特定查找算法。这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现 高级查找算法 。
2.2 优点
- 类似大学图书馆建书目索引,提高数据检索的效率,降低 数据库的IO成本 ,这也是创建索引最主要的原因。
- 通过创建唯一索引,可以保证数据库表中每一行 数据的唯一性 。
- 在实现数据的参考完整性方面,可以加速表和表之间的连接 。换句话说,对于有依赖关系的子表和父表联合查询时,可以提高查询速度。
- 在使用分组和排序子句进行数据查询时,可以显著 减少查询中分组和排序的时间 ,降低了CPU的消耗。
2.3 缺点
- 创建索引和维护索引要耗费时间 ,并且随着数据量的增加,所耗费的时间也会增加。
- 索引需要占磁盘空间 ,除了数据表占数据空间之外,每一个索引还要占一定的物理空间, 存储在磁盘上 ,如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸。
- 虽然索引大大提高了查询速度,同时却会 降低更新表的速度 。当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。
提示: 索引可以提高查询的速度,但是会影响记录的速度,最好的办法是先删除表中的索引,然后插入数据,插入完成后再创建索引 |
3.InnoDB中索引的推演
3.1 一个简单的索引设计方案
我们在根据某个搜索条件查找一些记录时为什么要遍历所有的数据页呢?因为各个页中的记录并没有规律,我们并不知道我们的搜索条件匹配哪些页中的记录,所以不得不依次遍历所有的数据页。所以如果我们 想快速的定位到需要查找的记录在哪些数据页 中该咋办?我们可以为快速定位记录所在的数据页而 建立一个目录 ,建这个目录必须完成下边这些事:
- 下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。
- 给所有的页建立一个目录项。
以 页28 为例,它对应 目录项2 ,这个目录项中包含着该页的页号 28 以及该页中用户记录的最小主键值 5 。我们只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键
值快速查找某条记录的功能了。比如:查找主键值为 20 的记录,具体查找过程分两步:
- 先从目录项中根据 二分法 快速确定出主键值为 20 的记录在 目录项3 中(因为
12 < 20 < 209,它对应的页是 页9 - 再根据前边说的在页中查找记录的方式去 页9 中定位具体的记录。
- 至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为 索引 。
3.2. InnoDB中的索引方案
3.2.1 迭代1次:目录项纪录的页
我们把前边使用到的目录项放到数据页中的样子就是这样:
从图中可以看出来,我们新分配了一个编号为30的页来专门存储目录项记录。这里再次强调 目录项记录 和普通的 用户记录 的不同点:
- 目录项记录 的 record_type 值是1,而 普通用户记录 的 record_type 值是0。
- 目录项记录只有 主键值和页的编号 两个列,而普通的用户记录的列是用户自己定义的,可能包含很多列 ,另外还有InnoDB自己添加的隐藏列。
- 了解:记录头信息里还有一个叫min_rec_mask 的属性,只有在存储 目录项记录 的页中的主键值最小的 目录项记录 的 min_rec_mask 值为
1
,其他别的记录的 min_rec_mask 值都是0
。
3.2.2 迭代2次:多个目录项纪录的页
从图中可以看出,我们插入了一条主键值为320的用户记录之后需要两个新的数据页:
- 为存储该用户记录而新生成了 页31
- 因为原先存储目录项记录的 页30的容量已满 (我们前边假设只能存储4条目录项记录),所以不得
不需要一个新的 页32 来存放 页31对应的目录项。
现在因为存储目录项记录的页不止一个,所以如果我们想根据主键值查找一条用户记录大致需要3个步骤,以查找主键值为 20 的记录为例:
- 确定 目录项记录页
我们现在的存储目录项记录的页有两个,即 页30 和 页32 ,又因为页30表示的目录项的主键值的范围是[1, 320) ,页32表示的目录项的主键值不小于 320 ,所以主键值为20 的记录对应的目录项记录在 页30 中。 - 通过目录项记录页 确定用户记录真实所在的页 。
在一个存储 目录项记录 的页中通过主键值定位一条目录项记录的方式说过了。 - 在真实存储用户记录的页中定位到具体的记录。
3.2.3 迭代3次:目录项记录页的目录页
如图,我们生成了一个存储更高级目录项的 页33 ,这个页中的两条记录分别代表页30和页32,如果用
户记录的主键值在 [1, 320) 之间,则到页30中查找更详细的目录项记录,如果主键值 不小于320 的话,就到页32中查找更详细的目录项记录。
我们可以用下边这个图来描述它:
这个数据结构,它的名称是 B+Tree
。
3.3 B+Tree
一个B+树的节点其实可以分成好多层,规定最下边的那层,也就是存放我们用户记录的那层为第 0 层,之后依次往上加。之前我们做了一个非常极端的假设:存放用户记录的页 最多存放3条记录 ,存放目录项记录的页 最多存放4条记录 。其实真实环境中一个页存放的记录数量是非常大的,假设所有存放用户记录的叶子节点代表的数据页可以存放 100条用户记录 ,所有存放目录项记录的内节点代表的数据页可以存放 1000条目录项记录 ,那么:
- 如果B+树只有
1
层,也就是只有1个用于存放用户记录的节点,最多能存放100
条记录。 - 如果B+树有
2
层,最多能存放1000×100=10,0000
条记录。 - 如果B+树有
3
层,最多能存放1000×1000×100=1,0000,0000
条记录。 - 如果B+树有
4
层,最多能存放1000×1000×1000×100=1000,0000,0000
条记录。相当多的记录!!!
3.4 常见索引概念
索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)和非聚簇(非聚集)索引。我们也把非聚集索引称为二级索引或者辅助索引。
1.聚簇索引
特点:
- 使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:
- 页内 的记录是按照主键的大小顺序排成一个 单向链表 。
- 各个存放 用户记录的页 也是根据页中用户记录的主键大小顺序排成一个 双向链表
- 存放 目录项记录的页 分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表 。
- B+树的 叶子节点 存储的是完整的用户记录。所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。
优点:
- 数据访问更快 ,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
- 聚簇索引对于主键的 排序查找 和 范围查找 速度非常快
- 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以 节省了大量的io操作 。
缺点:
- 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个 自增的ID列为主键
- 更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
- 二级索引访问需要两次索引查找 ,第一次找到主键值,第二次根据主键值找到行数据
2. 二级索引(辅助索引、非聚簇索引)
理解:聚簇索引中保存都是用户的真实数据
,而非聚簇索引保存的仅仅是该列的值 + 主键的值
,查找时需要回表
概念:回表 我们根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就是根据c2列的值查询一条完整的用户记录需要使用到 2
棵B+树!
问题:为什么我们还需要一次 回表 操作呢?直接把完整的用户记录放到叶子节点不OK吗?
小结: 聚簇索引与非聚簇索引的原理不同,在使用上也有一些区别: 1.聚簇索引的叶子节点存储的就是我们的数据记录,非聚簇索引的叶子节点存储的是数据位置。 非聚簇索引不会影响数据表的物理存储顺序。 2.一个表只能有一个聚簇索引,因为只能有一种排序存储的方式,但可以有多个非聚簇索引,也就是多个索引目录提供数据检索 3.使用聚簇索引的时候,数据的查询效率高,但如果对数据进行插入、删除、更新等操作,效率会比非聚簇索引低 |
3.联合索引(非聚簇索引)
我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照 c2和c3列 的大小进行排序,这个包含两层含义:
- 先把各个记录和页按照c2列进行排序。
- 在记录的c2列相同的情况下,采用c3列进行排序
注意一点,以c2和c3列的大小为排序规则建立的B+树称为 联合索引 ,本质上也是一个二级索引。它的意思与分别为c2和c3列分别建立索引的表述是不同的,不同点如下:
- 建立 联合索引 只会建立如上图一样的1棵B+树。
- 为c2和c3列分别建立索引会分别以c2和c3列的大小为排序规则建立2棵B+树。
3.5 InnoDB 的 B+ 树索引的注意事项
1.根页面位置万年不动
我们前边介绍B+树索引的时候,为了大家理解上的方便,先把存储用户记录的叶子节点都画出来,然后接着画存储目录项记录的内节点,实际上B+树的形成过程是这样的:
- 每当为某个表创建一个B+树索引(聚簇索引不是人为创建的,默认就有)的时候,都会为这个索引创建一个
根节点 页面。最开始表中没有数据的时候,每个B+数索引对应的根节点 中既没有用户记录,也没有目录项记录 - 随后向表中插入用户记录时,先把用户记录存储到这个
- 根节点中。当根节点中的可用空间用完时继续插入 记录。此时会将根节点中的所有记录复制到一个新分配的页,比如 页a中,然后对这个新页进行页分裂的操作,得到另一个新页,比如 页b。这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值) 的大小就会被分配到页a 或者 页b 中,而 根节点 便升级为存储目录项记录的页。
这个过程特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动,这样只要我们对某个表建立一个索引,那么它的根节点的页号便会被记录到某个地方,然后凡事InnoDB存储引擎需要用到这个索引的时候,都会从那个固定的地方去除根节点的页号,从而来访问这个索引。
2.内节点中目录记录的唯一性
我们知道B+树索引的内节点中目录项记录的内容是索引列 + 页号
的搭配,但是这个搭配对于二级索引来说有点不严谨,耳机索引还需要主键的列
3.一个页面最少存储2条记录
一个B+树只需要很少的层级就可以轻松存储数亿条记录,查询速度相当不错。这是因为B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录,直到最后访问到存储真实数据的目录,那如果一个大的目录中只存放一个子目录是个啥效果呢?那就是目录层级非常非常多,而且最后那个存放真实的目录中只能存放一条数据。
4.MyISAM中的索引方案
B+树索引适用存储引擎如表所示:
索引 | MyISAM | InnoDB | Memory |
---|---|---|---|
B-Tree索引 | 支持 | 支持 | 支持 |
即使多个存储引擎支持同一种类型的索引,但是他们的实现原理也是不同的。Innodb和MyISAM默认的索引是Btree索引;而Memory默认的索引是Hash索引。
MyISAM引擎使用 B+Tree
作为索引结构,叶子节点的data域存放的是 数据记录的地址
。
5.索引的代价
- 空间上的代价
每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会占用 16KB 的存储空间,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。 - 时间上的代价
每次对表中的数据进行增、删、改
操作时,都需要去修改各个B+树索引。而且我们讲过,B+树每层节点都是按照索引列的值从小到大的顺序排序
而组成了双向链表
。不论是叶子节点中的记录,还是内节点中的记录(也就是不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些记录移位 , 页面分裂 、 页面回收
等操作来维护好节点和记录的排序。如果我们建了许多索引,每个索引对应的B+树都要进行相关的维护操作,会给性能拖后腿。
6.MySQL数据结构选择的合理性
6.1 全表遍历
就是把表全部加载一遍
6.2 Hash结构
上图中哈希函数h有可能将两个不同的关键字映射到相同的位置,这叫做 碰撞 ,在数据库中一般采用 链接法 来解决。在链接法中,将散列到同一槽位的元素放在一个链表中,如下图所示:
Hash结构效率高,那为什么索引结构要设计成树型呢?
原因1:Hash索引仅能满足(=)(<>)和 IN 查询。如果进行范围查询 ,哈希型的索引,时间复杂度会退化为 O(n);而树形的“有序”特性,依然能够保持O(log2N)的高效率。
原因2:Hash索引还有一个缺陷,数据的存储是没有顺序的,在Order By的情况下,使用Hash索引还需要对数据重新排序
原因3:对于联合索引的情况,Hash值是将联合索引键合并后一起来计算的,无法对单独的一个键或者几个索引键进行查询
原因4:对于等值查询来说,通常Hash索引的效率更高,不过也存在一种情况,就是 索引列的重复值如果很多,效率就会降低。这是应为遇到Hash冲突是,需要遍历桶中的行指针来进行比较,找到查询的关键字,非常耗时,所以,Hash索引通常不会用到 重复值多的列上,比如列为性别,年龄的情况等。
Hash索引适用存储引擎如表所示:
索引 / 存储引擎 | MyISAM | InnoDB | Memory |
---|---|---|---|
HASH索引 | 不支持 | 不支持 | 支持 |
Hash索引的适用性:
采用自适应 Hash 索引目的是方便根据 SQL 的查询条件加速定位到叶子节点,特别是当 B+ 树比较深的时候,通过自适应 Hash 索引可以明显提高数据的检索效率。
我们可以通过 innodb_adaptive_hash_index 变量来查看是否开启了自适应 Hash,比如:
mysql> show variables like ‘%adaptive_hash_index’;
6,.3 二叉搜索树
如果我们利用二叉树作为索引结构,那么磁盘的IO次数和索引树的高度是相关的。
6.4 AVL树
为了解决上面二叉查找树退化成链表的问题,人们提出了 平衡二叉树 ,又称为AVL树(有别于AVL算法),他在二叉搜索树的基础上增加了约束,具有以下性质:
它是一颗空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗二叉平衡树。
这里说一下,常见的平衡二叉树有很多种,包括了
平衡二叉搜索树、红黑树、数堆、伸展树。平衡二叉 搜索树是最早提出来的自平衡二叉搜索树,当我们提到平衡二叉树时一般指的就是平衡二叉搜索树。事实上,第一棵树就属于平衡二叉搜索树,搜索时间复杂度就是O(log2n)
每访问一次节点就需要进行一次磁盘I/O操作,对于上面的树来说,我们需要进行5次I/O操作。虽然平衡二叉树的效率高,但是树的深度也同样高,这就意味着磁盘I/O操作次数多,会影响整个数据查询的效率。
针对同样的数据,我们把二叉树改成 M 叉树(M > 2)呢?当M=3时,同样的31个节点可以由下面的三叉树 进行存储:
6.5 B-Tree
B 树的结构如下图所示:
个 M 阶的 B 树(M>2)有以下的特性:
- 根节点的儿子数的范围是 [2,M]。
- 每个中间节点包含 k-1 个关键字和 k 个孩子,孩子的数量 = 关键字的数量 +1,k 的取值范围为
[ceil(M/2), M]。 - 叶子节点包括 k-1 个关键字(叶子节点没有孩子),k 的取值范围为 [ceil(M/2), M]。
- 假设中间节点节点的关键字为:Key[1], Key[2], …, Key[k-1],且关键字按照升序排序,即 Key[i]
<Key[i+1]。此时 k-1 个关键字相当于划分了 k 个范围,也就是对应着 k 个指针,即为:P[1], P[2], …,P[k],其中 P[1] 指向关键字小于 Key[1] 的子树,P[i] 指向关键字属于 (Key[i-1], Key[i]) 的子树,P[k]指向关键字大于 Key[k-1] 的子树。 - 所有叶子节点位于同一层。
小结: 1.B树在插入和删除节点的时候会导致树的不平衡,就通过自动调整节点的位置来保持树的自平衡。 2.关键字集合分布在整颗树中,即叶子节点和非叶子节点都存放数据。搜索有可能在非叶子节点结束 3.其搜索性能等价于在关键字全集内做一次二分查找 |
6.6 B+Tree
见 目录 B+Tree
B+ 树和 B 树的差异:
1.有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数
+1。
2.非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最小)。
3.非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而 B 树中,非叶子节点既保存索引,也保存数据记录 。
4.所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大小从小到大顺序链接。
6.7 R树
R-Tree在MySQL很少使用,仅支持 geometry数据类型 ,支持该类型的存储引擎只有myisam、bdb、 innodb、ndb、archive几种。举个R树在现实领域中能够解决的例子:查找20英里以内所有的餐厅。如果没有R树你会怎么解决?一般情况下我们会把餐厅的坐标(x,y)分为两个字段存放在数据库中,一个字段记录经度,另一个字段记录纬度。这样的话我们就需要遍历所有的餐厅获取其位置信息,然后计算是否满足要求。如果一个地区有100家餐厅的话,我们就要进行100次位置计算操作了,如果应用到谷歌、百度地图这种超大数据库中,这种方法便必定不可行了。R树就很好的 解决了这种高维空间搜索问题 。它把B树的思想很好的扩展到了多维空间,采用了B树分割空间的思想,并在添加、删除操作时采用合并、分解结点的方法,保证树的平衡性。因此,R树就是一棵用来 存储高维数据的平衡树 。相对于B-Tree,R-Tree 的优势在于范围查找。
索引 / 存储引擎 | MyISAM | InnoDB | Memory |
---|---|---|---|
R-Tree索引 | 支持 | 支持 | 不支持 |
九、索引的创建与设计原则
1.索引的声明与使用
通过命令查看索引
方式1:
SHOW CREATE TABLE
book;
方式2:
SHOW INDEX FROM
book;
1.1 索引的分类
MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。
- 从 功能逻辑 上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引、全文索引。
- 按照 物理实现方式 ,索引可以分为 2 种:聚簇索引和非聚簇索引。
- 按照 作用字段个数 进行划分,分成单列索引和联合索引。
1.2 创建索引
MySQL 支持多种方法在单个或多个列上创建索引;在创建表的定义语句 CREATE_TABLE 中指明索引列,使用 ALTER_TABLE 语句在存在的表上创建索引,或者使用 CREATE_INDEX 语句在已存在的表上添加索引
1. 创建表的时候创建索引
举例:
CREATE TABLE dept(
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(20)
);
CREATE TABLE emp(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20) UNIQUE, dept_id INT,
CONSTRAINT emp_dept_id_fk FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);
但是,如果显式创建表时创建索引的话,基本语法格式如下:
CREATE TABLE table_name [col_name data_type]
[UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length]) [ASC | DESC]
-
UNIQUE 、 FULLTEXT 和 SPATIAL 为可选参数,分别表示唯一索引、全文索引和空间索引;
-
INDEX 与 KEY 为同义词,两者的作用相同,用来指定创建索引;
-
index_name 指定索引的名称,为可选参数,如果不指定,那么MySQL默认col_name为索引名;
-
col_name 为需要创建索引的字段列,该列必须从数据表中定义的多个列中选择;
-
length 为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度;
-
ASC 或 DESC 指定升序或者降序的索引值存储。
1.创建普通索引
在book表中的year_publication字段上建立普通索引,SQL语句如下:
CREATE TABLE book(
book_id INT ,
book_name VARCHAR(100),
authors VARCHAR(100),
info VARCHAR(100) ,
comment VARCHAR(100),
year_publication YEAR,
INDEX(year_publication)
);
2.创建唯一索引
CREATE TABLE test1( id INT NOT NULL,
name varchar(30) NOT NULL,
UNIQUE INDEX uk_idx_id(id)
3.主键索引
设定为主键后数据库会自动建立索引,innodb为聚簇索引,语法:
- 随表一起建索引:
CREATE TABLE student (
id INT(10) UNSIGNED AUTO_INCREMENT ,
student_no VARCHAR(200),
student_name VARCHAR(200),
PRIMARY KEY(id)
);
- 删除主键索引:
ALTER TABLE student drop PRIMARY KEY ;
- 修改主键索引:必须先删除掉(drop)原索引,再新建(add)索引
4.创建单列索引
举例:
CREATE TABLE test2(
id INT NOT NULL,
name CHAR(50) NULL,
INDEX single_idx_name(name(20))
5.创建组合索引
举例:创建表test3,在表中的id、name和age字段上建立组合索引,SQL语句如下:
CREATE TABLE test3(
id INT(11) NOT NULL,
name CHAR(30) NOT NULL,
age INT(11) NOT NULL,
info VARCHAR(255),
INDEX multi_idx(id,name,age)
6.创建全文索引
举例1:创建表test4,在表中的info字段上建立全文索引,SQL语句如下:
CREATE TABLE test4( id INT NOT NULL,
name CHAR(30) NOT NULL,
age INT NOT NULL,
info VARCHAR(255),
FULLTEXT INDEX futxt_idx_info(info)
在MySQL5.7及之后版本中可以不指定最后的ENGINE了,因为在此版本中InnoDB支持全文索引。
全文索引用match+against方式查询:
SELECT * FROM papers WHERE MATCH(title,content) AGAINST (‘查询字符串’);
7.创建空间索引
空间索引创建中,要求空间类型的字段必须为 非空 。
举例:创建表test5,在空间类型为GEOMETRY的字段上创建空间索引,SQL语句如下:
CREATE TABLE test5(
geo GEOMETRY NOT NULL,
SPATIAL INDEX spa_idx_geo(geo)
1.3 在已经存在的表上创建/删除索引
在已经存在的表中创建索引可以使用 ALTER TABLE 语句或者CREATE INDEX 语句。
- 使用ALTER TABLE语句创建索引 ALTER TABLE语句创建索引的基本语法如下:
ALTER TABLE table_name ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY]
[index_name] (col_name[length],...) [ASC | DESC]
2.使用CREATE INDEX创建索引 CREATE INDEX语句可以在已经存在的表上添加索引,在MySQL中, CREATE INDEX被映射到一个ALTER TABLE语句上,基本语法结构为:
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
ON table_name (col_name[length],...) [ASC | DESC]
删除索引
使用ALTER TABLE删除索引 ALTER TABLE删除索引的基本语法格式如下:
ALTER TABLE table_name DROP INDEX index_name;
使用DROP INDEX语句删除索引 DROP INDEX删除索引的基本语法格式如下:
DROP INDEX index_name ON table_name;
提示 删除表中的列时,如果要删除的列为索引的组成部分,则该列也会从索引中删除。如果组成索引的所有列都被删除,则整个索引将被删除。
2.MySQL8.0索引新特性
2.1 支持降序索引
举例:分别在MySQL 5.7版本和MySQL 8.0版本中创建数据表ts1,结果如下:
CREATE TABLE ts1(
a int,
b int,
index idx_a_b(a,b desc));
2.2 隐藏索引
从MySQL 8.x开始支持 隐藏索引(invisible indexes) ,只需要将待删除的索引设置为隐藏索引,使查询优化器不再使用这个索引(即使使用force index(强制使用索引),优化器也不会使用该索引),确认将索引设置为隐藏索引后系统不受任何响应,就可以彻底删除索引。 这种通过先将索引设置为隐藏索引,再删除索引的方式就是软删除 。
1.创建表时直接创建
CREATE TABLE tablename(
propname1 type1[CONSTRAINT1], propname2 type2[CONSTRAINT2],
……
propnamen typen,
INDEX [indexname](propname1 [(length)]) INVISIBLE
);
上述语句比普通索引多了一个关键字INVISIBLE,用来标记索引为不可见索引。
2.在已经存在的表上创建
CREATE INDEX indexname
ON tablename(propname[(length)]) INVISIBLE;
3.通过ALTER TABLE语句创建
ALTER TABLE tablename
ADD INDEX indexname (propname [(length)]) INVISIBLE;
4.切换索引可见状态
ALTER TABLE tablename ALTER INDEX index_name INVISIBLE; #切换成隐藏索引
ALTER TABLE tablename ALTER INDEX index_name VISIBLE; #切换成非隐藏索引
注意 当索引被隐藏时,它的内容仍然是和正常索引一样实时更新的。如果一个索引需要长期被隐藏,那么可以将其删除,因为索引的存在会影响插入、更新和删除的性能。
3.索引的设计原则
3.1 哪些情况适合创建索引
- 字段的数值有唯一性的限制
- 频繁作为 WHERE 查询条件的字段
- 经常 GROUP BY 和 ORDER BY 的列
- UPDATE、DELETE 的 WHERE 条件列
- DISTINCT 字段需要创建索引
- 多表 JOIN 连接操作时,创建索引注意事项
首先,
连接表的数量尽量不要超过 3 张
其次,对 WHERE 条件创建索引
最后,对用于连接的字段创建索引
,并且该字段在多张表中的类型必须一致
- 使用列的类型小的创建索引
- 使用字符串前缀创建索引
count(distinct left
(列名, 索引长度))/count(*)
举例:
select count(distinct left(address,10)) / count(*) as sub10, – 截取前10个字符的选择度
- 区分度高(散列性高)的列适合作为索引
- 使用最频繁的列放到联合索引的左侧
- 在多个字段都要创建索引的情况下,联合索引优于单值索引
3.2 哪些情况不适合创建索引
- 在where中使用不到的字段,不要设置索引
- 数据量小的表最好不要使用索引
- 有大量重复数据的列上不要建立索引
- 避免对经常更新的表创建过多的索引
- 不建议用无序的值作为索引
- 删除不再使用或者很少使用的索引
- 不要定义冗余或重复的索引
十、性能分析工具的使用
1.查看系统性能参数
在MySQL中,可以使用 SHOW STATUS
语句查询一些MySQL数据库服务器的 性能参数 、 执行频率
。
SHOW STATUS语句语法如下:
SHOW [GLOBAL|SESSION] STATUS LIKE
‘参数’;
2.统计SQL的查询成本:last_query_cost
使用场景:它对于比较开销是非常有用的,特别是我们有好几种查询方式可选的时候。
SHOW STATUS LIKE 'last_query_cost';
3.定位执行慢的 SQL:慢查询日志
3.1 开启慢查询日志参数
1.开启slow_query_log
mysql >
set global
slow_query_log=‘ON’;
你能看到这时慢查询分析已经开启,同时文件保存在 /var/lib/mysql/atguigu02-slow.log 文件中。
2.修改long_query_time阈值
show variables like
‘%long_query_time%’;
这里如果我们想把时间缩短,比如设置为 1 秒,可以这样设置:
#测试发现:设置global的方式对当前session的long_query_time失效。对新连接的客户端有效。所以可以一并执行下述语句
mysql >set global
long_query_time = 1;
mysql>show global variables like
‘%long_query_time%’;mysql>
set
long_query_time=1;
mysql>show variables like
‘%long_query_time%’;
3.2 查看慢查询数目
查询当前系统中有多少条慢查询记录
SHOW GLOBAL STATUS LIKE
‘%Slow_queries%’;
3.3 慢查询日志分析工具:mysqldumpslow
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具 mysqldumpslow。
mysqldumpslow --help
mysqldumpslow 命令的具体参数如下:
- -a: 不将数字抽象成N,字符串抽象成S
- -s: 是表示按照何种方式排序:
c: 访问次数
l: 锁定时间
r: 返回记录
t: 查询时间
al:平均锁定时间 ar:平均返回记录数
at:平均查询时间 (默认方式)
ac:平均查询次数 - -t: 即为返回前面多少条的数据;
- -g: 后边搭配一个正则匹配模式,大小写不敏感的;
举例:
我们想要按照查询时间排序,查看前五条 SQL 语句,这样写即可:
mysqldumpslow -s t -t 5 /var/lib/mysql/atguigu01-slow.log
工作常用参考:
#得到返回记录集最多的10个SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
#得到访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
#得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g “left join” /var/lib/mysql/atguigu-slow.log
#另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more
3.4 关闭慢查询日志
MySQL服务器停止慢查询日志功能有两种方法:
方式1:永久性方式
[mysqld]
slow_query_log=OFF
或者,把slow_query_log一项注释掉 或 删除
[mysqld]
#slow_query_log =OFF
重启MySQL服务,执行如下语句查询慢日志功能。
SHOW VARIABLES LIKE
‘%slow%’; #查询慢查询日志所在目录
SHOW VARIABLES LIKE
‘%long_query_time%’; #查询超时时长
方式2:临时性方式
使用SET语句来设置。
(1)停止MySQL慢查询日志功能,具体SQL语句如下。
SET GLOBAL
slow_query_log=off;
(2)重启MySQL服务,使用SHOW语句查询慢查询日志功能信息,具体SQL语句如下
SHOW VARIABLES LIKE
‘%slow%’;
#以及
SHOW VARIABLES LIKE
‘%long_query_time%’;
3.5 删除慢日志查询
mysqladmin -uroot -p flush-logs slow
提示: 慢查询日志都是使用 mysqladmin flush-logs 命令来删除重建的。使用时一定要注意,一旦执行了这个命令,查询日志都只存在新的日志文件中,如果需要旧的查询日志,就必须事先备份 |
4.查看 SQL 执行成本:SHOW PROFILE
mysql >
show variables like
‘profiling’;
#开启 show profile:
mysql >set
profiling = ‘ON’;
#查看下当前会话都有哪些 profiles
mysql >show profiles;
#查看最近一次查询的开销
mysql >show profile;
使用示例:
show profile
cpu,block iofor query
2;
show profile的常用查询参数:
- ALL:显示所有的开销信息。
- BLOCK IO:显示块IO开销。
- CONTEXT SWITCHES:上下文切换开销。
- CPU:显示CPU开销信息。
- IPC:显示发送和接收开销信息。
- MEMORY:显示内存开销信息。
- PAGE FAULTS:显示页面错误开销信息。
- SOURCE:显示和Source_function,Source_file, Source_line相关的开销信息。
- SWAPS:显示交换次数开销信息。
5. 分析查询语句:EXPLAIN
列名 | 描述 | 作用 |
---|---|---|
id | 在一个大的查询语句中每个SELECT关键字都对应一个 唯一的id | EXPLAIN语句输出的每条记录都对应着某个单表的访问方法 |
select_type | SELECT关键字对应的那个查询的类型 | id如果相同,可以认为是一组,从上往下顺序执行 在所有组中,id值越大,优先级越高,越先执行 关注点:id号每个号码,表示一趟独立的查询, 一个sql的查询趟数越少越好 |
table | 表名 | |
partitions | 匹配的分区信息 | (可略) |
type ☆ | 针对单表的访问方法 | 结果值从最好到最坏依次是: system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL 其中比较重要的几个提取出来(见上图中的蓝色)。SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴开发手册要求) |
possible_keys | 可能用到的索引 | |
key | 实际上使用的索引 | |
key_len ☆ | 实际使用到的索引长度,主要针对联合索引,值越大越好 | |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 | |
rows ☆ | 预估的需要读取的记录条数 | |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 | |
Extra ☆ | 一些额外的信息 | Impossible WHERE Using where No matching min/max row Using index Using index condition Using join buffer (Block Nested Loop) Not exists |
小结 ● EXPLAIN不考虑各种Cache ● EXPLAIN不能显示MySQL在执行查询时所作的优化工作 ● EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况部分统计信息是估算的,并非精确值 |
6. EXPLAIN的进一步使用
6.1 EXPLAIN四种输出格式
EXPLAIN可以输出四种格式:
传统格式
,JSON格式
,TREE格式
以及可视化输出
1.传统格式
传统格式简单明了,输出是一个表格形式,概要说明查询计划。
2.JSON格式
JSON格式:在EXPLAIN单词和真正的查询语句中间加上 FORMAT=JSON 。
举例:
EXPLAIN
FORMAT=JSON SELECT …
"cost_info": {
"read_cost": "968.80",
"eval_cost": "193.76",
"prefix_cost": "3197.16",
"data_read_per_join": "1M"
3.TREE格式
TREE格式是8.0.16版本之后引入的新格式,主要根据查询的
各个部分之间的关系
和各部分的执行顺序
来描述如何查询。
举例:
EXPLAIN
FORMAT=treeSELECT
*FROM
s1INNER JOIN
s2ON
s1.key1 = s2.key2WHERE
s1.common_field = ‘a’\G
4.可视化输出
可视化输出,可以通过MySQL
Workbench可视化查看MySQL的执行计划。通过点击Workbench的放大镜图标,即可生成可视化的查询计划。
上图按从左到右的连接顺序显示表。红色框表示 全表扫描 ,而绿色框表示使用 索引查找
。对于每个表,显示使用的索引。还要注意的是,每个表格的框上方是每个表访问所发现的行数的估计值以及访问该表的成本。
6.2 SHOW WARNINGS的使用
mysql> EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;
mysql> SHOW WARNINGS\G
*************************** 1. row *************************** Level: Note
Code: 1003
Message: /* select#1 */ select `atguigu`.`s1`.`key1` AS `key1`,`atguigu`.`s2`.`key1` AS `key1` from `atguigu`.`s1` join `atguigu`.`s2` where ((`atguigu`.`s1`.`key1` =
`atguigu`.`s2`.`key1`) and (`atguigu`.`s2`.`common_field` is not null))
1 row in set (0.00 sec)
7.分析优化器执行计划:trace
SET optimizer_trace="enabled=on",end_markers_in_json=on;
### 设置内存大小
set optimizer_trace_max_mem_size=1000000;
开启后,可分析如下语句:
SELECT
INSERT
REPLACE
UPDATE
DELETE
EXPLAIN
SET
DECLARE
CASE
IF
RETURN
CALL
*************************** 1. row ***************************
//第1部分:查询语句
QUERY: select * from student where id < 10
//第2部分:QUERY字段对应语句的跟踪信息 TRACE: {
"steps": [
{
"join_preparation": { //预备工作 "select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `student`.`id` AS
`id`,`student`.`stuno` AS `stuno`,`student`.`name` AS `name`,`student`.`age` AS
`age`,`student`.`classId` AS `classId` from `student` where (`student`.`id` < 10)"
}
] /* steps */
} /* join_preparation */
},
{
"join_optimization": { //进行优化 "select#": 1,
"steps": [
{
"condition_processing": { //条件处理 "condition": "WHERE",
"original_condition": "(`student`.`id` < 10)", "steps": [
{
"transformation": "equality_propagation", "resulting_condition": "(`student`.`id` < 10)"
},
{
"transformation": "constant_propagation", "resulting_condition": "(`student`.`id` < 10)"
},
{
"transformation": "trivial_condition_removal", "resulting_condition": "(`student`.`id` < 10)"
}
] /* steps */
} /* condition_processing */
},
{
"substitute_generated_columns": { //替换生成的列
} /* substitute_generated_columns */
},
{
"table_dependencies": [ //表的依赖关系
{
"table": "`student`", "row_may_be_null": false, "map_bit": 0, "depends_on_map_bits": [
] /* depends_on_map_bits */
}
] /* table_dependencies */
},
{
"ref_optimizer_key_uses": [ //使用键
] /* ref_optimizer_key_uses */
},
{
"rows_estimation": [ //行判断
{
"table": "`student`", "range_analysis": {
"table_scan": { "rows": 3973767,
"cost": 408558
} /* table_scan */, //扫描表 "potential_range_indexes": [ //潜在的范围索引
{
"index": "PRIMARY",
"usable": true, "key_parts": [
"id"
] /* key_parts */
}
] /* potential_range_indexes */, "setup_range_conditions": [ //设置范围条件
] /* setup_range_conditions */, "group_index_range": {
"chosen": false,
"cause": "not_group_by_or_distinct"
} /* group_index_range */, "skip_scan_range": {
"potential_skip_scan_indexes": [
{
"index": "PRIMARY",
"usable": false,
"cause": "query_references_nonkey_column"
}
] /* potential_skip_scan_indexes */
} /* skip_scan_range */, "analyzing_range_alternatives": { //分析范围选项
"range_scan_alternatives": [
{
"index": "PRIMARY",
"ranges": [ "id < 10"
] /* ranges */,
"index_dives_for_eq_ranges": true, "rowid_ordered": true, "using_mrr": false,
"index_only": false, "rows": 9,
"cost": 1.91986,
"chosen": true
}
] /* range_scan_alternatives */, "analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
} /* analyzing_roworder_intersect */
} /* analyzing_range_alternatives */, "chosen_range_access_summary": { //选择范围访问摘要
"range_access_plan": { "type": "range_scan", "index": "PRIMARY",
"rows": 9,
"ranges": [ "id < 10"
] /* ranges */
} /* range_access_plan */, "rows_for_plan": 9,
"cost_for_plan": 1.91986, "chosen": true
} /* chosen_range_access_summary */
} /* range_analysis */
}
] /* rows_estimation */
},
{
"considered_execution_plans": [ //考虑执行计划
{
"plan_prefix": [
] /* plan_prefix */, "table": "`student`",
"best_access_path": { //最佳访问路径
"considered_access_paths": [
{
"rows_to_scan": 9, "access_type": "range", "range_details": {
"used_index": "PRIMARY"
} /* range_details */, "resulting_rows": 9,
"cost": 2.81986,
"chosen": true
}
] /* considered_access_paths */
} /* best_access_path */,
"condition_filtering_pct": 100, //行过滤百分比
"rows_for_plan": 9,
"cost_for_plan": 2.81986,
"chosen": true
}
] /* considered_execution_plans */
},
{
"attaching_conditions_to_tables": { //将条件附加到表上 "original_condition": "(`student`.`id` < 10)", "attached_conditions_computation": [
] /* attached_conditions_computation */, "attached_conditions_summary": [ //附加条件概要
{
"table": "`student`",
"attached": "(`student`.`id` < 10)"
}
] /* attached_conditions_summary */
} /* attaching_conditions_to_tables */
},
{
"finalizing_table_conditions": [
{
"table": "`student`",
"original_table_condition": "(`student`.`id` < 10)", "final_table_condition ": "(`student`.`id` < 10)"
}
] /* finalizing_table_conditions */
},
{
"refine_plan": [ //精简计划
{
"table": "`student`"
}
] /* refine_plan */
}
] /* steps */
} /* join_optimization */
},
{
"join_execution": { //执行 "select#": 1,
"steps": [
] /* steps */
} /* join_execution */
}
] /* steps */
}
//第3部分:跟踪信息过长时,被截断的跟踪信息的字节数。 MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0 //丢失的超出最大容量的字节
//第4部分:执行跟踪语句的用户是否有查看对象的权限。当不具有权限时,该列信息为1且TRACE字段为空,一般在调用带有SQL SECURITY DEFINER的视图或者是存储过程的情况下,会出现此问题。 INSUFFICIENT_PRIVILEGES: 0 //缺失权限
1 row in set (0.00 sec)
8.MySQL监控分析视图-sys schema
8.1 Sys schema视图摘要
1.
主机相关
:以host_summary开头,主要汇总了IO延迟的信息。
2.Innodb相关
:以innodb开头,汇总了innodb buffer信息和事务等待innodb锁的信息。
3.I/o相关
:以io开头,汇总了等待I/O、I/O使用量情况。
4.内存使用情况
:以memory开头,从主机、线程、事件等角度展示内存的使用情况
5.连接与会话信息
:processlist和session相关视图,总结了会话相关信息。
6.表相关
:以schema_table开头的视图,展示了表的统计信息。
7.索引信息
:统计了索引的使用情况,包含冗余索引和未使用的索引情况。
8.语句相关
:以statement开头,包含执行全表扫描、使用临时表、排序等的语句信息。
9.用户相关
:以user开头的视图,统计了用户使用的文件I/O、执行语句统计信息。
10.等待事件相关信息
:以wait开头,展示等待事件的延迟情况。
8.2 Sys schema视图使用场景
索引情况
#1. 查询冗余索引
select * from sys.schema_redundant_indexes;
#2. 查询未使用过的索引
select * from sys.schema_unused_indexes;
#3. 查询索引的使用情况
select index_name,rows_selected,rows_inserted,rows_updated,rows_deleted from sys.schema_index_statistics where table_schema='dbname' ;
表相关
# 1. 查询表的访问量
select table_schema,table_name,sum(io_read_requests+io_write_requests) as io from sys.schema_table_statistics group by table_schema,table_name order by io desc;
# 2. 查询占用bufferpool较多的表
select object_schema,object_name,allocated,data from sys.innodb_buffer_stats_by_table order by allocated limit 10;
# 3. 查看表的全表扫描情况
select * from sys.statements_with_full_table_scans where db='dbname';
语句相关
#1. 监控SQL执行的频率
select db,exec_count,query from sys.statement_analysis order by exec_count desc;
#2. 监控使用了排序的SQL
select db,exec_count,first_seen,last_seen,query from sys.statements_with_sorting limit 1;
#3. 监控使用了临时表或者磁盘临时表的SQL
select db,exec_count,tmp_tables,tmp_disk_tables,query
from sys.statement_analysis where tmp_tables>0 or tmp_disk_tables >0 order by (tmp_tables+tmp_disk_tables) desc;
IO相关
#1. 查看消耗磁盘IO的文件
select file,avg_read,avg_write,avg_read+avg_write as avg_io
from sys.io_global_by_file_by_bytes order by avg_read limit 10;
Innodb 相关
#1. 行锁阻塞情况
select * from sys.innodb_lock_waits;
风险提示: 通过sys库查询时,MySQL会 消耗大量的资源 去收集相关信息,严重的可能会导致业务请求被阻塞,从而引起故障。建议生产上 不要频繁 的去查询 sys 或者 performance_schema、information_schema来完成监控、巡检等工作。 |
十一、索引优化与查询优化
- 索引失效,没有充分利用到索引–索引建立
- 关联查询太多JOIN(设计缺陷或不得已的需求) – SQL优化
- 服务器调优及各个参数设置(缓冲、线程数等)-- 调整 my.cnf
- 数据过多 – 分库分表
大方向分为物理查询优化
和 逻辑查询优化
- 物理查询优化 是通过
索引
和表连接方式
等技术来进行优化,这里重点需要掌握索引的使用 - 逻辑查询优化 是通过SQL
等价变换
提升查询效率,直白一点就是说,换一种查询写法执行效率可能要更高
1. 索引失效案例
MySQL 中 提高性能
的一个最有效的方式就是对数据表 设计合理的索引
。索引提供了高效访问数据的方法,并且加快了查询的速度,因此索引对查询的速度有着至关重要的影响。
- 使用索引可以
快速地定位
表中的某条记录,从而提高数据库查询的速度,提高数据库的性能。 - 如果查询时没有使用索引,查询语句就会
扫描表中的所有记录
。在数据量大的情况下,这样查询的速度会很慢。
大多数情况下(默认)采用 B+树
来构建索引。只是空间列类型的索引使用 R-树
,并且MEMORY 表还支持 hash索引
。
其实。用不用索引都是优化器说了算。优化器是基于什么的优化器?基于cost开销
,它不是基于规则
,也不是基于语义
。怎么样开销小就怎么来。另外,SQL语句是否使用索引,跟数据库版本、数据量、数据选择都有关系
1.1 全值匹配我最爱(等值查询)
对经常查询的字段上建立合适的索引:
例如查询以下字段
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND classId = 4;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND classId = 4 AND NAME = 'ABCD';
建立相应的索引:
CREATE INDEX idx_age ON student(age);
CREATE INDEX idx_age_classId ON student(age,classId);
CREATE INDEX idx_age_classId_name ON student(age,classId,NAME);
1.2 最佳左前缀法则
1.3 主键插入顺序
如果此时再插入一条主键值为 9 的记录,那它插入的位置就如下图:
可这个数据页已经满了,再插进来咋办呢?我们需要把当前 页面分裂 成两个页面,把本页中的一些记录移动到新创建的这个页中。页面分裂和记录移位意味着什么?意味着:性能损耗 !所以如果我们想尽量避免这样无谓的性能损耗,最好让插入的记录的 主键值依次递增 ,这样就不会发生这样的性能损耗了。所以我们建议:让主键具有 AUTO_INCREMENT ,让存储引擎自己为表生成主键,而不是我们手动插入 。
1.4 计算、函数、类型转换(自动或手动)导致索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
创建索引
CREATE INDEX idx_name ON student(NAME);
1.5 类型转换导致索引失效
下列哪个sql语句可以用到索引。(假设name字段上设置有索引)
# 未使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name=123;
# 使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name='123';
1.6 范围条件右边的列索引失效
ALTER TABLE student DROP INDEX idx_name; ALTER TABLE student DROP INDEX idx_age;
ALTER TABLE student DROP INDEX idx_age_classid;
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;
create index idx_age_name_classid on student(age,name,classid);
将范围查询条件放置语句最后:
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name = 'abc' AND student.classId>20 ;
1.7 不等于(!= 或者<>)索引失效
1.8 is null可以使用索引,is not null无法使用索引
结论:最好在设计数据表的时候就将 字段设置为NOT NULL 约束 比如你可以将INT类型设置为0。将字符类型的默认值设置值为空字符串(“”)应用层面实现 |
# 未使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100;
#使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR name = 'Abel';
1.11 数据库和表的字符集统一使用utf8mb4
1.12 建议
- 对于单列索引,尽量选择针对当前query过滤性更好的索引
- 在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
- 在选择组合索引的时候,尽量选择能够包含当前query中的where子句中更多字段的索引。
- 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面
2.关联查询优化
— 未完待续
— 常用的
w
●
总结 有一些程序员喜欢使用ROOT超级用户来访问数据库,完全把 权限控制 放在 应用层面实现 |
[mysqld]
default_password_lifetime=180 #建立全局策略,设置密码每隔180天过期