热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MySQL学习-索引

为什么索引这么快,一个好的索引能将检索速度提升几个量级,这种效率离不开这个数据结构为什么需要"索引"?我们总得依据什么才能去找你想查的东西,那么我们就依据id1去寻找一条记录,怎么找呢?难道是"顺序检索"?

Balance-Tree & Balance+Tree

为什么索引这么快,一个好的索引能将检索速度提升几个量级,这种效率离不开这个数据结构

1.1 门路清

为什么需要"索引" ?

我们总得依据什么才能去找你想查的东西,那么我们就依据 id=1去寻找一条记录,怎么找呢? 难道是"顺序检索" ?

数据库里的东西现在大都大到分布在很多个磁盘页上。如果顺序检索基本就是噩梦。你知道"二分法" 比较快,那是因为数据已经被排过序了。 同样的如果现在存在一种排过序的数据结构,使得我们能快速去找出我们想要的东西在哪儿 ,这样一定很方便对吧。

所以归根到底,现在最大的问题的就是"这条记录到底存在哪" 。 解决办法是这样的: 

  • 依据这个键比如ID,去产生一个序号,这样就组成了 Key[序号]-Value[记录的位置] 的形式
  • 这种K-V结构,会变成数据结构的一个节点。 到时候我们拿着ID,计算出序号,根据这个序号,在这种数据结构里畅游快速找到这个节点
  • 找到对应节点后查找出记录的位置,去内存里取,完成" select * from users where id = 1 "

为什么叫它"索引" ?

在上面的介绍中我们已经看到了,实现快速找到内容的关键就是这个 "序号" 。这个所谓的序号就非常像"索引"这个名词

为什么不用二分法查找?

理论上,二分法已经是 O(logN), 非常快了。 实际上索引可能很多,多到你都没办法把索引全部加载到内存里,所以这些索引基本上都在磁盘里,依靠磁盘IO,分批次加载到内存里。

既然提到磁盘IO,就应该知道磁盘IO效率非常低,低到实际上就, 如果谁能减少磁盘IO次数,谁就会是最好的索引实现方案。

  • 二分法一次两个节点
    • 整个树非常的 "瘦高"  -> 
    • 我们需要向下遍历很多层 -> 
    • 我们需要向下加载很多次磁盘IO -> 效率不高
  • 我们想要把树变的矮胖,减少层数

MySQL学习 - 索引

1.2 Balance-Tree

MySQL学习 - 索引

1.2.1 Balance-Tree中的节点为什么是这样子 ?    查询数据的过程

  • 一个节点中会包含很多个键值对, 以及很多个 "指针p" 我们以 { 2 6 } 节点举例看里面有什么
  • 节点内部是这样安排的     {    [p1]     [序号=2]     [p2]     [序号=6]     [p3]      }
    • 假设一个现在 序号[6] 来了,正好能匹配上,前往6对应位置取回数据
    • 假设一个现在 序号[3] 来了,[3] 不匹配任何,但是位于2~6之间, 前往p2继续寻找
    • 假设一个现在 序号[1] 来了,[1] 不匹配任何, 且小于2,前往p1继续寻找
  • 节点内的所有元素都已经排过序了,因此拿着[序号]来节点里查找的时候效率也比较高

刚刚,我们就走完了一个节点,我们完成了一轮查找。每一轮开始之前我们会先执行磁盘IO,把下一个节点对应内容从磁盘加载到内存里,然后再在组内查找,找得到就退出,找不到继续

根据上述结论,我们也能发现一个重要结论: 既然我们不能一次性把所有索引都加载到内存里,既然我们要分批次做磁盘IO。那树的高度其实就是我们IO的次数,那么矮树就会是最快的方案

func balance_tree_search (node *Node , key int) (*Data,error) {

    if node == nil {
        return nil,fmt.Errorf("最终也没能找到对应序号")
    }

    for _, pair := range node {

        if (pair.Key == key){
            return pair.Data,nil
        }

        if (pair.Key > key) {
            // 考虑到节点内键值对是已经排序,从小到大的
            // 那既然上个键值对不满足,这个键值对又过于大
            // 那说明没有符合的,前往下一个节点
            return balance_tree_search( pair.NextNode, key )
        }
    }

    // 比节点内所有键值对都大,直接前往下一个
    return balance_tree_search( pair.NextNode, key )
}  

