数据库复习之——数据库索引

本文详细介绍了数据库索引的基本概念,包括索引定义、特点和相关概念,以及稀疏索引与稠密索引的对比。此外,文章还深入探讨了主索引与辅助索引、聚簇索引与非聚簇索引的差异,并简述了B+Tree索引的原理,包括B+树的索引类型和分裂与合并过程。最后,文章提及了散列索引的一般形式和可扩展散列索引的基本思想与问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、数据库索引的基本概念与分类

0、概念

a. 索引定义

是定义在Table之上,有助于无需检查所有记录而快速定位所需记录的一种辅助存储结构,由一系列索引项组成,每一索引项又由两部分组成:(1)索引字段,是由Table中某些(或某一)列中的值串接而成。索引中通常存储了索引字段的每个值(也有不是这样的);(2)行指针,指向Table中包含索引字段值的记录在磁盘上的存储位置。

b. 索引的特点

  • 索引文件是一种辅助存储结构,其存在与否不改变存储表的物理存储结构;但索引可以提高存储表的访问速度
  • 索引文件有两种组织方式:
    • 排序索引文件,按照索引字段值的某一顺序组织存储;
    • 散列索引文件,按照索引字段值使用散列函数分配到散列桶的存储方式;
  • 一张表上可以针对不同属性或者属性的组合建立不同的索引文件,可建立多个索引文件。Index值可以是Table中任何一个属性值或者多个属性值的组合(这里要注意:索引的有效性满足最左匹配原则)
  • 索引文件比主文件要小很多;
  • 有索引的时候,Update操作必须同步更新索引文件。

c. 相关概念

  • 字段、排序字段、索引字段
  • 码、主码又称为表键—具有唯一性和最小性
  • 排序码:对主文件进行排序存储的那些属性或属性组
  • 索引码:即索引字段,不一定具有唯一性
  • 搜索码:在主文件中查找记录的属性或属性集

1、稀疏索引与稠密索引

a. 定义

  • 稠密索引:对于在主文件中每一个记录(形成的索引值),都有一个索引项和它对应,指明该记录所在的位置;

    即索引文件中包含了主文件中对应字段的所有不同值

  • 稀疏索引:对于主文件中的部分记录(形成的索引值),有索引项与其对应;

    即索引文件中包含了主文件中的部分不同值

b. 稀疏索引如何定位一条记录

  • 定位索引字段值为K的记录,需要:
    • 找到相邻的小于k的最大索引字段值所对应的索引项
    • 从该索引项所对应的记录开始进行顺序检索Table
  • 使用稀疏索引的要求— 主文件必须是按照对应的索引字段顺序存储
  • 相比于稠密索引,稀疏索引的空间占用更少,维护任务更轻,但是速度慢;
    【需要注意的是:索引中不存在搜索码的值,不代表主文件中没有对应的搜索码的值】

c. 稠密索引

索引文件中若不存在搜索码的值,就代表主文件中无搜索码对应的记录

(1)候选键属性的稠密索引

  • 索引文件中的字段值与主文件中的相应字段的值是一对一的。

(2)非候选键属性的稠密索引

  • 索引文件中索引字段值是不重复的;而主文件按照索引字段排序,且索引字段不是候选键。
  • 索引文件中索引字段值是有重复的;而主文件并未按照索引字段排序,且索引字段不是候选键。(一对一关系)
  • 引入了指针桶来处理非候选键索引的多记录情况;而主文件未按照索引字段排序,且索引字段不是候选键。

2、主索引与辅助索引

  • 主索引:通常每个存储块有一个索引项,索引项的总数和存储表所占的存储块数目相同,将每一存储块的第一条记录称为锚记录,简称块锚
    • 主索引的字段值为块锚的索引字段值,而主索引的指针指向块锚所在的存储块;
    • 主索引是按索引字段值进行排序的有序文件,主索引通常建立在有序主文件基于主码的排序字段上,即主索引的索引字段与主文件的排序码(主码)有对应关系;
    • 另外,主索引是稀疏索引;
  • 辅助索引:定义在主文件的任一或多个非排序字段上的辅助存储结构。
    • 对某一非排序字段上的每一个不同值有一个索引项:索引字段就是该字段的不同值,而指针指向包含该记录的块或该记录本身;
    • 非排序字段为索引字段时,如果该字段值不唯一,需要采用一个类似链表的结构来保存包含该字段值的所有记录的位置;
    • 辅助索引是稠密索引,检索效率有时非常高;
  • 主索引 V.S. 辅助索引
    • 一个主文件仅仅只能有一个主索引,但是可以有多个辅助索引
    • 主索引建立在主码上/排序码上;而辅助索引建立在其他属性上;
    • 可以利用主索引来重新组织主文件数据,但是辅助索引不能改变主文件数据;
    • 主索引是稀疏索引、而辅助索引是稠密索引;

