手记

小伙面试时被连环追问数据库优化, 面试前如何埋点反杀? 网友看完直呼: 太硬核了!

前言

周五的早高峰, 各地软件园地铁站里中出现了不少穿着长袖加绒格子衫, 背双肩电脑包的年轻码农, 现在节气正值 [ 小雪 ] , 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%'

  • 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
  • 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后面,不然索引将不会生效。

看完不妨问问自己如何回答这些问题, 也可写上在评论区留言回答, 温故知新 …

0人推荐
随时随地看视频
慕课网APP