一文搞懂数据库索引原理

news2025/2/2 9:44:08

前言

写数据库,我第一时间就想到了MySQL、Oracle、索引、存储过程、查询优化等等。

不知道大家是不是跟我想得一样,我最想写的是索引,为啥呢?

以下这个面试场景,不知道大家熟悉不熟悉:

面试官:数据库有几千万的数据,查询又很慢我们怎么办?

面试者:加索引。

面试官:那索引有哪些数据类型?索引是怎么样的一种结构?哪些字段又适合索引呢?B+的优点?聚合索引和非聚合索引的区别?为什么说索引会降低插入、删除、修改等维护任务的速度?……..

面试者:面试官怎么出我们公司门来着。

是的大家可能都知道慢了加索引,那为啥加,在什么字段上加,以及索引的数据结构特点,优点啥的都比较模糊或者甚至不知道。

那我们也不多BB了,直接开始这次的面试吧。

正文

我看你简历上写到了熟悉MySQL数据库以及索引的相关知识,我们就从索引开始,索引有哪些数据结构?

Hash、B+

大家去设计索引的时候,会发现索引类型是可以选择的。

为什么哈希表、完全平衡二叉树、B树、B+树都可以优化查询,为何Mysql独独喜欢B+树?

我先聊一下Hash:

大家可以先看一下下面的动图

注意字段值所对应的数组下标是哈希算法随机算出来的,所以可能出现哈希冲突

那么对于这样一个索引结构,现在来执行下面的sql语句:

select * from sanguo where name='鸡蛋'

可以直接对‘鸡蛋’按哈希算法算出来一个数组下标,然后可以直接从数据中取出数据并拿到所对应那一行数据的地址,进而查询那一行数据, 那么如果现在执行下面的sql语句:

select * from sanguo where name>'鸡蛋'

则无能为力,因为哈希表的特点就是可以快速的精确查询,但是不支持范围查询

如果做成了索引,那速度也是很慢的,要全部扫描。

问个题外话,那Hash表在哪些场景比较适合?

等值查询的场景,就只有KV(Key,Value)的情况,例如Redis、Memcached等这些NoSQL的中间件。

你说的是无序的Hash表,那有没有有序的数据结构?

有序数组,它就比较优秀了呀,它在等值查询的和范围查询的时候都很Nice。

那它完全没有缺点么?

不是的,有序的适合静态数据,因为如果我们新增、删除、修改数据的时候就会改变他的结构。

比如你新增一个,那在你新增的位置后面所有的节点都会后移,成本很高。

那照你这么说他根本就不优秀啊,特点也没地方放。

此言差矣,可以用来做静态存储引擎啊,用来保存静态数据,例如你2019年的支付宝账单,2019年的淘宝购物记录等等都是很合适的,都是不会变动的历史数据。

有点东西啊小伙子,那二叉树呢?

二叉树的新增和结构如图:

二叉树的结构我就不在这里多BB了,不了解的朋友可以去看看数据结构章节。

二叉树是有序的,所以是支持范围查询的。

但是他的时间复杂度是O(log(N)),为了维持这个时间复杂度,更新的时间复杂度也得是O(log(N)),那就得保持这棵树是完全平衡二叉树了。

怎么听你一说,平衡二叉树用来做索引还不错呢?

此言差矣,索引也不只是在内存里面存储的,还是要落盘持久化的,可以看到图中才这么一点数据,如果数据多了,树高会很高,查询的成本就会随着树高的增加而增加。

为了节约成本很多公司的磁盘还是采用的机械硬盘,这样一次千万级别的查询差不多就要10秒了,这谁顶得住啊?

如果用B树呢?

同理来看看B树的结构:

可以发现同样的元素,B树的表示要比完全平衡二叉树要“矮”,原因在于B树中的一个节点可以存储多个元素。

B树其实就已经是一个不错的数据结构,用来做索引效果还是不错的。

那为啥没用B树,而用了B+树?

一样先看一下B加的结构:

我们可以发现同样的元素,B+树的表示要比B树要“胖”,原因在于B+树中的非叶子节点会冗余一份在叶子节点中,并且叶子节点之间用指针相连。

那么B+树到底有什么优势呢?

