Category: Database

3

分布式系统之leader-followers Replication深入介绍

我们在前面有简单讲过Replication的作用,简单说就是为在多个机器上保存同样的拷贝来服务的。有了这个拷贝之后我们就可以做很多事情,比如说它可以成为一个读的源从而分散读的压力,它可以在原来数据机器出问题(或者deploy)等的时候作为一个backup等等。 这个想法其实很简单,但真正在我们做这个拷贝的时候,会遇到很多问题,比如说我们是使用同步还是使用异步来进行同步多个拷贝,如何保证多个拷贝之间的一致性等等。那么本文就来从各个方面详细介绍这些内容。 Leaders和Followers 我们把每一个保存数据的节点称之为replica,当我们有多个节点的时候,最明显的一个问题就是怎么去保证每个节点的内容都是一样的呢?其中最常见的方法就是基于leader的模式(也成为master-slave模式或者active/passive模式)。总得来说,它的工作方法如下: 一个节点是leader。所有的写操作都必须经过leader。 其他的节点我们成为follower,每次leader写数据的时候,也把相关的内容发送到每一个follower(replication log),然后每个follower根据这些log来更新本地的数据。 当有读的操作的时候,既可以从leader读也可以从follower读。 同步VS.异步Replication 上面这种方法其实在很多关系型数据库中很常见。这里遇到的一个最大的问题就是使用同步还是异步来进行replication。 就比如上面的这个case,它实现的功能很简单。就是把user id 1234这个用户的picture更新一下,它首先发送了update的请求到了leader的replica上。在这之后,leader会把这个更新的请求分别发送给两个follower的replica,从而最终达到所有的replica上这个用户的picture都更新了的效果。 我们就以这个例子来分析一下同步和异步在这两个上面的差别。如下图所示,我们看到leader的返回是在Follower1 更新完成之后,也就是说follower1是一个同步的更新。而leader并没有等待follower2完成更新再返回,这也就意味着follower2的更新是一个异步的。 从这个图中可以看到follower1和follower2的更新其实都是由一个延时的,尽管大多数时候这个延时都很小,但是当我们遇到网络问题或者别的情况的时候(CPU占用率很高,内存不够等等),这个延时有可能会很大,谁也不能保证什么时候能被更新。 所以,同步更新的好处就是follower其实和leader是同步的,当leader有问题的时候我们甚至可以直接切换到同步更新的follower,当然它的问题也很明显,就是每一个更新(写操作)都需要等待这个follower的更新完成才行,这有可能会导致整个request延迟很久,甚至超时。所以,假如你想做同步的更新,一般来说也不会想让所有的节点都同步,而是选择leader和一个节点是同步的,这样就可以在leader出问题的切换和同步的效率之间达到一个很好的trade off。 当然,事实情况下基于leader的replication基本都是完全使用异步。这样的问题就是leader出问题,那些没有sync到别的node的数据就会丢失。好处就是leader完全不收别的节点的影响,哪怕别的节点都出问题也可以正常独立运行(通常这个时候就会出alert,然后oncall就到了表现的时候了,哈哈)。生产环境中这样的选择其实有很多原因,比如说为了保证随时都有节点是健康的,这些节点通常会分布在不同的地区(数据中心),所以他们之间的通信通常不是那么可靠,如果使用同步就会很容易出现问题等等。 Follower的建立 有时我们需要从无到有建立一个新的follower,这种情况在生产环境中特别常见,比如说机器的磁盘坏了,原有follower的数据就都不能用了,这个时候就需要从头开始建立,或者说某个时刻你没有足够的follower了(比如说数据中心出问题了,或者机器出问题了),你需要在一个新的机器上建立一个 follower,那么如何从头开始来建立一个节点呢? 首先想到的就是从leader上拷贝过来就好了,但是我们知道leader其实是在不停改变的,也就是说随时都在不停地被写入,而数据的拷贝速度不会很快(主要瓶颈在HDD的写入速度上,目前基本在200MB/s左右),所以在这个过程中不允许写可能也不是一个很好的方法。那么一般来说是怎么做的呢?大概会有以下的步骤: 得到一个数据库某一个节点的snapshot(这个snapshot不影响写)。大多数数据库都支持这样的功能。 拷贝这个snapshot到新的follower Follower联系leader去得到所有的在这个snapshot之后的操作(log sequence)。 然后follower根据得到的信息来apply snapshot之后 的操作,我们称这个过程为catch up。 处理节点的中断 我们上文中也提到了任何节点在任何时候都有可能出问题。那么要是真的出问题了,我们一般会怎么处理呢? 假如这个出问题的节点是follower,假如这个问题只是service...

