数据库核心技术学习报告Word文档格式.docx
- 文档编号:16831532
- 上传时间:2022-11-26
- 格式:DOCX
- 页数:20
- 大小:177.30KB
数据库核心技术学习报告Word文档格式.docx
《数据库核心技术学习报告Word文档格式.docx》由会员分享,可在线阅读,更多相关《数据库核心技术学习报告Word文档格式.docx(20页珍藏版)》请在冰豆网上搜索。
本文以sqlite与redis中的数据结构的异同点为出发点,着重分析sql和nosql架构中的区别以及他们不可替代的地方。
#pragmaonce
#include<
string>
extern"
C"
{
#include"
hiredis.h"
}
usingnamespacestd;
classmythRedisKey
public:
inttoint();
stringtostring();
mythRedisKey(redisReply*reply);
~mythRedisKey();
voidrelease();
private:
redisReply*_reply;
//voidrelease();
};
图1-1
2内部数据结构
2.1stringVSsimpledynamicstring(sds)
在大多数开源关系数据库中并没有对最基本的string进行优化,很多数据库例如sqlite甚至直接使用了constchar*来作为基本数据库的string结构作为储存结构。
故很多时候在不同系统上更加依赖类似strlen的处理效率。
而在Redis没有直接使用C语言传统字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串(simpledynamicstring,以下简称SDS)的数据结构,并将SDS用作Redis的默认字符串表示。
在Redis里面,C字符串只会作为字符串字面量(stringliteral)用在一些无须对字符串值进行修改的地方,比如打印日志:
redisLog(REDIS_WARNING,"
Redisisnowreadytoexit,byebye..."
);
当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis就会使用SDS来表示字符串值,比如在Redis的数据库里面,包含字符串值的键值对在底层都是由SDS实现的。
每个sds.h/sdshdr结构表示一个SDS值,如图2-1:
//记录buf数组中已使用字节的数量
structsdshdr
intlen;
//等于SDS所保存字符串的长度
intfree;
//记录buf数组中未使用字节的数量
charbuf[];
//字节数组,用于保存字符串
图2-1
如图2-2展示了一个SDS示例:
图2-2
∙free=0,表示这个SDS没有分配任何未使用空间。
∙len=5,表示这个SDS保存了一个五字节长的字符串。
∙buf属性是一个char类型的数组,也就是普通的c语言string类型数组。
他的前五个字节分别保存了'
R'
'
e'
d'
i'
s'
五个字符,而最后一个字节则保存了空字符'
\0'
。
SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由SDS函数自动完成的,所以这个空字符对于SDS的使用者来说是完全透明的。
所以遵循空字符结尾这一惯例的好处是,SDS可以直接重用一部分C字符串函数库里面的函数。
这么做的好处非常显而易见,和C字符串不同,因为SDS在len属性中记录了SDS本身的长度,所以获取一个SDS长度的复杂度仅为O
(1)。
故可以总结出比起C字符串,SDS具有以下优点:
●常数复杂度获取字符串长度。
●杜绝缓冲区溢出。
●减少修改字符串长度时所需的内存重分配次数。
●二进制安全。
●兼容部分C字符串函数。
b+树VS跳跃表
在关系数据库中为了快速访问随机文件中的记录,一般使用顺序索引。
每一个索引结构与一个特定的搜索码相关联。
顺序索引按照顺序存储搜索码的值,并且将每个搜索码与包含该搜索码的记录连接起来。
被索引文件中的记录自身也可以按照某种排序顺序存储。
一个文件可以有许多个索引组成,分别基于不同的搜索码。
如果包含记录文件按照某个搜索码指定的顺序排序,那么该搜索码对应的索引称为聚集索引或主索引。
搜索码指定的顺序与文件中记录的物理顺序不同的索引即为非聚集索引或者辅助索引。
如图2-3例子所示:
用户ID用作搜索码,记录按照该搜索码顺序存放。
索引记录根据数据记录的稠密程度分为稠密索引和稀疏索引
UserID
UserName
UserPwd
RealName
FullControl
1
root
pass
3
1012
test001
2
1013
123
1003
sony
1004
test2
1009
2222
222
22
1010
1014
test
1016
222198
123456
1017
222191
图2-3
2.2.1稠密索引
如果记录是排好序的,我们就可以在记录上建立稠密索引,它是这样一系列存储块:
块中只存放记录的键以及指向记录本身的指针,指针就是一个指向记录或存储块地址。
稠密索引文件中的索引块保持键的顺序与文件中的排序顺序一致。
既然我们假定查找键和指针所占存储空间远小于记录本身,我们就可以认为存储索引文件比存储数据文件所需存储块要少得多。
当内存容纳不下数据文件,但能容纳下索引文件时,索引的优势尤为明显。
这时,通过使用索引文件,我们每次查询只用一次I/O操作就能找到给定键值的记录。
稠密索引支持按给定键值查找相应记录的查询。
给定一个键值K,我们先在索引块中查找K。
当找到K后,我们按照K所对应的指针到数据文件中找到相应的记录。
似乎在找到K之前我们需要检索索引文件的每个存储块,或平均一半的存储块。
然而,由于有下面几个因素,基于索引的查找比它看起来更为有效:
1.索引块数量通常比数据块数量少。
2.由于键被排序,我们可以使用二分查找法来查找K。
若有n个索引块,我们只需查找log2n个块。
3.索引文件可能足够小,以至可以永久地存放在主存缓冲区中。
要是这样的话,查找键K时就只涉及主存访问而不需执行I/O操作。
2.2.2稀疏索引
稀疏索引只为数据文件的每个存储块设一个键-指针对,它比稠密索引节省了更多的存储空间,但查找给定值的记录需更多的时间。
只有当数据文件是按照某个查找键排序时,在该查找键上建立的稀疏索引才能被使用,而稠密索引则可以应用在任何的查找键。
如图2所示,稀疏索引只为每个存储块设一个键-指针对。
键值是每个数据块中第一个记录的对应值。
在已有稀疏索引的情况下,要找出查找键值为K的记录,我们得在索引中查找到键值小于或等于K的最大键值。
由于索引文件已按键排序,我们可以使用二分查找法来定位这个索引项,然后根据它的指针找到相应的数据块。
现在我们必须搜索这个数据块以找到键值为K的记录。
当然,数据块中必须有足够的格式化信息来标明其中的记录及记录内容。
2.2.3B+树
关于b+树这个经典数据结构在此无需赘述,如图2-4给出典型的3阶B+树示例。
通过这张图我们能很轻松找到他的特性:
1.所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
2.不可能在非叶子结点命中;
3.非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
4.更适合文件索引系统;
图2-4
B+树的基本操作总结如下所示:
查找操作
对B+树可以进行两种查找运算:
a.从最小关键字起顺序查找;
b.从根结点开始,进行随机查找。
在查找时,若非终端结点上的剧组机等于给定值,并不终止,而是继续向下直到叶子结点。
因此,在B+树中,不管查找成功与否,每次查找都是走了一条从根到叶子结点的路径。
其余同B-树的查找类似。
插入操作
B+树的插入与B树的插入过程类似。
不同的是B+树在叶结点上进行,如果叶结点中的关键码个数超过m,就必须分裂成关键码数目大致相同的两个结点,并保证上层结点中有这两个结点的最大关键码。
删除操作
B+树的删除也仅在叶子结点进行,当叶子结点中的最大关键字被删除时,其在非终端结点中的值可以作为一个“分界关键字”存在。
若因删除而使结点中关键字的个数少于m/2(m/2结果取上界,如5/2结果为3)时,其和兄弟结点的合并过程亦和B-树类似。
当然,B+树更适合做文件索引和数据库索引的原因是B+树的磁盘读写代价更低,同时B+树的查询效率更加稳定。
2.2.4跳跃表
在redis中,代替b+树的是跳跃表。
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序都使用跳跃表来代替平衡树。
Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。
举个例子,fruit-price是一个有序集合键,这个有序集合以水果名为成员,水果价钱为分值,保存了130款水果的价钱:
redis>
ZRANGE
fruit-price
0
2
WITHSCORES
1)"
banana"
2)"
5"
3)"
cherry"
4)"
6.5"
5)"
apple"
6)"
8"
ZCARD
fruit-price
(integer)130
fruit-price有序集合的所有数据都保存在一个跳跃表里面,其中每个跳跃表节点(node)都保存了一款水果的价钱信息,所有水果按价钱的高低从低到高在跳跃表里面排序:
∙·
跳跃表的第一个元素的成员为"
,它的分值为5;
跳跃表的第二个元素的成员为"
,它的分值为6.5;
跳跃表的第三个元素的成员为"
,它的分值为8;
Redis的跳跃表由redis.h/zskiplistNode和redis.h/zskiplist两个结构定义,其中zskiplistNode结构用于表示跳跃表节点,而zskiplist结构则用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等等。
图2-5
图2-5展示了一个跳跃表示例,位于图片最左边的是zskiplist结构,该结构包含以下属性,如图2-6所示:
header
指向跳跃表的表头节点
tail
指向跳跃表的表尾节点
level
记录目前跳跃表内
length
层数最大的那个节点的层数(表头节点的层数不计算在内)
图2-6
位于zskiplist结构右方的是四个zskiplistNode结构,该结构包含以下属性,如图2-7所示:
层(level)
节点中用L1、L2、L3等字样标记节点的各个层,L1代表第一层,L2代表第二层,以此类推。
每个层都带有两个属性:
前进指针和跨度。
前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。
在上面的图片中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度。
当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。
后退(backward)指针
节点中用BW字样标记节点的后退指针,它指向位于当前节点的前一个节点。
后退指针在程序从表尾向表头遍历时使用。
分值(score)
各个节点中的1.0、2.0和3.0是节点所保存的分值。
在跳跃表中,节点按各自所保存的分值从小到大排列。
成员对象(obj)
各个节点中的o1、o2和o3是节点所保存的成员对象。
图2-7
注意表头节点和其他节点的构造是一样的:
表头节点也有后退指针、分值和成员对象,不过表头节点的这些属性都不会被用到,所以图中省略了这些部分,只显示了表头节点的各个层。
跳跃表节点的实现由redis.h/zskiplistNode结构定义:
typedef
struct
zskiplistNode
//
层
zskiplistLevel
前进指针
*forward;
跨度
unsigned
int
span;
}
level[];
后退指针
*backward;
分值
double
score;
成员对象
robj
*obj;
zskiplistNode;
层:
跳跃表节点的level数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多,访问其他节点的速度就越快。
前进指针:
每个层都有一个指向表尾方向的前进指针(level[i].forward属性),用于从表头向表尾方向访问节点。
跨度:
层的跨度(level[i].span属性)用于记录两个节点之间的距离:
●两个节点之间的跨度越大,它们相距得就越远。
●指向NULL的所有前进指针的跨度都为0,因为它们没有连向任何节点。
后退指针:
节点的后退指针(backward属性)用于从表尾向表头方向访问节点:
跟可以一次跳过多个节点的前进指针不同,因为每个节点只有一个后退指针,所以每次只能后退至前一个节点。
分值和成员:
节点的分值(score属性)是一个double类型的浮点数,跳跃表中的所有节点都按分值从小到大来排序。
节点的成员对象(obj属性)是一个指针,它指向一个字符串对象,而字符串对象则保存着一个SDS值。
仅靠多个跳跃表节点就可以组成一个跳跃表,如图2-8所示。
图2-8
但通过使用一个zskiplist结构来持有这些节点,程序可以更方便地对整个跳跃表进行处理,比如快速访问跳跃表的表头节点和表尾节点,或者快速地获取跳跃表节点的数量(也即是跳跃表的长度)等信息,如图2-9所示。
图2-9
zskiplist结构的定义如下:
zskiplist
表头节点和表尾节点
*header,
*tail;
表中节点的数量
long
length;
表中层数最大的节点的层数
level;
zskiplist;
header和tail指针分别指向跳跃表的表头和表尾节点,通过这两个指针,程序定位表头节点和表尾节点的复杂度为O
(1)。
通过使用length属性来记录节点的数量,程序可以在O
(1)复杂度内返回跳跃表的长度。
level属性则用于在O
(1)复杂度内获取跳跃表中层高最大的那个节点的层数量,注意表头节点的层高并不计算在内。
故可得出跳跃表的几个特点如下
●跳跃表是有序集合的底层实现之一。
●Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。
●每个跳跃表节点的层高都是1至32之间的随机数。
●在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。
●跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。
2.3B+树码压缩VS压缩列表
2.3.1B+树码压缩
关系数据库与redis中都有关于压缩数据的算法,其中在关系数据库中在“实际的B+树”一章中有提及,但是似乎并不是所有关系数据库中都有这套算法。
而在redis中却非常看重压缩列表。
压缩列表和整数集合两套数据结构算法被称为redis内部精髓。
B+树的高度依赖于数据项的数目和索引的大小。
索引项的大小决定一页能存放索引项的数目,即树的扇出数。
既然树的高度与㏒扇出(数据项数)成正比,所以,使B+树的扇出数最大、高度最小是很重要的策略。
索引项包含一个搜索码值和页的指针,所以其大小要依赖于搜索码值的大小。
如果搜索码值非常大(例如一个很长的名字),一页就不能放很多索引项,导致扇出数很小,树的高度很大。
另一方面,索引项中的搜索码值仅仅由于知道搜索到达合适的叶子。
如果需要对给定的搜索码值的数据项进行定位,只要把这个搜索码值与(从根到期望的叶子的路径上的)索引项的搜索码值相比较。
在与索引级节点进行比较时,要找到包含索引码值k1和k2,并且期望码值k刚好落在k1和k2之间的两个索引项。
为了完成这个过程,不需要在索引项中存储完整的搜索码值。
例如,假设在一个节点内有两个邻近的索引项,它们的索引码值LesMiserables和LastSongs。
为了区别这两个值,只需要存储简短的形式Le和La就够了。
综上所示,关系数据库的压缩仅仅在于索引的压缩,而对数据几乎不压缩,与redis中的处理完全不同。
压缩列表(ziplist)是列表键和哈希键的底层实现之一。
当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。
2.3.2压缩列表
压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构。
一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
如图2-10展示了压缩列表的各个组成部分,以及各个组成部分的类型、长度以及用途。
图2-10
属性
类型
长度
用途
zlbytes
uint32_t
4字节
记录整个压缩列表占用的内存字节数:
在对压缩列表进行内存重分配,或者计算zlend的位置时使用
zltail
记录压缩列表尾节点记录压缩列表的起始地址便宜:
用于快速定位尾节点
zllen
uint16_t
2字节
记录压缩列表包含的节点数量:
当这个值小于UINT64_MAX(65535)时,这个属性的值就是压缩列表包含的节点数量;
当这个值等于UINT64_MAX时,节点的真实数量需要遍历整个压缩列表才能计算出
entryX
列表节点
不定
压缩列表包含的各个节点,节点长度由节点保存的内容决定
zlend
uint8_t
1字节
特殊值0xFF,用于标记压缩列表的末端
压缩列表节点结构:
∙previous_entry_length:
以字节为单位,记录压缩列表中前一个节点的长度。
previous_entry_length属性的长度可以是1字节或者5字节:
∙如果前一节点的长度小于254字节,那么previous_entry_length属性的长度为1字节
∙如果前一节点的长度大于等于254字节,那么previous_entry_length属性的长度为5字节:
其中第一字解会被设置为0xFE(254),而之后的四个字节用于保存前一节点的长度
encoding:
∙节点的encoding属性记录了节点content属性薄脆数据的类型以及长度:
∙一字节、两字节或者五字节长,值的最高位为00、01或者10的是字节数组编码:
这种编码便是节点的content属性保存着字节数组,数组的长度由编码除去最高两位之后的其他位记录;
∙一字节长,值的最高位以11开头的是整数编码:
这种编码表示节点的content属性保存着整数值,整数值的类型和长度由编码去除最高两位之后的其他位记录;
字节数组编码content:
编码
编码长度
content属性保存的值
00bbbbbb
长度小于等于63字节的字节数组
01bbbbbbxxxxxxxx
长度小于等于16383字节的字节数组
10______aaaaaaaabbbbbbbbccccccccdddddddd
5字节
长度小于等于4294967295的字节数组
整数编码:
11000000
int16_t类型的整数
11010000
int32_t类型的整数
11100000
int64_t类型的整数
11110000
24位有符号整数
11111110
8位有符号整数
1111xxxx
无contennt属性,xxxx保存了0到12之前的值
故可以总结出redis中的压缩列表的几个特点:
∙压缩列表是一种为节约内存而开发的顺序型数据结构。
压缩列
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据库 核心技术 学习 报告