其实很简单,我们看一下上面的数据结构,最开始的Hash不支持范围查询,二叉树树高很高,只有B树跟B+有的一比。

B树一个节点可以存储多个元素,相对于完全平衡二叉树整体的树高降低了,磁盘IO效率提高了。

而B+树是B树的升级版,只是把非叶子节点冗余一下,这么做的好处是为了提高范围查找的效率

提高了的原因也无非是会有指针指向下一个节点的叶子节点。

小结:到这里可以总结出来,Mysql选用B+树这种数据结构作为索引,可以提高查询索引时的磁盘IO效率,并且可以提高范围查询的效率,并且B+树里的元素也是有序的。

那么,一个B+树的节点中到底存多少个元素最合适你有了解过么?

额这个这个?卧*有点懵逼呀。

过了一会还是没想出,只能老实交代:这个不是很了解咳咳。

你可以换个角度来思考B+树中一个节点到底多大合适?

B+树中一个节点为一页或页的倍数最为合适

为啥?

因为如果一个节点的大小小于1页,那么读取这个节点的时候其实也会读出1页,造成资源的浪费。

如果一个节点的大小大于1页,比如1.2页,那么读取这个节点的时候会读出2页,也会造成资源的浪费。

所以为了不造成浪费,所以最后把一个节点的大小控制在1页、2页、3页、4页等倍数页大小最为合适。

你提到了页的概念,能跟我简单说一下么?

首先Mysql的基本存储结构是(记录都存在页里边):

  • 各个数据页可以组成一个双向链表
  • 每个数据页中的记录又可以组成一个单向链表
  • - 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录
  • 其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录

所以说,如果我们写 select * from user where username='丙丙'这样没有进行任何优化的sql语句,默认会这样做:

  • 定位到记录所在的页
  • - 需要遍历双向链表,找到所在的页
  • 从所在的页内中查找相应的记录
  • - 由于不是根据主键查询,只能遍历所在页的单链表了

很明显,在数据量很大的情况下这样查找会很慢!看起来跟回表有点点像。

哦?回表你聊一下。

卧槽,该死,我嘴干嘛。

回表大概就是我们有个主键为ID的索引,和一个普通name字段的索引,我们在普通字段上搜索:

sql select * from table where name = '丙丙'

执行的流程是先查询到name索引上的“丙丙”,然后找到他的id是2,最后去主键索引,找到id为2对应的值。

回到主键索引树搜索的过程,就是回表。不过也有方法避免回表,那就是覆盖索引

哦?那你再跟我聊一下覆盖索引呗?

!!!我这个嘴。。。

这个其实比较好理解,刚才我们是 select * ,查询所有的,我们如果只查询ID那,其实在Name字段的索引上就已经有了,那就不需要回表了。

覆盖索引可以减少树的搜索次数,提升性能,他也是我们在实际开发过程中经常用来优化查询效率的手段。

很多联合索引的建立,就是为了支持覆盖索引,特定的业务能极大的提升效率。

索引的最左匹配原则知道么?

最左匹配原则

  • 索引可以简单如一个列 (a),也可以复杂如多个列 (a,b,c,d),即联合索引。
  • 如果是联合索引,那么key也由多个列组成,同时,索引只能用于查找key是否存在(相等),遇到范围查询 (>、<、between、like左匹配)等就不能进一步匹配了,后续退化为线性查找。
  • 因此,列的排列顺序决定了可命中索引的列数

例子:

  • 如有索引 (a,b,c,d),查询条件 a=1 and b=2 and c>3 and d=4,则会在每个节点依次命中a、b、c,无法命中d。(c已经是范围查询了,d肯定是排不了序了)

总结

索引在数据库中是一个非常重要的知识点!