0

深入浅出理解数据的序列化和反序列化

一般来说,数据的处理有两种类型。一种是在内存中,比如我们常见的结构体,list,数组等等。而另外一种就是把数据写到文件中或者在网络中进行传输,这个时候的数据传输说白了就是比特流,那么接受方如何解析这些接收到的比特流呢?这个时候就需要对数据进行序列化,把相应的数据转化成可以自解释比特流。然后接收方就可以通过反序列化的方法把这些比特流再转化成相应的结构体等等类型。 各种语言自带的格式 很多语言都有自带的序列化方法,比如Java.io.Serializable,Python的pickle等等。它们用起来很方便,但是也存在一定的局限性: 假如序列化是来自于特定的语言,那么反序列化也得是相应的语言。这就给不同语言之间的交流(比如客户端和服务端使用不同语言)带来了困难。 因为允许反序列化时实例化任意的类,所以很容易造成漏洞,给安全攻击带来了可能。 这些语言特定库的向前和向后兼容性一般都不太好。 性能一般来说都不是很好,它们的CPU使用率以及压缩比一般来说都不是很理想。 所以一般来说不太会使用语言自带的序列化和反序列化函数,那么除了语言自带的函数还有哪些选择呢? JSON,XML和CSV 比较常见的不依赖于语言的序列化标准有JSON, XML。前者因为其是浏览器的内置支持格式而流行,后者则有时被大家认为太繁琐和复杂了。当然还有CSV格式也有很多人使用。这些格式其实对人的可读性来说都比较友好,但也各有其问题: 数字的序列化比较差。XML和CSV你基本上很难区分数字和包含数字的字符(除非特殊处理)。JSON虽然好一点,但是它不能区分整数和浮点数。 JSON和XML支持Unicode的字符串,但是不支持binary的字符串。虽然有一些方法来解决这个问题,但是也需要付出相应的代价。 CSV不支持schema,所以都是应用程序自己来决定每行和每列的内容。 除了这些问题外,其实JSON, XML和CSV都还是不错的,目前也还算比较流行。 二进制编码 JSON和XML还是不错的,但是他们的数据有时还是有点冗余,在小量级的数据下这个问题并不是很明显,但是当数据大了之后,这个问题就显得尤为突出。所以在此基础上就出现了很多二进制编码的技术,比如基于JSON的MeessagePack,BSON,BJSON,UBJSON等等,以及基于XML的WBXML等。 我们来以MessagePack为例来看一下如何处理下面这个JSON文档。 第一个byte是0x83,这里前面的4bit 0x80表示这个是一个object,后面的4bit 0x03表示这个里面有3个域。 第二个byte是0xa8,其中前4bit 0xa0是说这个是一个string,长度是由后4bit决定的0x08,也就是8byte的长度。 后面的8个byte就是userName的ASCII编码。 后面的0xa6和之前的0xa8是类似,只是长度这次变成6. 经过这样的编码之后长度就由原来的81byte缩小成了66byte,有大小的好处,但是也牺牲了可读性,究竟值不值得,其实还是仁者见仁,智者见智的事情。这里我们犹豫是否值得的一个重要原因就是其实大小缩小得并不是很明显,下面我们会介绍几种大小减少更明显的方法。 Thrift和Protocol Buffers Thrift和Protocol Buffers和上面的中心思想是类似的,但是他们各自尤其优点。其中Thrift 是由Facebook发明的,而Protocol Buffers(protobuf)是由Google开发的。 我们首先来看看Thrift,它首先需要定义一个Schema如下:...

