MySQL8.0高级篇(上)-架构与索引

文章目录

一、MySQL环境安装与介绍

1、MySQL安装

1.1 安装前说明

# 如果你是用rpm安装, 检查一下RPM PACKAGE
rpm -qa | grep -i mysql # -i 忽略大小写
# 检查mysql service
systemctl status mysqld.service

# MySQL的卸载
# 关闭 mysql 服务
systemctl stop mysqld.service
rpm -qa | grep -i mysql
# 查看当前 mysql 安装状况
yum list installed | grep mysql
# 卸载上述命令查询出的已安装程序
yum remove mysql-xxx mysql-xxx mysql-xxx mysqk-xxxx
# 务必卸载干净,反复执行rpm -qa | grep -i mysql确认是否有卸载残留

# 删除 mysql 相关文件
find / -name mysql
rm -rf xxx
# 删除 my.cnf
rm -rf /etc/my.cnf

1.2 MySQL的Linux版安装

官网下载地址:https://dev.mysql.com/downloads/

# 检查/tmp临时目录权限(必不可少)
# 由于mysql安装过程中,会通过mysql用户在/tmp目录下新建tmp_db文件,所以请给/tmp较大的权限
chmod -R 777 /tmp
# 安装前,检查依赖
rpm -qa|grep libaio
rpm -qa|grep net-tools
# ========================离线安装=====================
# https://dev.mysql.com/downloads/mysql/
# CentOS7下MySQL安装过程
# 将安装程序拷贝到/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
# rpm是Redhat Package Manage缩写,通过RPM的管理,用户可以把源代码包装成以rpm为扩展名的文件形式,易于安装。
# -i, –install 安装软件包
# -v, –verbose 提供更多的详细信息输出
# -h, –hash 软件包安装的时候列出哈希标记 (和 -v 一起使用效果更好),展示进度条
# 若存在mariadb-libs问题,则执行yum remove mysql-libs即可
# 服务的初始化
# 为了保证数据库目录与文件的所有者为 mysql 登录用户,如果你是以 root 身份运行 mysql 服务,需要执行下面的命令初始化
mysqld --initialize --user=mysql
# 说明: –initialize 选项默认以“安全”模式来初始化,则会为 root 用户生成一个密码并将该密码标记为过期,登录后你需要设置一个新的密码。生成的临时密码会往日志中记录一份
# ======================在线安装==========================
wget -i -c https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm
yum -y install mysql80-community-release-el7-7.noarch.rpm
yum -y install mysql-community-server

# 查看MySQL版本
mysql --version 
mysqladmin --version

# 启动mysql,加不加.service后缀都可以 
systemctl start mysqld.service 
systemctl stop mysqld.service 
systemctl restart mysqld.service 
systemctl status mysqld.service

# 查看MySQL服务是否自启动
systemctl list-unit-files|grep mysqld.service
# 开机自启
systemctl enable mysqld.service
# 关闭开机自启
systemctl disable mysqld.service

1.3 MySQL登录

# 查看临时密码
cat /var/log/mysqld.log
# 输入密码进行登录
mysql -hlocalhost -P3306 -uroot -p
# 首次登录需要更改密码,注意这里有密码检验,可以先设置这样(mysql8默认开启了安全加密插件模块)
ALTER USER 'root'@'localhost' IDENTIFIED BY 'ROOTroot123?';
# 查看MySQL完整的初始密码规则
SHOW VARIABLES LIKE 'validate_password%';
# 修改密码最小长度
set global validate_password.length=4;
# 验证密码的复杂程度,我们把它改成0,也可以是low/high/medium
set global validate_password.policy=0;
# 用户名检查,用户名和密码不能相同,我们也把它去掉
set global validate_password.check_user_name=off;
# 最后就可以改成简单的密码(生产环境要复杂)
ALTER USER 'root'@'localhost' IDENTIFIED BY 'root'

# 设置远程访问
# 在远程机器上使用telnet命令保证端口号开放访问
# telnet 192.168.249.139 3306
# 开启防火墙
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
# 修改允许远程登陆
use mysql;
select Host,User from user;
update user set host = '%' where user ='root';
flush privileges;
# 或者重新创建一个账号
CREATE USER 'root'@'%' IDENTIFIED BY 'root';
GRANT ALL ON *.* TO 'root'@'%'; 
# %是个 通配符 ,如果Host=192.168.1.%,那么就表示只要是IP地址前缀为“192.168.1.”的客户端都可以连接。如果Host=%,表示所有IP都有连接权限。
# 注意:在生产环境下不能为了省事将host设置为%,这样做会存在安全问题,具体的设置可以根据生产环境的IP进行设置。

# 如果可视化工具配置新连接报错:错误号码 2058,分析是 mysql 密码加密方法变了
# 解决方法一:升级远程连接工具版本
# 解决方法二:ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root';

1.4 字符集的相关操作

  • character_set_server:服务器级别的字符集
  • character_set_database:当前数据库的字符集
  • character_set_client:服务器解码请求时使用的字符集
  • character_set_connection:服务器处理请求时会把请求字符串从character_set_client转为character_set_connection
  • character_set_results:服务器向客户端返回数据时使用的字符集
show variables like 'character%';
  • 如果创建或修改列时没有显式的指定字符集和比较规则,则该列默认用表的字符集和比较规则
  • 如果创建表时没有显式的指定字符集和比较规则,则该表默认用数据库的字符集和比较规则
  • 如果创建数据库时没有显式的指定字符集和比较规则,则该数据库默认用服务器的字符集和比较规则
# 在mysql5.7之前的版本中,可以直接在/etc/my.cnf修改
character_set_server=utf8
# 然后重启即可,不过影响的是以后创建的数据库,不影响原来的
# 更改之前表的字符集
alter table xxx convert to character set 'utf8';
# 也可以在表或字段创建的时候指定,不过一般直接默认
CREATE TABLE 表名 (列的信息)
[[DEFAULT] CHARACTER SET 字符集名称]
[COLLATE 比较规则名称]]


# utf8相当于utf8mb3,三个字节,一般也够用
# utf8mb4四个字节,可以存储emoji

1.5 字符集与比较规则(了解)

  • utf8mb3 :阉割过的utf8 字符集,只使用1~3个字节表示字符
  • utf8mb4 :正宗的utf8 字符集,使用1~4个字节表示字符,可以描述emoji表情

MySQL版本一共支持41种字符集,其中的Default collation 列表示这种字符集中一种默认的比较规则,里面包含着该比较规则主要作用于哪种语言,比如utf8_polish_ci 表示以波兰语的规则比较, utf8_spanish_ci 是以西班牙语的规则比较, utf8_general_ci 是一种通用的比较规则

后缀英文释义描述
_aiaccent insensitive不区分重音
_asaccent sensitive区分重音
_cicase insensitive不区分大小写
_cscase sensitive区分大小写
_binbinary以二进制方式比较
#查看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 dbtest1 DEFAULT CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

#查看表的字符集
show create table employees;
#查看表的比较规则
show table status from atguigudb like 'employees';
#修改表的字符集和比较规则
ALTER TABLE emp1 DEFAULT CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

1.6 请求到响应过程中字符集的变化

系统变量描述系统变量描述
character_set_client服务器解码请求时使用的字符集
character_set_connection服务器处理请求时会把请求字符串从
character_set_client 转为character_set_connection
character_set_results服务器向客户端返回数据时使用的字符集
graph TB
A(客户端) --> |"使用操作系统的字符集编码请求字符串"| B(从character_set_client转换为character_set_connection)
B --> C(从character_set_connection转换为具体的列使用的字符集)
C --> D(将查询结果从具体的列上使用的字符集转换为character_set_results)
D --> |"使用操作系统的字符集解码响应的字符串"| A

第01章_Linux下MySQL的安装与使用.jpg

1.7 SQL大小写规范

在 SQL 中,关键字和函数名是不用区分字母大小写的,比如 SELECT、WHERE、ORDER、GROUP BY 等关键字,以及 ABS、MOD、ROUND、MAX 等函数名。不过在 SQL 中,你还是要确定大小写的规范,因为在 Linux 和 Windows 环境下,你可能会遇到不同的大小写问题。windows系统默认大小写不敏感,但是linux系统是大小写敏感的

# 默认为0,大小写敏感
# 设置1,大小写不敏感。创建的表,数据库都是以小写形式存放在磁盘上,对于sql语句都是转换为小写对表和数据库进行查
# 设置2,创建的表和数据库依据语句上格式存放,凡是查找都是转换为小写进行
SHOW VARIABLES LIKE '%lower_case_table_names%'

# 当想设置为大小写不敏感时,要在my.cnf 这个配置文件 [mysqld] 中加入lower_case_table_names=1 ,然后重启服务器
# 然后需要将原来的数据库都删除
# 建议不要改

# ===================建议=====================
# 关键字和函数名称全部大写;
# 数据库名、表名、表别名、字段名、字段别名等全部小写;
# SQL 语句必须以分号结尾。

1.8 sql_mode的合理设置

宽松模式:

如果设置的是宽松模式,那么我们在插入数据的时候,即便是给了一个错误的数据,也可能会被接受,并且不报错。

举例:我在创建一个表时,该表中有一个字段为name,给name设置的字段类型时char(10) ,如果我在插入数据的时候,其中name这个字段对应的有一条数据的**长度超过了10 **,例如’1234567890abc’,超过了设定的字段长度10,那么不会报错,并且取前10个字符存上,也就是说你这个数据被存为了’1234567890’,而’abc’就没有了。但是,我们给的这条数据是错误的,因为超过了字段长度,但是并没有报错,并且mysql自行处理并接受了,这就是宽松模式的效果。

应用场景:通过设置sql mode为宽松模式,来保证大多数sql符合标准的sql语法,这样应用在不同数据库之间进行迁移时,则不需要对业务sql 进行较大的修改

严格模式:

出现上面宽松模式的错误,应该报错才对,所以MySQL5.7版本就将sql_mode默认值改为了严格模式。所以在生产等环境中,我们必须采用的是严格模式,进而开发、测试环境的数据库也必须要设置,这样在开发测试阶段就可以发现问题。并且我们即便是用的MySQL5.6,也应该自行将其改为严格模式。

开发经验:MySQL等数据库总想把关于数据的所有操作都自己包揽下来,包括数据的校验,其实开发中,我们应该在自己开发的

目程序级别将这些校验给做了,虽然写项目的时候麻烦了一些步骤,但是这样做之后,我们在进行数据库迁移或者在项目的迁移时,就会方便很多。改为严格模式后可能会存在的问题:若设置模式中包含了NO_ZERO_DATE ,那么MySQL数据库不允许插入零日期,插入零日期会抛出错误而不是警告。例如,表中含字段TIMESTAMP列(如果未声明为NULL或显示DEFAULT子句)将自动分配DEFAULT ‘0000-00-00 00:00:00’(零时间戳),这显然是不满足sql_mode中的NO_ZERO_DATE而报错

-- 宽松模式举例
select * from employees group by department_id limit 10;
set sql_mode = ONLY_FULL_GROUP_BY;
select * from employees group by department_id limit 10;

-- 查看当前的sql_mode
select @@session.sql_mode
select @@global.sql_mode
show variables like 'sql_mode';

-- 临时设置方式:设置当前窗口中设置sql_mode
SET GLOBAL sql_mode = 'modes...'; 
SET SESSION sql_mode = 'modes...';

-- 改为严格模式。此方法只在当前会话中生效,关闭当前会话就不生效了。
set SESSION sql_mode='STRICT_TRANS_TABLES';
-- 改为严格模式。此方法在当前服务中生效,重启MySQL服务后失效。
set GLOBAL sql_mode='STRICT_TRANS_TABLES';

-- 永久设置方式:在/etc/my.cnf中配置sql_mode
-- 在my.cnf文件(windows系统是my.ini文件),新增,最后重启即可
[mysqld]
sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR
_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

2、MySQL的数据目录

2.1 MySQL8的主要目录结构

find / -name mysql
# /var/lib/mysql/
show variables like 'datadir'; 
# 相关命令目录:/usr/bin 和/usr/sbin。
# 配置文件目录:/usr/share/mysql-8.0(命令及配置文件),/etc/mysql(如my.cnf)

2.2 查看默认数据库

  • mysql

MySQL 系统自带的核心数据库,它存储了MySQL的用户账户和权限信息,一些存储过程、事件的定义信 息,一些运行过程中产生的日志信息,一些帮助信息以及时区信息等。

  • information_schema

MySQL 系统自带的数据库,这个数据库保存着MySQL服务器维护的所有其他数据库的信息,比如有哪些表、哪些视图、哪些触发器、哪些列、哪些索引。这些信息并不是真实的用户数据,而是一些描述性信息,有时候也称之为元数据。在系统数据库information_schema 中提供了一些以innodb_sys 开头的表,用于表示内部系统表。SHOW TABLES LIKE 'innodb_sys%';

  • performance_schema

MySQL 系统自带的数据库,这个数据库里主要保存MySQL服务器运行过程中的一些状态信息,可以用来监控 MySQL 服务的各类性能指标。包括统计最近执行了哪些语句,在执行过程的每个阶段都花费了多长时间,内存的使用情况等信息。

  • sys

MySQL 系统自带的数据库,这个数据库主要是通过视图的形式把information_schemaperformance_schema 结合起来,帮助系统管理员和开发人员监控 MySQL 的技术性能。

2.3 InnoDB存储引擎模式

cd /var/lib/mysql
ll
# 这个数据目录下的文件和子目录比较多,除了information_schema 这个系统数据库外,其他的数据库在数据目录下都有对应的子目录。

为了保存表结构,InnoDB在数据目录下对应的数据库子目录下创建了一个专门用于描述表结构的文件,即表名.frm

系统表空间(system tablespace)

默认情况下,InnoDB会在数据目录下创建一个名为ibdata1、大小为12M的自拓展文件,这个文件就是对应的系统表空间在文件系统上的表示。如果你想让系统表空间对应文件系统上多个实际文件,或者仅仅觉得原来的ibdata1 这个文件名难听,那可以在MySQL启动时配置对应的文件路径以及它们的大小,比如我们这样修改一下my.cnf 配置文件:

[server]
innodb_data_file_path=data1:512M;data2:512M:autoextend

独立表空间(file-per-table tablespace)

在MySQL5.6.6以及之后的版本中,InnoDB并不会默认的把各个表的数据存储到系统表空间中,而是为每一个表建立一个独立表空间,也就是说我们创建了多少个表,就有多少个独立表空间。使用独立表空间来存储表数据的话,会在该表所属数据库对应的子目录下创建一个表示该独立表空间的文件,文件名和表名相同,只不过添加了一个.ibd 的扩展名而已,所以完整的文件名称长这样:表名.ibd

系统表空间与独立表空间的设置

我们可以自己指定使用系统表空间还是独立表空间来存储数据,这个功能由启动参数innodb_file_per_table 控制,比如说我们想刻意将表数据都存储到系统表空间时,可以在启动MySQL服务器的时候这样配置:

[server]
innodb_file_per_table=0 # 0:代表使用系统表空间; 1:代表使用独立表空间

# show variables like 'innodb_file_per_table';

2.4 MyISAM存储引擎模式

  • test.frm 存储表结构(MySQL8.0 改为了 b.xxx.sdi )
  • test.MYD 存储数据 (MYData)
  • test.MYI 存储索引 (MYIndex)

2.5 小结

举例:数据库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.xxx.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语句"
  • -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 '密码']];
-- 用户名参数表示新建用户的账户,由用户(User) 和主机名(Host) 构成;
-- “[ ]”表示可选,也就是说,可以指定用户登录时需要密码验证,也可以不指定密码验证,这样用户可以直接登录。不过,不指定密码的方式不安全,不推荐使用。如果指定密码值,这里需要使用IDENTIFIED BY指定明文密码值。
-- CREATE USER语句可以同时创建多个用户

-- 举例默认host是 %
CREATE USER zhang3 IDENTIFIED BY '123123'; 
CREATE USER 'kangshifu'@'localhost' IDENTIFIED BY '123456';

-- ===========================修改用户=========================
UPDATE mysql.user SET USER='li4' WHERE USER='wang5';
FLUSH PRIVILEGES;

-- ===========================删除用户=========================
-- 使用DROP方式删除(推荐)
DROP USER user[,user];
-- 举例,默认删除host为%的用户
DROP USER li4 ;
-- 方式2:使用DELETE方式删除
DELETE FROM mysql.user WHERE Host='hostname' AND User='username';
FLUSH PRIVILEGES;
-- 不推荐通过DELETE FROM USER u WHERE USER='li4' 进行删除,系统会有残留信息保留。而drop user命令会删除用户以及对应的权限,执行命令后你会发现mysql.user表和mysql.db表的相应记录都消失了

1.3 设置用户密码

