21/8/6 读书笔记 数据库管理系统结构 函数式编程中对重用的考量

21/8/6 读书笔记

数据库系统概论 数据库管理系统层次

数据库管理系统设计是一个复杂的软件工程。围绕数据,数据库管理系统需要实现数据库的定义创建维护、数据的组织存储管理、数据库事务管理和运行管理等功能。与OS相比,数据库管理系统跨度更大、功能更多,从底层的存储管理和缓冲区管理一直延伸到最外层的用户接口。

数据库系统结构可以被划分为若干层次,从外到内包括:

  1. 应用层:处理来自应用程序的事务请求,需要考虑网络通信、嵌入式SQL等,将其转化为实际的数据库语言的操作语句。
  2. 语言处理层:对来自应用层的语句进行语法分析、视图消解、安全性和完整性检查以及进行查询优化,并通过对数据存取层的调用生成可执行代码。
  3. 数据存取层:为语言处理层提供基本的元组操作接口,同时负责封装存取路径的维护、事务管理、并发控制、数据库恢复的工作。
  4. 数据存储层:直接访问数据页和系统缓冲区,负责缓冲区管理、内外存交换等,为上层提供数据库数据的访问接口。
  5. 操作系统与数据库:操作系统负责来处理数据文件的物理块,通过与存储层的交互使得数据库内容能真切地写入实际的物理存储介质。硬件的驱动程序均来自操作系统,因此数据存储层需要借助操作系统来操作物理介质上的数据。

当用户通过数据库接口试图查询一条记录时,将经历:

  1. 用户通过应用程序向数据库应用层发送一条查询请求,应用层对请求进行解析,并生成SQL语句
  2. 随后在语言处理层读取数据字典,通过字典中存储的数据库结构信息和权限信息来检查语句,通过后进行查询优化生成一串单记录的存取操作序列。
  3. 数据存取层逐条执行操作序列,调用数据存储层的存储器接口
  4. 数据存储层先在缓冲区中查找记录,如果找到满足条件的记录则进入第7步,否则进入下一步骤
  5. 数据存储层查看当前数据库的存储结构,并决定应该从哪个文件和位置读取目标记录,然后向操作系统发出请求
  6. 操作系统响应请求,从文件中读取相关信息,并存放在系统缓冲区中,然后回到第4步
  7. 根据查询命令和数据字典的内容,数据库获知需要返回给用户的数据格式
  8. 按照所需的格式将返回数据从缓冲区移至应用程序工作区,并将执行状态信息(成功与否)返回应用程序

语言处理层

语言处理层所处理的对象是数据操作语句(例如SQL),其具有高度描述性,需要基于数据库内部的结构来进行语义分析。因此处理层的主要认为就是将这种描述性的语句同数据字典中描述的实际数据库结构相绑定,将语句解释为对特定单元组的操作,从而实现一连串确定的可执行的动作,我们称这个过程为束缚(bind)过程。

束缚可以在运行中进行,也可以在运行前进行:

  • 解释方法:语句的束缚在运行中进行,即当该语句真正进入执行时才将该语句进行束缚,生成操作序列。优点在于可以动态适应数据库的结构变化,缺点在于在循环结构中会重复解析同一语句造成效率下降。
  • 预编译方法:语句的束缚在运行前进行,即当该语句被用户提交后立即进行处理,并保存操作序列,需要运行时再取出。注意到如果预编译完成后,数据库结构发生变化,则会导致预编译结果失效,需要进行重编译

数据存取层

数据存取层处理的对象是单元组操作,其需要提供对逻辑结构上的数据库对象的操作(记录存取),并封装好日志管理、事务管理、并发控制、存取路径维护等诸多功能,因此也是数据库结构中功能子系统最多的层次。

  • 记录存取与事务管理子系统:提供存取一个元组的原语,以及事务定义与控制的操作。
  • 日志登记子系统:管理和处理日志记录,与事务管理子系统配合使用。
  • 控制信息管理系统:提供对数据字典中说明信息的增删改查,为语句执行提供参考。
  • 排序/合并子系统:对关系元组进行排序,以辅助索引的动态建立以及提高数据库操作的效率。
  • 存取路径维护子系统:对数据的增删改会导致数据的存取路径发生变化,比如B+树索引需要进行动态维护,就属于该子系统的工作。
  • 封锁子系统:自动进行封锁管理,实现并发控制。

数据存储层

数据存储层包括缓冲区管理、内外存交换以及外存管理等,其中缓冲区管理是其主要职责。

缓冲区存在的意义是:既将实际的外存设备与数据库系统分离开,无论外存如何改变,数据库系统都只需要读写缓冲区即可;又能利用缓冲区匹配内存和外存的速度差异,提高存取效率