0

数据库应用之数据分析

在早期数据库发明的时候主要是用来为实现商业功能的,比如说保存订单的信息,支付员工的工资等等。这类需求更多地是面向功能的,它的要求是相关的请求能够快速及时正确的执行,我们称这个流程为联机事务处理(OLTP, Online Transaction Processing)。 而随着数据库发展至今,一个更加常见的应用场景就是数据分析。比如说如何从淘宝订单中分析出商家每个月的销售情况,如何分析出哪些商品是爆款,如何得到某个新的功能给公司带来的点击量的增加等等。我们通常称这个流程为联机分析处理(OLAP, Online analytic processing)。 通常来说OLAP都是由商务部门来使用,最终呈现的形式可能是一个报告,因而它和我们传统的联机事务处理是不同的,它们主要的差别见下表: 场景 OLTP OLAP 读 每次查询的记录数据比较小,通常是查询某个key的情况 大量记录的聚合数据 写 随机访问,希望写的延时很小 批量导入或者event流 主要使用者 终端的用户 内部分析 数据显示方法 当前最新的数据 某段时间的历史数据 数据量级 GB 到TB TB 到PB Data Warehouse 在早期的时候,大家都会使用同样的数据库来进行OLAP的分析,后来越来越多的公司会使用专用的数据库来做这件事情,我们称之为Data warehouse。 当我们要使用一个独立的Data...

0

深入分析数据库中数据的存储和读取