-- 设置当前用户密码
-- 旧的写法如下(不推荐!!!!)
-- 修改当前用户的密码:(MySQL5.7测试有效)
SET PASSWORD = PASSWORD('123456');

-- 推荐写法
-- 方法一,使用ALTER USER命令来修改当前用户密码 用户可以使用ALTER命令来修改自身密码,如下语句代表修改当前登录用户的密码。
ALTER USER USER() IDENTIFIED BY 'new_password';
-- 方法二,使用SET语句来修改当前用户密码 使用root用户登录MySQL后,可以使用SET语句来修改密码,该语句会自动将密码加密后再赋给当前用户
SET PASSWORD='new_password';

-- 修改其它用户密码
-- 方法一,使用ALTER语句来修改普通用户的密码 可以使用ALTER USER语句来修改普通用户的密码
ALTER USER user [IDENTIFIED BY '新密码'][,user[IDENTIFIED BY '新密码']];
-- 方法二,使用SET命令来修改普通用户的密码 使用root用户登录到MySQL服务器后,可以使用SET语句来修改普通用户的密码
SET PASSWORD FOR 'username'@'hostname'='new_password';
-- 方法三,使用UPDATE语句修改普通用户的密码(不推荐)
UPDATE MySQL.user SET authentication_string=PASSWORD("123456") WHERE User = "username" AND Host = "hostname";

1.4 MySQL8密码管理(了解)

-- 密码过期策略
-- 在MySQL中,数据库管理员可以手动设置账号密码过期,也可以建立一个自动密码过期策略。
-- 过期策略可以是全局的,也可以为每个账号设置单独的过期策略
ALTER USER user PASSWORD EXPIRE;
ALTER USER 'kangshifu'@'localhost' PASSWORD EXPIRE;
-- 使用SQL语句更改该变量的值并持久化
-- 建立全局策略,设置密码每隔180天过期
SET PERSIST default_password_lifetime = 180; 
-- 配置文件my.cnf中进行维护
[mysqld]
default_password_lifetime=180 
-- 手动设置指定时间过期方式2:单独设置
-- 每个账号既可延用全局密码过期策略,也可单独设置策略。在CREATE USER 和ALTER USER 语句上加入PASSWORD EXPIRE 选项可实现单独设置策略
-- 设置kangshifu账号密码每90天过期:
CREATE USER 'kangshifu'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
ALTER USER 'kangshifu'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
-- 设置密码永不过期:
CREATE USER 'kangshifu'@'localhost' PASSWORD EXPIRE NEVER;
ALTER USER 'kangshifu'@'localhost' PASSWORD EXPIRE NEVER;
-- 延用全局密码过期策略:
CREATE USER 'kangshifu'@'localhost' PASSWORD EXPIRE DEFAULT;
ALTER USER 'kangshifu'@'localhost' PASSWORD EXPIRE DEFAULT;


-- 密码重用策略
-- 手动设置密码重用方式1:全局
-- 方式一,sql实现
-- 设置不能选择最近使用过的6个密码
SET PERSIST password_history = 6; 
-- 设置不能选择最近一年内的密码
SET PERSIST password_reuse_interval = 365; 
-- 方式二:my.cnf配置文件
[mysqld]
password_history=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 REUSE INTERVAL 365 DAY;
ALTER USER 'kangshifu'@'localhost' PASSWORD REUSE INTERVAL 365 DAY;
-- 既不能使用最近5个密码,也不能使用365天内的密码
CREATE USER 'kangshifu'@'localhost' PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 365 DAY;
ALTER USER 'kangshifu'@'localhost' PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 365 DAY;

2、权限管理

2.1 权限列表

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 权限操作

-- =======================授予权限==========================
-- 分别是通过把角色赋予用户给用户授权和直接给用户授权
-- 该权限如果发现没有该用户,则会直接新建一个用户
GRANT 权限1,权限2,…权限n ON 数据库名称.表名称 TO 用户名@用户地址 [IDENTIFIED BY '密码口令'];
-- 举例,给li4用户用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限
GRANT SELECT,INSERT,DELETE,UPDATE ON atguigudb.* TO li4@localhost ;
-- 授予通过网络方式登录的joe用户 ,对所有库所有表的全部权限,密码设为123。注意这里唯独不包括grant的权限
GRANT ALL PRIVILEGES ON *.* TO joe@'%' IDENTIFIED BY '123';
-- 如果结尾加了 WITH GRANT OPTION,该用户就可以将自己的权限分给他的子用户


-- =======================查看权限==========================
-- 查看当前用户权限
SHOW GRANTS;
SHOW GRANTS FOR CURRENT_USER;
SHOW GRANTS FOR CURRENT_USER();
-- 查看某用户的全局权限
SHOW GRANTS FOR 'user'@'主机地址' ;

-- =======================收回权限==========================
-- 收回权限就是取消已经赋予用户的某些权限。收回用户不必要的权限可以在一定程度上保证系统的安全性。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;
-- 注意: 须用户重新登录后才能生效

3、权限表

3.1 user表

user表是MySQL中最重要的一个权限表,** 记录用户账号和权限信息,有49个字段。

第03章_用户与权限管理.jpg

这些字段可以分成4类,分别是范围列(或用户列)、权限列、安全列和资源控制列
范围列(或用户列)

  • 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 字段

权限列

  • Grant_priv字段
    • 表示是否拥有GRANT权限
  • Shutdown_priv字段
    • 表示是否拥有停止MySQL服务的权限
  • Super_priv字段
    • 表示是否拥有超级权限
  • Execute_priv字段
    • 表示是否拥有EXECUTE权限。拥有EXECUTE权限,可以执行存储过程和函数。
  • Select_priv , Insert_priv等
    • 为该用户所拥有的权限

安全列

安全列只有6个字段,其中两个是ssl相关的(ssl_type、ssl_cipher),用于加密;两个是x509相关的(x509_issuer、x509_subject),用于标识用户;另外两个Plugin字段用于验证用户身份的插件,该字段不能为空。如果该字段为空,服务器就使用内建授权验证机制验证用户身份

资源控制列

资源控制列的字段用来限制用户使用的资源,包含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表用来对表的某一列设置权限desc mysql.tables_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
desc mysql.columns_priv;

3.4 procs_priv表

rocs_priv表可以对存储过程和存储函数设置操作权限desc mysql.procs_priv;

4、访问控制(了解)

4.1 连接核实阶段

当用户试图连接MySQL服务器时,服务器基于用户的身份以及用户是否能提供正确的密码验证身份来确定接受或者拒绝连接。即客户端用户会在连接请求中提供用户名、主机地址、用户密码,MySQL服务器接收到用户请求后,会使用user表中的host、user和authentication_string这3个字段匹配客户端提供信息

服务器只有在user表记录的Host和User字段匹配客户端主机名和用户名,并且提供正确的密码时才接受连接。如果连接核实没有通过,服务器就完全拒绝访问;否则,服务器接受连接,然后进入阶段2等待用户请求

4.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 角色的理解

引入角色的目的是方便管理拥有相同权限的用户

image.png

5.2 角色常见操作

-- ========================创建角色===========================
-- 创建角色使用CREATE ROLE 语句
CREATE ROLE 'role_name'[@'host_name'] [,'role_name'[@'host_name']]
-- 如果host_name省略,默认为% , role_name不可省略,不可为空
-- 举例
CREATE ROLE 'manager'@'localhost';

-- ========================赋予角色权限===========================
GRANT privileges ON table_name TO 'role_name'[@'host_name'];
SHOW PRIVILEGES\G;
-- 举例
GRANT SELECT ON demo.settlement TO 'manager';

-- ========================查看角色权限===========================
SHOW GRANTS FOR 'manager';
-- 只要你创建了一个角色,系统就会自动给你一个“ USAGE ”权限,意思是连接登录数据库的权限。
-- 代码的最后三行代表了我们给角色“manager”赋予的权限,也就是对商品信息表、盘点表和应付账款表的只读权限。
-- 结果显示,库管角色拥有商品信息表的只读权限和盘点表的增删改查权限。

-- ========================回收角色权限===========================
REVOKE privileges ON tablename FROM 'rolename';
-- 举例,撤销school_write角色的权限。
REVOKE INSERT, UPDATE, DELETE ON school.* FROM 'school_write';
-- 撤销后使用SHOW语句查看school_write对应的权限
SHOW GRANTS FOR 'school_write';

-- ========================删除角色===========================
DROP ROLE role [,role2]
-- 如果你删除了角色,那么用户也就失去了通过这个角色所获得的所有权限

5.3 角色权限赋予

-- ========================给用户赋予角色===========================
-- 角色创建并授权后,要赋给用户并处于激活状态才能发挥作用。给用户添加角色可使用GRANT语句
GRANT role [,role2,...] TO user [,user2,...];
-- 举例
GRANT 'school_read' TO 'kangshifu'@'localhost';
HOW GRANTS FOR 'kangshifu'@'localhost';
-- 查询当前角色
SELECT CURRENT_ROLE();

-- ========================激活角色===========================
-- 方式1:使用set default role 命令激活角色
SET DEFAULT ROLE ALL TO 'kangshifu'@'localhost';
-- 方式2:将activate_all_roles_on_login设置为ON
-- 对所有角色永久激活
show variables like 'activate_all_roles_on_login';
SET GLOBAL activate_all_roles_on_login=ON;

-- ========================撤销用户的角色===========================
REVOKE role FROM user;
-- 举例
REVOKE 'school_read' FROM 'kangshifu'@'localhost';
SHOW GRANTS FOR 'kangshifu'@'localhost';

-- ========================设置强制角色(mandatory role)===========================
-- 给每个用户创建的默认角色,不需要手动设置,无法被回收和删除
-- 服务启动前设置
[mysqld]
mandatory_roles='role1,role2@localhost,r3@%.atguigu.com'
-- 运行时设置
-- 系统重启后仍然有效
SET PERSIST mandatory_roles = 'role1,role2@localhost,r3@%.example.com';
-- 系统重启后失效
SET GLOBAL mandatory_roles = 'role1,role2@localhost,r3@%.example.com'; 

三、逻辑架构与存储引擎

1、逻辑架构剖析

1.1 服务器处理客户端请求

MySQL是典型的C/S架构,即Clinet/Server 架构,服务端程序使用的mysqld。客户端进程向服务器进程发送一段文本(SQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)

image-20220615133227202.png

1.2 Connectors

Connectors,指的是不同语言中与SQL的交互。MySQL首先是一个网络程序,在TCP之上定义了自己的应用层协议。所以要使用MySQL,我们可以编写代码,跟MySQL Server 建立TCP连接,之后按照其定义好的协议进行交互。或者比较方便的方法是调用SDK,比如Native C API、JDBC、PHP等各语言MySQL Connecotr,或者通过JDBC。但通过SDK来访问MySQL,本质上还是在TCP连接上通过MySQL协议跟MySQL进行交互,MySQL Server结构可以分为三层:连接层、服务层、引擎层

1.3 第一层:连接层

系统(客户端)访问 MySQL 服务器前,做的第一件事就是建立 TCP 连接。 经过三次握手建立连接成功后, MySQL 服务器对 TCP 传输过来的账号密码做身份认证、权限获取。

  • 用户名或密码不对,会收到一个Access denied for user错误,客户端程序结束执行
  • 用户名密码认证通过,会从权限表查出账号拥有的权限与连接关联,之后的权限判断逻辑,都将依赖于此时读到的权限

TCP 连接收到请求后,必须要分配给一个线程专门与这个客户端的交互。所以还会有个线程池,去走后面的流程。每一个连接从线程池中获取线程,省去了创建和销毁线程的开销。所以连接管理的职责是负责认证、管理连接、获取权限信息。

1.4 第二层:服务层

第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化:如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是SELECT语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。

  • 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.5 第三层:引擎层

插件式存储引擎层( Storage Engines),真正的负责了MySQL中数据的存储和提取,对物理服务器级别维护的底层数据执行操作,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样 我们可以根据自己的实际需要进行选取。可以通过show engines;查看

1.6 存储层

所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在文件系统 上,以文件的方式存在的,并完成与存储引擎的交互。当然有些存储引擎比如InnoDB,也支持不使用文件系统直接管理裸设备,但现代文件系统的实现使得这样做没有必要了。在文件系统之下,可以使用本地磁盘,可以使用 DAS、NAS、SAN等各种存储系统。

2、SQL执行流程

2.1 MySQL中的SQL执行流程

image-20220615141934531.png
1、查询缓存

Server 如果在查询缓存中发现了这条 SQL 语句,就会直接将结果返回给客户端;如果没 有,就进入到解析器阶段。需要说明的是,因为查询缓存往往效率不高,所以在 MySQL8.0 之后就抛弃了这个功能

# 一般建议大家在静态表里使用查询缓存,例如系统配置表,MySQL也提供了这种“`按需使用`”的方式。
#可以将 my.cnf 参数 query_cache_type 设置成 DEMAND,代表当 sql 语句中有 SQL_CACHE关键字时才缓存
# query_cache_type 有3个值。 0代表关闭查询缓存OFF,1代表开启ON,2代表(DEMAND)
query_cache_type=2
SELECT SQl_CACHE * FROM test WHERE ID=5;
# MySQL5.7中,查看当前 mysql 实例是否开启缓存机制
show global variables like "%query_cache_type%";
# 监控查询缓存的命中率:
show status like '%Qcache%';

# Qcache_free_blocks: 表示查询缓存中海油多少剩余的blocks,如果该值显示较大,则说明查询缓存中的`内部碎片`过多了,可能在一定的时间进行整理。
#Qcache_free_memory: 查询缓存的内存大小,通过这个参数可以很清晰的知道当前系统的查询内存是否够用,DBA可以根据实际情况做出调整。
#Qcache_hits: 表示有 `多少次命中缓存`。我们主要可以通过该值来验证我们的查询缓存的效果。数字越大,缓存效果越理想。
#Qcache_inserts: 表示`多少次未命中然后插入`,意思是新来的SQL请求在缓存中未找到,不得不执行查询处理,执行查询处理后把结果insert到查询缓存中。这样的情况的次数越多,表示查询缓存应用到的比较少,效果也就不理想。当然系统刚启动后,查询缓存是空的,这也正常。
#Qcache_lowmem_prunes: 该参数记录有`多少条查询因为内存不足而被移除`出查询缓存。通过这个值,用户可以适当的调整缓存大小。
#Qcache_not_cached: 表示因为query_cache_type的设置而没有被缓存的查询数量。
#Qcache_queries_in_cache: 当前缓存中`缓存的查询数量`。
#Qcache_total_blocks: 当前缓存的block数量。

2、解析器

在解析器中对 SQL 语句进行语法分析、语义分析

分析器先做“ 词法分析 ”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面 的字符串分别是什么,代表什么。 MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语 句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。接着,要做“ 语法分析 ”。根据词法分析的结果,语法分析器(比如:Bison)会根据语法规则,判断你输 入的这个 SQL 语句是否 满足 MySQL 语法

3、优化器

在优化器中会确定 SQL 语句的执行路径,比如是根据 全表检索 ,还是根据 索引检索 等。

经过解释器,MySQL就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。比如:优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联 (join) 的时候,决定各个表的连接顺序,还有表达式简化、子查询转为连接、外连接转为内连接等。

在查询优化器中,可以分为 逻辑查询 优化阶段和 物理查询 优化阶段。逻辑查询优化就是通过改变SQL语句的内容来使得SQL查询更高效,同时为物理查询优化提供更多的候选执行计划。通常采用的方式是对SQL语句进行等价变换,对查询进行重写,而查询重写的数学基础就是关系代数。对条件表达式进行等价谓词重写、条件简化,对视图进行重写,对子查询进行优化,对连接语义进行了外连接消除、嵌套连接消除等。物理查询优化是基于关系代数进行的查询重写,而关系代数的每一步都对应着物理计算,这些物理计算往往存在多种算法,因此需要计算各种物理路径的代价,从中选择代价最小的作为执行计划。在这个阶段里,对于单表和多表连接的操作,需要高效地使用索引,提升查询效率

4、执行器

在执行之前需要判断该用户是否 具备权限 。如果没有,就会返回权限错误。如果具备权限,就执行 SQL 查询并返回结果。在 MySQL8.0 以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存

2.2 MySQL8中SQL执行原理

-- 了解查询语句底层执行的过程:`select @profiling` 或者 `show variables like '%profiling'` 查看是否开启计划。
-- 开启它可以让MySQL收集在SQL
select @@profiling;
show variables like 'profiling';
-- profiling=0 代表关闭,我们需要把 profiling 打开,即设置为 1
set profiling=1;
-- 然后我们执行一个 SQL 查询(你可以执行任何一个 SQL 查询)
select * from employees;
-- 查看当前会话所产生的所有 profiles
show profiles;
-- 显示执行计划,查看程序的执行步骤
show profile;
-- 查询指定的 Query ID
show profile for query 7;
-- 查询更丰富的内容
show profile cpu,block io for query 6;