缓冲区管理的主要核心是页面淘汰查找算法:当需要向数据库写回某个数据时,数据存储层并不直接将数据写回,而是将缓冲区中该记录对应页面设一标志。页面淘汰算法按策略将该页面淘汰时才将该页面写回;当需要从数据库读取某个数据时,需要先在缓冲区进行查找,找不到时再触发页面淘汰来将新页调入。


函数式编程思想 模式与重用

让我们更加深入地探讨设计模式在函数式编程中的地意义。如果我们认为当下语境中的设计模式特指GoF在圣经中提到的那些用途导向的具体的设计模式的话,那么很多的设计模式在函数式编程下变得不一样了。函数式编程下用于搭建程序的材料已经不一样了,因此我们解决问题的手法也不再一样了。

书中指出设计模式在函数式编程中有三种下场:

  • 模式本身成为了语言的一部分,我们不再需要刻意地构建相应的类结构
  • 模式描述的解决方法依然具有意义,但是实现的细节有所变化
  • 由于新的语言提供了前所未有的特性,使得面对老问题有了新的解决方案,使得原有的设计模式失去意义

当模式融入语言

函数式编程中,我们将函数作为了一等公民,则意味着函数本身就成为了“对象”。在Java中我们能很好地体会到这种区别,我们知道一个只拥有一个方法的接口称为函数式接口,而匿名函数在传递时就相当于一个函数式接口。由于Java不能支持函数直接作为参数进行传递,因此为了具有函数式编程的特点而采用了函数式接口这种特殊设计。

而在真正能将变量赋值为一个函数的语言中,比如python、Groovy等,我们就会发现原来的有的设计模式的实现会发生变化,以简单工厂模式为例:

def computerOf = {type -> 
	def of = [MacBook: new Laptop(), Ipad: new Tablet()]
	return of[type]
}.memoize()

这里我们实际上就利用Groovy实现了一个电脑类的工厂,其中需要注意到变量of中的键值对中的值实际上是一个函数,使得new Laptop()语段是具备缓求值性质的。

同时,某些模式更是直接融入了语言,以单例模式为例:

@Singleton class Earth{
	...
}

Groovy下只要采用@Singleton注解就能把Earth类声明为单例的,这正是设计模式被语言和运行时所吸收的典型案例。

当然,函数式编程的新视角使得很多原来的设计模式有了新的考量。比如柯里化可以塑造出函数工厂,其可以在很多问题中取代原有的设计模式的思路。我们必须意识到设计模式是在面向对象的语境下提出的,而在函数式编程下我们考虑同样的问题并不一定需要采用原有的OOP下的思路。

重用的层次:结构化 or 函数化

面向对象封装不确定性而使得代码易懂,函数式编程减小不确定性而使得代码易懂。

这里的不确定性我认为是指状态。面向对象偏向于给对象赋予状态,然后使得该状态与外界隔绝,就是封装了不确定性;函数式编程偏向于消除状态,比如纯函数,因此使得不确定性减少。

面向对象编程中强调结构化,当一段代码具有重用的价值时,我们就倾向于将其提取出来成为一个类,并通过继承来访问它。长此以往,我们所构建的重用就会具有显著的结构性,也就是我们能从类图中看到的那些纷繁复杂的继承和实现所构成的网络。我们认为这种通过继承实现的代码重用是通过耦合而达到的,被重用的对象通常将不确定性封装在自身。当然,如果你是一个训练有素的OOP编程者,通过继承来构造优美的代码对你来说是一件习以为常的事情。但是对于广大的编程者来说,理清楚何时应该进行耦合以及如何进行耦合并不容易,这使得他们容易写出臃肿的混乱的代码。

类之间的耦合通常是因为状态而产生的!以我自己的理解,就好比是自行车与人,如果每个自行车具有自己的状态belongs,那么每个自行车的对象与每个人的对象就是关联的,A严格与belongs=A的自行车对象相联系,这就隐含了限制性。而如果自行车没有自己的状态,就好比共享单车,那么任意人可以关联于任意自行车,这就移除了限制性。而这里所谈及的“关联”,就好比“复用”,子类复用父类的方法,就好比人骑单车。

函数式编程中则是强调复合,认为一个被重用的对象不应该使得对象间被绑定,而当这种复用是发生在函数层面时,函数就应该是纯函数。由于纯函数没有状态,所以将它的重用不会受语言带来的限制。如果在Java的语境下看这个被重用的对象,它就是一个全是static方法的工具类,没有内部的状态。

我们需要注意到,面向对象下耦合并不是洪水猛兽,而是“必要之恶”。在很多面向对象的特征明显的应用场景里,比如图形界面组件、对象关系映射等领域,构建对象间的继承层次是必要的,因为在这些领域里重用通常发生在较高的水平上,比如按钮对象、画布对象等。但是在一些其他领域,面向对象的性质不是很明显时,比如进行科学计算、数据处理等,重用的粒度较小,通常是函数或者算法,我们就没有必要用结构化的思想指导重用了。

posted @ 2021-08-06 10:27  neumy  阅读(105)  评论(0)    收藏  举报