我们日常的开发或多或少都会和数据库打交道,那么数据库中数据都是如何存储来保证读写的效率呢?本文就来详细地介绍数据库中数据的存储和读写。 最简单的数据库 我们首先来看一个最简单的通过bash来实现的数据库,它就是一个键值数据库,通过Bash函数来实现读写。 这里有两个函数,一个是写函数,就是简单的写入key和value对。另外一个函数是db_get()函数,它可以读出最新写入的一行数据。 我们可以这样使用它,这里我们就是写入了两个key,value,一个是123456,对应的后面的Json格式数据:'{“name”:”San Francisco”,”attractions”:[“Exploratorium”]}’,另外一个键值是42,它对应的是'{“name”:”San Francisco”,”attractions”:[“Golden Gate Bridge”]}’,最后我们可以调用get函数得到42对应的值。这个实现算是简单明了,一目了然了。 那么假如一个键值被设置了多次该怎么保存了,如下图所示: 我们可以看到,42被重写了,这里最简单的实现就是继续往后面加一行,然后在读取的时候从后往前读(后面就是最新的),就可以得到最新的值了。 其实在真正的数据库中,也有类似的实现就是一个不断增加的log文件,我们会不停地往里面写,当然真实的数据考虑的问题会复杂很多,比如写到一半出问题了,或者写出问题了等等。但是总得思想还是类似的。 聪明的你也许会问,假如我要删除一个key怎么办呢?一般来说,对于删除的操作,我们需要插入一个特殊记录在最后,当我们遍历到这个记录的时候,我们就知道,这个key是被删除了。 当然这种实现的另一个问题就是,随着我们不停地写这个文件的大小是不断增加的,哪怕我们是更新一个同样的key,这个文件也是在不断增大,它最终会带来文件大小的问题。对于这个问题的一般解决方法就是做merge的操作,也就是保存最新的记录即可,这个操作在我们key的值比较少的情况下可以大大缩小文件的大小。 当然还有一个问题也许你会比较好奇,为什么要一直重复往最后加,而不是去更新呢?这是一个很好的问题,有这样几个原因: 一直往后面加,其实可以充分利用到HDD的sequential写,假如你对HDD的读写有研究,你就会知道,sequential写的速度和效率要比随机写高很多,这个主要的原因就是磁盘的磁头不需要随机地不停进行改变。即使是SSD,sequential写相对于随机写也会延长SSD的寿命。 并发和crash的恢复会简单很多。你不需要太担心写到一半发生crash的情况。基本只要做好checksum,就可以知道最后有多少log可能是不对的,去除即可,而不用担心某一个地方更新只更新了一半的情况。 这样的实现其实写的效率还是不错的,因为你只要负责在文件最后不停地写就可以了,但是当数据大了之后,读就会成为问题,你找一个key的值,需要从后往前不停地遍历,也就是o(n)的复杂度了,那么有没有什么好的办法来加快这个进程呢?答案显然是有的,这就是我们下面要讨论的哈希索引。 哈希索引 还是以上面的数据库来作为例子,我们知道文件其实存在磁盘上,它是有一个offset的。假如我们能够有一个in-memory的hash表来保存key和offset的对对应关系,那么读的效率显然会提高很多,如下图所示: 有了这个哈希表,我们就可以知道42对应的位置在64 offset,然后就很容易读到对应的内容,而不需要从后往前遍历。这里的哈希索引有一个问题,就是它其实是in-memory的,也就是说假如发生crash,我们得要重新建立它,这就需要重新遍历这个disk来得到所需要的索引,一个比较好的方法就是我们在disk也保存一份哈希索引的备份,这样出了问题,我们也可以快速恢复。 初一听起来这样的哈希索引还是不错,但是细想一下就会发现其实它也有很多问题: 假如key的数目很多,也就意味着我们需要保存到哈希索引中的内容也很多。 范围的查找效率很低,比如说找key001到key002之间的值,就需要遍历所有的key才能找到相应的答案。 那么如何来解决这些问题呢?我们来看看SSTable和LSM-Tree。 SSTable 我们发现上面介绍的数据库实现中,每一行都是一个key value的对,其实不同key之间的顺序并不重要。也就是说在数据库中,key=42的记录在前面还是key=53的记录在前并不影响最终的结果。这样一来,我们可以在merge的时候,按照key的顺序来排序,我们称这种方法为Sorted String Table简称SSTable。 它的实现也很简单,流程如下: 当有写到来,首先写到一个in-memory的平衡树结构中(比如红黑树),这样开始写到memory而不是disk的原因是一般来说,维护一个有序的memory的结构相比维护一个有序的disk文件要方便很多,我们称这个为memtable。 当memtable越来越大,比如说大于一定的阈值,就会写到磁盘中,因为memtable本身是有序的,所以有序地写到磁盘中就相对比较容易了,我们称每次的写入为一个segment。...

0

Facebook是如何加速SQL查询