3、聚簇索引与非聚簇索引

  • 聚簇索引:索引中邻近的记录在主文件中也是邻近存储的;
  • 非聚簇索引:索引中邻近的记录在主文件中不一定是邻近存储的;
  • 两者的特点:
    • 如果主文件的某个排序字段不是主码,那么每个记录的该字段值不是唯一的;此时该字段为聚簇字段,而聚簇索引通常建立聚簇字段上;
    • 聚簇索引通常是对聚簇字段上每个不同的值有一个索引项(聚簇索引中索引项的总和与主文件中聚簇字段的不同值的数量是相同的),那么当主文件中具有相同聚簇字段值的记录可能存放在不同的存储块中,那么聚簇索引中索引项的指针指向第一个存储块;
    • 一个主文件只能有一个聚簇索引文件,但是可以有多个非聚簇索引文件;
    • 主索引通常是聚簇索引、而辅助索引通常是非聚簇索引;
    • 另外:主索引\聚簇索引是可以决定一个记录的存储位置的;而非聚簇索引\辅助索引则只能用于查询,指出已存记录的位置;

4、倒排索引(Solr等的实现原理)

​ 通过知道一个文章中包含了哪些单词,形成一个kv,之后再将kv反转,得到一个单词出现在哪些文章中;

5、其他结构的索引

  • 多级索引:索引项比较多的时候,对索引文件再建立索引,以此类推,形成多级索引;
  • 多属性索引:索引字段是由Table中多个属性组合在一起形成的索引;
  • 散列索引:使用散列技术组织的索引;
  • 网络索引:使用多索引字段进行交叉联合定位与检索;

二、B+Tree 索引

是一种以树形结构来组织索引项的多级索引;

1、 B+树的一些约定

在这里插入图片描述
(1) P i P_i Pi — 指向索引块、数据块或数据块中某条记录的指针
(2) K i K_i Ki — 索引字段值
(3)计算一个块中存放多少个索引项(n的大小计算):

  • 有n-1个索引项,n个指针;
  • 索引字段值 x x x K i − 1 ≤ x < K i K_{i-1} \le x < K_i Ki1x<Ki 范围内的时候,由 P i P_i Pi 指针指向;
  • 而索引字段值 x x x K i ≤ x < K i + 1 K_{i} \le x < K_{i+1} Kix<Ki+1 范围内的时候,由 P i + 1 P_{i+1} Pi+1 指针指向;

【说明:一个 K i K_i Ki 左边的指针,总是指向小于 K i K_i Ki 的数据;而右边的指针总是指向大于等于 K i K_i Ki 且小于 K i + 1 K_{i+1} Ki+1 的数据。】

示例:

​ 假设一个存储块的大小为: 4096 B y t e 4096Byte 4096Byte;整数型索引字段值大小为: 4 B y t e 4Byte 4Byte;指针大小为: 8 B y t e 8Byte 8Byte

​ 那么该存储块中可以存放多少个索引项呢?即n应该满足: 4 × ( n − 1 ) + 8 × n ≤ 4096 4 \times (n - 1) + 8 \times n \le 4096 4×(n1)+8×n4096

​ 计算得 n n n 的最大值为 : 341,即该存储块最多可以存放341个索引项;

(4)叶子节点的指针指向主文件的数据块或者数据记录, 而叶子节点的最后一个指针指向下一个数据块;

(5)非叶子节点指针指向索引块;并不会直接指向主文件的数据块或者数据库记录;

在这里插入图片描述

(6)一个索引块中指针个数为n,其实际使用的索引指真个数d必须要满足: n 2 ≤ d ≤ n \frac{n}{2} \le d \le n 2ndn;而根节点要至少满足有2个指针被使用;