1.2.2 Balance-Tree的插入过程

MySQL学习 - 索引

MySQL学习 - 索引

  • 现在是这样,因为BT不能允许一个节点里的有过多的键值对,因此当 [序号4] 过来的时候
  • 实际节点 { 3 5 } 不能容纳 [序号4] 
  • 如果不能容纳,那么我们会一路向上找到能容纳的下的节点,于是我们找到根节点
  • 相对应的,根节点变成两个,多出一个位置用于存放指针,相对应的树结构做出调整
  •  {   [p1]  [序号=4]  [p2]   }                   ->                 {   [p1] [序号=4] [p2] [序号=9] [p3]   }

自调整的过程虽然很漫长,看起来也很麻烦,但是这个恰好是满足了BT的自调整性质

1.2.3 Balance-Tree的删除过程

MySQL学习 - 索引

MySQL学习 - 索引

  • 假设我们现在想要删除这个红色的节点,但是删除后 [序号12] 左边会缺少一个位置
  • 于是我们现在需要调整一下,旋转一下,成为满足要求的Balance-Tree

1.2.4 Balance-Tree的定义

  • 依据自旋转过程,保证树都是一样高的,m (也叫秩m)
  • 每个节点上元素的数量  [m/2 ~ m]
  • 节点上元素需要排序

1.3 Balance+Tree

MySQL学习 - 索引

  • 在B+T中,中间节点并不会存储任何跟"数据在那儿"相关的信息,只会做数据索引,指引你前往叶子节点。 
    • 无论怎样你都要遍历一下叶子节点,比BT更加稳定
    • 不需要前往中间节点,遍历全部数据的时候比BT快
  • B+T所有节点都会按顺序保存好数据,并且所有叶子节点都是串在一起的,这样当数据来的时候我们只要按照粉色的路径一路寻找就好了

关于索引,你需要知道

2.1 多个列,组合索引

  • 在你 create table 的时候假设你定义出 A+B+C 成为你的组合索引,你需要知道这三列现在开始是不等价的,实际上这里面只有第一列 A 重量级最高
    • 在上面我们提到了[序号],那么 A+B+C 现在就是这条数据的序号,在进行索引比较的时候会 先比较A,A相同了比较B,B相同了再去比较C
      • 多说一句,我们不喜欢使用字符串作为索引的原因,是因为"字符串比较" 会比 "整数比较" 开销更大,那种很长的字符串比较就更是麻烦了
    • A重量级最高:  如果你想真的从索引中受益,那么你的WHERE筛选条件中一定要带上这个重量级最高的A,否则索引没有真的发挥作用,举个例子,你可以这么使用索引
      • WHERE A=1                            (首列精确匹配)
      • WHERE A IS LIKE 1                  (首列的近似匹配)
      • WHERE A=1 AND B IS LIKE 2  (首列精确,二列近似)
    • 如果你尝试在筛选条件里不带上A,那么本次查找索引就根本没有发挥作用
  • 我们都知道 BT&B+T 能拥有一个很良好的"排序性质",我们既然能按照 排序 的方式快速找到一个键值对,那么在BT&B+T为基础的索引上
    • 完成 ORDER BY 很快
    • 完成范围查找很快 ->  B IS IN ( 20,100 )

2.2 哈希索引

假设我们定义出A+B+C作为索引列,哈希索引就是针对每一条记录计算出hash(A,B,C) 对应的值是这条记录存储的位置,哈希索引非常快,但是也有自身对应的一些弊端

  • 因为已经没有排序结构了,因此ORDER BY 功能已经没有那么快了
  • 哈希功能需要所有索引列参与,你不能在只有B&C的情况下去指望使用上哈希索引

2.2.1 哈希冲突

假设现在两条记录能哈希出同一个值,这种时候:

// 如果只依赖hash 则返回两条记录
SELECT * FROM users WHERE hash(name) = 1;
>> liangxiaohan 23 M
   zhangxiaoming 24 F