在PB级别的数据上进行查询是一件事,如何在Facebook这种级别的产品上使用则是另外一件事。今年早期时候,他们把Alluxio分布式文件系统集成到了他们的数据架构上,实现了存储和计算的分离,以此用来加速查询。 Facebook很早就使用Apache hadoop,并且到现在还可以在Facebook的架构中看到他。它们已经把数据存储从Hadoop clusters中移开了,但是仍然使用HDFS在Warm Storage中访问数据,这是一个由Facebook开发的定制的分布式文件系统,用来在分离Hadoop之后进行数据存储的。 Facebook内部使用了很多Presto来进行SQL查询,A/B testing以及服务一些关键的仪表,比如每日的活跃用户(DAU)或者每月的平均用户等等。作为一个没有存储模块的计算单元,Presto对计算和存储分离的架构非常友好。 为了让SQL查询的延时在一个可以工作的范围(比如在一秒左右),几年之前,Facebook实现了一个数据Caching的策略。Dubbed Raptor,这个系统要求用户在Presto cluster的SSD上创建ETL pipeline来cache数据。 Raptor加速了查询的速度,因为很多查询都再也不用通过网络了,但是同时也带来了很多别的问题。 开始的时候,把计算和存储放在一起,就限制了公司去把他们分开来扩展,这显然是不好的。它也导致了数据的碎片化,从而降低了用户的体验,这主要是因为查询有时会hit warm storage,有时会hit 本地SSD的cache。而且它也是的数据的安全和隐私保护更加复杂,因为你不仅仅要保护Warm Storage中的数据,还需要保护本地SSD的数据。 去年秋天,Facebook开发实现Raptor的替代方案。它是基于Alluxio,这个新的系统的查询性能和Raptor类似,但是不需要在SSD中cache数据。 Alluxio是一个虚拟的分布式文件系统,它连接了多个计算引擎和后端存储系统。它是由Haoyuan在UC伯克利 AMPLab开发的。Facebook基于Alluxio开发了一个新的版本,让它可以在Java上运行。 Facebook使用ORC文件格式来存储数据,并且通过HDFC接口来让Presto访问。Alluxio的实现可以在SSD上工作,并且把数据存储在本地,从而加速访问,但是本地的cache却不再需要了,而且Alluxio也可以在没有SSD的情况下使用Warm storage来加速访问。 当Alluxio在上百个Facebook数据中心的节点上安装之后,用户就可以体验到和之前Raptor类似的查询性能。它们可以使用很多Join的查询来查询PB级别的数据,没有Alluxio或者Raptor之前,这些查询可能需要超过10s才能返回。在一些能够hit oft-queried表格的查询,Alluxio可以相比Prestor负载提高了30%到50%,这显然是一个不错的提升。 根据James七月份的报告,相比于Raptor,Alluxio cache减少了57%从远端数据存储单元读取的操作。Alluxio cache的hit rate查过了90%。 Jain说,现在还有一个问题,就是当有很大的数据请求过来的时候,就很容易让cache的hit rate降低,它们现在正在想办法解决这个问题。 这个方案的另外一个优点是金钱和安全,因为Presto计算单元和数据存储是分开的,这样可以分开进行提升,而不会有浪费。在安全性方面,也不需要再担心SSD上数据的安全,而只需要关注warm storage上数据的安全即可。 当然Raptor也没有完全被取代,不过预计在未来六个月会被完全取代。

0

MySQL数据库服务介绍

MySQL团队现在把它引入到了Oracle Cloud架构上(OCI),这个服务是100%由MySQL服务开发,管理和支持的。 MySQL数据库服务和你所了解的MySQL几乎是一样的,只是说现在在云平台上支持了而已。它会自动执行很多耗时的任务,比如MySQL instance provisioning,补丁和升级,以及备份和恢复。用户可以很方便地扩展MySQL,检测云资源以及实现安全策略。用户的应用可以通过标准的MySQL protocol来访问MySQL数据库。典型的管理任务可以通过OCI web控制台,REST API, CLI或者DevOps工具进行自动化,集成操作。 MySQL数据库服务在多个OCI region中可以使用,这样可以很方便符合你所在地的政府和区域要求。当然更多的区域支持也正在实现中。 由MySQL开发团队创建 一个最大的优势就是MySQL数据库服务是由MySQL开发团队开发管理和支持的。我们来看看这为什么很重要: MySQL数据库服务是由MySQL社区和商业版同一个团队开发和维护的。 这个服务是一直最新的,包含最新的安全更新。 可以得到所有的最新的功能,包括使用最新的X-Protocol和X DevAPi实现的NoSQL Document Store。 MySQL数据库服务由MySQL专家配置的默认config。当然也支持用户来自定义化。 因为MySQL服务是在云上,你可以有最大的灵活性来使用你的部署策略。 用户可以使用MySQL本地replication和MySQL Shell来快速和方便地把workloads移到云上。 不像其他的云数据库服务,它们的服务支持都是有限的。MySQL数据库的服务支持是不需要额外花费的。 MySQL数据库服务是基于Oracle Cloud架构进行创建的,继承了所有Oracle cloud的特性,所以可以放心运行各种关键和核心的任务。 价格:只有Amazon RDS的三分之一不到 MySQL数据库服务大概是目前主流云提供商最便宜的选择了 — Amazon RDS, Microsoft...

