# 如果你是用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
官网下载地址: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
# 查看临时密码
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';
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
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 | 以二进制方式比较 |
#查看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';
系统变量描述 | 系统变量描述 |
---|---|
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
在 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 语句必须以分号结尾。
宽松模式:
如果设置的是宽松模式,那么我们在插入数据的时候,即便是给了一个错误的数据,也可能会被接受,并且不报错。
举例:我在创建一个表时,该表中有一个字段为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
find / -name mysql
# /var/lib/mysql/
show variables like 'datadir';
# 相关命令目录:/usr/bin 和/usr/sbin。
# 配置文件目录:/usr/share/mysql-8.0(命令及配置文件),/etc/mysql(如my.cnf)
MySQL 系统自带的核心数据库,它存储了MySQL的用户账户和权限信息,一些存储过程、事件的定义信 息,一些运行过程中产生的日志信息,一些帮助信息以及时区信息等。
MySQL 系统自带的数据库,这个数据库保存着MySQL服务器维护的所有其他数据库的信息,比如有哪些表、哪些视图、哪些触发器、哪些列、哪些索引。这些信息并不是真实的用户数据,而是一些描述性信息,有时候也称之为元数据。在系统数据库information_schema 中提供了一些以innodb_sys 开头的表,用于表示内部系统表。SHOW TABLES LIKE 'innodb_sys%';
MySQL 系统自带的数据库,这个数据库里主要保存MySQL服务器运行过程中的一些状态信息,可以用来监控 MySQL 服务的各类性能指标。包括统计最近执行了哪些语句,在执行过程的每个阶段都花费了多长时间,内存的使用情况等信息。
MySQL 系统自带的数据库,这个数据库主要是通过视图的形式把information_schema
和performance_schema
结合起来,帮助系统管理员和开发人员监控 MySQL 的技术性能。
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';
举例:数据库a , **表b **
1、如果表b采用InnoDB
,data\a中会产生1个或者2个文件:
b.frm
:描述表结构文件,字段长度等ibdata1
中b.ibd
文件(存储数据信息和索引信息)此外:
① MySQL5.7 中会在data/a的目录下生成db.opt
文件用于保存数据库的相关配置。比如:字符集、比较
规则。而MySQL8.0不再提供db.opt文件。
② MySQL8.0中不再单独提供b.frm,而是合并在b.ibd文件中。
2、如果表b采用MyISAM
,data\a中会产生3个文件:
b.frm
:描述表结构文件,字段长度等。MySQL8.0 中 b.xxx.sdi
:描述表结构文件,字段长度等b.MYD (MYData)
:数据信息文件,存储数据信息(如果采用独立表存储模式)b.MYI (MYIndex)
:存放索引信息文件# 启动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服务器。-- =====================创建用户===========================
-- 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表的相应记录都消失了
-- 设置当前用户密码
-- 旧的写法如下(不推荐!!!!)
-- 修改当前用户的密码:(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";
-- 密码过期策略
-- 在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;
show privileges;
查看权限
-- =======================授予权限==========================
-- 分别是通过把角色赋予用户给用户授权和直接给用户授权
-- 该权限如果发现没有该用户,则会直接新建一个用户
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;
-- 注意: 须用户重新登录后才能生效
user表是MySQL中最重要的一个权限表,** 记录用户账号和权限信息,有49个字段。
这些字段可以分成4类,分别是范围列(或用户列)、权限列、安全列和资源控制列
范围列(或用户列)
%
表示所有远程通过 TCP方式的连接IP 地址
如 (192.168.1.2、127.0.0.1) 通过制定ip地址进行的TCP方式的连接机器名
通过制定网络中的机器名进行的TCP方式的连接::1
IPv6的本地ip地址,等同于IPv4的 127.0.0.1localhost
本地方式通过命令行方式的连接 ,比如mysql -u xxx -p xxx 方式的连接。权限列
安全列
安全列只有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;
-- 使用DESCRIBE查看db表的基本结构
DESCRIBE mysql.db;
用户列
db表用户列有3个字段,分别是Host、User、Db。这3个字段分别表示主机名、用户名和数据库名。表示从某个主机连接某个用户对某个数据库的操作权限,这3个字段的组合构成了db表的主键。
权限列
Create_routine_priv和Alter_routine_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
四个字段分别表示主机名、数据库名、用户名和表名。Table_priv
表示对象的操作权限。包括Select、Insert、Update、Delete、Create、Drop、Grant、References、Index和Alterdesc mysql.columns_priv;
rocs_priv表可以对存储过程和存储函数设置操作权限
,desc mysql.procs_priv;
当用户试图连接MySQL服务器时,服务器基于用户的身份以及用户是否能提供正确的密码验证身份来确定接受或者拒绝连接。即客户端用户会在连接请求中提供用户名、主机地址、用户密码,MySQL服务器接收到用户请求后,会使用user表中的host、user和authentication_string这3个字段匹配客户端提供信息。
服务器只有在user表记录的Host和User字段匹配客户端主机名和用户名,并且提供正确的密码时才接受连接。如果连接核实没有通过,服务器就完全拒绝访问;否则,服务器接受连接,然后进入阶段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也不会继续检查下一层级的表。
引入角色的目的是方便管理拥有相同权限的用户
-- ========================创建角色===========================
-- 创建角色使用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]
-- 如果你删除了角色,那么用户也就失去了通过这个角色所获得的所有权限
-- ========================给用户赋予角色===========================
-- 角色创建并授权后,要赋给用户并处于激活状态才能发挥作用。给用户添加角色可使用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';
MySQL是典型的C/S架构,即Clinet/Server 架构,服务端程序使用的mysqld。客户端进程向服务器进程发送一段文本(SQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)
Connectors,指的是不同语言中与SQL的交互。MySQL首先是一个网络程序,在TCP之上定义了自己的应用层协议。所以要使用MySQL,我们可以编写代码,跟MySQL Server 建立TCP连接,之后按照其定义好的协议进行交互。或者比较方便的方法是调用SDK,比如Native C API、JDBC、PHP等各语言MySQL Connecotr,或者通过JDBC。但通过SDK来访问MySQL,本质上还是在TCP连接上通过MySQL协议跟MySQL进行交互,MySQL Server结构可以分为三层:连接层、服务层、引擎层
系统(客户端)访问 MySQL 服务器前,做的第一件事就是建立 TCP 连接。 经过三次握手建立连接成功后, MySQL 服务器对 TCP 传输过来的账号密码做身份认证、权限获取。
TCP 连接收到请求后,必须要分配给一个线程专门与这个客户端的交互。所以还会有个线程池,去走后面的流程。每一个连接从线程池中获取线程,省去了创建和销毁线程的开销。所以连接管理的职责是负责认证、管理连接、获取权限信息。
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询
,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询
并创建相应的内部解析树
,并对其完成相应的优化
:如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是SELECT语句,服务器还会查询内部的缓存
。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
SELECT id,name FROM student WHERE gender = '女';
这个SELECT查询先根据WHERE语句进行 选取 ,而不是将表全部查询出来以后再进行gender过 滤。 这个SELECT查询先根据id和name进行属性 投影 ,而不是将属性全部取出以后再进行过 滤,将这两个查询条件 连接 起来生成最终查询结果。插件式存储引擎层( Storage Engines),真正的负责了MySQL中数据的存储和提取,对物理服务器级别维护的底层数据执行操作,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样 我们可以根据自己的实际需要进行选取。可以通过show engines;
查看
所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在文件系统 上,以文件的方式存在的,并完成与存储引擎的交互。当然有些存储引擎比如InnoDB,也支持不使用文件系统直接管理裸设备,但现代文件系统的实现使得这样做没有必要了。在文件系统之下,可以使用本地磁盘,可以使用 DAS、NAS、SAN等各种存储系统。
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 以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存
-- 了解查询语句底层执行的过程:`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多了缓存,相同查询两次可能执行过程会不一样
InnoDB 存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页面(包括读页面、写页面、创建新页面等操作)。而磁盘 I/O 需要消耗的时间很多,而在内存中进行操作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS 会申请占用内存来作为数据缓冲池 ,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访问
在 InnoDB 存储引擎中有一部分数据会放到内存中,缓冲池则占了这部分内存的大部分,它用来存储各种数据的缓存,包括了数据页、索引页、插入缓冲、锁信息、自适应 Hash 和数据字典信息等
缓存原则
“位置 * 频次”这个原则,可以帮我们对 I/O 访问效率进行优化。首先,位置决定效率,提供缓冲池就是为了在内存中可以直接访问数据。其次,频次决定优先级顺序。因为缓冲池的大小是有限的,比如磁盘有 200G,但是内存只有 16G,缓冲池大小只有 1G,就无法将所有数据都加载到缓冲池里,这时就涉及到优先级顺序,会优先对使用频次高的热数据进行加载;
**缓冲池的预读特性,**缓冲池的作用就是提升 I/O 效率,而我们进行读取数据的时候存在一个“局部性原理”,也就是说我们使用了一些数据,大概率还会使用它周围的一些数据,因此采用“预读”的机制提前加载,可以减少未来可能的磁盘 I/O 操作
冲池管理器会尽量将经常使用的数据保存起来,在数据库进行页面读操作的时候,首先会判断该页面 是否在缓冲池中,如果存在就直接读取,如果不存在,就会通过内存或磁盘将页面存放到缓冲池中再进行读取。
如果我们执行 SQL 语句的时候更新了缓存池中的数据,那么这些数据会马上同步到磁盘上吗?
实际上,当我们对数据库中的记录进行修改的时候,首先会修改缓冲池中页里面的记录信息,然后数据库会以一定的频率刷新到磁盘中。注意并不是每次发生更新操作,都会立即进行磁盘回写。缓冲池会采用一种叫做** checkpoint 的机制 将数据回写到磁盘上,这样做的好处就是提升了数据库的整体性能。比如,当缓冲池不够用时**,需要释放掉一些不常用的页,此时就可以强行采用checkpoint的方式,将不常用的脏页回写到磁盘上,然后再从缓存池中将这些页释放掉。这里的脏页 (dirty page) 指的是缓冲池中被修改过的页,与磁盘上的数据页不一致。
如果你使用的是 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
-- 修改多个实例
[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 实例。
当我们查询数据的时候,会先去 Buffer Pool 中查询。如果 Buffer Pool 中不存在,存储引擎会先将数据从磁盘加载到 Buffer Pool 中,然后将数据返回给客户端;同理,当我们更新某个数据的时候,如果这个数据不存在于 Buffer Pool,同样会先数据加载进来,然后修改内存的数据。被修改的数据会在之后统一刷入磁盘。
更新到一半突然发生错误了,想要回滚到更新之前的版本,该怎么办?连数据持久化的保证、事务回滚都做不到还谈什么崩溃恢复?答案:Redo Log & Undo Log
-- 查看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
存储引擎是负责对表中的数据进行提取和写入工作的,我们可以为 不同的表设置不同的存储引擎 ,也就是 说不同的表可以有不同的物理存储结构,不同的提取和写入方式
-- 我们之前创建表的语句都没有指定表的存储引擎,那就会使用默认的存储引擎 InnoDB,如果我们想显 式的指定一下表的存储引擎
CREATE TABLE 表名(
建表语句;
) ENGINE = 存储引擎名称;
-- 修改
ALTER TABLE 表名 ENGINE = 存储引擎名称;
ALTER TABLE engine_demo_table ENGINE = InnoDB;
-- 查看表结构
SHOW CREATE TABLE engine_demo_table\G
InnoDB 引擎:具备外键支持功能的事务存储引擎
大于等于5.5之后,默认采用InnoDB引擎
MyISAM 引擎:主要的非事务处理存储引擎
Archive 引擎:用于数据存档
Blackhole 引擎:丢弃写操作,读操作会返回空内容
CSV 引擎:存储数据时,以逗号分隔各个数据项
Memory 引擎:置于内存的表
Memory采用的逻辑介质是内存 ,响应速度很快 ,但是当mysqld守护进程崩溃的时候数据会丢失 。另外,要求存储的数据是数据长度不变的格式,比如,Blob和Text类型的数据不可用(长度不固定的)。
主要特征:
使用Memory存储引擎的场景:
Federated 引擎:访问远程表
NDB引擎:MySQL集群专用存储引擎
也叫做 NDB Cluster 存储引擎,主要用于 MySQL Cluster 分布式集群 环境,类似于 Oracle 的 RAC 集 群
索引是存储引擎用于快速找到数据记录的一种数据结构,MySQL进行数据查找时,首先查看查询条件是否命中某条索引,符合则通过索引查找相关数据,如果不符合则需要全表扫描,即需要一条一条地查找记录,直到找到与条件符合的记录
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
索引的本质:索引是数据结构。你可以简单理解为“排好序的快速查找数据结构”,满足特定查找算法。 这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现 高级查找算法 。索引是在存储引擎中实现的,因此每种存储引擎的索引不一定完全相同,并且每种存储引擎不一定支持所有索引类型。同时,存储引擎可以定义每个表的 最大索引数和 最大索引长度。所有存储引擎支持每个表至少16个索引,总索引长度至少为256字节。有些存储引擎支持更多的索引数和更大的索引长度。
假设目前表中的记录比较少,所有的记录都可以被存放到一个页中,在查找记录的时候可以根据搜索条件的不同分为两种情况:
二分法
快速定位到对应的槽,然后再遍历该槽对用分组中的记录即可快速找到指定记录。最小记录
开始 依次遍历单链表中的每条记录
, 然后对比每条记录是不是符合搜索条件。很显然,这种查找的效率是非常低的。在很多页中查找记录的活动可以分为两个步骤:
在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能 从第一个页沿着双向链表 一直往下找,在每一个页中根据我们上面的查找方式去查 找指定的记录。因为要遍历所有的数据页,所以这种方式显然是 超级耗时 的。如果一个表有一亿条记录呢?此时 索引 应运而生
首先建一个表
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表的行格式示意图
随着数据量的增大(一页一般是16K),存放数据远远大于一页,这就需要**给所有的页建立一个目录项,**每个页对应一个目录项,每个目录项包括下边两个部分:页的用户记录中最小的主键值,我们用 key 来表示;页号,我们用 page_on 表示。至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为 索引
InnoDB怎么区分一条记录是普通的 用户记录 还是 目录项记录 呢?使用记录头信息里的 record_type 属性,它的各自取值代表的意思如下:0:普通的用户记录;1:目录项记录;2:最小记录;3:最大记录
从图中可以看出来,我们新分配了一个编号为30的页来专门存储目录项记录。这里再次强调 目录项记录 和普通的 用户记录 的不同点:
相同点:两者用的是一样的数据页,都会为主键值生成 Page Directory (页目录),从而在按照主键值进行查找时可以使用 二分法 来加快查询速度。现在以查找主键为 20 的记录为例,根据某个主键值去查找记录的步骤就可以大致拆分成下边两步:
随着数据量增大,如果我们表中的数据非常多则会产生很多存储目录项记录的页,那我们怎么根据主键值快速定位一个存储目录项记录的页呢?那就为这些存储目录项记录的页再生成一个更高级的目录,就像是一个多级目录一样,大目录里嵌套小目录,小目录里才是实际的数据,所以现在各个页的示意图就是这样子;这个数据结构,它的名称是 B+树
对于**B+Tree,**一个B+树的节点其实可以分成好多层,规定最下边的那层,也就是存放我们用户记录的那层为第 0 层, 之后依次往上加。之前我们做了一个非常极端的假设:存放用户记录的页 最多存放3条记录 ,存放目录项 记录的页 最多存放4条记录 。其实真实环境中一个页存放的记录数量是非常大的,假设所有存放用户记录 的叶子节点代表的数据页可以存放 100条用户记录 ,所有存放目录项记录的内节点代表的数据页可以存 放 1000条目录项记录 ,那么:
你的表里能存放 100000000000 条记录吗?所以一般情况下,我们用到的 B+树都不会超过4层 ,那我们通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页),又因为在每个页面内有所谓的 Page Directory (页目录),所以在页面内也可以通过 二分法 实现快速 定位记录。
索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)和非聚簇(非聚集)索引。我们也把非聚集 索引称为二级索引或者辅助索引。
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(所有的用户记录都存储在了叶子结点),也就是所谓的 索引即数据,数据即索引。术语"聚簇"表示当前数据行和相邻的键值聚簇的存储在一起
特点:
页内
的记录是按照主键的大小顺序排成一个 单向链表
用户记录的页
也是根据页中用户记录的主键大小顺序排成一个 双向链表
目录项记录的页
分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个 双向链表
我们把具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引
的叶子节点处。这种聚簇索引并不需要我们在MySQL语句中显式的使用INDEX 语句去创建, InnDB
存储引擎会 自动
的为我们创建聚簇索引。
优点:
数据访问更快
,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快排序查找
和 范围查找
速度非常快节省了大量的io操作
缺点:
插入速度严重依赖于插入顺序
,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
更新主键的代价很高
,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新二级索引访问需要两次索引查找
,第一次找到主键值,第二次根据主键值找到行数据如果我们想以别的列作为搜索条件,我们可以多建几颗B+树,不同的B+树中的数据采用不同的排列规则。比方说我们用c2列的大小作为数据页、页中记录的排序规则,再建一课B+树,
这个B+树与上边介绍的聚簇索引有几处不同
**概念:回表 **
我们根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根 据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就 是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树
小结
叶子节点
存储的就是我们的数据记录
, 非聚簇索引的叶子节点存储的是数据位置
。非聚簇索引不会影响数据表的物理存储顺序。只能有一个聚簇索引
,因为只能有一种排序存储的方式,但可以有多个非聚簇索引
,也就是多个索引目录提供数据检索。查询效率高
,但如果对数据进行插入,删除,更新等操作,效率会比非聚簇索引低。我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按 照 c2和c3列 的大小进行排序,这个包含两层含义:
如图所示,我们需要注意以下几点:
注意一点,以c2和c3列的大小为排序规则建立的B+树称为 联合索引 ,本质上也是一个二级索引。它的意 思与分别为c2和c3列分别建立索引的表述是不同的,不同点如下:
实际上B+树的形成过程是这样的:
根结点
页面。最开始表中没有数据的时候,每个B+树索引对应的 根结点
中即没有用户记录,也没有目录项记录。根节点
中。空间用完时
继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如 页a
中,然后对这个新页进行 页分裂
的操作,得到另一个新页,比如页b
。这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到 页a
或者 页b
中,而 根节点
便升级为存储目录项记录的页。这个过程特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要我们对某个表建议一个索引,那么它的根节点的页号便会被记录到某个地方。然后凡是 InnoDB
存储引擎需要用到这个索引的时候,都会从哪个固定的地方取出根节点的页号,从而来访问这个索引
为了让新插入记录找到自己在那个页面,我们需要保证在B+树的同一层页节点的目录项记录除页号这个字段以外是唯一的。所以对于二级索引的内节点的目录项记录的内容实际上是由三个部分构成的:
最后一个B+树只需要很少的层级就可以轻松存储数亿条记录,查询速度相当不错!这是因为B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录,直到最后访问到存储真实数据的目录。那如果一个大的目录中只存放一个子目录是个啥效果呢?那就是目录层级非常非常多,而且最后的那个存放真实数据的目录中只存放一条数据。所以 InnoDB 的一个数据页至少可以存放两条记录。
索引 / 存储引擎 | MyISAM | InnoDB | Memory |
---|---|---|---|
B-Tree索引 | 支持 | 支持 | 支持 |
即使多个存储引擎支持同一种类型的索引,但是他们的实现原理也是不同的。Innodb和MyISAM默认的索 引是Btree索引;而Memory默认的索引是Hash索引。MyISAM引擎使用 B+Tree 作为索引结构,叶子节点的data域存放的是 数据记录的地址
MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的。
索引是个好东西,可不能乱建,它在空间和时间上都会有消耗:
一个表上索引建的越多,就会占用越多的存储空间,在增删改记录的时候性能就越差。为了能建立又好又少的索引,我们得学学这些索引在哪些条件下起作用的。
另外,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';
为了减少IO,索引树会一次性加载吗?
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-树更适合实际应用中操作系统的文件索引和数据库索引?
Hash 索引与 B+ 树索引的区别
Hash 索引与 B+ 树索引是在建索引的时候手动指定的吗?
针对InnoDB和MyISAM存储引擎,都会默认采用B+树索引,无法使用Hash 索引。InnoDB提供的自适应Hash是不需要手动指定的。如果是Memory/Heap和NDB存储引擎,是可以进行选择Hash索引的。
R树概念
R-Tree在MySQL很少使用,仅支持 geometry数据类型 ,支持该类型的存储引擎只有myisam、bdb、 innodb、ndb、archive几种
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)的概念。行、页、区、段、表空间的关系如下图所示
区(Extent)是比页大一级的存储结构,在InnoDB存储引擎中,一个区会分配64个连续的页。因为InnoDB中的页大小默认是16KB,所以一个区的大小是64*16KB= 1MB。段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在InnoDB中是连续的64个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。
页如果按类型划分的话,常见的有数据页(保存B+树节点)、系统表、Undo 页 和 事物数据页等。数据页是我们最常使用的页。数据页的 16KB 大小的存储空间被划分为七个部分,分别是文件头(File Header)、页头(Page Header)、最大最小记录(Infimum + supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer),页结构的示意图如下所示
这7个部分如下所示
B+树是如何进行记录检索的?
如果通过B+树的索引查询行记录,首先是从B+树的根开始,逐层检索,直到找到叶子节点,也就是找到对应的数据页为止,将数据页加载到内存中,页目录中的槽(slot)采用二分查找的方式先找到一个粗略的记录分组,然后再在分组中通过链表遍历的方式查找记录。
普通索引和唯一索引在查询效率上有什么不同?
唯一索引就是在普通索引上增加了约束性,也就是关键字唯一,找到了关键字就停止检索。而普通索引,可能会存在用户记录中的关键字相同的情况,根据页结构的原理,当我们读取一条记录的时候,不是单独将这条记录从磁盘中读出去,而是将这个记录所在的页加载到内存中进行读取。InnoDB存储引擎的页大小为16KB,在一个页中可能存储着上千个记录,因此在普通索引的字段上进行查找也就是在内存中多几次“判断下一条记录"的操作,对于CPU来说,这些操作所消耗的时间是可以忽略不计的。所以对一个索引字段进行检索,采用普通索引还是唯一索引在检索效率上基本上没有差别。
见文件InnoDB数据库存储结构,可以二进制打开看看mysql5.1,默认是COMPACT行格式
在MySQL 8.0中,默认行格式就是Dynamic,Dynamic、Compressed行格式和Compact行格式挺像,只不过在处理行溢出数据时有分歧:
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,有些页甚至哪个段都不属于。碎片区直属于表空间,并不属于任何一个段。
所以此后为某个段分配存储空间的策略是这样的:
所以现在段不能仅定义为是某些区的集合,更精确的应该是某些零散的页面以及一些完整的区的集合。
表空间可以看做是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 开头的表中的字段并不完全一样
MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。从 功能逻辑 上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引、全文索引。 按照 物理实现方式 ,索引可以分为 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;
第一支持降序索引,降序索引以降序存储键值。虽然在语法上,从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";
-- 第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);
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等,它们占用的存储空间依次递增,能表示的整数范围当然也是依次递增
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个。原因:
1、在where中使用不到的字段,不要设置索引
WHERE条件 (包括 GROUP BY、ORDER BY) 里用不到的字段不需要创建索引,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的。
2、 数据量小的表最好不要使用索引
如果表记录太少,比如少于1000个,那么是不需要创建索引的。表记录太少,是否创建索引 对查询效率的影响并不大。甚至说,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。
3、有大量重复数据的列上不要建立索引
在条件表达式中经常用到的不同值较多的列上建立索引,但字段中如果有大量重复数据,也不用创建索引。比如在学生表的"性别"字段上只有“男”与“女”两个不同值,因此无须建立索引。如果建立索引,不但不会提高查询效率,反而会严重降低数据更新速度。结论:当数据重复度大,比如 高于 10% 的时候,也不需要对这个字段使用索引
4、避免对经常更新的表创建过多的索引
第一层含义:频繁更新的字段不一定要创建索引。因为更新数据的时候,也需要更新索引,如果索引太多,在更新索引的时候也会造成负担,从而影响效率。
第二层含义:避免对经常更新的表创建过多的索引,并且索引中的列尽可能少。此时,虽然提高了查询速度,同时却降低更新表的速度。
5、不建议用无序的值作为索引
例如身份证、UUID(在索引比较时需要转为ASCII,并且插入时可能造成页分裂)、MD5、HASH、无序长字 符串等
6、删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响
7、不要定义夯余或重复的索引
SHOW [GLOBAL|SESSION] STATUS LIKE '参数';
-- 若查询MySQL服务器的连接次数,则可以执行如下语句
SHOW STATUS LIKE 'Connections';
-- 若查询服务器工作时间
SHOW STATUS LIKE 'Uptime';
-- 若查询MySQL服务器的慢查询次数
SHOW STATUS LIKE 'Slow_queries';
-- 慢查询次数参数可以结合慢查询日志找出慢查询语句,然后针对慢查询语句进行`表结构优化`或者`查询语句优化`。
-- 查看相关的指令情况
SHOW STATUS LIKE 'Innodb_rows_%';
一条SQL查询语句在执行前需要查询执行计划,如果存在多种执行计划的话,MySQL会计算每个执行计划所需要的成本,从中选择成本最小的一个作为最终执行的执行计划。
如果我们想要查看某条SQL语句的查询成本,可以在执行完这条SQL语句之后,通过查看当前会话中的last_query_cost变量值来得到当前查询的成本: SHOW STATUS LIKE 'last_query_cost'
。它通常也是我们评价一个查询的执行效率的一个常用指标。这个查询成本对应的是SQL 语句所需要读取的读页的数量。
**使用场景:**它对于比较开销是非常有用的,特别是我们有好几种查询方式可选的时候
SQL查询时一个动态的过程,从页加载的角度来看,我们可以得到以下两点结论:
位置决定效率
。如果页就在数据库缓冲池
中,那么效率是最高的,否则还需要从内存
或者磁盘
中进行读取,当然针对单个页的读取来说,如果页存在于内存中,会比在磁盘中读取效率高很多。批量决定效率
。如果我们从磁盘中对单一页进行随机读,那么效率是很低的(差不多10ms),而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多,甚至要快于单个页面在内存中的随机读取。所以说,遇到I/O并不用担心,方法找对了,效率还是很高的。我们首先要考虑数据存放的位置,如果是进程使用的数据就要尽量放到
缓冲池
中,其次我们可以充分利用磁盘的吞吐能力,一次性批量读取数据,这样单个页的读取效率也就得到了提升。
MySQL的慢查询日志,用来记录在MysQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上(不含10秒)的语句,认为是超出了我们的最大忍耐时间值。
它的主要作用是,帮助我们发现那些执行时间特别长的SQL查询,并且有针对性地进行优化,从而提高系统的整体效率。当我们的数据库服务器发生阻塞、运行变慢的时候,检查一下慢查询日志,找到那些慢查询,对解决问题很有帮助。比如一条sql执行超过5秒钟,我们就算慢sQL,希望能收集超过5秒的sql,结合explain进行全面分析。
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
-- 开启 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%';
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);
SELECT * FROM student WHERE stuno = 3455655;
SELECT * FROM student WHERE name = 'oQmLUr';
show status like 'slow_queries';
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具 mysqldumpslow 。mysqldumpslow --help
,mysqldumpslow 命令的具体参数如下:
# 举例:我们想要按照查询时间排序,查看前五条 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
-- 永久性方式,或者,把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命令来删除重建的。使用时一定要注意,一旦执行了这个命令,慢查询日志都只存在新的日志文件中,如果需要旧的查询日志,就必须事先备份
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的常用查询参数: **
日常开发需注意的结论:
如果在show profile诊断结果中出现了以上4条结果中的任何一条,则sql语句需要优化。
注意:
不过SHOW PROFILE命令将被启用,我们可以从 information_schema 中的 profiling 数据表进行查看
官网介绍:
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语句的各个输出项,可以有针对性的提升我们查询语句的性能。
EXPLAIN SELECT select_options
-- 或者
DESCRIBE SELECT select_options
EXPLAIN SELECT 1;
EXPLAIN 语句输出的各个列的作用如下:
列名 | 描述 |
---|---|
id | 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id |
select_type | SELECT关键字对应的那个查询的类型 |
table | 表名 |
type | 针对单表的访问方法⭐ |
possible_keys | 可能用到的索引 |
key | 实际用到的索引 |
key_len | 实际用到的索引长度 |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 预估的需要读取的记录条数 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra | 一些额外的信息 |
1、table
不论我们的查询语句有多复杂,里边儿 包含了多少个表 ,到最后也是需要对每个表进行 单表访问 的,所 以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该 表的表名(有时不是真实的表名字,可能是简称)
2、id
3、select_type
一条大的查询语句里边可以包含若干个SELECT关键字,每个SELECT关键字代表着一个小的查询语句,而每个SELECT关键字的FROM子句中都可以包含若干张表(这些表用来做连接查询),每一张表都对应着执行计划输出中的一条记录,对于在同一个SELECT关键字中的表来说,它们的id值是相同的。MySQL为每一个SELECT关键字代表的小查询都定义了一个称之为select_type的属性,意思是我们只要知道了某个小查询的select_type属性,就知道了这个小查询在整个大查询中扮演了一个什么角色
EXPLAIN SELECT * FROM s1 INNER JOIN s2;
EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a';
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
EXPLAIN SELECT * FROM (SELECT key1, count(*) as c FROM s1 GROUP BY key1) AS derived_s1 where c > 1;
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2);
4、partitions (可略)
5、type ☆
结果值从最好到最坏依次是: 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到底将如何执行给定的查询语句。
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的放大镜图标,即可生成可视化的查询计划
在我们使用EXPLAIN语句查看了某个查询的执行计划后,紧接着还可以使用SHOW WARNINGS语句查看与这个查询的执行计划有关的一些扩展信息
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的视图或者是存储过程的情况下,会出现此问题
关于MysQL的性能监控和问题诊断,我们一般都从performance_schema中去获取想要的数据,在MySQL5.7.7版本中新增sys schema,它将performance_schema和information_schema中的数据以更容易理解的方式总结归纳为"视图”,其目的就是为了降低查询performance_schema的复杂度,让DBA能够快速的定位问题。
-- ============================索引情况
-- 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来完成监控、巡检等工作。
一般性建议
MySQL从4.1版本开始支持子查询,使用子查询可以进行SELECT语句的嵌套查询,即一个SELECT查询的结 果作为另一个SELECT语句的条件。 子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作 。
**子查询是 MySQL 的一项重要的功能,可以帮助我们通过一个 SQL 语句实现比较复杂的查询。但是,子 查询的执行效率不高。**原因:
**在MySQL中,可以使用连接(JOIN)查询来替代子查询。**连接查询 不需要建立临时表 ,其 速度比子查询 要快 ,如果查询中使用索引的话,性能就会更好
在MySQL中,支持两种排序方式,分别是 FileSort
和 Index
排序。
效率更高
内存中
进行排序,占用CPU较多
。如果待排结果较大,会产生临时文件 I/O 到磁盘进行排序的情况,效率较低优化建议:
避免全表扫描
,在 ORDER BY 子句 避免使用 FileSort 排序
。当然,某些情况下全表扫描,或者 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方式排序
1.两个索引同时存在,mysql自动选择最优的方案。(对于这个例子,mysal选择idx_age _stuno_name)。但是,随着数据量的变化,选择的索引也会随之变化的。
2.当【范围条件】和【group by或者order by】的字段出现二选一时,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
filesort算法:双路排序和单路排序
MySQL 4.1之前是使用双路排序 ,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和 order by列 ,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出;从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段 。
从磁盘读取查询需要的 所有列 ,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了
优化策略
max_length_for_sort_data
和 sort_buffer_size
参数的设置一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是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;
**一个索引包含了满足查询结果的数据就叫做覆盖索引,**简单说就是, 索引列+主键 包含 SELECT 到 FROM之间查询的列
好处
由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
弊端
索引字段的维护总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。这是业务DBA,或者称为业务数据架构师的工作。
**使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。**前面 已经讲过区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。
结论: 使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是你在选择是否使用前缀索引时需要考虑的一个因素
Index Condition Pushdown(ICP)是MySQL 5.6中新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式
-- 默认情况下启动索引条件下推。可以通过设置系统变量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的使用条件
InnDB
和MyISAM
表,包括分区表InnoDB
和MyISAM
表InnoDB
表,ICP仅用于二级索引
。ICP的目标是减少全行读取次数,从而减少I/O操作**对于查询过程,**假设,执行查询的语句是 select id from test where k=5
那么,这个不同带来的性能差距会有多少呢?答案是, 微乎其微。为了说明普通索引和唯一索引对更新语句性能的影响这个问题,介绍一下change buffer。
对于更新过程,当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话, 在不影响数据一致性的前提下, InooDB会将这些更新操作缓存在change buffer中 ,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。
将change buffer中的操作应用到原数据页,得到最新结果的过程称为 merge 。除了 访问这个数据页 会触 发merge外,系统有 后台线程会定期 merge。在 数据库正常关闭(shutdown) 的过程中,也会执行merge 操作。如果能够将更新操作先记录在change buffer, 减少读磁盘 ,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool 的,所以这种方式还能够 避免占用内存,提高内存利用率。唯一索引的更新就不能使用change buffer ,实际上也只有普通索引可以使用。
change buffer的使用场景
1、EXISTS 和 IN 的区分
索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。哪个表小就用哪个表来驱动,A表小就用EXISTS,B表小就用IN。
2、COUNT(*)与COUNT(具体字段)效率
COUNT(*)
和COUNT(1)
都是对所有结果进行COUNT
,COUNT(*)
和COUNT(1)
本质上并没有区别(二者执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的)。如果有WHERE子句,则是对所有符合筛选条件的数据行进行统计;如果没有WHERE子句,则是对数据表的数据行数进行统计。O(1)
的复杂度,这是因为每张 MyISAM的数据表都有一个meta信息存储了row_count
值,而一致性则由表级锁来保证;如果是InnoDB存储引擎,因为InnoDB支持事务,采用行级锁和MVCC机制,所以无法像MyISAM一样,维护一个row_count变量,因此需要采用扫描全表
,是O(n)
的复杂度,进行循环+计数的方式来完成统计。COUNT(具体字段)
来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于COUNT(*)
和COUNT(1)
来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。如果有多个二级索引,会使用key_len小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。3、关于SELECT(*)
在表查询中,建议明确字段,不要使用 * 作为查询的字段列表,推荐使用SELECT <字段列表> 查询
。原因:
4、LIMIT 1 对优化的影响
针对的是会扫描全表的 SQL 语句,如果你可以确定结果集只有一条,那么加上 LIMIT 1 的时候,当找到一条结果的时候就不会继续扫描了,这样会加快查询速度;如果数据表已经对字段建立了唯一索引,那么可以通过索引进行查询,不会全表扫描的话,就不需要加上 LIMIT 1 了。
5、多使用COMMIT
只要有可能,在程序中尽量多使用 COMMIT,这样程序的性能得到提高,需求也会因为 COMMIT 所释放 的资源而减少。COMMIT 所释放的资源:
用户是我们的服务对象,因此他们的反馈是最直接的。虽然他们不会直接提出技术建议,但是有些问题往往是用户第一时间发现的。我们要重视用户的反馈,找到和数据相关的问题。
我们可以通过查看数据库日志和操作系统日志等方式找出异常情况,通过它们来定位遇到的问题。
通过监控服务器的CPU、内存、I/o等使用情况,可以实时了解服务器的性能使用,与历史情况进行对比。
在数据库的监控中,活动会话(Active Session)监控是一个重要的指标。通过它,你可以清楚地了解数据库当前是否处于非常繁忙的状态,是否存在SQL堆积等。
除了活动会话监控以外,我们也可以对事务、锁等待等进行监控,这些都可以帮助我们对数据库的运行状态有更全面的认识
1、选择适合的 DBMS
2、优化表设计
3、优化逻辑查询
4、优化物理查询
5、使用 Redis 或 Memcached 作为缓存
6、库级优化
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。
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。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
拆分表的思路是,把1个包含很多字段的表拆分成2个或者多个相对较小的表。这样做的原因是,这些表中某些字段的操作频率很高(热数据),经常要进行查询或者更新操作,而另外一些字段的使用频率却很低(冷数据),冷热数据分离,可以减小表的宽度。如果放在一个表里面,每次查询都要读取大记录,会消耗较多的资源。
MySQL限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节。表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO。冷热数据分离的目的是:①减少磁盘lO,保证热数据的内存缓存命中率。②更有效的利用缓存,避免读入无用的冷数据。
对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入中间表中,然后将原来的联合查询改为对中间表的查询,以此来提高查询效率。
首先,分析经常联合查询表中的字段;然后,使用这些字段建立一个中间表,并将原来联合查询的表的数据插入中间表中;最后,使用中间表来进行查询。
设计数据库表时应尽量遵循范式理论的规约,尽可能减少冗余字段,让数据库设计看起来精致、优雅。 但是,合理地加入冗余字段可以提高查询速度
列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少,在遍历时所需要的IO次数也就越多,索引的性能也就越差。
1、MyISAM引擎的表
① 禁用索引
② 禁用唯一性检查
③ 使用批量插入
④ 使用LOAD DATA INFILE 批量导入
2、InnoDB引擎的表
① 禁用唯一性检查
② 禁用外键检查
③ 禁止自动提交
在设计字段的时候,如果业务允许,建议尽量使用非空约束。这样做的好处是:
MySQL提供了分析表、检查表和优化表的语句。分析表主要是分析关键字的分布,检查表主要是检查表是否存在错误,优化表主要是消除删除或者更新造成的空间浪费
ANALYZE/CHECK/OPTIMIZE TABLE xxx
上述这些方法都是有利有弊的。比如:
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下
禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制 在一个月的范围内;
在MySQL 8.0中可以设置 服务器语句超时的限制 ,单位可以达到 毫秒级别 。当中断的执行语句超过设置的 毫秒数后,服务器将终止查询影响不大的事务或连接,然后将错误报给客户端。设置服务器语句超时的限制,可以通过设置系统变量 MAX_EXECUTION_TIME
来实现。默认情况下, MAX_EXECUTION_TIME的值为0,代表没有时间限制。
-- 指定该会话中SELECT语句的超时时间
SET GLOBAL MAX_EXECUTION_TIME=2000;
MySQL8.0使用CREATE TABLESPACE
语句来创建一个全局通用表空间。全局表空间可以被所有的数据库的表共享,而且相比于独享表空间,使用手动创建共享表空间可以节约元数据方面的内存。可以在创建表的时候,指定属于哪个表空间,也可以对已有表进行表空间修改等。
不可见索引的特性对于性能调试非常有用。在MySQL 8.0中,索引可以被"隐藏"和“显示”。当一个索引被隐藏时,它不会被查询优化器所使用。也就是说,管理员可以隐藏一个索引,然后观察对数据库的影响。如果数据库性能有所下降,就说明这个索引是有用的,于是将其"恢复显示"即可;如果数据库性能看不出变化,就说明这个索引是多余的,可以删掉了。
需要注意的是当索引被隐藏时,它的内容仍然是和正常索引一样实时更新的。如果一个索引需要长期被隐藏,那么可以将其删除,因为索引的存在会影响插入、更新和删除的性能。数据表中的主键不能被设置为invisible。
参考
https://www.bilibili.com/video/BV1iq4y1u7vj