// 最好的办法是不仅使用hash同时也指定索引列自身的值
// hash冲突下,形成链表,存储引擎遍历链表所有行
SELECT * FROM users WHERE hash(name) = 1 and name = "liangxiaohan"
>> liangxiaohan 23 M

2.2.2 自创索引

InnoDB支持哈希,但他的支持是指,它会自优化你的B树索引成为"某种程度上的"哈希索引。针对这一点,你可以自己实现一个简单的哈希索引

// 更新表,新建一列用于存放哈希值
ALTER TABLE ADD COLUMN name_crc VARCHAR(20)

// 关于哈希值,你可以使用 TRIGGER 实现自动插入
// 你只负责插入name就行了,关于crc32哈希值它每次会自己计算
CREATE TRIGGER crc_create BEFORE INSERT ON users 
FOR EACH ROW SET NEW.name_crc = crc32(NEW.name)

2.3 聚簇索引

2.3.1 关于聚簇索引,你需要知道

  • 聚簇索引 并不是一种新型的索引,它所代表的只是一种数据存储方式
  • 在聚簇索引,相当于一种变形B+T
    • 它的中间节点同样不负责存储任何数据
    • 它的叶子节点会存下这条记录的所有内容实体,而不是一个指针
    • 叶子节点首位相接
  • 一个表只能有一个聚簇索引:按照聚簇索引的要求,数据会被存在一个指定的位置。但是数据不能既在这里又在哪里,所以只能有一个聚簇索引
  • 它之所以被叫做“聚簇” : 是因为按照索引的要求,数据全都被存储在了指定位置,并且索引上相邻的位置,他们也存储的很近

2.3.2 聚簇索引的优点 & 缺点

  • 优点
    • "聚簇"可能可以发挥出很大的威力: 假设我们令用户ID成为聚簇索引,那么这个用户可能所有相关的信息全存在一起,我们有可能存在一次IO读取所有邮件
    • 访问更快:  聚簇索引将索引+数据全都保存在同一个BT中,读数据通常更快
  • 缺点
    • 假设一个应用,像是读邮件这种应用,本质上是IO密集应用,如果这种时候我们能好好利用聚簇索引,效果卓群。但是如果数据全都存在内存中,并不涉及磁盘IO那就没有多少优势了
    • 最大的问题:  执行插入的速度 。  因为在聚簇索引中,所有的数据已经是直接存在B+T中并且排序了,那么问题就来了
      • 如果我按照聚簇索引顺序插入 -> 速度很快
      • 如果我随机插入 -> 涉及数据的复制移动等,速度感人
    • 还是关于聚簇的随机插入: 页分裂。 假设现在一个内存页已经填满了数据,但是现在有一个数据试图在中间插入,存储引擎会将这一页分裂成两页,将第一页的一部分复制到第二页去
      • 效率极低
      • 产生内存碎片
      • 因为内存不连续导致扫描全表变慢
    • 如果真的要随机插入,记得在插入完成以后使用 OPTIOMIZE TABLE 重新整理内存

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 我们


推荐阅读
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • MySQL语句大全:创建、授权、查询、修改等【MySQL】的使用方法详解
    本文详细介绍了MySQL语句的使用方法,包括创建用户、授权、查询、修改等操作。通过连接MySQL数据库,可以使用命令创建用户,并指定该用户在哪个主机上可以登录。同时,还可以设置用户的登录密码。通过本文,您可以全面了解MySQL语句的使用方法。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 开发笔记:select from具体执行相关知识介绍及案例分析
    本文由编程笔记小编整理,主要介绍了select from具体执行相关的知识,包括数据插入、查询最小rowID、查询每个重复名字的最小rowID、删除重复数据等操作,并提供了案例分析。希望对读者有一定的参考价值。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • MySQL外键1对多问题的解决方法及实例
    本文介绍了解决MySQL外键1对多问题的方法,通过准备数据、创建表和设置外键关联等步骤,实现了用户分组和插入数据的功能。详细介绍了数据准备的过程和外键关联的设置,以及插入数据的示例。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
零泉爱
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有