MongoDB 4.4 最新特性介绍 0

MongoDB 4.4 最新特性介绍

MongoDB 4.4就要发布了,本文就来介绍一些这个版本的一些最新的功能。 MongoDB 4.4中改进的功能 对冲读取 MongoDB可以并行处理读请求,然后从最有效的节点来获取结果,从而可以减少应用的延时。 可定义的共享秘钥 在scale过程中修改数据分布的时候会在共享秘钥中加入后缀。 镜像读取 这个功能可以对副本的cache预先warm,从而可以减少计划的维护或者outage的时候主选举消耗。 聚合增强 聚合增强有了很多方面的改进,包括定义和汇总聚合表达式,从不同的mongo collection中组合数据到一个结果中,字符串处理以及数组处理的新操作。下面是一些加强: $out $out现在可以把输出collection的结果到不同的数据库,而早期的版本只能输出到同一个数据库中。 $indexStats $indexStats在新的版本中有了一些新的域,包括下面这些: Building 这是一个Boolean的flag,用来表示正在建立的索引 Spec 每一个索引的规格文档 Shard 分片的名字 $merge $merge可以在要升级的同一个collection中输出。另外,可以在一个collection中数据并显示在pipeline上,比如$lookup。 $planCacheStates的改变 $planCacheStates现在可以同时在mongod和mongos instance上运行。另外,$planCacheStats在运行的时候有一个新的域我们称之为host域。PlanCache.list()是$planCacheStats聚合阶段的包装。 $collStats改变 $collStats在扩充文档中已经可以接收查询统计信息。另外Collection扫描有下面这些域: 域名字 描述 nonTailble 一个64位整数,用来表示多个查询上的一个collection...

0

关系型数据库进阶之Log Manager

我们在之前的文章中聊到,数据库为了提高它的性能,会把数据存在memory buffer中。但是这里有一个问题,就是假如一个已经committed的transaction crash掉了,你因为这个crash丢掉了memory中的数据,那就会出现了Durability的问题。当然你也可以把所有的数据都写到磁盘中,但是同样的问题,假如写到一半就crash了,这里就会发生原子性的问题。 所以这里的我们的原则是任何通过transaction进行写的修改都要么是全部完成要么就是什么都不做。 为了解决这个问题,有两个方法: Shadow copies/pages: 每一个transaction都创建它自己的数据库(或者部分数据库)备份,然后在这个备份上进行操作。假如有错误,这个备份就移除掉。假如成功了,就切换到这个备份上,而把旧的备份删除掉。 Transaction log:transaction log是一个存储空间。在把数据写到磁盘之前,数据库把相关的信息写到transaction info中,因此即使有crash或者cancel的时候,数据库也知道怎么来完成或者移除响应的没有完成的transaction。 WAL Shadow copies/pages的思想其实很好,唯一的问题就是在大的数据库中,会额外浪费很多磁盘的空间。这也是为什么现在主流的数据库都是用transaction log来解决这个问题。Transaction log必须存储在一个稳定的存储空间中。 大多数数据库(至少Oracle, SQL Server,DB2, PostgreSQL, MySQL以及SQLite)都使用Write-Ahead Logging protocol (WAL)来处理transaction log。这个protocol主要有三个规则: 每一个对数据库的修改都产生一个log记录,并且log记录必须在数据写到磁盘前写到transaction log中。 log记录必须按照顺序写,也就是说发生在记录A先发生,那么记录A就必须先写。 当一个transaction committed了,这个commit的顺序必须在transaction介绍前写到transaction log中 这些工作都是在log manager中处理的,他位于cache...

0

关系型数据库进阶之Transaction manager