-- 除了查看cpu、io阻塞等参数情况,还可以查询下列参数的利用情况
Syntax:
SHOW PROFILE [type [, type] ... ]
	[FOR QUERY n]
	[LIMIT row_count [OFFSET offset]]

type: {
	| ALL -- 显示所有参数的开销信息
	| BLOCK IO -- 显示IO的相关开销
	| CONTEXT SWITCHES -- 上下文切换相关开销
	| CPU -- 显示CPU相关开销信息
	| IPC -- 显示发送和接收相关开销信息
	| MEMORY -- 显示内存相关开销信息
	| PAGE FAULTS -- 显示页面错误相关开销信息
	| SOURCE -- 显示和Source_function,Source_file,Source_line 相关的开销信息
	| SWAPS -- 显示交换次数相关的开销信息
}

-- 发现两次查询当前情况都一致,说明没有缓存,在 8.0 版本之后,MySQL 不再支持缓存的查询
-- mysql5.7多了缓存,相同查询两次可能执行过程会不一样

3、数据库缓冲池(buffer pool)

InnoDB 存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页面(包括读页面、写页面、创建新页面等操作)。而磁盘 I/O 需要消耗的时间很多,而在内存中进行操作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS 会申请占用内存来作为数据缓冲池 ,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访问

3.1 缓冲池(Buffer Pool)

在 InnoDB 存储引擎中有一部分数据会放到内存中,缓冲池则占了这部分内存的大部分,它用来存储各种数据的缓存,包括了数据页、索引页、插入缓冲、锁信息、自适应 Hash 和数据字典信息

缓存原则

位置 * 频次”这个原则,可以帮我们对 I/O 访问效率进行优化。首先,位置决定效率,提供缓冲池就是为了在内存中可以直接访问数据。其次,频次决定优先级顺序。因为缓冲池的大小是有限的,比如磁盘有 200G,但是内存只有 16G,缓冲池大小只有 1G,就无法将所有数据都加载到缓冲池里,这时就涉及到优先级顺序,会优先对使用频次高的热数据进行加载;
**缓冲池的预读特性,**缓冲池的作用就是提升 I/O 效率,而我们进行读取数据的时候存在一个“局部性原理”,也就是说我们使用了一些数据,大概率还会使用它周围的一些数据,因此采用“预读”的机制提前加载,可以减少未来可能的磁盘 I/O 操作

3.2 缓冲池如何读取数据

冲池管理器会尽量将经常使用的数据保存起来,在数据库进行页面读操作的时候,首先会判断该页面 是否在缓冲池中,如果存在就直接读取,如果不存在,就会通过内存或磁盘将页面存放到缓冲池中再进行读取。

image-20220615193131719.png

如果我们执行 SQL 语句的时候更新了缓存池中的数据,那么这些数据会马上同步到磁盘上吗?

实际上,当我们对数据库中的记录进行修改的时候,首先会修改缓冲池中页里面的记录信息,然后数据库会以一定的频率刷新到磁盘中。注意并不是每次发生更新操作,都会立即进行磁盘回写。缓冲池会采用一种叫做** checkpoint 的机制 将数据回写到磁盘上,这样做的好处就是提升了数据库的整体性能。比如,当缓冲池不够用时**,需要释放掉一些不常用的页,此时就可以强行采用checkpoint的方式,将不常用的脏页回写到磁盘上,然后再从缓存池中将这些页释放掉。这里的脏页 (dirty page) 指的是缓冲池中被修改过的页,与磁盘上的数据页不一致。

3.3 查看/设置缓冲池

如果你使用的是 MySQL MyISAM 存储引擎,它只缓存索引,不缓存数据,对应的键缓存参数为key_buffer_size,你可以用它进行查看。如果你使用的是 InnoDB 存储引擎,可以通过查看 innodb_buffer_pool_size 变量来查看缓冲池的大小

-- 此时 InnoDB 的缓冲池大小只有 134217728/1024/1024=128MB
show variables like 'innodb_buffer_pool_size';
-- 我们可以修改缓冲池大小,比如改为256MB
set global innodb_buffer_pool_size = 268435456;
-- 或者配置文件修改
[server]
innodb_buffer_pool_size = 268435456

3.4 多个Buffer Pool实例

-- 修改多个实例
[server]
innodb_buffer_pool_instances = 2
-- 查看个数
show variables like 'innodb_buffer_pool_instances';
-- 每个 Buffer Pool 实例实际占多少内存空间
-- innodb_buffer_pool_size/innodb_buffer_pool_instances

不是说 Buffer Pool 实例创建的越多越好,分别管理各个 Buffer Pool 也是需要性能开销的,InnDB规定:当innodb_buffer_pool_size的值小于1G的时候设置多个实例是无效的,InnoDB会默认把innodb_buffer_pool_instances的值修改为1。而我们鼓励在 Buffer Pool 大于等于 1G 的时候设置多个 Buffer Pool 实例。

3.5 引申问题

当我们查询数据的时候,会先去 Buffer Pool 中查询。如果 Buffer Pool 中不存在,存储引擎会先将数据从磁盘加载到 Buffer Pool 中,然后将数据返回给客户端;同理,当我们更新某个数据的时候,如果这个数据不存在于 Buffer Pool,同样会先数据加载进来,然后修改内存的数据。被修改的数据会在之后统一刷入磁盘。

image-20220615222455867.png

更新到一半突然发生错误了,想要回滚到更新之前的版本,该怎么办?连数据持久化的保证、事务回滚都做不到还谈什么崩溃恢复?答案:Redo Log & Undo Log

4、存储引擎

4.1 查看与设置默认引擎

-- 查看mysql提供什么存储引擎
show engines;
-- 查看默认的存储引擎
show variables like '%storage_engine%';
SELECT @@default_storage_engine;

-- 修改默认的存储引擎
-- 如果在创建表的语句中没有显式指定表的存储引擎的话,那就会默认使用 InnoDB 作为表的存储引擎
SET DEFAULT_STORAGE_ENGINE=MyISAM;
-- 或者修改配置文件
default-storage-engine=MyISAM
-- 重启服务
systemctl restart mysqld.service

4.2 设置表的存储引擎

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

-- 我们之前创建表的语句都没有指定表的存储引擎,那就会使用默认的存储引擎 InnoDB,如果我们想显 式的指定一下表的存储引擎
CREATE TABLE 表名(
建表语句;
) ENGINE = 存储引擎名称;
-- 修改
ALTER TABLE 表名 ENGINE = 存储引擎名称;
ALTER TABLE engine_demo_table ENGINE = InnoDB;
-- 查看表结构
SHOW CREATE TABLE engine_demo_table\G

4.3 引擎介绍

InnoDB 引擎:具备外键支持功能的事务存储引擎

  • MySQL从3.23.34a开始就包含InnoDB存储引擎。 大于等于5.5之后,默认采用InnoDB引擎
  • InnoDB是MySQL的 默认事务型引擎 ,它被设计用来处理大量的短期(short-lived)事务。可以确保事务的完整提交(Commit)和回滚(Rollback)
  • 除了增加和查询外,还需要更新、删除操作,那么,应优先选择InnoDB存储引擎。 除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎
  • 数据文件结构:(在《第02章_MySQL数据目录》章节已讲)
    • 表名.frm 存储表结构(MySQL8.0时,合并在表名.ibd中)
    • 表名.ibd 存储数据和索引
  • InnoDB是 为处理巨大数据量的最大性能设计
    • 在以前的版本中,字典数据以元数据文件、非事务表等来存储。现在这些元数据文件被删除 了。比如: .frm , .par , .trn , .isl , .db.opt 等都在MySQL8.0中不存在了
  • 对比MyISAM的存储引擎, InnoDB写的处理效率差一些 ,并且会占用更多的磁盘空间以保存数据和索引
  • MyISAM只缓存索引,不缓存真实数据;InnoDB不仅缓存索引还要缓存真实数据, 对内存要求较 高 ,而且内存大小对性能有决定性的影响

MyISAM 引擎:主要的非事务处理存储引擎

  • MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务、行级 锁、外键 ,有一个毫无疑问的缺陷就是崩溃后无法安全恢复 。
  • 5.5之前默认的存储引擎
  • 优势是访问的速度快 ,对事务完整性没有要求或者以SELECT、INSERT为主的应用
  • 针对数据统计有额外的常数存储。故而 count(*) 的查询效率很高 数据文件结构:(在《第02章_MySQL数据目录》章节已讲)
    • 表名.frm 存储表结构
    • 表名.MYD 存储数据 (MYData)
    • 表名.MYI 存储索引 (MYIndex)
  • 应用场景:只读应用或者以读为主的业务

Archive 引擎:用于数据存档

image-20220616124743732.png

Blackhole 引擎:丢弃写操作,读操作会返回空内容

CSV 引擎:存储数据时,以逗号分隔各个数据项

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表中的数据如果突然间丢失的话也没有太大的关系

Federated 引擎:访问远程表

NDB引擎:MySQL集群专用存储引擎

也叫做 NDB Cluster 存储引擎,主要用于 MySQL Cluster 分布式集群 环境,类似于 Oracle 的 RAC 集 群

4.4 引擎对比

image-20220616125928861.png

四、索引与InnoDB数据存储结构

1、索引概述

1.1 索引作用

索引是存储引擎用于快速找到数据记录的一种数据结构,MySQL进行数据查找时,首先查看查询条件是否命中某条索引,符合则通过索引查找相关数据,如果不符合则需要全表扫描,即需要一条一条地查找记录,直到找到与条件符合的记录

image-20220616142723266.png

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。

索引的本质:索引是数据结构。你可以简单理解为“排好序的快速查找数据结构”,满足特定查找算法。 这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现 高级查找算法 。索引是在存储引擎中实现的,因此每种存储引擎的索引不一定完全相同,并且每种存储引擎不一定支持所有索引类型。同时,存储引擎可以定义每个表的 最大索引数和 最大索引长度。所有存储引擎支持每个表至少16个索引,总索引长度至少为256字节。有些存储引擎支持更多的索引数和更大的索引长度。

1.2 索引优点

  • 类似大学图书馆建书目索引,提高数据检索的效率,降低 数据库的IO成本 ,这也是创建索引最主 要的原因
  • 通过创建唯一索引,可以保证数据库表中每一行 数据的唯一性
  • 在实现数据的 参考完整性方面,可以 加速表和表之间的连接 。换句话说,对于有依赖关系的子表和父表联合查询时, 可以提高查询速度
  • 在使用分组和排序子句进行数据查询时,可以显著 减少查询中分组和排序的时间 ,降低了CPU的消耗

1.3 缺点

  • 创建索引和维护索引要 耗费时间 ,并 且随着数据量的增加,所耗费的时间也会增加
  • 索引需要占 磁盘空间 ,除了数据表占数据空间之 外,每一个索引还要占一定的物理空间, 存储在磁盘上 ,如果有大量的索引,索引文件就可能比数据文 件更快达到最大文件尺寸
  • 虽然索引大大提高了查询速度,同时却会 降低更新表的速度 。当对表 中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。 因此,选择使用索引时,需要综合考虑索引的优点和缺点
  • 索引可以提高查询的速度,但是会影响插入记录的速度。这种情况下,最好的办法是先删除表中的索引,然后插入数据,插入完成后再创建索引

2、InnoDB中索引的推演

2.1 索引之前的查找

假设目前表中的记录比较少,所有的记录都可以被存放到一个页中,在查找记录的时候可以根据搜索条件的不同分为两种情况:

  • 以主键为搜索条件,可以在页目录中使用 二分法 快速定位到对应的槽,然后再遍历该槽对用分组中的记录即可快速找到指定记录。
  • 以其他列作为搜索条件,因为在数据页中并没有对非主键列简历所谓的页目录,所以我们无法通过二分法快速定位相应的槽。这种情况下只能从 最小记录 开始 依次遍历单链表中的每条记录, 然后对比每条记录是不是符合搜索条件。很显然,这种查找的效率是非常低的。

很多页中查找记录的活动可以分为两个步骤:

  • 定位到记录所在的页
  • 从所在的页内中查找相应的记录

在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能 从第一个页沿着双向链表 一直往下找,在每一个页中根据我们上面的查找方式去查 找指定的记录。因为要遍历所有的数据页,所以这种方式显然是 超级耗时 的。如果一个表有一亿条记录呢?此时 索引 应运而生

2.2 设计索引

首先建一个表

mysql> CREATE TABLE index_demo(
-> c1 INT,
-> c2 INT,
-> c3 CHAR(1),
-> PRIMARY KEY(c1)
-> ) ROW_FORMAT = Compact;

这个新建的 index_demo 表中有2个INT类型的列,1个CHAR(1)类型的列,而且我们规定了c1列为主键, 这个表使用 Compact 行格式来实际存储记录的。这里我们简化了index_demo表的行格式示意图

image-20220616152453203.png

随着数据量的增大(一页一般是16K),存放数据远远大于一页,这就需要**给所有的页建立一个目录项,**每个页对应一个目录项,每个目录项包括下边两个部分:页的用户记录中最小的主键值,我们用 key 来表示;页号,我们用 page_on 表示。至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为 索引

image-20220616160857381.png

InnoDB怎么区分一条记录是普通的 用户记录 还是 目录项记录 呢?使用记录头信息里的 record_type 属性,它的各自取值代表的意思如下:0:普通的用户记录;1:目录项记录;2:最小记录;3:最大记录

image-20220616162944404.png

从图中可以看出来,我们新分配了一个编号为30的页来专门存储目录项记录。这里再次强调 目录项记录 和普通的 用户记录 的不同点:

  • 目录项记录 的 record_type 值是1,而 普通用户记录 的 record_type 值是0。
  • 目录项记录只有 主键值和页的编号 两个列,而普通的用户记录的列是用户自己定义的,可能包含 很多列 ,另外还有InnoDB自己添加的隐藏列。
  • 了解:记录头信息里还有一个叫 min_rec_mask 的属性,只有在存储 目录项记录 的页中的主键值最小的 目录项记录min_rec_mask 值为 1 ,其他别的记录的 min_rec_mask 值都是 0

相同点:两者用的是一样的数据页,都会为主键值生成 Page Directory (页目录),从而在按照主键值进行查找时可以使用 二分法 来加快查询速度。现在以查找主键为 20 的记录为例,根据某个主键值去查找记录的步骤就可以大致拆分成下边两步:

  • 先到存储 目录项记录 的页,也就是页30中通过 二分法 快速定位到对应目录项,因为 12 < 20 < 209 ,所以定位到对应的记录所在的页就是页9。
  • 再到存储用户记录的页9中根据 二分法 快速定位到主键值为 20 的用户记录

随着数据量增大,如果我们表中的数据非常多则会产生很多存储目录项记录的页,那我们怎么根据主键值快速定位一个存储目录项记录的页呢?那就为这些存储目录项记录的页再生成一个更高级的目录,就像是一个多级目录一样,大目录里嵌套小目录,小目录里才是实际的数据,所以现在各个页的示意图就是这样子;这个数据结构,它的名称是 B+树

image-20220616173512780.png

对于**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 条记录。相当多的记录

你的表里能存放 100000000000 条记录吗?所以一般情况下,我们用到的 B+树都不会超过4层 ,那我们通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页),又因为在每个页面内有所谓的 Page Directory (页目录),所以在页面内也可以通过 二分法 实现快速 定位记录。

2.3 常见索引概念

索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)和非聚簇(非聚集)索引。我们也把非聚集 索引称为二级索引或者辅助索引。

聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(所有的用户记录都存储在了叶子结点),也就是所谓的 索引即数据,数据即索引。术语"聚簇"表示当前数据行和相邻的键值聚簇的存储在一起
特点:

  • 使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:
    • 页内 的记录是按照主键的大小顺序排成一个 单向链表
    • 各个存放 用户记录的页 也是根据页中用户记录的主键大小顺序排成一个 双向链表
    • 存放 目录项记录的页 分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个 双向链表
  • B+树的 叶子节点 存储的是完整的用户记录。所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。

我们把具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引的叶子节点处。这种聚簇索引并不需要我们在MySQL语句中显式的使用INDEX 语句去创建, InnDB 存储引擎会 自动 的为我们创建聚簇索引。
优点:

  • 数据访问更快 ,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
  • 聚簇索引对于主键的 排序查找范围查找 速度非常快
  • 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多 个数据块中提取数据,所以 节省了大量的io操作

缺点:

  • 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
  • 更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
  • 二级索引访问需要两次索引查找 ,第一次找到主键值,第二次根据主键值找到行数据
二级索引(辅助索引、非聚簇索引)

如果我们想以别的列作为搜索条件,我们可以多建几颗B+树,不同的B+树中的数据采用不同的排列规则。比方说我们用c2列的大小作为数据页、页中记录的排序规则,再建一课B+树,