(7)索引字段值重复出现于叶节点和非叶节点,其中只有叶节点的指针指向主文件;

​ 并且叶节点包含了全部的索引项(或者说,仅叶节点就能覆盖所有键值的索引);

​ 另外索引字段值在叶节点中是按顺序排列的;(即:仅叶节点块的集合就是主文件完整的索引)

2、 B+树可以建立的索引类型

I. 使用B+树建立键属性的稠密索引

  • 索引字段是主文件的主键,索引是稠密的;
  • 主文件可以是按主键排序存储的,也可以是不按主键排序存储的;
  • 指针指向的是记录

II. 使用B+树建立键属性的稀疏索引

  • 索引字段是主文件的主键,索引是稀疏的;
  • 主文件必须是按照主键顺序存储的;
  • 指针指向的是数据块;

III. 使用B+树建立非键属性的稠密索引

(1)第一种情况:即索引文件无重复,且主文件排序存储;

  • 索引字段是主文件的非键属性,索引是稠密的;
  • 主文件是按照非键属性排序存储的;
  • 索引文件的索引字段是无重复的;
  • 指针指向的是记录;

(2)第二种情况:即索引文件有重复,且主文件并非排序存储;

  • 索引字段是主文件的非键属性,索引是稠密的;
  • 主文件并不是按照非键属性排序存储的;
  • 索引文件的索引字段是有重复的;
  • 指针指向的是记录;

3、 B+树分裂与合并

(1)键值插入与节点分裂

  • 找到要保存新插入键值记录的叶子节点;
  • 如果该叶子节点已满,也申请新的节点;若该叶子节点不满,直接插入即可;
  • 调整找到的叶子节点的键值记录,使其均衡存放于两个叶节点中(分裂);
  • 调整指针,使其指向新叶子节点;
  • 找到指向新叶子节点的非叶节点;
  • 该非叶子节点也已经满了,再次申请新的节点
  • 调整找到的非叶子节点的键值记录,使其均衡存放于两个非叶节点中(分裂);
  • 调整各节点的指针,使其指向正确;

【注意】:

  • key 1:当节点全满,无法插入新的元素的时候,将其分裂;
  • key 2:要由叶子节点向根节点逐层处理;
  • key 3:调整指针**(难点)**

(2)键值删除与节点合并

  • 在叶子节点中找到等于要删除键值的记录,删除相应的指针及其主文件中对应的记录;
  • 调整其左(右)节点及本节点中的键值记录,使其均衡存放于两个叶节点中;
    • 如果发现删除记录之后,该节点左右兄弟节点中可以借一个元素来达到均衡状态,那么不需要合并节点
    • 如果发现无法从左右两边的兄弟节点中借元素,那么需要对节点进行合并操作;
  • 如有调整,则进一步调整其上层非叶节点,重新确定其键值,以满足大于等于该层键值的记录都在其右侧;
  • 依次向上调整,直到根节点;

【注意】:

  • key 1:当指针数目小于规定数目的时候,合并
  • key 2:由叶节点向根节点逐层处理
  • key 3:指针调整**(难点)**

三、散列索引

1、 一般散列索引

(1)一些基本的概念(参考《数据结构》— 散列一章的介绍)

  • 有M个桶,每个桶是有相同的容量;
  • 散列函数 h ( K ) h(K) h(K), 可将键值 K 映射到 M 个桶的某个桶里;
  • 将具有键值K的记录Record(K)存储到 h(K) 对应的桶里;
  • 散列函数的选取的目标:
    • 对于集合中的任意一个关键字(记录),经过散列函数映射到地址集合中任何一个地址的概率是近乎相等的;
    • 也就是说,将记录能均匀的映射到M个桶里;

(2)散列的一些问题

  • 当桶的数量M是固定值的时候,此为静态散列索引
    • 静态散列中,M的数不变,那么当M过大的时候,浪费;当M过小的时候,将会产生过多的溢出桶,过多的溢出桶会增加散列索引的检索时间。
  • 而当桶的数量M是随着键值的增加而动态增加的时候,此为动态散列索引
    • 动态散列索引中,散列函数 h(k) 与桶的数量M相关,那么M的变化将会影响原来存储的内容;
    • 之前桶中存储的内容,因M的变化,可能需要再散列;