上面谈的其实就是索引最基本的东西,N叉树,跳表、LSM我都没讲,同时要创建出好的索引要顾及到很多的方面:

  • 最左前缀匹配原则。这是非常重要、非常重要、非常重要(重要的事情说三遍)的原则,MySQL会一直向右匹配直到遇到范围查询 (>,<,BETWEEN,LIKE)就停止匹配。
  • 尽量选择区分度高的列作为索引,区分度的公式是 COUNT(DISTINCT col)/COUNT(*)。表示字段不重复的比率,比率越大我们扫描的记录数就越少。
  • 索引列不能参与计算,尽量保持列“干净”。比如, FROM_UNIXTIME(create_time)='2016-06-06' 就不能使用索引,原因很简单,B+树中存储的都是数据表中的字段值,但是进行检索时,需要把所有元素都应用函数才能比较,显然这样的代价太大。所以语句要写成 :create_time=UNIX_TIMESTAMP('2016-06-06')。
  • 尽可能的扩展索引,不要新建立索引。比如表中已经有了a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
  • 单个多列组合索引和多个单列索引的检索查询效果不同,因为在执行SQL时,MySQL只能使用一个索引,会从多个单列索引中选择一个限制最为严格的索引(经指正,在MySQL5.0以后的版本中,有“合并索引”的策略,翻看了《高性能MySQL 第三版》,书作者认为:还是应该建立起比较好的索引,而不应该依赖于“合并索引”这么一个策略)。
  • “合并索引”策略简单来讲,就是使用多个单列索引,然后将这些结果用“union或者and”来合并起来

 

 

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/762542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

郑州网站域名升级https通配符证书

新创建的网站如果没有安装SSL证书&#xff0c;在客户端与服务器传输信息时会使用明文传输&#xff0c;明文传输的数据容易被其他人截获或者插入违法信息&#xff0c;会对网站所有者和访问网站的客户带来危害。而部署了SSL证书将网站域名由http升级为https&#xff0c;会在客户端…

【PHP面试题51】Swoole是什么?有什么特点,主要解决了什么问题

文章目录 一、前言二、什么是Swoole&#xff1f;三、如何使用Swoole&#xff1f;四、Swoole主要解决了什么问题&#xff1f;4.1 高性能4.2 高并发4.3 异步编程4.4 多协议支持 五、总结 一、前言 本文已收录于PHP全栈系列专栏&#xff1a;PHP面试专区。 计划将全覆盖PHP开发领域…

jdk1.8安装教程及环境变量配置(含jdk8,11,13安装文件)

目录 友情提醒第一章、JVM、JRE、JDK介绍第二章、下载和安装JDK2.1&#xff09;百度网盘直接下载免安装2.2&#xff09;官网下载安装JDK&#xff08;需要收费&#xff09; 第三章、环境变量配置3.1&#xff09;windows环境变量配置3.2&#xff09;验证环境变量是否配置成功 友情…

spark启动HA时workers为0,且为standby状态

今天学习一个spark视频&#xff0c;在启动StandAloneHa模式的时候&#xff0c;发现workers为0&#xff0c;而且spark两个master的状态都为standby&#xff0c;找了很久&#xff0c;才知道我用的spark3.2 最低支撑的zookeeper版本为3.5.x,而且zookeeper的安装包是需要带bin的那个…

自学C++(4)

1、友元 友元的目的就是让一个函数或者类访问另一个类中private成员 1.1全局函数做友元 写法&#xff1a;在类外定义一个全局函数&#xff0c;如果这个函数访问到了类中private成员&#xff0c;则需要在这个类中声明这个函数为private类型。 这里需要注意的是全局函数形参的…

Spark MLlib快速入门(1)逻辑回归、Kmeans、决策树、Pipeline、交叉验证

Spark MLlib快速入门(1)逻辑回归、Kmeans、决策树案例 除了scikit-learn外&#xff0c;在spark中也提供了机器学习库&#xff0c;即Spark MLlib。 在Spark MLlib机器学习库提供两套算法实现的API&#xff1a;基于RDD API和基于DataFrame API。今天&#xff0c;主要介绍下Data…

ModuleNotFoundError: No module named ‘pkg_resources‘

原因可能是因为setuptools升级版本过高&#xff0c;把setuptools降级到44.0.0及以下即可。 pip uninstall setuptoolspip install setuptools44.0.0 -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

【洛谷】P3865 【模板】ST 表

先上ACcode: #include<bits/stdc.h> using namespace std; const int N1e510,M1e520; int n,m,f[N][20]; void solve(){cin>>n>>m;for(int i1;i<n;i) cin>>f[i][0];for(int j1;j<20;j)for(int i1;i(1<<j)-1<n;i)f[i][j]max(f[i][j-1],…