image-20220616203852043.png

这个B+树与上边介绍的聚簇索引有几处不同

image-20220616210404733.png

**概念:回表 **

我们根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根 据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就 是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树

小结

  • 聚簇索引的叶子节点存储的就是我们的数据记录, 非聚簇索引的叶子节点存储的是数据位置。非聚簇索引不会影响数据表的物理存储顺序。
  • 一个表只能有一个聚簇索引,因为只能有一种排序存储的方式,但可以有多个非聚簇索引,也就是多个索引目录提供数据检索。
  • 使用聚簇索引的时候,数据的查询效率高,但如果对数据进行插入,删除,更新等操作,效率会比非聚簇索引低。
联合索引

我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按 照 c2和c3列 的大小进行排序,这个包含两层含义:

  • 先把各个记录和页按照c2列进行排序。
  • 在记录的c2列相同的情况下,采用c3列进行排序

image-20220616215251172.png

如图所示,我们需要注意以下几点:

  • 每条目录项都有c2、c3、页号这三个部分组成,各条记录先按照c2列的值进行排序,如果记录的c2列相同,则按照c3列的值进行排序
  • B+树叶子节点处的用户记录由c2、c3和主键c1列组成

注意一点,以c2和c3列的大小为排序规则建立的B+树称为 联合索引 ,本质上也是一个二级索引。它的意 思与分别为c2和c3列分别建立索引的表述是不同的,不同点如下:

  • 建立 联合索引 只会建立如上图一样的1棵B+树。
  • 为c2和c3列分别建立索引会分别以c2和c3列的大小为排序规则建立2棵B+树。

2.4 InnoDB的B+树索引的注意事项

实际上B+树的形成过程是这样的:

  • 每当为某个表创建一个B+树索引(聚簇索引不是人为创建的,默认就有)的时候,都会为这个索引创建一个 根结点 页面。最开始表中没有数据的时候,每个B+树索引对应的 根结点 中即没有用户记录,也没有目录项记录。
  • 随后向表中插入用户记录时,先把用户记录存储到这个根节点 中。
  • 当根节点中的可用 空间用完时 继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如 页a 中,然后对这个新页进行 页分裂 的操作,得到另一个新页,比如页b 。这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到 页a 或者 页b 中,而 根节点 便升级为存储目录项记录的页。

这个过程特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要我们对某个表建议一个索引,那么它的根节点的页号便会被记录到某个地方。然后凡是 InnoDB 存储引擎需要用到这个索引的时候,都会从哪个固定的地方取出根节点的页号,从而来访问这个索引

为了让新插入记录找到自己在那个页面,我们需要保证在B+树的同一层页节点的目录项记录除页号这个字段以外是唯一的。所以对于二级索引的内节点的目录项记录的内容实际上是由三个部分构成的:

  • 索引列的值
  • 主键值
  • 页号

最后一个B+树只需要很少的层级就可以轻松存储数亿条记录,查询速度相当不错!这是因为B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录,直到最后访问到存储真实数据的目录。那如果一个大的目录中只存放一个子目录是个啥效果呢?那就是目录层级非常非常多,而且最后的那个存放真实数据的目录中只存放一条数据。所以 InnoDB 的一个数据页至少可以存放两条记录

3、MyISAM中的索引方案

索引 / 存储引擎MyISAMInnoDBMemory
B-Tree索引支持支持支持

即使多个存储引擎支持同一种类型的索引,但是他们的实现原理也是不同的。Innodb和MyISAM默认的索 引是Btree索引;而Memory默认的索引是Hash索引。MyISAM引擎使用 B+Tree 作为索引结构,叶子节点的data域存放的是 数据记录的地址

3.1 MyISAM索引与总结

image-20220617160325201.png

image-20220617160413479.png

3.2 MyISAM 与 InnoDB对比

MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的。

  • 在InnoDB存储引擎中,我们只需要根据主键值对 聚簇索引 进行一次查找就能找到对应的记录,而在 MyISAM 中却需要进行一次 回表 操作,意味着MyISAM中建立的索引相当于全部都是 二级索引 。
  • InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是 分离的 ,索引文件仅保存数 据记录的地址。
  • InnoDB的非聚簇索引data域存储相应记录 主键的值 ,而MyISAM索引记录的是 地址 。换句话说, InnoDB的所有非聚簇索引都引用主键作为data域。
  • MyISAM的回表操作是十分 快速 的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通 过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
  • InnoDB要求表 必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个 可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐 含字段作为主键,这个字段长度为6个字节,类型为长整型

3.3 索引的代价

索引是个好东西,可不能乱建,它在空间和时间上都会有消耗:

  • 空间上的代价
    每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会 占用 16KB 的存储空间,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。
  • 时间上的代价
    每次对表中的数据进行 增、删、改 操作时,都需要去修改各个B+树索引。而且我们讲过,B+树每 层节点都是按照索引列的值 从小到大的顺序排序 而组成了 双向链表 。不论是叶子节点中的记录,还 是内节点中的记录(也就是不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序 而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需 要额外的时间进行一些 记录移位 , 页面分裂 、 页面回收 等操作来维护好节点和记录的排序。如果 我们建了许多索引,每个索引对应的B+树都要进行相关的维护操作,会给性能拖后腿。

一个表上索引建的越多,就会占用越多的存储空间,在增删改记录的时候性能就越差。为了能建立又好又少的索引,我们得学学这些索引在哪些条件下起作用的。

另外,InnoDB本身不支持 Hash索引,但是提供自适应 Hash索引(Adaptive Hash Index)。什么情况下才会使用自适应Hash索引呢?如果某个数据经常被访问,当满足一定条件的时候,就会将这个数据页的地址存放到Hash表中。这样下次查询的时候,就可以直接找到这个页面的所在位置。这样让B+树也具备了Hash索引的优点。自适应 Hash 索引目的是方便根据 SQL 的查询条件加速定位到叶子节点,特别是当 B+ 树比较深的时候,通过自适应 Hash 索引可以明显提高数据的检索效率。我们可以通过 innodb_adaptive_hash_index 变量来查看是否开启了自适应 Hash,比如:show variables like '%adaptive_hash_index';

3.4 思考题

为了减少IO,索引树会一次性加载吗?

  • 数据库索引是存储在磁盘上的,如果数据量很大,必然导致索引的大小也会很大,超过几个G。
  • 当我们利用索引查询时候,是不可能将全部几个G的索引都加载进内存的,我们能做的只能是:逐一加载每一个磁盘页,因为磁盘页对应着索引树的节点。

B+树的存储能力如何?为何说一般查找行记录,最多只需1~3次磁盘IO

InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为103。也就是说一个深度为3的B+Tree索引可以维护103*103*103=10亿条记录。(这里假定一个数据页也存储10^3条行记录数据了)实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree 的高度一般都在2~4层。MysQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1-3次磁盘I/O操作。

为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?

  • B+树的磁盘读写代价更低,B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说Io读写次数也就降低了。
  • B+树的查询效率更加稳定,由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

Hash 索引与 B+ 树索引的区别

  • Hash索引不能进行范围查询,而B+树可以。这是因为Hash索引指向的数据是无序的,而B+树的叶子节点是个有序的链表
  • Hash索引不支持联合索引的最左侧原则(即联合索引的部分索引无法使用),而B+树可以。对于联合索引来说,Hash索引在计算Hash值的时候是将索引键合并后再一起计算Hash值,所以不会针对每个索引单独计算Hash值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用
  • Hash索引不支持ORDER BY排序,因为Hash索引指向的数据是无序的,因此无法起到排序优化的作用,而主树索引数据是有序的,可以起到对该字段ORDER BY排序优化的作用。同理,我们也无法用Hash索引进行模糊查询,而B+树使用LIKE进行模糊查询的时候,LIKE后面后模糊查询(比如%结尾)的话就可以起到优化作用
  • InnoDB 不支持哈希索引

Hash 索引与 B+ 树索引是在建索引的时候手动指定的吗?

针对InnoDB和MyISAM存储引擎,都会默认采用B+树索引,无法使用Hash 索引。InnoDB提供的自适应Hash是不需要手动指定的。如果是Memory/Heap和NDB存储引擎,是可以进行选择Hash索引的。

R树概念

R-Tree在MySQL很少使用,仅支持 geometry数据类型 ,支持该类型的存储引擎只有myisam、bdb、 innodb、ndb、archive几种

4、InnoDB数据存储结构

4.1 数据库的存储结构:页

lnnoDB将数据划分为若干个页,InnoDB中页的大小默认为16KB,以页作为磁盘和内存之间交互的基本单位,也就是一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。也就是说,在数据库中,不论读一行,还是读多行,都是将这些行所在的页进行加载。也就是说,数据库管理存储空间的基本单位是页(Page),数据库Ⅳ/O操作的最小单位是页。一个页中可以存储多个行记录。

不同的数据库管理系统(简称DBMS)的页大小不同。比如在 MySQL 的 InnoDB 存储引擎中,默认页的大小是 16KB,我们可以通过下面的命令来进行查看:show variables like '%innodb_page_size%';SQL Server 中页的大小为 8KB,而在 Oracle 中我们用术语 “块” (Block)来表示 “页”,Oracle 支持的快大小为2KB, 4KB, 8KB, 16KB, 32KB 和 64KB。

另外在数据库中,还存在着区(Extent)、段(Segment)和表空间(Tablespace)的概念。行、页、区、段、表空间的关系如下图所示

image-20220617194256988.png

区(Extent)是比页大一级的存储结构,在InnoDB存储引擎中,一个区会分配64个连续的页。因为InnoDB中的页大小默认是16KB,所以一个区的大小是64*16KB= 1MB。段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在InnoDB中是连续的64个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。

4.2 页的内部结构(重要)

页如果按类型划分的话,常见的有数据页(保存B+树节点)、系统表、Undo 页 和 事物数据页等。数据页是我们最常使用的页。数据页的 16KB 大小的存储空间被划分为七个部分,分别是文件头(File Header)、页头(Page Header)、最大最小记录(Infimum + supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer),页结构的示意图如下所示

image-20220617195012446.png

这7个部分如下所示

image-20220617195148164.png

B+树是如何进行记录检索的?

如果通过B+树的索引查询行记录,首先是从B+树的根开始,逐层检索,直到找到叶子节点,也就是找到对应的数据页为止,将数据页加载到内存中,页目录中的槽(slot)采用二分查找的方式先找到一个粗略的记录分组,然后再在分组中通过链表遍历的方式查找记录。

普通索引和唯一索引在查询效率上有什么不同?

唯一索引就是在普通索引上增加了约束性,也就是关键字唯一,找到了关键字就停止检索。而普通索引,可能会存在用户记录中的关键字相同的情况,根据页结构的原理,当我们读取一条记录的时候,不是单独将这条记录从磁盘中读出去,而是将这个记录所在的页加载到内存中进行读取。InnoDB存储引擎的页大小为16KB,在一个页中可能存储着上千个记录,因此在普通索引的字段上进行查找也就是在内存中多几次“判断下一条记录"的操作,对于CPU来说,这些操作所消耗的时间是可以忽略不计的。所以对一个索引字段进行检索,采用普通索引还是唯一索引在检索效率上基本上没有差别。

4.3 InnoDB行格式 (或记录格式)

见文件InnoDB数据库存储结构,可以二进制打开看看mysql5.1,默认是COMPACT行格式

image.png

在MySQL 8.0中,默认行格式就是Dynamic,Dynamic、Compressed行格式和Compact行格式挺像,只不过在处理行溢出数据时有分歧:

  • Compressed和Dynamic两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式,实际的数据都存放在Off Page(溢出页)中
  • Compact和Redundant两种格式会在记录的真实数据处存储一部分数据(存放768个前缀字节)。

4.4 区、段与碎片区

区的作用

B+树的每一层中的页都会形成一个双向链表,如果是以页为单位来分配存储空间的话,双向链表相邻的两个页之间的物理位置可能离得非常远。我们介绍B+树索引的适用场景的时候特别提到范围查询只需要定位到最左边的记录和最右边的记录,然后沿着双向链表一直扫描就可以了,而如果链表中相邻的两个页物理位置离得非常远,就是所谓的随机I/0。再一次强调,磁盘的速度和内存的速度差了好几个数量级,随机I/O是非常慢的,所以我们应该尽量让链表中相邻的页的物理位置也相邻,这样进行范围查询的时候才可以使用所谓的顺序I/O
引入区的概念,一个区就是在物理位置上连续的64个页。因为InnoDB 中的页大小默认是16KB,所以一个区的大小是64*16KB= 1MB

在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,甚至在表中的数据特别多的时候,可以一次性分配多个连续的区。虽然可能造成一点点空间的浪费(数据不足以填充满整个区),但是从性能角度看,可以消除很多的随机I/o,功大于过!

段的作用

对于范围查询,其实是对B+树叶子节点中的记录进行顺序扫描,而如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话,进行范围扫描的效果就大打折扣了。所以InnoDB对B+树的叶子节点非叶子节点进行了区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段( segment ),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。

除了索引的叶子节点段和非叶子节点段之外,InnoDB中还有为存储一些特殊的数据而定义的段,比如回滚段。所以,常见的段有数据段、索引段、回滚段。数据段即为B+树的叶子节点,索引段即为B+树的非叶子节点。在InnoDB存储引擎中,对段的管理都是由引擎自身所完成,DBA不能也没有必要对其进行控制。这从一定程度上简化了DBA对于段的管理。段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念,由若干个零散的页面以及一些完整的区组成。

为什么要有碎片区

默认情况下,一个使用InnoDB存储引擎的表只有一个聚簇索引,一个索引会生成2个段,而段是以区为单位申请存储空间的,一个区默认占用1M (64*16Kb=1024Kb)存储空间,所以**默认情况下一个只存了几条记录的小表也需要2M的存储空间么?**以后每次添加一个索引都要多申请2M的存储空间么?这对于存储记录比较少的表简直是天大的浪费。

为了考虑以完整的区为单位分配给某个段对于数据量较小的表太浪费存储空间的这种情况,InnoDB提出了一个碎片(fragment)区的概念。在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间,并不属于任何一个段。
所以此后为某个段分配存储空间的策略是这样的:

  • 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的
  • 当某个段已经占用了32个碎片区页面之后,就会申请以完整的区为单位来分配存储空间

所以现在段不能仅定义为是某些区的集合,更精确的应该是某些零散的页面以及一些完整的区的集合。

区的分类
  • 空闲的区 (FREE) : 现在还没有用到这个区中的任何页面。
  • 有剩余空间的碎片区 (FREE_FRAG):表示碎片区中还有可用的页面。
  • 没有剩余空间的碎片区 (FULL_FRAG):表示碎片区中的所有页面都被使用,没有空闲页面。
  • 附属于某个段的区 (FSEG):每一个索引都可以分为叶子节点段和非叶子节点段。

4.5 表空间

表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。表空间是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。表空间数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间(System tablespace)、独立表空间(File-per-table tablespace)、撤销表空间(Undo Tablespace)和临时表空间(Temporary Tablespace)等。

独立表空间

独立表空间,即每张表有一个独立的表空间,也就是数据和索引信息都会保存在自己的表空间中。独立的表空间 (即:单表) 可以在不同的数据库之间进行 迁移。空间可以回收 (DROP TABLE 操作可自动回收表空间;其他情况,表空间不能自己回收) 。如果对于统计分析或是日志表,删除大量数据后可以通过:alter table TableName engine=innodb; 回收不用的空间。对于使用独立表空间的表,不管怎么删除,表空间的碎片不会太严重的影响性能,而且还有机会处理.

独立表空间结构由段、区、页组成,我们到数据目录里看,会发现一个新建的表对应的 .ibd 文件只占用了 96K,才6个页面大小 (MySQL5.7中),这是因为一开始表空间占用的空间很小,因为表里边都没有数据。不过别忘了这些 .ibd 文件是自扩展的,随着表中数据的增多,表空间对应的文件也逐渐增大,可以查看表空间类型:show variables like 'innodb_file_per_table';能看到innodb_file_per_table=ON, 这就意味着每张表都会单词保存一个 .ibd 文件。

系统表空间

系统表空间的结构和独立表空间基本类似,只不过由于整个MySQL进程只有一个系统表空间,在系统表空间中会额外记录一些有关整个系统信息的页面,这部分是独立表空间中没有的。

在 information_scheme 数据库中的这些以 INNODB_SYS 开头的表并不是真正的内部系统表 (内部系统表就是我们上边以 SYS 开头的那些表),而是在存储引擎启动时读取这些以 SYS 开头的系统表,然后填充到这些以 INNODB_SYS 开头的表中。以 INNODB_SYS 开头的表和以 SYS 开头的表中的字段并不完全一样

5、索引的创建

5.1 索引的声明

MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。从 功能逻辑 上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引、全文索引。 按照 物理实现方式 ,索引可以分为 2 种:聚簇索引和非聚簇索引。 按照 作用字段个数 进行划分,分成单列索引和联合索引

  • 普通索引。在创建普通索引时,不附加任何限制条件,只是用于提高查询效率。这类索引可以创建在任何数据类型中,其值是否唯一和非空,要由字段本身的完整性约束条件决定。建立索引以后,可以通过索引进行查询例如,在表student的字段name 上建立一个普通索引,查询记录时就可以根据该索引进行查询。
  • 唯一性索引。使用UNIQUE参数可以设置索引为唯一性索引,在创建唯一性索引时,限制该索引的值必须是唯一的,但允许有空值。在一张数据表里可以有多个唯一索引。例如,在表student的字段email中创建唯一性索引,那么字段email的值就必须是唯一的。通过唯一性索引可以更快速地确定某条记录。
  • **主键索引。**主键索引就是一种特殊的唯一性索引,在唯一索引的基础上增加了不为空的约束,也就是NOTNULL+UNIQUE,一张表里最多只有一个主键索引。这是由主键索引的物理实现方式决定的,因为数据存储在文件中只能按照一种顺序进行存储。
  • **单列索引。**在表中的单个字段上创建索引。单列索引只根据该字段进行索引。单列索可以是普通索引,也可以是唯一性索引,还可以是全文索引。只要保证该索引只对应一个字段即可。一个表可以有多个单列索引。
  • **多列(联合)索引。**多列索引是在表的多个字段组合上创建一个索引。该索引指向创建时对应的多个字段,可以通过这几个字段进行查询,但是只有查询条件中使用了这些字段中的第一个字段时才会被使用。
  • **全文索引。**全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用【分词技术】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果,全文索引非常适合大型数据集,对于小的数据集,它的用处比较小。现在基本被solr、elasticSearch代替
  • **空间索引。**使用参数SPATIAL可以设置索引为空间索引。空间索引只能建立在空间数据类型上,这样可以提高系统获取空间数据的效率。MySQL中的空间数据类型包括GEOMETRY、POINT、LINESTRING和POLYGON等。目前只有MyISAM存储引擎支持空间检索,而且索引的字段不能为空值。对于初学者来说,这类索引很少会用到。

小结:不同的存储引擎支持的索引类型也不一样

  • InnoDB :支持 B-tree、Full-text 等索引,不支持 Hash 索引
  • MyISAM : 支持 B-tree、Full-text 等索引,不支持 Hash 索引
  • Memory :支持 B-tree、Hash 等 索引,不支持 Full-text 索引
  • NDB :支持 Hash 索引,不支持 B-tree、Full-text 等索引
  • Archive :不支 持 B-tree、Hash、Full-text 等索引

5.2索引的使用

-- 使用CREATE TABLE创建表时,除了可以定义列的数据类型外,还可以定义主键约束、外键约束或者唯一性约束,而不论创建哪种约束,在定义约束的同时相当于在指定列上创建了一个索引
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 指定升序或者降序的索引值存储

-- 创建普通索引
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)
);
-- 创建唯一索引
CREATE TABLE test1(
id INT NOT NULL,
name varchar(30) NOT NULL,
UNIQUE INDEX uk_idx_id(id)
);
SHOW INDEX FROM test1 \G;