(3)动态散列索引的类别

  • 可扩展散列索引
  • 线性散列索引

2、 可扩展散列索引

I. 基本思想

  • 为桶引入一个间接层,即用一个指向块的指针数组来表示桶,而不是用数据块本身组成的数组来表示桶
  • 指针数组的长度可增长,其长度总是2的幂。因此,指针数组每增长一次,其长度都会翻倍;但是并非每个桶都有一个数据块,当某些桶中的所有记录可放在一个块中的时候,那么这些桶可能共享一个块。
  • 散列函数h为每个键计算出一个K位的二进制序列,K足够大;但是桶的数量总是使用从序列第一位或者最后一位算起的若干位(i),此位数 i 小于k。也就是说,当 i 是使用的位数时,桶数将会有2的i次方项;
    在这里插入图片描述

按照上述例子:

(1)参数 k k k 是所可能使用的最多位数。即可能的最大桶的数目为 2 k 2^k 2k

(2)参数 i i i 是散列函数当前已经使用到的最多位数。即当前桶的数量为 2 i 2^i 2i

(3)右上角的标记表示:本块散列函数使用的位数(可以是 i ,也可以是比 i 小的数)。

II. 例子

在执行插入操作的时候:

  • 将待插入元素经过Hash函数得到一个二进制序列
  • 取待插入元素的二进制序列的前 i 位,用以确定应插入到哪个索引块
    • 如果该索引块指向的地址块没满,那么直接插入即可
    • 如果已经满了,那么需要扩展散列桶,进行分裂:
      • (1)根据实际情况判断 i 是否需要增加;
      • (2) 并且重新散列该地址的数据到两个块中其他的数据不变

III. 存在的问题

  • 桶的数组需要翻倍的时候,需要做大量的工作,将重散列之外的其他桶指向同一个块;

  • 桶数量翻倍之后,需要占用更大的空间,甚至存在内存装不下的情况;

  • 在极端情况下,假设当每个块存放两个记录,但是数据库中有大量的记录,这些记录在经过Hash后得到的二进制序列的前20位都是相同的,那么桶数组将不断地进行分离,同时桶中还存在大量未使用的空桶;

    III中的问题,可以通过线性散列表来解决!

3、 线性散列索引

I. 基本的概念与基本思想

  • 桶数 N 的选择总是使存储块的平均记录数保持与存储块所能容纳的记录总数成一个固定的比例,如80%。超过比例则桶数增1

  • 存储块并不总是可分裂,故允许存在溢出块,

  • 用来做桶数组序号的二进制位总是从散列函数得到的位序列的右端(即低位)开始取;

  • 假定散列函数值的 i 位正在用来给桶数组编号,且键值为K的记录要插入到编号为 a1…ai 的桶里,其中 a1…ai 是h(K) 的后 i 位。将a1…ai当作二进制整数,设其为m

    • 那么 m < n,则编号为m的桶存在并将该记录存入桶中;

    • 如果 $ n \le m < 2^i$ ,说明 编号为 m 的桶不存在,因此我们要将记录存放到编号为 $ m-2^{i-1} $ 的桶里;也就是将 a1改为0时 所对应桶;(其实桶编号为 a 2 . . . a i a_2...a_i a2...ai

      在这里插入图片描述
      对上图而言,

      • i 表示 当前使用的散列函数的位数;
      • n 表示当前的桶数;
      • r 表示当前散列表中的记录总数,要求 r <= 1.7n

II. 桶的分裂与增加

  • 键值由低位向高位次序使用。对应索引项存储在由桶数组指针指向的存储块;
  • r n \frac{r}{n} nr (当前的索引项数量 除以 桶的数量)大于一定比例时,按次序增加 1 个桶;
  • 假设该桶为 1 a 2 a 3 . . . a i 1a_2a_3...a_i 1a2a3...ai ,则该桶由 0 a 2 a 3 . . . a i 0a_2a_3...a_i 0a2a3...ai 对应的块儿分裂而来
  • 当桶数超过 2 i 2^i 2i 个桶的时候,使 i 增加 1 ;

III. 例子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zhang L.R.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值