数据库核心技术学习报告.docx

上传人:b****6 文档编号:3925343 上传时间:2022-11-26 格式:DOCX 页数:20 大小:177.30KB
下载 相关 举报
数据库核心技术学习报告.docx_第1页
第1页 / 共20页
数据库核心技术学习报告.docx_第2页
第2页 / 共20页
数据库核心技术学习报告.docx_第3页
第3页 / 共20页
数据库核心技术学习报告.docx_第4页
第4页 / 共20页
数据库核心技术学习报告.docx_第5页
第5页 / 共20页
点击查看更多>>
下载资源
资源描述

数据库核心技术学习报告.docx

《数据库核心技术学习报告.docx》由会员分享,可在线阅读,更多相关《数据库核心技术学习报告.docx(20页珍藏版)》请在冰豆网上搜索。

数据库核心技术学习报告.docx

数据库核心技术学习报告

非关系数据库与关系数据库异同报告

高级数据库课程报告

2015021593黄天驰

目录

1引言2

2内部数据结构4

2.1stringVSsimpledynamicstring(sds)4

2.2顺序索引&b+树VS跳跃表6

2.2.1稠密索引6

2.2.2稀疏索引7

2.2.3B+树8

2.2.4跳跃表9

2.3B+树码压缩VS压缩列表15

2.3.1B+树码压缩15

2.3.2压缩列表16

2.4整数集合19

3数据测试22

4小结25

5附录27

4.1引用文献:

27

1引言

在进入研究生学习之前,我曾经和小伙伴们共同开发了一款游戏——这款游戏有非常高的实时性,基本要与服务器长连接并不断交换数据。

然而,这些实时交换的数据并不是数据库想去记录的——他们是动态数据,可能并不需要保存在数据库里,甚至数据库不需要去知道这些东西。

可能在游戏完成后,我仅仅需要保存他的成绩进入数据库即可。

但是,我可能又需要了解这些所谓的脏数据,毕竟他可能记载了大部分玩家的行为,作为我来说这是优化游戏,让游戏变得更加好玩的关键点。

说到底,就是高并发,高实时性的脏数据。

最初,在我构建的游戏服务器grandserver中选择了mysql作为记录所有数据的服务器,包括静态数据和动态数据。

起初,一切一帆风顺,所有操作在封装的协议中顺利交互。

但是到了游戏制作,也就是数据频繁交互的阶段,我总是比预想中更晚收到我想要的指令,或者说,两者交互时间非常晚。

在查询mysql记录时发现,我总是不停的在改一整张表。

效率跟不上后,我放弃了mysql,在仔细甄选后,选择了nosql技术中的redis作为数据库缓存服务器。

随后我的代码被封装了成了图1-1:

在使用该类调用脏数据操作后,通过lua层的简单交互,终于让数据变得正常。

但是当时我在做完解决方案后留下了几个问题,还未解决即准备考研。

问题如下:

●nosql盛行的时代,sql存在的意义。

●nosql甚至可以在增加一个sql语句解释器的情况下完全变为sql数据库,那sql的存在意义到底在哪里。

经过将近半年的学习,在了解了双方内部的操作与区别后,我个人认为双方各有所长。

本文以sqlite与redis中的数据结构的异同点为出发点,着重分析sql和nosql架构中的区别以及他们不可替代的地方。

#pragmaonce

#include

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字符串函数。

2.2顺序索引&b+树VS跳跃表

在关系数据库中为了快速访问随机文件中的记录,一般使用顺序索引。

每一个索引结构与一个特定的搜索码相关联。

顺序索引按照顺序存储搜索码的值,并且将每个搜索码与包含该搜索码的记录连接起来。

被索引文件中的记录自身也可以按照某种排序顺序存储。

一个文件可以有许多个索引组成,分别基于不同的搜索码。

如果包含记录文件按照某个搜索码指定的顺序排序,那么该搜索码对应的索引称为聚集索引或主索引。

搜索码指定的顺序与文件中记录的物理顺序不同的索引即为非聚集索引或者辅助索引。

如图2-3例子所示:

用户ID用作搜索码,记录按照该搜索码顺序存放。

索引记录根据数据记录的稠密程度分为稠密索引和稀疏索引

UserID

UserName

UserPwd

RealName

FullControl

1

root

pass

root

3

1012

test001

test001

test001

2

1013

123

123

123

2

1003

sony

sony

sony

2

1004

test2

test2

test2

2

1009

2222

222

22

2

1010

1

1

1

2

1014

test

test

test

2

1016

222198

123456

222198

2

1017

222191

123456

222191

2

图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"

redis> ZCARD fruit-price

(integer)130

fruit-price有序集合的所有数据都保存在一个跳跃表里面,其中每个跳跃表节点(node)都保存了一款水果的价钱信息,所有水果按价钱的高低从低到高在跳跃表里面排序:

∙·跳跃表的第一个元素的成员为"banana",它的分值为5;

∙·跳跃表的第二个元素的成员为"cherry",它的分值为6.5;

∙·跳跃表的第三个元素的成员为"apple",它的分值为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 {

// 层

struct zskiplistLevel {

        // 前进指针

        struct zskiplistNode *forward;

        // 跨度

       unsigned int span;

} level[];

// 后退指针

struct zskiplistNode *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结构的定义如下:

typedef struct zskiplist {

// 表头节点和表尾节点

struct zskiplistNode *header, *tail;

 // 表中节点的数量

unsigned long length;

// 表中层数最大的节点的层数

int 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

uint32_t

4字节

记录压缩列表尾节点记录压缩列表的起始地址便宜:

用于快速定位尾节点

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

1字节

长度小于等于63字节的字节数组

01bbbbbbxxxxxxxx

2字节

长度小于等于16383字节的字节数组

10______aaaaaaaabbbbbbbbccccccccdddddddd

5字节

长度小于等于4294967295的字节数组

整数编码:

编码

编码长度

content属性保存的值

11000000

1字节

int16_t类型的整数

11010000

1字节

int32_t类型的整数

11100000

1字节

int64_t类型的整数

11110000

1字节

24位有符号整数

11111110

1字节

8位有符号整数

1111xxxx

1字节

无contennt属性,xxxx保存了0到12之前的值

故可以总结出redis中的压缩列表的几个特点:

∙压缩列表是一种为节约内存而开发的顺序型数据结构。

∙·压缩列

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1