在前面的文章中,我们介绍了查询管理,查询优化以及数据管理,本文就来继续介绍Transaction manager相关的内容。它主要是用来保证每一个query都在它自己的transaction中执行。不过在此之前,我们需要理解一下ACID transaction。 ACID ACID transaction是一个工作单元,它主要包含4个方面: 原子性:transaction是“要么完成所有,要么什么也不做”,哪怕这个操作需要10个小时来执行。假如transaction crash,那么所有的state都需要roll back到之前的状态。 隔离性:假如有两个transactionA和B一起执行,不管A是在transactionB之前还是中间还是之后完成,结果都是一样。 耐用性:当transaction committed(成功完成),不管发生什么数据都会保存到数据库。 一致性:只有有效的数据才会写到数据库,一致性是和原子性以及隔离性相关。 在同一个transaction中,你可以运行多个SQL query来读,创建,更新和删除数据。但是当她们使用同样的数据的时候,就会造成混乱。我们来看下面这个例子: Transaction 1 从account A中取出100块钱,并存到account B中 Transaction 1 从account A中取出50块钱,并存到account B中。 我们来从这个例子中看看ACID中各个属性: 原子性:不管发生什么,你都不能从account A中取出100块钱,而不存到account B中去。 隔离性:需要确保假如T1, T2同时发生,最终account A被取出150块并且account B收入150块。而不会发生别的,比如account B只收入50块之类的。...

0

关系型数据库进阶之数据管理

我们在前面已经介绍了客户端管理,查询管理,今天来介绍数据管理。 在这一步中,查询管理会执行相应的查询,这个时候就需要从表和index中得到数据了。它需要数据管理来获取数据,不过这里有两个问题: 关系型数据库使用的是transaction的模式,所以你不能够在任何时候都得到数据,因为同时可能有别人在使用或者修改数据。 数据的获取是数据库中所有操作最慢的操作,所以数据管理需要足够聪明,来把数据保存在内存buffer中。 本文我们就会来讨论关系型数据库是如何处理这两个问题的。 Cache管理 就像我们前面几篇文章提到的一样,数据库的瓶颈就在于磁盘I/O,所以为了改进性能,需要使用cache管理器。 Query的执行器并不是从文件系统直接获取数据,而是通过cache manager来请求数据。Cache manager有一个memory的cache,我们称之为buffer pool。显然动态地从cache请求数据会增加数据获取的速度。一般来说memory相比磁盘来说会快100到100K倍,当然这还取决于决定的硬件和读写方式。 但是这里就会有另外一个问题,就是cache manager需要在query执行之前就获取到相应的数据,否则就还需要访问磁盘来数据。 预获取 query执行器其实是知道它所需要的数据,因为它知道整个query的所有流程。基于此,我们可以让query执行器处理第一部分数据的时候,就要去cache manager去预加载第二部分的数据,然后当它处理第二部分的数据的时候,就让cache manager去预加载第三部分的数据,这样循环下去。 Cache manager会把所有的数据保存在它的buffer pool中,为了检查相关数据是否还需要,cache manager在所有的cache数据中加入了一个额外的信息(我们称之为latch)。 当然有时候query执行器也不知道它下面要什么数据,这个时候就会使用一些特殊的预加载(比如,query执行器要数据1,3,5,那么它很有可能在未来需要数据7,9,11)或者顺序获取(这种情况下,cache manager就继续加载磁盘中后面的数据)。 为了探测预获取的效率,数据库提供一个称之为buffer/cache hit ratio的指标,这个指标会显示当请求一个数据的时候,有多少次是可以直接在cache中获取而不需要访问磁盘。 但是另外一个问题就是memory的大小毕竟有效,所以假如要加载新的数据,那么就得去除一些旧的数据,而这些加载和去除都是需要磁盘以及网络I/O消耗的。假如有一个查询经常执行,那么我们就会频繁地加载和去除相关的数据,这显然是不太合理的,那该如何处理呢?现代的数据库使用一种称之为buffer替换的策略。 buffer替换策略 现代的数据库(至少SQL Server, MySQL, Oracle和DB2)基本都使用LRU算法。 LRU就是Least Recently...