-- 设定为主键后数据库会自动建立索引,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)索引

-- 创建组合索引
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)
);
-- 使用EXPLAIN语句查看索引的使用情况
EXPLAIN SELECT * FROM test3 WHERE id=1 AND name='songhongkang' \G;

--  创建全文索引
-- FULLTEXT全文索引可以用于全文检索,并且只为 `CHAR` 、`VARCHAR` 和 `TEXT` 列创建索引。索引总是对整个列进行,不支持局部 (前缀) 索引
-- 在MySQL5.7及之后版本中可以不指定最后的ENGINE了,因为在此版本中InnoDB支持全文索引
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)
) ENGINE=MyISAM;
-- 全文索引用match+against方式查询。全文索引比 like + % 快 N 倍,但是可能存在精度问题
-- 如果需要全文索引的是大量数据,建议先添加数据,再创建索引
SELECT * FROM papers WHERE MATCH(title,content) AGAINST ('查询字符串');

-- 创建空间索引
CREATE TABLE test5(
geo GEOMETRY NOT NULL,
SPATIAL INDEX spa_idx_geo(geo)
) ENGINE=MyISAM;


-- ==========================已经存在的表上创建索引======================
ALTER TABLE table_name ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY]
[index_name] (col_name[length],...) [ASC | DESC]
-- 第二种方式
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
ON table_name (col_name[length],...) [ASC | DESC]

-- ========================删除索引=====================================
--  删除表中的列时,如果要删除的列为索引的组成部分,则该列也会从索引中删除。如果组成索引的所有列都被删除,则整个索引将被删除。
-- AUTO_INCREMENT约束字段的唯一索引不能被删除
ALTER TABLE table_name DROP INDEX index_name;
DROP INDEX index_name ON table_name;

5.3 MySQL8.0索引新特性

第一支持降序索引,降序索引以降序存储键值。虽然在语法上,从MySQL 4版本开始就已经支持降序索引的语法了,但实际上DESC定义是被忽略的,直到MySQL 8.x版本才开始真正支持降序索引 (仅限于InnoDBc存储引擎)。MySQL在8.0版本之前创建的仍然是升序索引,使用时进行反向扫描,这大大降低了数据库的效率。在某些场景下,降序索引意义重大。例如,如果一个查询,需要对多个列进行排序,且顺序要求不一致,那么使用降序索引将会避免数据库使用额外的文件排序操作,从而提高性能。

第二支持隐藏索引。在MySQL 5.7版本及之前,只能通过显式的方式删除索引。此时,如果删除索引后出现错误,又只能通过显式创建索引的方式将删除的索引创建回来。如果数据表中的数据量非常大,或者数据表本身比较 大,这种操作就会消耗系统过多的资源,操作成本非常高。从MySQL 8.x开始支持 隐藏索引(invisible indexes) ,只需要将待删除的索引设置为隐藏索引,使 查询优化器不再使用这个索引(即使使用force index(强制使用索引),优化器也不会使用该索引), 确认将索引设置为隐藏索引后系统不受任何响应,就可以彻底删除索引。 这种通过先将索引设置为隐藏索引,再删除索引的方式就是软删除。同时,如果你想验证某个索引删除之后的 查询性能影响,就可以暂时先隐藏该索引

主键不能被设置为隐藏索引。当表中没有显式主键时,表中第一个唯一非空索引会成为隐式主键,也不能设置为隐藏索引

-- 创建表时直接创建
CREATE TABLE tablename(
propname1 type1[CONSTRAINT1],
propname2 type2[CONSTRAINT2],
propnamen typen,
INDEX [indexname](propname1 [(length)]) INVISIBLE
);
-- 在已经存在的表上创建
CREATE INDEX indexname
ON tablename(propname[(length)]) INVISIBLE;

-- 通过ALTER TABLE语句创建
ALTER TABLE tablename
ADD INDEX indexname (propname [(length)]) INVISIBLE;
-- 换索引可见状态
-- 如果将index_cname索引切换成可见状态,通过explain查看执行计划,发现优化器选择了index_cname索引
-- 当索引被隐藏时,它的内容仍然是和正常索引一样实时更新的。如果一个索引需要长期被隐藏,那么可以将其删除,因为索引的存在会影响插入、更新和删除的性能
ALTER TABLE tablename ALTER INDEX index_name INVISIBLE; --切换成隐藏索引
ALTER TABLE tablename ALTER INDEX index_name VISIBLE; --切换成非隐藏索引

-- 使隐藏索引对查询优化器可见
-- 如果use_invisible_indexes 设置为off (默认),优化器会忽略隐藏索引。如果设置为on,即使隐藏索引不可见,优化器在生成执行计 划时仍会考虑使用隐藏索引
 select @@optimizer_switch \G
-- 在输出的结果信息中找到如下属性配置,use_invisible_indexes=off
-- 使隐藏索引对查询优化器可见
set session optimizer_switch="use_invisible_indexes=on";
-- 如果需要使隐藏索引对查询优化器不可见
set session optimizer_switch="use_invisible_indexes=off";

6、索引的设计原则

6.1 数据准备

-- 第1步创建数据库、创建表
CREATE DATABASE atguigudb1;
USE atguigudb1;
-- 1.创建学生表和课程表
CREATE TABLE `student_info` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`student_id` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`course_id` INT NOT NULL ,
`class_id` INT(11) DEFAULT NULL,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `course` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`course_id` INT NOT NULL ,
`course_name` VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

-- 第2步创建模拟数据必需的存储函数
#函数1:创建随机产生字符串函数
DELIMITER //
CREATE FUNCTION rand_string(n INT)
	RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN
	DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
	DECLARE return_str VARCHAR(255) DEFAULT '';
    DECLARE i INT DEFAULT 0;
    WHILE i < n DO
    	SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
    	SET i = i + 1;
    END WHILE;
    RETURN return_str;
END //
DELIMITER ;

-- 函数2:创建随机数函数
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
RETURN i;
END //
DELIMITER ;

-- 如果报错This function has none of DETERMINISTIC......,由于开启过慢查询日志bin-log, 我们就必须为我们的function指定一个参数
-- 主从复制,主机会将写操作记录在bin-log日志中。从机读取bin-log日志,执行语句来同步数据
-- 如果使 用函数来操作数据,会导致从机和主键操作时间不一致。所以,默认情况下,mysql不开启创建函数设置
-- 查看mysql是否允许创建函数
show variables like 'log_bin_trust_function_creators';
set global log_bin_trust_function_creators=1;

