前言
周五的早高峰, 各地软件园地铁站里中出现了不少穿着长袖加绒格子衫, 背双肩电脑包的年轻码农, 现在节气正值 [ 小雪 ] , 11月的全国性突然降温 , 让经历过996摧残的猿们一出地铁站就冻的打了个激灵 , 很庆幸的告诉大家距离放年假还剩不到 37 个工作日, 要买火车票的赶紧预约抢, 要租男朋友的赶紧联系我.
前几天我, 张大胖 , Mason 下班约好一起去吃海X捞火锅, 大家去了都围到到一张小火锅桌子上, 服务员把菜单递给我们.
这时张大胖拿着菜单就开口了, “今天不要点绿色的菜, 我最近手里的股票全部绿油油的 太护眼了, 先来个 牛油麻辣锅底! 来3份 草原牛肉片, 来1份牛肉丸子, 来3份牛楠, 剩下的你们点吧”.
我吸了口气说 “你这点全是肉, 合着哥几个今天陪你来长膘了” .
Mason 看我俩要掐起来 赶紧接上: “要不来一份西兰花, 一份花菜, 这样看着火锅汤色也不错~”.
张大胖皱了下眉毛, 叹了口气 “我前段时间去面试, 被面试官连环追问, Mysql数据库优化 一路追问到 B+树索引底层 ,我恨不得当场GG, 今天你又点树, 想想就头痛 ~” .
Mason 笑了笑说 "这个Mysql B+索引, 你每天都在用但是不知道它原理, 面试官估计心里在犯嘀咕, 这个人其它方面都合格, 但是就怕 ‘新同事来了,数据库变慢了’ 正确的使用并理解数据库索引就是最好的优化反之"
张大胖听的一头雾水然后问 到底怎么表述数据库优化给面试官才能抱的Offer归呢 ?
Mason 喝了口水说道 "那今天我们就边打边炉边聊聊数据库优化, 我以前专门整理过数据库优化相关的知识"
我急忙打断说 "要不先上菜吧, 中午就吃了碗热干面, 我现在太饿了"
数据库优化主要有3个方面
- SQL层面
- 使用 Join 替代子查询 (子程序查询结果会形成临时表)
- 正确书写SQL 使索引生效
- SELECT语句务必指明字段名称
- 当只需要一条数据的时候,使用limit 1
- 排序字段没有用到索引,就尽量少排序
- Ps: 平时写SQL ,多用 EXPLAIN 检查SQL质量 …
- 表结构层面
- 设计表时重中之重就是考虑这张表, 大概有什么操作要上什么索引.
- 增加冗余字段和中间表 (空间换时间)
- 确定字段正确存储范围 …
- 物理架构层面
- 数据库缓存配置
- 读写分离 当数据库读远大于写,查询多的情况,就可以考虑主数据负责写操作,从数据库负责读操作,一主多重,从而把数据读写分离,最后还可以结合redis等缓存来配合分担数据的读操作,大大的降低数据库的压力。
- **分库分表《阿里巴巴Java开发手册》提出单表行数超过500万行或者单表容量超过2GB,才推荐分库分表。**性能由综合因素决定,抛开业务复杂度,影响程度依次是硬件配置、MySQL配置、数据表设计、索引优化。500万这个值仅供参考,并非铁律。
- 对大表进行历史归档, 比如美团外卖订单只能看近一年的订单…
里奥ii:如何优化MySQL千万级大表,我写了6000字的解读
MySQL B+ Tree 索引原理
先来看看 这两种树形数据结构模拟自增ID索引场景 Ps: 大家有空也可以玩玩 数据可视化
两款经典树形数据结构,
-
左 : 二叉查找树, 右 : 平衡二叉树
- 二叉查找树概述
- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
- 假设用来做索引: 单一子树数据发生变化时无法进行左旋右旋平衡, 会导致二叉树严重不平衡, 恶劣情况查询复杂度由 O(log n) 跌为 O(n) 的链表.
-
平衡二叉树概述
- 它必须是二叉查找树 .
- 每个节点的左子树和右子树的高度差至多为1。
- 每次数据发生变换会进行 对应的 左旋 或 右旋 来平衡树的结构. 有效保证了+ 查询复杂度为 O(log n).
-
假设用来做索引:
- 1.在平衡二叉树中, 一颗层级为 100 树, 找到最末端的节点, 需要IO 100 次. IO效率很低.,
- 2. 预加载关联节点数据少, 每次加载IO只加载一个节点, 每次IO操作一页 (4KB数据) 。
图示讲解AVL平衡二叉树的左旋和右旋
综上所述: 这两种经典树形数据结构都不是最理想的数据库索引树, 当当当 !!!
大名鼎鼎的 B+树就横空出世了
- B+ 树定义
- B+树包含2种类型的结点:内部结点(也称索引结点)和叶子结点。根结点本身即可以是内部结点,也可以是叶子结点。根结点的关键字个数最少可以只有1个。
- B+树与B树最大的不同是内部结点不保存数据,只用于索引,所有数据(或者说记录)都保存在叶子结点中。
- m阶B+树表示了内部结点最多有m-1个关键字(或者说内部结点最多有m个子树),阶数m同时限制了叶子结点最多存储m-1个记录。
- 内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。
- 每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。
-
B+ 树的特点
- 非叶子节点不存储 data, 只存储索引 (冗余) 可放更多的索引.
- 叶子节点包括所有索引字段.
- 叶子节点用指针有序连接, 提高区间访问的性能.
- 常见的 B+树 为三层, 每个节点磁盘存储默认大小 16KB, 索引节点存有多个索引和多个指针(一个索引为 bigint类型约 8KB ,一个指针约 6 KB, 最多可存 1170个索引指针组合).
- 一次加载一个索引节点有效利于IO资源, 读取任意叶子节点都只需 3次 IO操作.
-
B+ 树在 MySQL 的应用
综上所述: 矮胖的B + 树 恰好弥补了 瘦高平衡二叉树的两点不足. 层级少, IO预加载数据多.
那在MySQL数据库中是如何使用 B+ Tree 构建自己的索引呢?
- InnoDB 索引方式 (部分内容来自官网)
InnoDB是一种兼顾了高可靠性和高性能的通用存储引擎。在MySQL 5.7中,InnoDB是默认的MySQL存储引擎。除非您配置了其他默认存储引擎,否则发出CREATE TABLE不带ENGINE= 子句的语句将创建一个InnoDB表。
InnoDB 的主要优势
- 它的DML操作遵循 ACID模型,并 具有具有 提交,回滚和 崩溃恢复 功能的事务, 以保护用户数据。有关更多信息,请参见 第14.2节“ InnoDB和ACID模型”。
- 行级锁定和Oracle风格的一致读取可提高多用户并发性和性能。有关更多信息,请参见第14.7节“ InnoDB锁定和事务模型”。
- InnoDB表格将您的数据排列在磁盘上以基于主键优化查询 。每个 InnoDB表都有一个称为聚集索引的主键索引,该索引 组织数据以最小化主键查找的I / O。有关更多信息,请参见第14.6.2.1节“聚集索引和二级索引”。
- 维护数据 完整性, InnoDB支持 FOREIGN KEY约束。使用外键检查插入,更新和删除操作,以确保它们不会导致不同表之间的不一致。有关更多信息,请参见 第13.1.18.6节“外键约束”。
- 在 test_innodb表中执行此SQL 会如何使用 InnoDB 索引 ?
select * from test_innodb where name = 'to%'
- 在 test_innodb表中执行此SQL 会如何使用 InnoDB 索引 ?
-
InnoDB 的缺点
- 5.7 以前不支持 全文检索, 最常用索引, 除了它 其他MySQL 索引都被官方称作替代存储引擎, 地位接近封神.
-
物理层面 (一张 test_innodb表默认有 2个存储文件组成)
- test_innodb.frm : 表结构定义
- test_innodb.ibd : 存放 test_innodb 表的数据和索引的文件
-
使用InnoDB表时的最佳做法 。
- 使用查询频率最高的一个或多个列为每个表 指定一个主键, 如果没有明显的主键,则指定一个 自动增量值。
- 使用联接时,无论是基于多个表中相同的ID值,还是从多个表中提取数据。为了提高连接性能,请在连接列上定义 外键,并在每个表中使用相同的数据类型声明这些列。添加外键可确保对引用的列进行索引,从而可以提高性能。外键还将删除或更新传播到所有受影响的表,并且如果父表中不存在相应的ID,则可以防止在子表中插入数据。
- 关闭自动提交。每秒提交数百次会限制性能(受存储设备的写入速度限制)。
- 分组组相关的DML 操作成 交易,通过包围他们START TRANSACTION和 COMMIT报表。虽然你不想过于频繁地提交,你也不想发出的巨大的批次 INSERT, UPDATE或 DELETE陈述,不提交运行小时。
- 不使用LOCK TABLES 语句。InnoDB可以一次处理多个会话,一次读写同一张表,而无需牺牲可靠性或高性能。要获得对一组行的排他性写访问权限,请使用 SELECT … FOR UPDATE语法仅锁定要更新的行。
- 启用该 innodb_file_per_table选项或使用常规表空间将表的数据和索引放入单独的文件中,而不是 系统表空间中。
innodb_file_per_table 默认情况下启用 该选项。 - 评估您的数据和访问模式是否受益于InnoDB表或页面 压缩功能。您可以在InnoDB不牺牲读/写能力的情况下压缩表。
- MyISAM 索引方式
创建的表占用的空间很小。表级锁定限制了读/写工作负载中的性能,因此它通常用于Web和数据仓库配置中的只读或只读工作负载中。
MyISAM 的主要优势
-
所有数据值均以低字节开头存储。这使数据机和操作系统独立。二进制可移植性的唯一要求是机器使用二进制补码带符号整数和IEEE浮点格式。这些要求已在主流机器中广泛使用。二进制兼容性可能不适用于有时具有特殊处理器的嵌入式系统。
先存储低字节数据没有明显的速度损失;表行中的字节通常是未对齐的,按顺序读取未对齐的字节所需的处理要多于反向的顺序。而且,与其他代码相比,服务器中获取列值的代码不是时间紧迫的。 -
所有数字键值都先存储高字节,以实现更好的索引压缩。
-
在支持大文件的文件系统和操作系统上,支持大文件。
- 在 test_myisam 表中执行此SQL 会如何使用 MyISAM 索引 ?
select * from test_myisam where id = 20
- 在 test_myisam 表中执行此SQL 会如何使用 MyISAM 索引 ?
-
MyISAM 的缺点
- 不支持事务, 仅支持 表级锁,系统奔溃后,MyISAM恢复起来比较困难.
-
物理层面 (一张 test_myisam 表默认有 3个存储文件组成)
- test_myisam.frm : 表结构定义
- test_myisam.MYD : 表数据 (文件后缀名全称 Mysql Data)
- test_myisam.MYI : 表索引 (文件后缀名全称 Mysql Index , 各字段索引之间独立)
- Innodb VS Myisam 本质上的区别
经典数据库优化面试问题
- 为什么索引结构默认使用B + Tree,而不是hash桶,二叉查找树,平衡二叉树?
hash:虽然可以快速定位,但是没有顺序,IO复杂度高。
二叉树:树的高度不均匀,不能自平衡,查找效率跟数据有关(树的高度),并且IO代价高。
平衡二叉树:树的高度随着数据量增加而增加,IO代价高。
B-Tree: 常见的 B+树 为三层, 每个节点磁盘存储默认大小 16KB, 索引节点存有多个索引和多个指针(一个索引为 bigint类型约 8KB ,一个指针约 6 KB, 最多可存 1170个索引指针组合). 一次加载一个索引(16KB)节点有效利于IO资源, 读取当前索引任意叶子节点都只需 3次 IO操作.
- 下列这行SQL 不使用索引的原因 ?
select * from test_innodb where name = '%to%'
先问是不是再问为什么, 我们先建一张表测试一波, 全模糊匹配是否使用索引 !
-- auto-generated definition
create table test_fuzzytext_index
(
id int auto_increment primary key,
context text not null,
context_fulltext text not null,
context_index varchar(100) not null
);
给 test_fuzzytext_index 的 文本类型的一些字段 加上索引形成和不加索引的对照组 !
-- 0. context 字段不添加索引
-- 1. context_index 字段 添加INDEX(普通索引)
ALTER TABLE test_fuzzytext_index ADD INDEX index_name (context_index);
-- 2. context_fulltext 字段 添加FULLTEXT(全文索引)
ALTER TABLE test_fuzzytext_index ADD FULLTEXT (context_fulltext);
最终表结构
Explain - Type 指标
访问类型,SQL查询优化中一个重要指标,查询性能从好到坏依次是
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般来说,好的SQL查询至少达到range级别,最好能达到ref。
- system:const连接类型的特例,表只有一行记录(等于系统表,平时不会出现,可以忽略不计)
- const:表中有且只有一个匹配行时使用,对主键或唯一索引的查询,效率最高,将主键置于WHERE列表中,MySQL就能将该查询转换为一个const
- eq_ref:唯一性索引或主键查找,对于每个索引键,表中只有一条记录与之匹配
- ref:非唯一性索引查找,返回匹配某个单独值的所有行(多行)
- ref_null:类似ref类型,附加对NULL值列的查询
- index_merge:索引合并优化方法(MySQL 5.6以后)
- range:索引范围扫描,常见于bettween、<、>、in查询,这种索引列上的范围扫描比全索引扫描要好。只需要开始于某个点,结束于另一个点,不用扫描全部索引
- index:全表索引扫描,使用索引而非数据行扫描
- ALL:全表扫描
index与ALL区别:index类型只遍历索引树,索引文件通常比数据文件小(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)。
Explain - Extra 指标
- distinct:优化distinct操作,在找到第一个匹配的元组后即停止找同样值的动作
- Not exists:使用not exists优化查询
- Using filesort:使用额外操作进行排序,而不是按照索引进行排序,通常出现在
- order by或group by查询
- Using index:使用覆盖索引进行查询,效率高
- Using temporary:使用临时表处理查询,常用于排序、子查询、分组查询
- Using where:MySQL服务器层使用WHERE条件过滤数据
- select tables optimized away:直接通过索引获得数据,不用访问表
预先随机插入一些文本数据到表中. 使用查询语句.
explain select * from test_fuzzytext_index tfi where tfi.context like '%索引%'
explain select * from test_fuzzytext_index tfi where tfi.context like '索引%'
分析结果:
%索引% type = ALL Extra = Using where , 结果集 50+ 耗时平均 130ms
索引% type = ALL Extra = Using where , 结果集 50+ 耗时平均 130ms
综上所述: 没有索引的字段默认全表扫描
explain select * from test_fuzzytext_index tfi where tfi.context_index like '%索引%'
select * from test_fuzzytext_index tfi where tfi.context_index like 索引%';
分析结果:
%索引% type = ALL Extra = Using where , 结果集 50+ 耗时平均 130ms
索引% type = renge Extra = Using where , 结果集 50+ 耗时平均 125ms
综上所述: %匹配词% 全模糊 没有使用索引, 右模糊type 为 renge ,
select * from test_fuzzytext_index where MATCH(context_fulltext) AGAINST ('+索引' in boolean mode);
分析结果:
使用了全文索引后 type = fulltext Extra = Using where; Ft_hints: no_ranking, 结果集 50+ 耗时平均 59ms
综上所述: 全文检索type为 fulltext, 注意目前 MySQL 全文索引只支持根据空格分词, 意思是 中文分词要提前用空格分词存入, 仅适合数据量小的场景. 随着数据增加检索速度也会和普通索引拉开差距.
select * from test_fuzzytext_index where MATCH(context_fulltext) AGAINST ('+索引' in boolean mode);
总结一下 %代表一个或多个字符的通配符, %关键字% 场景不使用索引, 只会进行全表扫描. 如果有搜索检索文章内容的需求, 可以使用 fulltext 索引 满足大多数 搜索场景. MySQL 5.7 支持.
那为什么 %关键字% 不使用索引呢?
在where条件后对索引字段加了函数转换或者运算逻辑**(+、-、*、/、!、<>、%、like’%_’(%放在前面)、or、in (疑问、可能存在成本问题)、exist等)**的处理,比如对时间戳字段进行日期格式化函数都会引起索引失效。
被优化器分析后, 发现走索引还不如不走索引, 效率更高.
- 为什么常见的 B+ 树是三层, 可以更多吗?
B+ 树 初始化时只有一层, 会随着数据扩大进行树的层级扩容, 那么MySQL生产环境是怎么样的呢 ?
索引节点: 一个索引为 bigint类型约 8 KB ,一个指针约 6 KB, 最多可存 1170个索引指针组合
数据节点: 大约 16KB , 索引节点不存储数据 , Mysql 一页 为 16 KB.
MySQL B+ Tree 三层树的最大行数为 : 1170 * 1170 * 16 = 21,902,400
+ Mysql 数据库索引的常用种类有几种,每种场景是什么 ?
主要有两种, 一种是 InnoDB 索引, 一种是 替代索引方案.
InnoDB 是 Mysql 5.7 的默认索引, 支持现代数据库理念的一切操作, 比如 事务, 行级锁, 数据库可恢复性好,默认顺序索引等…
替代索引方案中, 在一定的特定场景可用, 比如 MyISAM 可以在大量查询场景使用. CSV 可以用来做 数据分析, 使用场景有限.
-
InnoDB索引方式 与 MyISAM索引方式 的不同点 ?
-
三个外键关联表, 怎么写 SQL 会使用索引 ?
使用多列索引并根据最左匹配原则, 保证表结构设计阶段主表与关联表之间的关联字段的数据类型、数据长度、字段的编码格式以及字段的排序规则需要保持一致 .
在Mysql建立多列索引(联合索引)有最左前缀的原则,即最左优先。
如果我们建立了一个2列的联合索引(col1,col2),实际上已经建立了两个联合索引(col1)、(col1,col2);
如果有一个3列索引(col1,col2,col3),实际上已经建立了三个联合索引(col1)、(col1,col2)、(col1,col2,col3)。
1、b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+树是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道第一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。
2、比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。(这种情况无法用到联合索引)
- 使用多列索引时,怎么写 SQL 会使用索引?
当一张表的查询方式比较固定,这时候可以尝试创建多列索引,查询时应当遵从组合索引的规则,最左原则,查询时使用最频繁的一列放在最左边,
例:index(user_id,user_name,user_type)这是一个组合索引,当查询时如果想走索引则
sql:select * from userInfo where user_id='001' and user_name='小张' and user_type='1';
-- 这个时候是走了索引的,但是
select * from userInfo where user_name='小张' and user_type='1';
-- 这时user_id没有在where条件内将不走索引;
-- 此例,user_id字段必须出现在where后面,不然索引将不会生效。
看完不妨问问自己如何回答这些问题, 也可写上在评论区留言回答, 温故知新 …