存储引擎
1.MySQL 的体系结构可以分为四层
- 连接层:最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限
- 服务层:第二层架构主要完成大多数的核心服务功能,如 SQL 接口,并完成缓存的查询,SQL 的分析和优化,部分内置函数的执行。所有跨存储的引擎的功能也在这一层实现,如 过程、函数等
- 引擎层:存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 和存储引擎进行通信。
- 存储层:数据存储层,主要将数据(如:redolog、undolog、数据、索引、二进制日志、错误日志、查询日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。
点击此处即可领取282G网络安全学习资源
2.介绍存储引擎
- 属于表的存储引擎,就是存储引擎是在创建表的时候指定的,通过 show create table 表名 可以查看
- 通过 show engines 可以查看当前数据库支持的引擎
engine 引擎名
support 是否支持
comment 描述
transactions 是否支持事务
XA 是否支持分布式事务处理规范 [就是事务相关的一种规范]
Savepoints 是否支持保存点[用于取消部分事务,结束事务时,会自动删除该事务中所定义的所有保存点]
- 我们可以在创建表的时候指定存储引擎 engine = 存储引擎名
create table my_myisam{
id int,
username varchar(10),
} engine = MyISAM;
3.存储引擎的特点
InnoDB 存储引擎:
是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后, InnoDB 是默认的 MySQL 存储引擎
- DML 操作遵循 ACID 模型,支持事务
- 行级锁,提高并发访问性能
- 支持外键 Foreign Key 约束,保证数据的完整性和正确性
(1)表空间文件
- nnoDB 引擎的每张表都会对应一个 xxx.idb 这样的表空间文件,存储表结构、数据和索引
早期表结构 xxx.frm,在 MySQL8 中采用 xxx.sdi 的形式
- 通过 innodb_file_per_table 参数可以指定是一张表对应一个表空间文件,还是多张表对应一个表空间文件
默认是一张表对应一个表空间文件 [Value = ON]
通过 show variables like 'innodb_file_per_table' 指令可以查看配置情况
- 我们可以打开 MySQL 数据存放目录 C:\ProgramData\MySQL\MySQL Server 8.0\Data\zwh 查看表空间文件
- 在 Dos 中使用 idb2sdi 指令可以从 idb 文件中提取 sdi(表结构)信息
(2)逻辑存储空间
- 表空间:用来存储索引空间和数据空间,是逻辑存储结构的最高层次,对应 xxx.idb 文件
- 段:表空间由多个段组成,例如:数据段、索引段、回滚段等,由引擎自己完成段的管理
- 区:一个段又包含多个区,区是表空间的单元结构,每个区的大小为 1M
- 页:也是组成区的最小单元,大小为 16KB,所以一个区中有64个连续的页,页也是 InnoDB 存储引擎磁盘管理的最小单元,为了保证页的连续性, InnoDB 存储引擎每次从磁盘申请 4-5 个区
- 行:InnoDB 存储引擎是面向行的,也就是说数据按照行进行存放的,每一行除了定义表时所指定的字段以外,还包含两个隐藏字段 【事务id、滚动指针】
MyISAM 存储引擎:
- 是 MySQL 早期默认的存储引擎
不支持事务和外键
支持表锁,但不支持行锁
访问速度快
- 文件格式
xxx.sdi 存储表结构信息
xxx.MYD 存储数据信息
xxx.MYI 存储索引
Memory 存储引擎
- 表数据是存储在内存中的,所以这些表只能作为临时表或缓存使用
内存存放、hash 索引(默认)
- 文件格式
xxx.sdi 存储表结构信息
4.这三种引擎都有什么区别呢?
如何选择存储引擎呢?
-
根据应用系统的特点选择合适的存储引擎,复杂的应用系统还可以选择多种存储引擎组合使用
-
InnoDB 应用场景:
应用对事务的完整性有较高的要求,并发条件下要求数据的一致性
数据除了插入和查询外还有很多的更新、删除操作 -
MyISAM 应用场景:
以插入和查询操作为主,很少删除和更新操作,对事务完成性、并发要求不高 -
Memory 应用场景:
将数据保存到内存中,访问速度快,用于临时表和缓存功能的实现
对表的大小有限制,而且无法保证数据的安全性
索引
索引概述
-
索引是帮助 MySQL 高效获取数据的数据结构(有序)
-
正常我们要查询一条语句可能要扫描全表,使用索引可能遍历几个数
就可以获得结果,接下来通过案例简单说明:
假设我们要查询表中年龄为 45 的所有用户信息: select * from user where age = 45
在无索引情况下,就需要从第一行开始扫描,一直扫描到最后一行,我们称之为 全表扫描,性能很低
如果我们针对于这张表建立了索引,假设索引结构就是二叉树,那么也就意味着,会对age这个字段建 立一个二叉树的索引结构 【原理介绍示意图,并非索引真实数据结构】
因为这是一颗排序二叉树,所以我们只需要遍历三个数据就可以获得结果,极大的提高了查询效率
- 那么索引就没有缺点吗?
索引 虽然可以提高数据检索的效率,降低数据库 IO 成本,但是索引也要占用一定的数据空间
索引列对数据进行排序,降低数据排序成本、降低 CPU 的消耗,提高查询效率的同时,降低了表的更新速度
索引结构
1.内容概述
MySQL 的索引是子啊存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:
这几种是 MySQL 数据库所支持的所有的索引结构,下面是不同存储引擎对这几种索引的支持情况
2.二叉树
如果选择二叉树作为索引结构,会存在以下缺点:
- 顺序插入时,会形成一个链表,查询性能较低
- 大数据量情况下,层级较深,检索速度较慢
如果想避免出现二叉树很高的情况,我们当然可以选择红黑树,因为红黑树是一颗自平衡二叉树
但是大数据量情况下,还会产生较深的层级,检索速度还是很慢
3.B树
B-Tree (B树)是一种多叉路平衡查找树,相对于二叉树,B树每个节点可以有多个分支,我们称之为多叉
-
以一颗最大度数(max-degree) 为5的b树为例,那么这个 B树 每个结点最多可以存储 4个key,5个指针 【度数就是结点个数】
-
一旦结点存储的 key 数量达到5,就会裂变,中间元素向上分裂
-
在 B 树中,非叶子结点和叶子结点都会存放数据
4.B+ 树
-
B+树是B树的变种,我们可以看到
绿色的部分是索引部分,只具有索引作用,不存储任何数据
红色的部分是数据存储部分,在其叶子结点中存储具体的数据 -
B+树相对于B树有以下几个区别:
所有的数据都会存储到叶子结点上
叶子结点形成一个单向链表
非叶子结点仅仅起到索引数据作用
上面介绍的是标准的 B+ 树结构,MySQL对其进行了优化,增加了指向相邻叶子结点的链表指针,提高了区间访问的性能,有利于排序
5.Hash
哈希索引采用 hash 算法,将键值换成新的 hash 值,映射到对应的槽位上,然后存储在 hash 表中
如果多个键值映射到了同一个槽位,那么就用链表来解决 hash 冲突
hash 索引有以下特点:
- 只能用于对等比较 (in, =) 不能用于范围查询 (> < >= <=, …)
- 无法利用索引完成排序操作
- 查询效率较高,不出现哈希冲突的情况下只需要检索一次,效率远远超过 B+ 树
- 在 MySQL 中 Memory 存储引擎支持 Hash索引,因为 InnoDB 有自适应 hash 功能,所以 InnoDB 存储引擎也能用哈希索引
为什么 InnoDB 存储引擎选择 B+树索引结构?
- 相对于二叉树,层级更少,搜索效率高
- B-tree 无论叶子结点还是非叶子结点都会保存数据,因为页的大小是固定的,所以一页存储的键值、指针减少,和 B+tree 保存同样的数据树的高度更高,所以性能不如 B+树好
- 相对于 hahs 索引,B+tree 支持范围匹配和排序操作
索引分类
在 MySQL 数据库中,将索引分为以下几类:主键索引、唯一索引、常规索引、全文索引
而在 InnoDB 存储引擎中,根据索引的存储形式,又分为以下两种:
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引
- 如果不存在主键,将使用第一个唯一索引作为聚集索引
- 如果表没有主见,也没有合适的唯一索引,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引
我们来看一下聚集索引和二级索引的具体结构:
- 聚集索引的叶子结点下挂的是这一行的数据
- 二级索引的叶子节点下挂的是该字段的主键值
我们来分析一下执行SQL语句时查找流程:select * from user where name = ‘Arm’
第一步,根据 name 字段进行查询,所以先根据 name = ‘Arm’ 到 name 字段的二级索引中进行匹配查找,但是二级索引中只能查找到 Arm 对应的主键值 10
第二步,因为我们要获得的是 Arm 的全部信息,所以根据主键值10,到聚集索引中查找,最终找到10对应的行 row
第三步,返回这一行的数据
此处采用的是回表查询:先到二级索引中查找数据,找到主键值,然后再到聚合索引中根据主键值,获取数据
我们来看两道题思考一下:
以下两条 SQL 语句,哪个执
// 将 id 设为主键,name 设置了二级索引
A. select * from user where id = 10;
B. select * from user where name = 'Arm';
答:A 选项的执行效率更高,因为 A 选项的语句直接走的聚集索引,然而B选项要先走二级索引,然后再走聚集索引,也就是我们常说的回表查询
InnoDB 主键索引的 B+树高度为多高呢?
假设:一行数据大小为 1k, 一页中可以存储 16 行, InnoDB 的指针占用 6 个字节的空间,主键最多占用 8 个字节 (bigint)
(1) 如果高度为 2,假设页中有n个key,那么就有 n + 1 个指针
n * 8 + (n - 1) * 6 = 16 * 1024, 解得 n 约为 1170
1171 * 16 = 18736 // 1171 个指针对应 1171 个子节点(页),1页存储16行
所以 如果树的高度为2,则可以存储 18000 多条记录
(2) 如果高度为 3
1171 * 1171 * 16 = 21939856 // 一层1171个指针,两层两个 1171 相乘,最后乘一个指针对应页的记录条数
所以 如果树的高度为3,则可以存储 2200w 左右的记录
索引语法
在 MySQL 中与索引相关的基础语法:
- 创建索引 create [unique | fulltext] index index_name on table_name (index_col_name, …)
- 查看索引 show index from table_name
- 删除索引 drop index index_name on table_name
单纯看命令有可能看不懂或记不住,所以通过案例来进行演示
1.准备数据
create table tb_user(
id int primary key auto_increment comment ‘主键’,
name varchar(50) not null comment ‘用户名’,
phone varchar(11) not null comment ‘手机号’,
email varchar(100) comment ‘邮箱’,
profession varchar(11) comment ‘专业’,
age tinyint unsigned comment ‘年龄’,
gender char(1) comment ‘性别 , 1: 男, 2: 女’,
status char(1) comment ‘状态’,
createtime datetime comment ‘创建时间’
) comment ‘系统用户表’;
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘吕布’, ‘17799990000’, ‘lvbu666@163.com’, ‘软件工程’, 23, ‘1’,
‘6’, ‘2001-02-02 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘曹操’, ‘17799990001’, ‘caocao666@qq.com’, ‘通讯工程’, 33,
‘1’, ‘0’, ‘2001-03-05 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘赵云’, ‘17799990002’, ‘17799990@139.com’, ‘英语’, 34, ‘1’,
‘2’, ‘2002-03-02 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘孙悟空’, ‘17799990003’, ‘17799990@sina.com’, ‘工程造价’, 54,
‘1’, ‘0’, ‘2001-07-02 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘花木兰’, ‘17799990004’, ‘19980729@sina.com’, ‘软件工程’, 23,
‘2’, ‘1’, ‘2001-04-22 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘大乔’, ‘17799990005’, ‘daqiao666@sina.com’, ‘舞蹈’, 22, ‘2’,
‘0’, ‘2001-02-07 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘露娜’, ‘17799990006’, ‘luna_love@sina.com’, ‘应用数学’, 24,
‘2’, ‘0’, ‘2001-02-08 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘程咬金’, ‘17799990007’, ‘chengyaojin@163.com’, ‘化工’, 38,
‘1’, ‘5’, ‘2001-05-23 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘项羽’, ‘17799990008’, ‘xiaoyu666@qq.com’, ‘金属材料’, 43,
‘1’, ‘0’, ‘2001-09-18 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘白起’, ‘17799990009’, ‘baiqi666@sina.com’, ‘机械工程及其自动
化’, 27, ‘1’, ‘2’, ‘2001-08-16 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘韩信’, ‘17799990010’, ‘hanxin520@163.com’, ‘无机非金属材料工
程’, 27, ‘1’, ‘0’, ‘2001-06-12 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘荆轲’, ‘17799990011’, ‘jingke123@163.com’, ‘会计’, 29, ‘1’,
‘0’, ‘2001-05-11 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘兰陵王’, ‘17799990012’, ‘lanlinwang666@126.com’, ‘工程造价’,
44, ‘1’, ‘1’, ‘2001-04-09 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘狂铁’, ‘17799990013’, ‘kuangtie@sina.com’, ‘应用数学’, 43,
‘1’, ‘2’, ‘2001-04-10 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘貂蝉’, ‘17799990014’, ‘84958948374@qq.com’, ‘软件工程’, 40,
‘2’, ‘3’, ‘2001-02-12 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘妲己’, ‘17799990015’, ‘2783238293@qq.com’, ‘软件工程’, 31,
‘2’, ‘0’, ‘2001-01-30 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘芈月’, ‘17799990016’, ‘xiaomin2001@sina.com’, ‘工业经济’, 35,
‘2’, ‘0’, ‘2000-05-03 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘嬴政’, ‘17799990017’, ‘8839434342@qq.com’, ‘化工’, 38, ‘1’,
‘1’, ‘2001-08-08 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘狄仁杰’, ‘17799990018’, ‘jujiamlm8166@163.com’, ‘国际贸易’,
30, ‘1’, ‘0’, ‘2007-03-12 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘安琪拉’, ‘17799990019’, ‘jdodm1h@126.com’, ‘城市规划’, 51,
‘2’, ‘0’, ‘2001-08-15 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘典韦’, ‘17799990020’, ‘ycaunanjian@163.com’, ‘城市规划’, 52,
‘1’, ‘2’, ‘2000-04-12 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘廉颇’, ‘17799990021’, ‘lianpo321@126.com’, ‘土木工程’, 19,
‘1’, ‘3’, ‘2002-07-18 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘后羿’, ‘17799990022’, ‘altycj2000@139.com’, ‘城市园林’, 20,
‘1’, ‘0’, ‘2002-03-10 00:00:00’);
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES (‘姜子牙’, ‘17799990023’, ‘37483844@qq.com’, ‘工程造价’, 29,
‘1’, ‘4’, ‘2003-05-26 00:00:00’);
– A. name字段为姓名字段,该字段的值可能会重复,为该字段创建索引。
create unique index name_index on tb_user (name);
show index from tb_user;
– B. phone手机号字段的值,是非空,且唯一的,为该字段创建唯一索引。
create unique index phone_index on tb_user (phone);
– C. 为profession、age、status创建联合索引。
create index pas on tb_user (profession, age,status
);
– D. 为email建立合适的索引来提升查询效率。
create unique index email_index on tb_user (email);
– 查看所有索引
show index from tb_user;
SQL性能分析
1.SQL 执行频率分析
在 MySQL 客户端连接成功后,通过 show [session | global] status 命令可以提供服务器状态信息
mysql复制代码-- 插入次数
show global status like ‘Com_insert’;
– 删除次数
show global status like ‘Com_delete’;
– 查询次数
show global status like ‘Com_select’;
– 更新次数
show global status like ‘Com_update’;
主要的作用就是可以让我们知道当前数据库是以查询为主还是增删改为主,从而为数据库优化提供参考依据,要是以查询为主就要对数据库索引进行优化了
2.慢查询日志
我们可以借助慢查询日志去定义对哪些查询语句进行优化,慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认 10 秒)的所有 SQL语句的日志
mysql复制代码–
我使用的是 Linux 中 Docker 运行的 MySQL8 容器
– 进入容器内部 >> 进入 MySQL >> 查看慢查询相关内容
show variables like ‘slow%’;
– 如果没有开启慢查询,可以通过下面的指令开启
set global slow_query_log = ON;
– 设置慢 sql 阈值time,超过阈值就会被记录为慢 sql
set global slow_launch_time = time;
– 因为我不知道我的mysql容器数据卷,让我挂载到哪里去了
docker inspect mysql8
– 找到 Mounts 部分,上面是Linux中的目录,下面的MySQL容器中的目录
“/mydata/mysql/conf”
“/etc/mysql/conf.d”
“/mydata/mysql/data”
“/var/lib/mysql”
“/mydata/mysql/log”
“/logs”
– 根据上图的 slow_query_log_file 我找到了我慢查询文件位置
/mydata/mysql/data/cea8370ff66d-slow.log
– 为了模拟慢查询,我创建了一个表
create table test(
id int,
name varchar(10)
);
– 网上搜了一段通过存储过程模拟循环插入大量数据
CREATE PROCEDURE insert_data(IN start INT, IN end INT)
BEGIN
DECLARE i INT DEFAULT start;
WHILE i <= end DO
INSERT INTO test
VALUES (i, “test_datas”);
SET i = i + 1;
END WHILE;
END $$
DELIMITER ;
– 执行下面这个方法开始插入数据,本来想插入 100w 条数据,实在太慢了就插入了 15w 条
CALL insert_data(1, 10000000);
– 执行查询,没让我失望还是达到了慢查询的标准
select * from test;
– 然后我们去 Linux 服务器中查看一下慢查询日志
tail -f /mydata/mysql/data/cea8370ff66d-slow.log
通过慢查询日志,就可以定位到执行效率较低的 SQL,从而有针对性的进行优化
3.profile 详情
show profiles 能够帮助我们看到 SQL 语句时间主要消耗在哪块,有助于我们进行 SQL 优化
可以看到当前 MySQK 是否支持 profile 操作 [1 YES(开启) 0 NO(关闭)]
select @@have_profiling;
– @@profiling 用于控制查询分析的详细程度
– 值为0代表不会记录任何信息、值为1代表仅记录主要查询的信息、值为2还会记录所有子查询的信息
set profiling = 1;
接下来通过案例介绍 profiles 的用法
select * from tb_user;
select * from tb_user where id = 1;
select * from tb_user where name = ‘白起’;
select count(*) from tb_user;
– 查看每一条 SQL 的耗时情况
show profiles;
– 查看指定 query_id 的 SQL 语句各个阶段执行耗时情况
show profile for query 94;
– 查看指定 query_id 的 SQL 语句 CPU 的使用情况
show profile cpu for query 94;
4.explain
explain 或者 desc 命令获取 MySQL 如何执行 select 语句的信息,包括在 select 语句执行过程中表如何连接和连接的顺序
直接在 select 语句前使用关键字 explain | desc
explain select 字段列表 from 表名 where 条件列表;
索引使用
1.最左前缀法则
如果使用了联合索引(索引了多个列),要遵守最左前缀法则
- 查询从索引的最左列开始,如果 where 部分没有最左边那列,那么索引失效
- 如果有最左边的列,但是中间跳过了某个字段,那么从这个字段后的所有索引不生效
接下来通过案例演示与分析:
show index from table tb_user;
有一组聚合索引 (profession、age、status)
– 完整索引执行计划
explain select * from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’;
– 舍弃末尾的字段
explain select * from tb_user where profession = ‘软件工程’ and age = 31;
– 只保留最左边的字段
explain select * from tb_user where profession = ‘软件工程’;
以上的这三组测试中,我们发现只要联合索引最左边的字段 profession存在,索引就会生效,只不 过索引的长度不同。 而且由以上三组测试,我们也可以推测出profession字段索引长度为47、age 字段索引长度为2、status字段索引长度为5
– 没有最左边的列
explain select * from tb_user where age = 31 and status = ‘0’;
explain select * from tb_user where status = ‘0’;
explain select * from tb_user where age = 31;
– 中间跳过一列,索引部分生效
explain select * from tb_user where profession = ‘软件工程’ and status = ‘0’;
上面三组测试,因为不满足最左前缀法则,聚合索引最左边的列不存在,所以索引未生效
下面的这组测试,有最左边的那列,但是跳过了一列,所以后面的索引未生效 >> 索引长度就为 47
最左前缀法则指的是在执行 DQL 语句时,where 条件部分联合索引最左边的字段必须存在,与条件的先后顺序无关。
2.范围查询
联合索引中,出现范围查询 (>,<),范围查询右侧的列索引失效
– 范围查询使用了 > | <,范围查询右侧的索引失效
explain select * from tb_user where profession = ‘软件工程’ and age > 30 and status = ‘0’;
– 范围查询使用了 >=、<= 时,是不会出现索引失效的
explain select * from tb_user where profession = ‘软件工程’ and age >= 30 and status = ‘0’;
3.索引失效情况
(1)在索引列上进行运算操作,索引将失效
– 当根据 phone 字段进行等值匹配查询时,索引生效
explain select * from tb_user where phone = ‘17799990015’;
– 当根据 phone 字段进行函数运算操作之后,索引失效
explain select * from tb_user where substring(phone, 12, 2) = ‘15’;
(2)字符串类型字段使用时,不加引号,索引将失效
– 使用单引号和不使用单引号的聚合索引
explain select * from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’;
explain select * from tb_user where profession = ‘软件工程’ and age = 31 and status = 0;
– 使用单引号和不使用单引号的常规索引
explain select * from tb_user where phone = ‘17799990015’;
explain select * from tb_user where phone = 17799990015;
对于字符串不加单引号,对于查询结果没有什么影响,但是数据库存在隐式类型转换,索引将失效。
(3)如果仅仅是尾部模糊查询,索引不会失效,如果是头部模糊查询匹配,则索引失效
– 模糊匹配,首部模糊失效、尾部模糊索引不失效
explain select * from tb_user where profession like ‘软件%’;
explain select * from tb_user where profession like ‘%工程’;
explain select * from tb_user where profession like ‘%工%’;
在 like 模糊查询中,在关键字后面加 %,索引可以生效。但是在关键字前面加了%,索引将会失效
(4)用 or 分割的条件,如果 or 前面的列有索引,而后面的列中没有索引,那么设计的索引都不会被用到
explain select * from tb_user where id = 10 or age = 23;
– 特意试一下让没有索引的字段放在 or 前面,有索引的字段放在 or 后面
explain select * from tb_user where age = 23 or id = 10;
explain select * from tb_user where phone = ‘17799990017’ or age = 23;
– 我们可以对 age 字段建立索引测试:
create index age_index on tb_user(age);
– 再次测试
explain select * from tb_user where id = 10 or age = 23;
总结,当 or 连接的条件,左右两侧字段都有索引时,索引才会生效
(5)如果 MySQL评估使用索引比全表更慢,则不使用索引
– MySQL 评估使用索引比全表搜索更慢的情况下,那么就不会使用索引
explain select * from tb_user where phone >= ‘17799990005’;
explain select * from tb_user where phone >= ‘17799990015’;
经过测试我们发现,相同的SQL语句,只是传入的字段值不同,最终的执行计划也完全不一样,这是为 什么呢? 就是因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃 索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不 如走全表扫描来的快,此时索引就会失效
– 查看 is null 与 is not null 操作是否走索引
explain select * from tb_user where profession is null;
explain select * from tb_user where profession is not null;
查询时MySQL会评估,走索引快,还是全表扫描快,如果全表 扫描更快,则放弃索引走全表扫描。 is null 、is not null是否走索引,得具体情况具体 分析,并不是固定的
4.SQL 提示
mysql复制代码-- 查询 profession = ‘软件工程’ 的执行计划会用哪个索引
explain select * from tb_user where profession = ‘软件工程’;
– 为 profession 列创建常规索引
create index profession_index on tb_user(profession);
– 再次进行测试
explain select * from tb_user where profession = ‘软件工程’;
测试结果,我们可以看到,possible_keys中 pas,profession_index 这两个索引都可能用到,最终MySQL选择了 pas 索引。这是MySQL自动选择的结果
如果我们想指定使用哪个索引,或者不想用哪个索引就要用到 MySQL 中的 SQL 提示啦
- SQL 提示是优化数据库的一个重要手段,就是在 SQL 语句中加入一些人为的提示来达到优化操作的目的
- 相关语法:
– use index: 建议 MySQL 使用哪一个索引完成此次查询[建议不一定生效,mysql 内部会进行评估]
explain select * from tb_user use index(profession_index) where profession = ‘软件工程’;
– ignore index: 忽略指定的索引
explain select * from tb_user ignore index(profession_index) where profession = ‘软件工程’;
– force index: 强制使用索引
explain select * from tb_user force index(profession_index) where profession = ‘软件工程’
5.覆盖索引
什么是覆盖索引?
- 查询使用了索引,并且需要返回的列在该索引中已经全部能够找到
- 尽量使用覆盖索引,减少 select * 的使用,减少回表操作,提高效率
– 推荐使用覆盖索引,不用回表
explain select id, profession from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’ ;
explain select id,profession,age, status from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’ ;
explain select id,profession,age, status, name from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’ ;
explain select * from tb_user where profession = ‘软件工程’ and age = 31 and status= ‘0’;
我们可以看到四条 SQL 语句的执行计划前面所有的指标都是一样的,只有 Extra 部分是不同的
为,在tb_user表中有一个联合索引 pas,该索引关联了三个字段 profession、age、status,而这个索引也是一个二级索引,所以叶子节点下面挂的是这一行的主 键id。 所以当我们查询返回的数据在 id、profession、age、status 之中,则直接走二级索引 直接返回数据了。 如果超出这个范围,就需要拿到主键id,再去扫描聚集索引,再获取额外的数据了,这个过程就是回表。 而我们如果一直使用select * 查询返回所有字段值,很容易就会造成回表查询(除非是根据主键查询,此时只会扫描聚集索引)
如果查询中使用了索引,同时 Extra 列为空,可能有以下几种原因:
1.索引的选择性较低:当需要从表中查询的记录数量很多时,索引选择性会降低,此时 MySQL 仍会使用索引,但会放弃使用索引的排序功能,因此 Extra 列会显示为空。
2.查询的结果集太小:如果查询的结果集很小,MySQL 可能会选择全表扫描而不使用索引,因为全表扫描可能比使用索引更快。在这种情况下,Extra 为 NULL 是正常的。
3.没有使用索引的特殊功能:如果查询使用的索引支持某些特殊的功能,如覆盖索引(Covering Index)、索引合并(Index Merge)等,MySQL 仍然会使用索引,但 Extra 列不会显示特殊功能的相关信息,因此会显示为空。
如果对覆盖索引和回表查询不是很理解,可以看看下面几张图
我们思考一个问题一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对 以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from tb_user where username = ‘zhangsan’;
答:针对 username、password 建立联合索引,create index sp on user(username, password);因为这样做可以避免上述 SQL 在查询的过程中出现回表查询
6.前缀索引
当字段类型为字符串(varchar, text, longtext等)时,有时候需要索引很长的字符串,索引空间变大意味着查询时浪费大量的磁盘 IO,影响查询效率,所以我们可以将字符串的一部分前缀建立索引,这样可以大大节省索引空间,从而提升索引效率
(1) 语法格式
create index index_name on table_name(column(n))
– 为 email 设置前缀索引
create index email_index on tb_user(email(5));
– 查看当前 tb_user 的所有索引
show index from tb_user;
(2)前缀长度
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值, 索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的 [一列去重后记录数 / 总记录数]
select count(distinct email) / count() from tb_user;
select count(distinct substring(email, 1, 5)) / count() from tb_user;
(3)前缀索引的查询流程
7.单列索引与联合索引
- 单列索引:一个索引只包含单个列
- 联合索引:一个索引包含多个列
在and连接的两个字段 phone、name上都是有单列索引的,但是 最终mysql只会选择一个索引,也就是说,只能走一个字段的索引,此时是会回表查询的
但是,我们再来创建一个phone和name字段的联合索引,查询时,就走了联合索引,而在联合索引中包含 phone、name的信息,在叶子节点下挂的是对 应的主键id,所以查询是无需回表查询的,这样就可以提升查询效率
索引设计原则
- 针对于数据量较大,且查询比较频繁的表建立索引
- 针对于常作为查询条件(where)、排序(order by) 、分组(group by) 操作的字段建立索引
- 见谅选择分区度高的列作为索引,尽量建立唯一索引,分区度越高,使用索引的效率越高
- 如果是字符串类型的字段,字段的长度较长,针对于字段的特点,建立前缀索引
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提升查询效率
- 要控制索引的数量,索引不是多多益善,索引越多,维护索引结构的代价也越大,会影响增删改的效率
- 如果索引列不能存储 NULL 值,请在创建表时使用 NOT NULL 约束它。当优化器知道每列是否包含 NULL 值时,它可以更好地确定哪一索引最有效地用于查询
网络安全学习路线图
最后,给大家分享一个超棒的网络安全学习路线图(文末有高清图和XMIND文件)
点击此处即可免费获得282G网络安全学习资源
还有一些学习中收集的视频、文档资源,有需要的可以自取:
每个成长路线对应板块的配套视频:
当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。