C\C++ 使用ping判断ip是否能连通

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan 简介&#xff1a; ping是一种用于测试网络连接的工具&#xff0c;它通过发送数据包到目标设备并等待其响应来工作&#xff0c;以检查网络是否连通。下面是例子. 效果&#xff1a; 代码…

【C语言】初阶结构体

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在回炉重造C语言&#xff08;2023暑假&#xff09; ✈️专栏&#xff1a;【C语言航路】 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你…

使用Vue-Router实现页面跳转

安装 npm install vue-router4 --save项目中新建router.js文件 import { createRouter,createWebHashHistory } from "vue-router"; import Home from "/pages/Home.vue" import Banner from "/pages/Banner.vue" import Post from "/pag…

每日一题2023.7.17|根据层序遍历构建二叉树

根据层序遍历构建二叉树 解题思路&#xff1a; 如果所给层序遍历数组非空&#xff0c;先构建出root结点并加入辅助队列qu&#xff0c;之后当qu非空时&#xff0c;每次从qu中取出一个结点node&#xff0c;从头开始取数组中的元素作为node的左右孩子结点。并将非空的左右孩子结点…

5、深入LoadBalance

一、介绍 Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器,抽象和实现&#xff0c;用来替代Ribbon&#xff08;已经停更&#xff09;&#xff0c; 二、Ribbon和Loadbalance 对比 组件组件提供的负载策略支持负载的客户端Ribbon随机 RandomRule轮询 …

css的应用详解

一、css的应用 1、在标签上的应用 <img style"height: 500px; width:500px" src"/static/1.jpg" /> <div style"color:red;">祖国</div>将样式直接写在标签上 2、在head标签中应用style标签 <!DOCTYPE html> <ht…

IntelliJ IDEA 下载安装教程(详细图文)

IntelliJ IDEA 下载安装教程&#xff08;详细图文&#xff09; 简介 IDEA 全称 IntelliJ IDEA&#xff0c;是java编程语言的集成开发环境。IntelliJ在业界被公认为最好的Java开发工具 下载安装 ► 官网地址&#xff1a;https://www.jetbrains.com/idea ► 快速下载 (官方安装…

【SQL】保姆级教程:MySQL配置全过程 编辑器DBeaver的安装配置

MySQL 是最流行的关系型数据库管理系统&#xff0c;在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System&#xff1a;关系数据库管理系统)应用软件之一。 文章目录 一、什么是数据库&#xff1f;二、RDBMS 术语三、MySQL 安装四、安装DBeaver五、dbea…

Python异步编程框架asyncio使用方法

asyncio概念 Python异步编程框架asyncio是Python 3.4及以上版本提供的一个标准库&#xff0c;用于编写异步IO并发代码。asyncio提供了一种基于协程的高效的方式来实现异步编程&#xff0c;它可以使我们在单线程中处理大量的并发IO操作&#xff0c;提高程序的并发能力和性能。 …

输入 jar cvf 后指令提示‘jar‘ 不是内部或外部命令,也不是可运行的程序

输入 jar cvf 后指令提示jar 不是内部或外部命令&#xff0c;也不是可运行的程序 一堆说jdk系统环境配置的文章&#xff0c;我都看哭了&#xff0c;还好有这位老兄拯救了我&#xff01;&#xff01;&#xff01;献上地址 https://www.cnblogs.com/wadezhou/p/16647474.html 我输…

AI图像生成无需API开发连接集简云数据表,实现快速增强图片分辨率

1 场景描述 当设计接到一个需求时&#xff0c;按照常规的工作安排&#xff0c;从对接需求到最后完成效果图最短时间都要在5天左右&#xff0c;如果遇到高要求的客户或领导&#xff0c;后期还需要在电脑上进一步调整细节&#xff0c;一张成片起码要花上数小时时间去完成。 而人工…

Python_paramiko_与linux交互

一、基础功能介绍 # codingutf-8 import paramiko from time import sleep# 建立通信 transport paramiko.Transport((192.168.0.7, 22)) print(transport) # <paramiko.Transport at 0x5745ed0 (unconnected)># 建立连接 transport.connect(usernameroot, password1…