-- 第3步创建插入模拟数据的存储过程
--  存储过程1:创建插入课程表存储过程
DELIMITER //
CREATE PROCEDURE insert_course( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; --设置手动提交事务
REPEAT --循环
SET i = i + 1; --赋值
INSERT INTO course (course_id, course_name ) VALUES
(rand_num(10000,10100),rand_string(6));
UNTIL i = max_num
END REPEAT;
COMMIT; --提交事务
END //
DELIMITER ;

-- 存储过程2:创建插入学生信息表存储过程
DELIMITER //
CREATE PROCEDURE insert_stu( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; --设置手动提交事务
REPEAT --循环
SET i = i + 1; --赋值
INSERT INTO student_info (course_id, class_id ,student_id ,NAME ) VALUES
(rand_num(10000,10100),rand_num(10000,10200),rand_num(1,200000),rand_string(6));
UNTIL i = max_num
END REPEAT;
COMMIT; --提交事务
END //
DELIMITER ;

-- 第4步:调用存储过程
CALL insert_course(100);
CALL insert_stu(1000000);

6.2 适合索引的场景

1、字段的数值有唯一性的限制

索引本身可以起到约束的作用,比如唯一索引、主键索引都是可以起到唯一性约束的,因此在我们的数据表中,如果某个字段是唯一性的,就可以直接创建唯一性索引,或者主键索引。这样可以更快速地通过该索引来确定某条记录。例如,学生表中学号是具有唯一性的字段,为该字段建立唯一性索引可以很快确定某个学生的信息,如果使用姓名的话,可能存在同名现象,从而降低查询速度。

业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。(来源:Alibaba) 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的。

2、频繁作为 WHERE 查询条件的字段

某个字段在SELECT语句的 WHERE 条件中经常被使用到,那么就需要给这个字段创建索引了。尤其是在 数据量大的情况下,创建普通索引就可以大幅提升数据查询的效率。 比如student_info数据表(含100万条数据),假设我们想要查询 student_id=123110 的用户信息

3、经常 GROUP BY 和 ORDER BY 的列

索引就是让数据按照某种顺序进行存储或检索,因此当我们使用 GROUP BY 对数据进行分组查询,或者使用 ORDER BY 对数据进行排序的时候,就需要对分组或者排序的字段进行索引 。如果待排序的列有多个,那么可以在这些列上建立组合索引

4、UPDATE、DELETE 的 WHERE 条件列
对数据按照某个条件进行查询后再进行 UPDATE 或 DELETE 的操作,如果对 WHERE 字段创建了索引,就能大幅提升效率。原理是因为我们需要先根据 WHERE 条件列检索出来这条记录,然后再对它进行更新或删除。如果进行更新的时候,更新的字段是非索引字段,提升的效率会更明显,这是因为非索引字段更新不需要对索引进行维护

5、DISTINCT 字段需要创建索引

有时候我们需要对某个字段进行去重,使用 DISTINCT,那么对这个字段创建索引,也会提升查询效率。

6、多表 JOIN 连接操作时,创建索引注意事项

首先, 连接表的数量尽量不要超过 3 张,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增 长会非常快,严重影响查询的效率。 其次, 对 WHERE 条件创建索引,因为 WHERE 才是对数据条件的过滤。如果在数据量非常大的情况下, 没有 WHERE 条件过滤是非常可怕的。 最后, 对用于连接的字段创建索引,并且该字段在多张表中的 类型必须一致 。比如 course_id 在 student_info 表和 course 表中都为 int(11) 类型,而不能一个为 int 另一个为 varchar 类型

7、使用列的类型小的创建索引

我们这里所说的类型大小指的就是该类型表示的数据范围的大小。我们在定义表结构的时候要显式的指定列的类型,以整数类型为例,有TINYINT、MEDIUNINT、INT、BIGINT等,它们占用的存储空间依次递增,能表示的整数范围当然也是依次递增

  • 数据类型越小,在查询时进行的比较操作越快
  • 数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘I/0带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率

8、使用字符串前缀创建索引

我们可以通过截取字段的前面一部分内容建立索引,这个就叫前缀索引。这样在查找记录时虽然不能精确的定位到记录的位置,但是能定位到相应前缀所在的位置,然后根据前缀相同的记录的主键值回表查询完整的字符串值。既节约空间,又减少了字符串的比较时间,还大体能解决排序的问题

create table shop(address varchar(120) not null);
alter table shop add index(address(12));

-- 越接近于1越好,说明越有区分度
count(distinct left(列名, 索引长度))/count(*)
select count(distinct left(address,10)) / count(*) as sub10, -- 截取前10个字符的选择度
count(distinct left(address,15)) / count(*) as sub11, -- 截取前15个字符的选择度
count(distinct left(address,20)) / count(*) as sub12, -- 截取前20个字符的选择度
count(distinct left(address,25)) / count(*) as sub13 -- 截取前25个字符的选择度
from shop;

9、区分度高(散列性高)的列适合作为索引

列的基数 指的是某一列中不重复数据的个数,比方说某个列包含值 2, 5, 8, 2, 5, 8, 2, 5, 8,虽然有9条记录,但该列的基数却是3。也就是说**在记录行数一定的情况下,列的基数越大,该列中的值越分散;列的基数越小,该列中的值越集中。**这个列的基数指标非常重要,直接影响我们是否能有效的利用索引。最好为列的基数大的列简历索引,为基数太小的列的简历索引效果可能不好。可以使用公式select count(distinct a) / count(*) from t1 计算区分度,越接近1越好,一般超过33%就算比较高效的索引了。扩展:联合索引把区分度搞(散列性高)的列放在前面。

10、使用最频繁的列放到联合索引的左侧

11、在多个字段都要创建索引的情况下,联合索引优于单值索引

6.3 限制索引的数目

在实际工作中,我们也需要注意平衡,索引的数目不是越多越好。我们需要限制每张表上的索引数量,建议单张表索引数量不超过6个。原因:

  • 每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
  • 索引会影响INSERT、DELETE、UPDATE等语句的性能,因为表中的数据更改的同时,索引也会进行调整和更新,会造成负担。
  • 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,会增加MySQL优化器生成执行计划时间,降低查询性能。

6.4 不适合创建索引场景

1、在where中使用不到的字段,不要设置索引

WHERE条件 (包括 GROUP BY、ORDER BY) 里用不到的字段不需要创建索引,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的。

2、 数据量小的表最好不要使用索引

如果表记录太少,比如少于1000个,那么是不需要创建索引的。表记录太少,是否创建索引 对查询效率的影响并不大。甚至说,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。

3、有大量重复数据的列上不要建立索引

在条件表达式中经常用到的不同值较多的列上建立索引,但字段中如果有大量重复数据,也不用创建索引。比如在学生表的"性别"字段上只有“男”与“女”两个不同值,因此无须建立索引。如果建立索引,不但不会提高查询效率,反而会严重降低数据更新速度。结论:当数据重复度大,比如 高于 10% 的时候,也不需要对这个字段使用索引

4、避免对经常更新的表创建过多的索引

第一层含义:频繁更新的字段不一定要创建索引。因为更新数据的时候,也需要更新索引,如果索引太多,在更新索引的时候也会造成负担,从而影响效率。

第二层含义:避免对经常更新的表创建过多的索引,并且索引中的列尽可能少。此时,虽然提高了查询速度,同时却降低更新表的速度。

5、不建议用无序的值作为索引

例如身份证、UUID(在索引比较时需要转为ASCII,并且插入时可能造成页分裂)、MD5、HASH、无序长字 符串等

6、删除不再使用或者很少使用的索引

表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响

7、不要定义夯余或重复的索引

五、性能分析工具与索引优化

1、查看系统性能参数

SHOW [GLOBAL|SESSION] STATUS LIKE '参数';
  • Connections:连接MySQL服务器的次数。
  • Uptime:MySQL服务器的上线时间。
  • Slow_queries:慢查询的次数。
  • Innodb_rows_read:Select查询返回的行数
  • Innodb_rows_inserted:执行INSERT操作插入的行数
  • Innodb_rows_updated:执行UPDATE操作更新的 行数
  • Innodb_rows_deleted:执行DELETE操作删除的行数
  • Com_select:查询操作的次数。
  • Com_insert:插入操作的次数。对于批量插入的 INSERT 操作,只累加一次。
  • Com_update:更新操作 的次数。
  • Com_delete:删除操作的次数。
-- 若查询MySQL服务器的连接次数,则可以执行如下语句
SHOW STATUS LIKE 'Connections';
-- 若查询服务器工作时间
SHOW STATUS LIKE 'Uptime';
-- 若查询MySQL服务器的慢查询次数
SHOW STATUS LIKE 'Slow_queries';
-- 慢查询次数参数可以结合慢查询日志找出慢查询语句,然后针对慢查询语句进行`表结构优化`或者`查询语句优化`。
-- 查看相关的指令情况
SHOW STATUS LIKE 'Innodb_rows_%';

2、统计SQL的查询成本: last_query_cost

一条SQL查询语句在执行前需要查询执行计划,如果存在多种执行计划的话,MySQL会计算每个执行计划所需要的成本,从中选择成本最小的一个作为最终执行的执行计划。

如果我们想要查看某条SQL语句的查询成本,可以在执行完这条SQL语句之后,通过查看当前会话中的last_query_cost变量值来得到当前查询的成本: SHOW STATUS LIKE 'last_query_cost'。它通常也是我们评价一个查询的执行效率的一个常用指标。这个查询成本对应的是SQL 语句所需要读取的读页的数量。

**使用场景:**它对于比较开销是非常有用的,特别是我们有好几种查询方式可选的时候

SQL查询时一个动态的过程,从页加载的角度来看,我们可以得到以下两点结论:

  1. 位置决定效率。如果页就在数据库 缓冲池 中,那么效率是最高的,否则还需要从 内存 或者 磁盘 中进行读取,当然针对单个页的读取来说,如果页存在于内存中,会比在磁盘中读取效率高很多。
  2. 批量决定效率。如果我们从磁盘中对单一页进行随机读,那么效率是很低的(差不多10ms),而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多,甚至要快于单个页面在内存中的随机读取。

所以说,遇到I/O并不用担心,方法找对了,效率还是很高的。我们首先要考虑数据存放的位置,如果是进程使用的数据就要尽量放到缓冲池中,其次我们可以充分利用磁盘的吞吐能力,一次性批量读取数据,这样单个页的读取效率也就得到了提升。

3、 定位执行慢的 SQL:慢查询日志

3.1 慢查询概述

MySQL的慢查询日志,用来记录在MysQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上(不含10秒)的语句,认为是超出了我们的最大忍耐时间值。

它的主要作用是,帮助我们发现那些执行时间特别长的SQL查询,并且有针对性地进行优化,从而提高系统的整体效率。当我们的数据库服务器发生阻塞、运行变慢的时候,检查一下慢查询日志,找到那些慢查询,对解决问题很有帮助。比如一条sql执行超过5秒钟,我们就算慢sQL,希望能收集超过5秒的sql,结合explain进行全面分析。

默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。

3.2 慢查询日志参数开启与查看

-- 开启 slow_query_log
show variables like '%slow_query_log';
set global slow_query_log='ON';
-- 能看到这时慢查询分析已经开启,同时文件保存在 /var/lib/mysql/atguigu02-slow.log`文件 中
-- 修改 long_query_time 阈值
show variables like '%long_query_time%';
-- 设置为 1 秒
-- 测试发现:设置global的方式对当前session的long_query_time失效。对新连接的客户端有效。所以可以一并执行下述语句
set global long_query_time = 1;
show global variables like '%long_query_time%';

set long_query_time=1;
show variables like '%long_query_time%';

-- 补充:配置文件中一并设置参数
-- 修改 `my.cnf` 文件,[mysqld] 下增加或修改参数 `long_query_time、slow_query_log` 和 `slow_query_log_file` 后,然后重启 MySQL 服务器
[mysqld]
slow_query_log=ON  # 开启慢查询日志开关
slow_query_log_file=/var/lib/mysql/atguigu-low.log  # 慢查询日志的目录和文件名信息
long_query_time=3  # 设置慢查询的阈值为3秒,超出此设定值的SQL即被记录到慢查询日志
log_output=FILE
-- 如果不指定存储路径,慢查询日志默认存储到MySQL数据库的数据文件夹下。
-- 如果不指定文件名,默认文件名为hostname_slow.log

-- 查询当前系统中有多少条慢查询记录
SHOW GLOBAL STATUS LIKE '%Slow_queries%';

3.3 案例演示

CREATE TABLE `student` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `stuno` INT NOT NULL ,
    `name` VARCHAR(20) DEFAULT NULL,
    `age` INT(3) DEFAULT NULL,
    `classId` INT(11) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 如果创建函数出错,This function has none of DETERMINISTIC......
set global log_bin_trust_function_creators=1; -- 不加global只是当前窗口有效。


-- 步骤3:创建函数,随机产生字符串:
DELIMITER //
CREATE FUNCTION rand_string(n INT)
	RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN
	DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
	DECLARE return_str VARCHAR(255) DEFAULT '';
    DECLARE i INT DEFAULT 0;
    WHILE i < n DO
    	SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
    	SET i = i + 1;
    END WHILE;
    RETURN return_str;
END //
DELIMITER ;
-- 测试
SELECT rand_string(10);
-- 产生随机数值
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
    DECLARE i INT DEFAULT 0;
    SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
    RETURN i;
END //
DELIMITER ;
-- 测试:
SELECT rand_num(10,100);

-- 步骤4:创建存储过程
DELIMITER //
CREATE PROCEDURE insert_stu1( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
    SET autocommit = 0;--#设置手动提交事务
    REPEAT --循环
    SET i = i + 1; --赋值
    INSERT INTO student (stuno, NAME ,age ,classId ) VALUES
    ((START+i),rand_string(6),rand_num(10,100),rand_num(10,1000));
    UNTIL i = max_num
    END REPEAT;
    COMMIT; --提交事务
END //
DELIMITER ;
-- 步骤5:调用存储过程
-- 调用刚刚写好的函数, 4000000条记录,从100001号开始
CALL insert_stu1(100001,4000000);

3.4 测试与分析

SELECT * FROM student WHERE stuno = 3455655;
SELECT * FROM student WHERE name = 'oQmLUr';
show status like 'slow_queries';

image-20220628195650079.png

3.5 慢查询日志分析工具: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.6 关闭与删除慢查询日志

-- 永久性方式,或者,把slow_query_log一项注释掉 或 删除
[mysqld]
slow_query_log=OFF
-- 重启MySQL服务,执行如下语句查询慢日志功能
SHOW VARIABLES LIKE '%slow%'; --查询慢查询日志所在目录
SHOW VARIABLES LIKE '%long_query_time%'; --查询超时时长

-- 临时性方式
-- 停止MySQL慢查询日志功能
SET GLOBAL slow_query_log=off;

-- 删除慢查询日志
-- 使用SHOW语句显示慢查询日志信息
SHOW VARIABLES LIKE `slow_query_log%`;
-- 从执行结果可以看出,慢查询日志的目录默认为MySQL的数据目录,在该目录下 `手动删除慢查询日志文件` 即可。
-- 使用命令 `mysqladmin flush-logs` 来重新生成查询日志文件,具体命令如下,执行完毕会在数据目录下重新生成慢查询日志文件。
mysqladmin -uroot -p flush-logs slow
-- 慢查询日志都是使用mysqladmin flush-logs命令来删除重建的。使用时一定要注意,一旦执行了这个命令,慢查询日志都只存在新的日志文件中,如果需要旧的查询日志,就必须事先备份

4、查看 SQL 执行成本:SHOW PROFILE

show profile 在《逻辑架构》章节中讲过,这里作为复习。show profile 是 MySQL 提供的可以用来分析当前会话中 SQL 都做了什么、执行的资源消耗工具的情况,可用于 sql 调优的测量。默认情况下处于关闭状态,并保存最近15次的运行结果。我们可以在会话级别开启这个功能。

show variables like 'profiling';
-- 开启 show profile;
set profiling = 'ON';
show profiles;
-- 查看最近一次查询的开销
show profile;
show profile cpu,block io for query 2;

**show profile的常用查询参数: **

  • ALL:显示所有的开销信息。
  • BLOCK IO:显示块IO开销。
  • CONTEXT SWITCHES:上下文切换开销。
  • CPU:显示CPU开销信息。
  • IPC:显示发送和接收开销信息。
  • MEMORY:显示内存开销信 息。
  • PAGE FAULTS:显示页面错误开销信息。
  • SOURCE:显示和Source_function,Source_file, Source_line相关的开销信息。
  • SWAPS:显示交换次数开销信息。

日常开发需注意的结论:

  • converting HEAP to MyISAM: 查询结果太大,内存不够,数据往磁盘上搬了。
  • Creating tmp table:创建临时表。先拷贝数据到临时表,用完后再删除临时表。
  • Copying to tmp table on disk:把内存中临时表复制到磁盘上,警惕!
  • locked。

如果在show profile诊断结果中出现了以上4条结果中的任何一条,则sql语句需要优化。

注意:

不过SHOW PROFILE命令将被启用,我们可以从 information_schema 中的 profiling 数据表进行查看

5、分析查询语句:EXPLAIN(重要)

5.1 概述

官网介绍:
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html

定位了查询慢的SQL之后,我们就可以使用EXPLAIN或DESCRIBE工具做针对性的分析查询语句。DESCRIBE语句的使用方法与EXPLAIN语句是一样的,并且分析结果也是一样的。

MysQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供它认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)。这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。MySQL为我们提供了EXPLAIN语句来帮助我们查看某个查询语句的具体执行计划,大家看懂EXPLAIN语句的各个输出项,可以有针对性的提升我们查询语句的性能。

  • MySQL 5.6.3以前只能 EXPLAIN SELECT ;MYSQL 5.6.3以后就可以 EXPLAIN SELECT,UPDATE, DELETE
  • 在5.7以前的版本中,想要显示 partitions 需要使用 explain partitions 命令;想要显示 filtered 需要使用 explain extended 命令。在5.7版本后,默认explain直接显示partitions和 filtered中的信息。

5.2 基本语法

EXPLAIN SELECT select_options
-- 或者
DESCRIBE SELECT select_options
EXPLAIN SELECT 1;

EXPLAIN 语句输出的各个列的作用如下:

列名描述
id在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
select_typeSELECT关键字对应的那个查询的类型
table表名
type针对单表的访问方法⭐
possible_keys可能用到的索引
key实际用到的索引
key_len实际用到的索引长度
ref当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows预估的需要读取的记录条数
filtered某个表经过搜索条件过滤后剩余记录条数的百分比
Extra一些额外的信息

5.3 EXPLAIN各列作用

1、table
不论我们的查询语句有多复杂,里边儿 包含了多少个表 ,到最后也是需要对每个表进行 单表访问 的,所 以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该 表的表名(有时不是真实的表名字,可能是简称)
2、id

  • id如果相同,可以认为是一组,从上往下顺序执行
  • 在所有组中,id值越大,优先级越高,越先执行
  • 关注点:id号每个号码,表示一趟独立的查询, 一个sql的查询趟数越少越好

3、select_type
一条大的查询语句里边可以包含若干个SELECT关键字,每个SELECT关键字代表着一个小的查询语句,而每个SELECT关键字的FROM子句中都可以包含若干张表(这些表用来做连接查询),每一张表都对应着执行计划输出中的一条记录,对于在同一个SELECT关键字中的表来说,它们的id值是相同的。MySQL为每一个SELECT关键字代表的小查询都定义了一个称之为select_type的属性,意思是我们只要知道了某个小查询的select_type属性,就知道了这个小查询在整个大查询中扮演了一个什么角色

  • SIMPLE,查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型,连接查询也算是 SIMPLE 类型
EXPLAIN SELECT * FROM s1 INNER JOIN s2;
  • PRIMARY,对于包含UNION、UNION ALL或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type的值就是PRIMARY
EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
  • UNION,对于包含UNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询意外,其余的小查询的select_type值就是UNION
  • UNION RESULT,MySQL 选择使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT
  • SUBQUERY,如果包含子查询的查询语句不能够转为对应的semi-join的形式,并且该子查询是不相关子查询,并且查询优化器决定采用将该子查询物化的方案来执行该子查询时,该子查询的第一个SELECT关键字代表的那个查询的select_type就是SUBQUERY
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
  • DEPENDENT SUBQUERY,相关查询的子查询
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a';
  • DEPENDENT UNION
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
  • DERIVED,select_type是DERIVED, 说明该子查询是以物化的方式执行的
EXPLAIN SELECT * FROM (SELECT key1, count(*) as c FROM s1 GROUP BY key1) AS derived_s1 where c > 1;
  • MATERIALIZED,当查询优化器在执行包含子查询的语句时,选择将子查询物化之后的外层查询进行连接查询时,该子查询对应的select_type属性就是DERIVED
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2);
  • UNCACHEABLE SUBQUERY
  • UNCACHEABLE UNION

4、partitions (可略)
5、type ☆

  • system,当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的
  • const,当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const
  • eq_ref,在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较)。则对该被驱动表的访问方法就是eq_ref
  • ref,当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
  • fulltext,全文索引
  • ref_or_null,当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null
  • index_merge,一般情况下对于某个表的查询只能使用到一个索引,但单表访问方法时在某些场景下可以使用Interseation、union、Sort-Union这三种索引合并的方式来执行查询
  • unique_subquery,类似于两表连接中被驱动表的eq_ref访问方法,unique_subquery是针对在一些包含IN子查询的查询语句中,如果查询优化器决定将IN子查询转换为EXISTS子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type列的值就是unique_subquery
  • index_subquery,index_subquery 与 unique_subquery 类似,只不过访问子查询中的表时使用的是普通的索引
  • range,范围
  • index,当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index
  • ALL,最熟悉的全表扫描

结果值从最好到最坏依次是: system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL 。SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴 开发手册要求)

6、possible_keys和key

在EXPLAIN语句输出的执行计划中,possible_keys列表示在某个查询语句中,对某个列执行单表查询时可能用到的索引有哪些。一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key列表示实际用到的索引有哪些,如果为NULL,则没有使用索引

7、key_len ☆

实际使用到的索引长度 (即:字节数),帮你检查是否充分的利用了索引,值越大越好,主要针对于联合索引,有一定的参考意义。

8、ref

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是const、eq_ref、ref、ref_or_null 、unique_subquery,、index_subquery其中之一时,ref列展示的就是与索引列作等值匹配的结构是什么,比如只是一个常数或者是某个列。

9、rows ☆

预估的需要读取的记录条数,值越小越好。

10、filtered

某个表经过搜索条件过滤后剩余记录条数的百分比。如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。对于单表查询来说,这个filtered的值没有什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数 (即: rows * filtered)

11、Extra ☆

用来说明一些额外信息的,包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。

  • No tables used,当查询语句没有FROM子句时将会提示该额外信息
  • Impossible WHERE,当查询语句的WHERE子句永远为FALSE时将会提示该额外信息
  • Using where,搜索没有索引覆盖的列
  • No matching min/max row,当查询列表处有MIN或者MAX聚合函数,但是并没有符合WHERE子句中的搜索条件的记录时
  • Using index,当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用覆盖索引的情况下,在Extra列将会提示该额外信息。比方说下边这个查询中只需要用到idx_key1而不需要回表操
  • Using index condition,有些搜索条件中虽然出现了索引列,但却不能使用到索引,比如范围查询了
  • Using join buffer (Block Nested Loop),在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法
  • Not exists,当我们使用左(外)连接时,如果WHERE子句中包含要求被驱动表的某个列等于NULL值的搜索条件,而且那个列是不允许存储NULL值的,那么在该表的执行计划的Extra列就会提示这个信息
  • Using intersect(…) 、 Using union(…) 和 Using sort_union(…),如果执行计划的Extra列出现了Using intersect(…)提示,说明准备使用Intersect索引合并的方式执行查询,括号中的…表示需要进行索引合并的索引名称;如果出现Using union(…)提示,说明准备使用Union索引合并的方式执行查询;如果出现Using sort_union(…)提示,说明准备使用Sort-Union索引合并的方式执行查询
  • Zero limit,当我们的LIMIT子句的参数为0时,表示压根儿不打算从表中读取任何记录,将会提示该额外信息
  • Using filesort,有一些情况下对结果集中的记录进行排序是可以使用到索引的,如果查询中需要使用filesort的方式进行排序的记录非常多,那么这个过程是很耗费性能的,我们最好想办法将使用文件排序的执行方式改为索引进行排序
  • Using temporary,在许多查询的执行过程中,MySQL可能会借助临时表来完成一些功能,比如去重、排序之类的,比如我们在执行许多包含DISTINCT、GROUP BY、UNION等子句的查询过程中,如果不能有效利用索引来完成查询,MysQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的Extra列将会显示Using temporary提示

6、EXPLAIN进阶

6.1 EXPLAIN四种输出格式

EXPLAIN可以输出四种格式: 传统格式 ,JSON格式 , TREE格式 以及 可视化输出 。用户可以根据需要选择适用于自己的格式。

-- 传统格式简单明了,输出是一个表格形式,概要说明查询计划
EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;
-- 第1种格式中介绍的`EXPLAIN`语句输出中缺少了一个衡量执行好坏的重要属性 —— `成本`。而JSON格式是四种格式里面输出`信息最详尽`的格式,里面包含了执行的成本信息
EXPLAIN FORMAT=JSON SELECT ....
-- TREE格式是8.0.16版本之后引入的新格式,主要根据查询的 `各个部分之间的关系` 和 `各部分的执行顺序` 来描述如何查询
EXPLAIN FORMAT=tree SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a'\G
-- 可视化输出,可以通过MySQL Workbench可视化查看MySQL的执行计划。通过点击Workbench的放大镜图标,即可生成可视化的查询计划

6.2 SHOW WARNINGS的使用

在我们使用EXPLAIN语句查看了某个查询的执行计划后,紧接着还可以使用SHOW WARNINGS语句查看与这个查询的执行计划有关的一些扩展信息

7、分析优化器执行计划:trace

OPTIMIZER_TRACE是MySQL 5.6引入的一项跟踪功能,它可以跟踪优化器做出的各种决策〈比如访问表的方法、各种开销计算、各种转换等),并将跟踪结果记录到INFORMATION_SCHEMA.OPTIMIZER_TRACE表中。此功能默认关闭。开启trace,并设置格式为JSON,同时设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示。|

SET optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;

-- 开启后进行测试
select * from student where id < 10;
--  查询 information_schema.optimizer_trace 就可以知道MySQL是如何执行SQL的
select * from information_schema.optimizer_trace\G

-- ==================================================
-- 第1部分:查询语句
-- 第2部分:QUERY字段对应语句的跟踪信息
-- 第3部分:跟踪信息过长时,被截断的跟踪信息的字节数
-- 第4部分:执行跟踪语句的用户是否有查看对象的权限。当不具有权限时,该列信息为1且TRACE字段为空,一般在调用带有SQL SECURITY DEFINER的视图或者是存储过程的情况下,会出现此问题

8、MySQL监控分析视图-sys schema

关于MysQL的性能监控和问题诊断,我们一般都从performance_schema中去获取想要的数据,在MySQL5.7.7版本中新增sys schema,它将performance_schema和information_schema中的数据以更容易理解的方式总结归纳为"视图”,其目的就是为了降低查询performance_schema的复杂度,让DBA能够快速的定位问题。

8.1 Sys schema视图摘要

  • 主机相关:以host_summary开头,主要汇总了IO延迟的信息
  • Innodb相关:以innodb开头,汇总了innodb buffer信息和事务等待innodb锁的信息
  • I/o相关:以io开头,汇总了等待I/O、I/O使用量情况
  • 内存使用情况:以memory开头,从主机、线程、事件等角度展示内存的使用情况
  • 连接与会话信息:processlist和session相关视图,总结了会话相关信息
  • 表相关:以schema_table开头的视图,展示了表的统计信息
  • 索引信息:统计了索引的使用情况,包含冗余索引和未使用的索引情况
  • 语句相关:以statement开头,包含执行全表扫描、使用临时表、排序等的语句信息
  • 用户相关:以user开头的视图,统计了用户使用的文件I/O、执行语句统计信息
  • 等待事件相关信息:以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来完成监控、巡检等工作。

9、查询优化

9.1 索引生/失效场景

  • 全值匹配,索引加快速度
  • 最佳左前缀法则,在检索数据时从联合索引的最左边开始匹配
  • 主键插入顺序,自定义的主键列 id 拥有 AUTO_INCREMENT 属性,在插入记录时存储引擎会自动为我们填入自增的主键值。这样的主键占用空间小,顺序写入,减少页分裂
  • 计算、函数、类型转换(自动或手动)导致索引失效
  • 类型转换导致索引失效,比如int和char的等值查找
  • **范围条件右边的列索引失效,**范围右边的列不能使用索引了,主要针对索引,sql的前后顺序无所谓
  • 不等于(!= 或者<>)索引失效
  • **is null可以使用索引,is not null无法使用索引,**最好在设计数据库的时候就将字段设置为 NOT NULL 约束,比如你可以将 INT 类型的字段,默认值设置为0。将字符类型的默认值设置为空字符串(‘’)
  • **like以通配符%开头索引失效,**如果匹配字符串的第一个字符为’%‘,索引就不会起作用。只有’%'不在第一个位置,索引才会起作用
  • OR 前后存在非索引的列,索引失效

一般性建议

  • 统一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。不 同的 字符集 进行比较前需要进行 转换 会造成索引失效。
  • 对于单列索引,尽量选择针对当前query过滤性更好的索引
  • 在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
  • 在选择组合索引的时候,尽量选择能够当前query中where子句中更多的索引
  • 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面

9.2 关联查询优化

  • 保证被驱动表的JOIN字段已经创建了索引
  • 需要JOIN 的字段,数据类型保持绝对一致。
  • LEFT JOIN 时,选择小表作为驱动表, 大表作为被驱动表 。减少外层循环的次数。
  • INNER JOIN 时,MySQL会自动将 小结果集的表选为驱动表 。选择相信MySQL优化策略。
  • 能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)
  • 不建议使用子查询(结果集不走索引),建议将子查询SQL拆开结合程序多次查询,或使用 JOIN 来代替子查询。
  • 衍生表建不了索引

9.3 子查询优化

MySQL从4.1版本开始支持子查询,使用子查询可以进行SELECT语句的嵌套查询,即一个SELECT查询的结 果作为另一个SELECT语句的条件。 子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作 。
**子查询是 MySQL 的一项重要的功能,可以帮助我们通过一个 SQL 语句实现比较复杂的查询。但是,子 查询的执行效率不高。**原因:

  • 执行子查询时,MySQL需要为内层查询语句的查询结果 建立一个临时表 ,然后外层查询语句从临时表 中查询记录。查询完毕后,再 撤销这些临时表 。这样会消耗过多的CPU和IO资源,产生大量的慢查询。
  • 子查询的结果集存储的临时表,不论是**内存临时表还是磁盘临时表都 不会存在索引 **,所以查询性能会 受到一定的影响。
  • 对于返回结果集比较大的子查询,其对查询性能的影响也就越大。

**在MySQL中,可以使用连接(JOIN)查询来替代子查询。**连接查询 不需要建立临时表 ,其 速度比子查询 要快 ,如果查询中使用索引的话,性能就会更好

9.4 排序优化

在MySQL中,支持两种排序方式,分别是 FileSortIndex 排序。

  • Index 排序中,索引可以保证数据的有序性,不需要再进行排序,效率更高
  • FileSort 排序则一般在 内存中 进行排序,占用CPU较多。如果待排结果较大,会产生临时文件 I/O 到磁盘进行排序的情况,效率较低

优化建议:

  • SQL 中,可以在 WHERE 子句和 ORDER BY 子句中使用索引,目的是在 WHERE 子句中 避免全表扫描 ,在 ORDER BY 子句 避免使用 FileSort 排序 。当然,某些情况下全表扫描,或者 FileSort 排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率。
  • 尽量使用 Index 完成 ORDER BY 排序。如果 WHERE 和 ORDER BY 后面是相同的列就使用单索引列; 如果不同就使用联合索引。
  • 无法使用 Index 时,需要对 FileSort 方式进行调优
INDEX a_b_c(a,b,c)
-- order by 能使用索引最左前缀
- ORDER BY a
- ORDER BY a,b
- ORDER BY a,b,c
- ORDER BY a DESC,b DESC,c DESC
-- 如果WHERE使用索引的最左前缀定义为常量,则order by 能使用索引
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b = const ORDER BY c
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b > const ORDER BY b,c
-- 不能使用索引进行排序
- ORDER BY a ASC,b DESC,c DESC /* 排序不一致 */
- WHERE g = const ORDER BY b,c /*丢失a索引*/
- WHERE a = const ORDER BY c /*丢失b索引*/
- WHERE a = const ORDER BY a,d /*d不是索引的一部分*/
- WHERE a in (...) ORDER BY b,c /*对于排序来说,多个相等条件也是范围查询*/

ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序

  • 方案一: 为了去掉filesort我们可以把索引建成联合索引
  • 方案二:尽量让where的过滤条件和排序使用上索引

1.两个索引同时存在,mysql自动选择最优的方案。(对于这个例子,mysal选择idx_age _stuno_name)。但是,随着数据量的变化,选择的索引也会随之变化的。

2.当【范围条件】和【group by或者order by】的字段出现二选一时,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。

filesort算法:双路排序和单路排序

  • 双路排序 (慢)

MySQL 4.1之前是使用双路排序 ,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和 order by列 ,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出;从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段 。

  • 单路排序 (快,现在用)

从磁盘读取查询需要的 所有列 ,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了

优化策略

  • 尝试提高 sort_buffer_size,InnoDB默认是1048576字节,1MB
  • 尝试提高 max_length_for_sort_data
  • Order by 时select * 是一个大忌。最好只Query需要的字段

9.5 GROUP BY优化

  • group by 使用索引的原则几乎跟order by一致 ,group by 即使没有过滤条件用到索引,也可以直接使用索引。
  • group by 先排序再分组,遵照索引建的最佳左前缀法则
  • 当无法使用索引列,增大 max_length_for_sort_datasort_buffer_size 参数的设置
  • where效率高于having,能写在where限定的条件就不要写在having中了
  • 减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。Order by、group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。
  • 包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行 以内,否则SQL会很慢。

9.6 优化分页查询

一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是limit 2000000,10,此时需要MysQL排序前2000010记录,仅仅返回2o00000-2000010的记录,其他记录丢弃,查询排序的代价非常大。

-- 原句
EXPLAIN SELECT * FROM student LIMIT 2000000,10;
-- 优化思路一
-- 在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10) a WHERE t.id = a.id;

-- 优化思路二
-- 该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询
EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;

10、索引优化

10.1 优先考虑覆盖索引

**一个索引包含了满足查询结果的数据就叫做覆盖索引,**简单说就是, 索引列+主键 包含 SELECT 到 FROM之间查询的列

好处

  • **避免Innodb表进行索引的二次查询(回表),**Innodb是以聚集索引的顺序来存储的,对于Innodb来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据,在查找到相应的键值后,还需通过主键进行二次查询才能获取我们真实所需要的数据。在覆盖索引中,二级索引的键值中可以获取所要的数据,避免了对主键的二次查询,减少了IO操作,提升了查询效率
  • 可以把随机lO变成顺序IO加快查询效率,由于覆盖索引是按键值的顺序存储的,对于I0密集型的范围查找来说,对比随机从磁盘读取每一行的数据IO要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的IO转变成索引查找的顺序IO

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

弊端

索引字段的维护总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。这是业务DBA,或者称为业务数据架构师的工作。

10.2 如何给字符串添加索引

**使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。**前面 已经讲过区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。

结论: 使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是你在选择是否使用前缀索引时需要考虑的一个因素

10.3 索引下推

Index Condition Pushdown(ICP)是MySQL 5.6中新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式

  • 如果没有ICP,存储引擎会遍历索引以定位基表中的行,并将它们返回给MySQL服务器,由MySQL服务器评估WHERE后面的条件是否保留行
  • 启用ICP后,如果部分WHERE条件可以仅使用索引中的列进行筛选,则MySQL服务器会把这部分WHERE条件放到存储引擎筛选。然后,存储引擎通过使用索引条目来筛选数据,并且只有在满足这一条件时才从表中读取行
  • 好处:ICP可以减少存储引擎必须访问基表的次数和MySQL服务器必须访问存储引擎的次数。但是,ICP的加速效果取决于在存储引擎内通过ICP筛选掉的数据的比例
-- 默认情况下启动索引条件下推。可以通过设置系统变量optimizer_switch控制:index_condition_pushdown

-- 打开索引下推
SET optimizer_switch = 'index_condition_pushdown=on';
-- 关闭索引下推
SET optimizer_switch = 'index_condition_pushdown=off';
-- 当使用索引条件下推是,EXPLAIN语句输出结果中Extra列内容显示为Using index condition

-- 例如有索引(zipcode,lastname,address),如果没有索引下推就会全表,因为失效了
SELECT FROM people 
WHERE zipcode= '88B801' 
AND lastname LIKE '%张%' 
AND address LIKE '%北京市%';

ICP的使用条件

  • 如果表的访问类型为 range 、 ref 、 eq_ref 或者 ref_or_null 可以使用ICP
  • ICP可以使用InnDBMyISAM表,包括分区表InnoDBMyISAM
  • 对于InnoDB表,ICP仅用于二级索引。ICP的目标是减少全行读取次数,从而减少I/O操作
  • 当SQL使用覆盖索引时,不支持ICP优化方法。因为这种情况下使用ICP不会减少I/O
  • 相关子查询的条件不能使用ICP

10.4 普通索引 vs 唯一索引

**对于查询过程,**假设,执行查询的语句是 select id from test where k=5

  • 对于普通索引来说,查找到满足条件的第一个记录(5,500)后,需要查找下一个记录,直到碰到第一个不满足k=5条件的记录。
  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检 索。

那么,这个不同带来的性能差距会有多少呢?答案是, 微乎其微。为了说明普通索引和唯一索引对更新语句性能的影响这个问题,介绍一下change buffer。

对于更新过程,当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话, 在不影响数据一致性的前提下, InooDB会将这些更新操作缓存在change buffer中 ,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

将change buffer中的操作应用到原数据页,得到最新结果的过程称为 merge 。除了 访问这个数据页 会触 发merge外,系统有 后台线程会定期 merge。在 数据库正常关闭(shutdown) 的过程中,也会执行merge 操作。如果能够将更新操作先记录在change buffer, 减少读磁盘 ,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool 的,所以这种方式还能够 避免占用内存,提高内存利用率。唯一索引的更新就不能使用change buffer ,实际上也只有普通索引可以使用。

change buffer的使用场景

  • 普通索引和唯一索引应该怎么选择?其实,这两类索引在查询能力上是没差别的,主要考虑的是 对 更新性能 的影响。所以,建议你 尽量选择普通索引
  • 在实际使用中会发现, 普通索引 和 change buffer 的配合使用,对于 数据量大 的表的更新优化 还是很明显的
  • 如果所有的更新后面,都马上伴随着对这个记录的查询 ,那么你应该关闭change buffer。而在 其他情况下,change buffer都能提升更新性能。
  • 由于唯一索引用不上change buffer的优化机制,因此如果 业务可以接受 ,从性能角度出发建议优 先考虑非唯一索引。但是如果"业务可能无法确保"的情况下,怎么处理呢?
    • 首先, 业务正确性优先 。我们的前提是“业务代码已经保证不会写入重复数据”的情况下,讨论性能 问题。如果业务不能保证,或者业务就是要求数据库来做约束,那么没得选,必须创建唯一索引。 这种情况下,本节的意义在于,如果碰上了大量插入数据慢、内存命中率低的时候,给你多提供一 个排查思路。
    • 然后,在一些“ 归档库 ”的场景,你是可以考虑使用唯一索引的。比如,线上数据只需要保留半年, 然后历史数据保存在归档库。这时候,归档数据已经是确保没有唯一键冲突了。要提高归档效率, 可以考虑把表里面的唯一索引改成普通索引。

10.5 其它查询优化策略

1、EXISTS 和 IN 的区分

索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。哪个表小就用哪个表来驱动,A表小就用EXISTS,B表小就用IN。

2、COUNT(*)与COUNT(具体字段)效率

  • COUNT(*)COUNT(1)都是对所有结果进行COUNTCOUNT(*)COUNT(1)本质上并没有区别(二者执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的)。如果有WHERE子句,则是对所有符合筛选条件的数据行进行统计;如果没有WHERE子句,则是对数据表的数据行数进行统计。
  • 如果是MylSAM存储引擎,统计数据表的行数只需要O(1)的复杂度,这是因为每张 MyISAM的数据表都有一个meta信息存储了row_count值,而一致性则由表级锁来保证;如果是InnoDB存储引擎,因为InnoDB支持事务,采用行级锁和MVCC机制,所以无法像MyISAM一样,维护一个row_count变量,因此需要采用扫描全表,是O(n)的复杂度,进行循环+计数的方式来完成统计。
  • 在InnoDB引擎中,如果采用COUNT(具体字段)来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于COUNT(*)COUNT(1)来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。如果有多个二级索引,会使用key_len小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。

3、关于SELECT(*)

在表查询中,建议明确字段,不要使用 * 作为查询的字段列表,推荐使用SELECT <字段列表> 查询。原因:

  • MySQL 在解析的过程中,会通过查询数据字典 将"*"按序转换成所有列名,这会大大的耗费资源和时间。
  • 无法使用 覆盖索引

4、LIMIT 1 对优化的影响

针对的是会扫描全表的 SQL 语句,如果你可以确定结果集只有一条,那么加上 LIMIT 1 的时候,当找到一条结果的时候就不会继续扫描了,这样会加快查询速度;如果数据表已经对字段建立了唯一索引,那么可以通过索引进行查询,不会全表扫描的话,就不需要加上 LIMIT 1 了。

5、多使用COMMIT

只要有可能,在程序中尽量多使用 COMMIT,这样程序的性能得到提高,需求也会因为 COMMIT 所释放 的资源而减少。COMMIT 所释放的资源:

  • 回滚段上用于恢复数据的信息
  • 被程序语句获得的锁
  • redo / undo log buffer 中的空间
  • 管理上述 3 种资源中的内部花费

六、数据库其它调优策略

1、数据库调优的措施

1.1 调优的目标

  • 尽可能节省系统资源 ,以便系统可以提供更大负荷的服务。(吞吐量更大)
  • 合理的结构设计和参数调整,以提高用户操作响应的速度 。(响应速度更快)
  • 减少系统的瓶颈,提高MySQL数据库整体的性能。

1.2 如何定位调优问题

  • 用户的反馈(主要)

用户是我们的服务对象,因此他们的反馈是最直接的。虽然他们不会直接提出技术建议,但是有些问题往往是用户第一时间发现的。我们要重视用户的反馈,找到和数据相关的问题。

  • 日志分析((主要)

我们可以通过查看数据库日志和操作系统日志等方式找出异常情况,通过它们来定位遇到的问题。

  • 服务器资源使用监控

通过监控服务器的CPU、内存、I/o等使用情况,可以实时了解服务器的性能使用,与历史情况进行对比。

  • 数据库内部状况监控

在数据库的监控中,活动会话(Active Session)监控是一个重要的指标。通过它,你可以清楚地了解数据库当前是否处于非常繁忙的状态,是否存在SQL堆积等。

  • 其它

除了活动会话监控以外,我们也可以对事务、锁等待等进行监控,这些都可以帮助我们对数据库的运行状态有更全面的认识

1.3 调优的维度和步骤

1、选择适合的 DBMS
2、优化表设计
3、优化逻辑查询
4、优化物理查询
5、使用 Redis 或 Memcached 作为缓存
6、库级优化

2、优化MySQL服务器

2.1 优化服务器硬件

  • 配置较大的内存 。足够大的显存是提高MySQL数据库性能的方法之一。内存的速度比磁盘I/O快得多,可以通过增加系统的缓冲区容量使数据在内存中停留的时间更长,以减少磁盘I/O。
  • 配置高速磁盘系统,以减少读盘的等待时间,提高响应速度。磁盘的I/O能力,也就是它的寻道能力,目前的SCSI高速旋转的是7200转/分钟,这样的速度,一旦访问的用户量上去,磁盘的压力就会过大,如果是每天的网站pv (page view) 在150w,这样的一般的配置就无法满足这样的需求了。现在SSD盛行,在SSD上随机访问和顺序访问性能差不多,使用SSD可以减少随机IO带来的性能损耗。
  • 合理分布磁盘I/O,把磁盘I/O分散在多个设备,以减少资源竞争,提高冰箱操作能力。
  • 配置多处理器, MySQL是多线程的数据库,多处理器可同时执行多个线程

2.2 优化MySQL的参数

  • innodb_buffer_pool_size :这个参数是Mysql数据库最重要的参数之一,表示InnoDB类型的 表 和索引的最大缓存 。它不仅仅缓存 索引数据 ,还会缓存 表的数据 。这个值越大,查询的速度就会越 快。值太大会影响操作系统的性能。
  • key_buffer_size :表示 索引缓冲区的大小 。索引缓冲区是所有的 线程共享 。增加索引缓冲区可 以得到更好处理的索引(对所有读和多重写)。当然,这个值不是越大越好,它的大小取决于内存 的大小。如果这个值太大,就会导致操作系统频繁换页,也会降低系统性能。对于内存在 4GB 左右 的服务器该参数可设置为 256M 或 384M 。
  • table_cache :表示 同时打开的表的个数 。这个值越大,能够同时打开的表的个数越多。物理内 存越大,设置就越大。默认为2402,调到512-1024最佳。这个值不是越大越好,因为同时打开的表 太多会影响操作系统的性能。
  • query_cache_size :表示 查询缓冲区的大小 。可以通过在MySQL控制台观察,如果 Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况,就要增加Query_cache_size 的值;如果Qcache_hits的值非常大,则表明查询缓冲使用非常频繁,如果该值较小反而会影响效 率,那么可以考虑不用查询缓存;Qcache_free_blocks,如果该值非常大,则表明缓冲区中碎片很 多。MySQL8.0之后失效。该参数需要和query_cache_type配合使用。
  • query_cache_type 的值是0时,所有的查询都不使用查询缓存区。但是query_cache_type=0并不 会导致MySQL释放query_cache_size所配置的缓存区内存。
    • query_cache_type=1时,所有的查询都将使用查询缓存区,除非在查询语句中指定 SQL_NO_CACHE ,如SELECT SQL_NO_CACHE * FROM tbl_name。
    • query_cache_type=2时,只有在查询语句中使用 SQL_CACHE 关键字,查询才会使用查询缓 存区。使用查询缓存区可以提高查询的速度,这种方式只适用于修改操作少且经常执行相同的 查询操作的情况。
  • sort_buffer_size :表示每个 需要进行排序的线程分配的缓冲区的大小 。增加这个参数的值可以 提高 ORDER BY 或 GROUP BY 操作的速度。默认数值是2 097 144字节(约2MB)。对于内存在4GB 左右的服务器推荐设置为6-8M,如果有100个连接,那么实际分配的总共排序缓冲区大小为100 × 6 = 600MB。
  • join_buffer_size = 8M :表示 联合查询操作所能使用的缓冲区大小 ,和sort_buffer_size一样, 该参数对应的分配内存也是每个连接独享。
  • read_buffer_size :表示 每个线程连续扫描时为扫描的每个表分配的缓冲区的大小(字节) 。当线 程从表中连续读取记录时需要用到这个缓冲区。SET SESSION read_buffer_size=n可以临时设置该参 数的值。默认为64K,可以设置为4M。
  • innodb_flush_log_at_trx_commit :表示 何时将缓冲区的数据写入日志文件 ,并且将日志文件 写入磁盘中。该参数对于innoDB引擎非常重要。该参数有3个值,分别为0、1和2。该参数的默认值 为1。
    • 值为 0 时,表示 每秒1次 的频率将数据写入日志文件并将日志文件写入磁盘。每个事务的 commit并不会触发前面的任何操作。该模式速度最快,但不太安全,mysqld进程的崩溃会导 致上一秒钟所有事务数据的丢失。
    • 值为 1 时,表示 每次提交事务时 将数据写入日志文件并将日志文件写入磁盘进行同步。该模 式是最安全的,但也是最慢的一种方式。因为每次事务提交或事务外的指令都需要把日志写入 (flush)硬盘。
    • 值为 2 时,表示 每次提交事务时 将数据写入日志文件, 每隔1秒 将日志文件写入磁盘。该模 式速度较快,也比0安全,只有在操作系统崩溃或者系统断电的情况下,上一秒钟所有事务数 据才可能丢失。
  • innodb_log_buffer_size :这是 InnoDB 存储引擎的 事务日志所使用的缓冲区 。为了提高性能, 也是先将信息写入 Innodb Log Buffer 中,当满足 innodb_flush_log_trx_commit 参数所设置的相应条 件(或者日志缓冲区写满)之后,才会将日志写到文件(或者同步到磁盘)中。
  • max_connections :表示 允许连接到MySQL数据库的最大数量 ,默认值是 151 。如果状态变量 connection_errors_max_connections 不为零,并且一直增长,则说明不断有连接请求因数据库连接 数已达到允许最大值而失败,这是可以考虑增大max_connections 的值。在Linux 平台下,性能好的 服务器,支持 500-1000 个连接不是难事,需要根据服务器性能进行评估设定。这个连接数 不是越大 越好 ,因为这些连接会浪费内存的资源。过多的连接可能会导致MySQL服务器僵死。
  • back_log :用于 控制MySQL监听TCP端口时设置的积压请求栈大小 。如果MySql的连接数达到 max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即 back_log,如果等待连接的数量超过back_log,将不被授予连接资源,将会报错。5.6.6 版本之前默 认值为 50 , 之后的版本默认为 50 + (max_connections / 5), 对于Linux系统推荐设置为小于512 的整数,但最大不超过900。
    如果需要数据库在较短的时间内处理大量连接请求, 可以考虑适当增大back_log 的值。
  • thread_cache_size : 线程池缓存线程数量的大小 ,当客户端断开连接后将当前线程缓存起来, 当在接到新的连接请求时快速响应无需创建新的线程 。这尤其对那些使用短连接的应用程序来说可 以极大的提高创建连接的效率。那么为了提高性能可以增大该参数的值。默认为60,可以设置为 120。
  • wait_timeout:指定 一个请求的最大连接时间 ,对于4GB左右内存的服务器可以设置为5-10。
  • interactive_timeout :表示服务器在关闭连接前等待行动的秒数。
[mysqld]
port = 3306 
serverid = 1 
socket = /tmp/mysql.sock 
skip-locking #避免MySQL的外部锁定,减少出错几率增强稳定性。 
skip-name-resolve #禁止MySQL对外部连接进行DNS解析,使用这一选项可以消除MySQL进行DNS解析的时间。但需要注意,如果开启该选项,则所有远程主机连接授权都要使用IP地址方式,否则MySQL将无法正常处理连接请求! 
back_log = 384
key_buffer_size = 256M 
max_allowed_packet = 4M 
thread_stack = 256K
table_cache = 128K 
sort_buffer_size = 6M 
read_buffer_size = 4M
read_rnd_buffer_size=16M 
join_buffer_size = 8M 
myisam_sort_buffer_size =64M 
table_cache = 512 
thread_cache_size = 64 
query_cache_size = 64M
tmp_table_size = 256M 
max_connections = 768 
max_connect_errors = 10000000
wait_timeout = 10 
thread_concurrency = 8 #该参数取值为服务器逻辑CPU数量*2,在本例中,服务器有2颗物理CPU,而每颗物理CPU又支持H.T超线程,所以实际取值为4*2=8
skip-networking #开启该选项可以彻底关闭MySQL的TCP/IP连接方式,如果WEB服务器是以远程连接的方式访问MySQL数据库服务器则不要开启该选项!否则将无法正常连接! 
table_cache=1024
innodb_additional_mem_pool_size=4M #默认为2M 
innodb_flush_log_at_trx_commit=1
innodb_log_buffer_size=2M #默认为1M 
innodb_thread_concurrency=8 #你的服务器CPU有几个就设置为几。建议用默认一般为8 
tmp_table_size=64M #默认为16M,调到64-256最挂
thread_cache_size=120 
query_cache_size=32M

3、优化数据库结构

3.1 拆分表:冷热数据分离

拆分表的思路是,把1个包含很多字段的表拆分成2个或者多个相对较小的表。这样做的原因是,这些表中某些字段的操作频率很高(热数据),经常要进行查询或者更新操作,而另外一些字段的使用频率却很低(冷数据),冷热数据分离,可以减小表的宽度。如果放在一个表里面,每次查询都要读取大记录,会消耗较多的资源。

MySQL限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节。表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO。冷热数据分离的目的是:①减少磁盘lO,保证热数据的内存缓存命中率。②更有效的利用缓存,避免读入无用的冷数据

3.2 增加中间表

对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入中间表中,然后将原来的联合查询改为对中间表的查询,以此来提高查询效率。

首先,分析经常联合查询表中的字段;然后,使用这些字段建立一个中间表,并将原来联合查询的表的数据插入中间表中;最后,使用中间表来进行查询。

3.3 增加冗余字段

设计数据库表时应尽量遵循范式理论的规约,尽可能减少冗余字段,让数据库设计看起来精致、优雅。 但是,合理地加入冗余字段可以提高查询速度

3.4 优化数据类型

列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少,在遍历时所需要的IO次数也就越多,索引的性能也就越差。

  • 对整数类型数据进行优化
  • 既可以使用文本类型也可以使用整数类型的字段,要选择使用整数类型
  • 避免使用TEXT、BLOB数据类型
  • 避免使用ENUM类型
  • 使用TIMESTAMP存储时间
  • 用DECIMAL代替FLOAT和DOUBLE存储精确浮点数

3.5 优化插入记录的速度

1、MyISAM引擎的表
① 禁用索引
② 禁用唯一性检查
③ 使用批量插入
④ 使用LOAD DATA INFILE 批量导入
2、InnoDB引擎的表
① 禁用唯一性检查
② 禁用外键检查
③ 禁止自动提交

3.6 使用非空约束

在设计字段的时候,如果业务允许,建议尽量使用非空约束。这样做的好处是:

  • 进行比较和计算时,省去要对NULL值的字段判断是否为空的开销,提高存储效率。
  • 非空字段也容易创建索引。因为索引NULL列需要额外的空间来保存,所以要占用更多的空间。使用非空约束,就可以节省存储空间(每个字段1个bit)。

3.7 分析表、检查表与优化表

MySQL提供了分析表、检查表和优化表的语句。分析表主要是分析关键字的分布,检查表主要是检查表是否存在错误,优化表主要是消除删除或者更新造成的空间浪费

ANALYZE/CHECK/OPTIMIZE TABLE xxx

3.8 小结

上述这些方法都是有利有弊的。比如:

  • 修改数据类型,节省存储空间的同时,你要考虑到数据不能超过取值范围;
  • 增加冗余字段的时候,不要忘了确保数据一致性;
  • 把大表拆分,也意味着你的查询会增加新的连接,从而增加额外的开销和运维的成本。

4、大表优化

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下

4.1 限定查询的范围

禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制 在一个月的范围内;

4.2 读/写分离

4.3 垂直拆分与水平拆分

5、其它调优策略

5.1 服务器语句超时处理

在MySQL 8.0中可以设置 服务器语句超时的限制 ,单位可以达到 毫秒级别 。当中断的执行语句超过设置的 毫秒数后,服务器将终止查询影响不大的事务或连接,然后将错误报给客户端。设置服务器语句超时的限制,可以通过设置系统变量 MAX_EXECUTION_TIME 来实现。默认情况下, MAX_EXECUTION_TIME的值为0,代表没有时间限制

-- 指定该会话中SELECT语句的超时时间
SET GLOBAL MAX_EXECUTION_TIME=2000;

5.2 创建全局通用表空间

MySQL8.0使用CREATE TABLESPACE语句来创建一个全局通用表空间。全局表空间可以被所有的数据库的表共享,而且相比于独享表空间,使用手动创建共享表空间可以节约元数据方面的内存。可以在创建表的时候,指定属于哪个表空间,也可以对已有表进行表空间修改等。

5.3 MySQL 8.0新特性:隐藏索引对调优的帮助

不可见索引的特性对于性能调试非常有用。在MySQL 8.0中,索引可以被"隐藏"和“显示”。当一个索引被隐藏时,它不会被查询优化器所使用。也就是说,管理员可以隐藏一个索引,然后观察对数据库的影响。如果数据库性能有所下降,就说明这个索引是有用的,于是将其"恢复显示"即可;如果数据库性能看不出变化,就说明这个索引是多余的,可以删掉了。

需要注意的是当索引被隐藏时,它的内容仍然是和正常索引一样实时更新的。如果一个索引需要长期被隐藏,那么可以将其删除,因为索引的存在会影响插入、更新和删除的性能。数据表中的主键不能被设置为invisible


参考
https://www.bilibili.com/video/BV1iq4y1u7vj

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值