1.1 Oracle概貌
本节将从宏观的角度讲解在UNIX环境下Oracle的进程及其状态转换的原理。在优化Oracle的过程中,我们可以将Oracle数据库的组成抽象成I/O、CPU和内存三大组件。其中,I/O又可以分为存储和网络两个方面。I/O消耗对应物理读、网络延迟、查询结果数据返回等存储的读写,以及数据传输等;CPU消耗对应进程切换、SQL解析和执行、数据处理等;内存则可以看作CPU和I/O之间的缓冲地带,大量排序操作或内存不足都会导致交换区数据被换出。
对Oracle进行故障诊断或性能优化有自底向上和自顶向下两种思路。解决局部性故障的正确思路是自底向上,原因是我们首先要确保Oracle数据库运行的基础环境的健壮性,再诊断其上层的应用。如果连底层都确保不了,那么上层就可能会出现各种奇怪的表象。解决全局性问题(比如全局性的性能问题)时,正确的思路应该是自顶向下,这时需要综合操作系统、存储及网络等方面的知识,从全局的角度来考虑问题。
1.1.1 串联Oracle知识体系的挑战
在学习Oracle的过程中,想要串联起Oracle庞大的知识体系,并在面对数据库问题时灵活应用并非易事。虽然DBA的职业前景很好,但大多数DBA会卡在串联Oracle知识体系这个阶段止步不前,最终疏于对数据库的学习或转到其他技术方向。为什么会这样呢?因为Oracle作为系统软件,南向接口(底层)涉及各类存储技术(文件系统、裸设备、ASM、云等)及光纤网络等,北向接口涉及中间件、应用等技术。作为深度“嵌入”操作系统底层的系统软件,Oracle的横向接口又与操作系统的进程通信、控制和调度密切相关。而且Oracle自10g版本开始逐渐发展起自己的“操作系统技术”,比如,拥有了自己独立的存储管理和集群文件系统。如果想要串联Oracle知识体系,则首先需要对这些相关技术有不同程度的了解或掌握。Oracle与相关领域技术的关联性很强,仅仅学好Oracle数据库本身的知识体系是不够的。
学习Oracle的有两个关键点:一个是了解上述系统,另一个是实践。有些知识点仅理解理论不够,还需要通过实践来慢慢领会,积累经验。以索引的创建为例,索引是一把双刃剑,既可以让SQL查询操作变快,又会使SQL DML操作变慢。如果在运行较稳定的系统中为了优化性能而创建一个索引,那么这个操作也许会让整个系统突然变慢,让人进退两难。另外,如果为了减少表碎片和降低高水位而导出/导入表,那么虽然导出可能会很顺利,但导入的时候可能会出现无限等待的情况,使我们根本无法预估导入任务的结束时间,而DBA要保证天一亮业务就能正常运行。类似这样的例子还有很多。这些经验虽然能从书本上学到,但没有亲身体会很难深刻领悟,只有切身实践才能牢记在心。
串联Oracle技术,不仅需要掌握好内部原理,还要懂得相关技术跨界交互的原理。比如,Oracle自有的会话和进程与操作系统的进程之间的跨界点,Oracle用户态进程与内核态进程的跨界转换,存储上RAID(独立冗余磁盘阵列)技术组合出来的Lun与ASM磁盘组之间的跨界点,传统SAN存储架构至云端存储的跨界点,在RAC(真正应用集群)中,GI(集群栈)基础环境与其上层应用之间的边界,Oracle网络与TCP/IP网络传输层之间的跨界点,等等。因此,本书除了讲解Oracle内部原理之外,还将用大量篇幅介绍与这些跨界点相关的技术。本书的重点和意义更多在于探讨融合及连接Oracle内外部边界点的通信原理。
对Oracle疑难问题的处理,实际上就是对以上跨界点进行挑战式的融合应用。面对实际难题时,如果个人对这些跨界点的掌握和融合应用能力不足,则需要不同岗位的工作人员协助。假设我们遇到这样一个疑难问题:数据库工程师检查后说数据库没有问题,操作系统工程师检查后说系统没有问题,存储和网络工程师检查后也分别说存储和网络正常,但是问题仍然存在。在这种情况下,如果我们在一定程度上掌握了跨界点的交互通信原理,则至少能够进一步确定解决问题的正确方向,能够与专业人员进行有效沟通。
我们在学习Oracle时如果遇到某个瓶颈,突破的办法除了了解跨界知识以外,还需要尝试独立解决疑难问题,因为解决疑难问题的过程就是对挑战式学习成果的融合应用及演练,以及将Oracle技术融会贯通的过程。另外的突破方法就是写文章,进行技术分享(或讲课),甚至写书等。这也是笔者尝试写本书的原因——在写作与分享的过程中,可以重新领悟并串联起Oracle数据库所涉及的跨界点。在笔者看来,传递知识、分享技术、点亮别人是一个技术爱好者的使命及人生意义。
言归正传,在开始Oracle相关技术的融合之旅之前,我们还需要初步理解Oracle运行所涉及的UNIX系统体系结构,尤其需要了解现代操作系统时分复用(Time-Division Multiplexing,TDM)方式的实现原理,也就是作为程序载体的进程在执行任务时各种状态的变化过程。所以,接下来先简要介绍UNIX系统体系结构的核心概念,接着讨论进程在Oracle内核与UNIX内核之间的通信和状态转换过程。如果不够了解进程及状态的变换过程,那么想要理解进程在Oracle内核与UNIX内核之间的状态转换过程就会有一定的难度,因此本书也会适当介绍进程相关的知识。
1.1.2 UNIX体系结构简介
严格意义上,可以将操作系统视为一种软件,它相当于一种控制计算机硬件资源、为程序提供运行环境的软件。我们通常将这种软件称为内核,因为它相对较小,并且位于环境的核心。UNIX系统的体系结构如图1-1所示。
图1-1 UNIX系统的体系结构示意图
内核的接口通常被称为系统调用(System Call)。公共库函数建立在系统调用接口之上,应用程序既可以使用公共库函数,又可以使用系统调用。Shell是一个特殊的应用程序,可为运行其他应用程序提供接口。(在不同的UNIX环境中,Shell的语法稍有不同。)
文件和进程这两类实体是UNIX系统模型中的两个重要概念。图1-2所示的是UNIX系统的内核结构,从中可以看到各种模块及它们之间的相互关系。内核包括两个主要成分:文件子系统和进程控制子系统。
UNIX系统的内核结构包含3个层次:用户级、内核级和硬件级。系统调用接口与函数库体现了图1-1中描绘的应用程序与内核间的边界。系统调用看起来类似于C程序中普通函数的调用,而函数库则把这些函数调用映射为进入操作系统所需的原语。程序常常使用标准I/O库,这样其他的库程序就可以提供对系统调用的更高级的使用方法了。
图1-2 UNIX系统的内核结构示意图
文件子系统对文件的管理包括分配文件空间、管理空闲空间、控制对文件的存取,以及为用户检索数据等内容。进程通过一个特定的系统调用集合与文件子系统进行交互,比如,通过系统调用open、close、read、write、stat(查询一个文件的属性)、chown(改变文件的所有者)及chmod(改变文件的存取许可权)等。
文件子系统使用一个缓冲机制存取文件数据,缓冲机制用于调节内核与二级存储设备之间的数据流。缓冲机制与块I/O设备驱动程序进行交互,以便启动往内核去的数据传送及从内核来的数据传送。设备驱动程序是用来控制外围设备操作的内核模块。块I/O设备是随机存取的存储设备,或者说,设备驱动程序使得它们对于系统的其他部分来说好像是随机存取的存储设备。例如,一个磁带驱动程序可以允许内核把一个磁带装置看作一个随机存取的存储设备。文件子系统还可以在没有缓冲机制干预的情况下直接与“原始”I/O设备驱动程序交互。原始设备有时也被称为字符设备,包括所有不是块设备的设备。
进程控制子系统负责进程同步、进程间通信、内存管理和进程调度。当要执行一个文件而把该文件装入存储器时,文件子系统与进程控制子系统会进行交互,进程控制子系统在执行可执行文件之前会把它们先读到主存(即内存)中。
系统调用可用于控制进程,比如通过fork()创建新进程,通过exec()把程序的映像覆盖到正在运行的进程上,通过exit()结束进程的执行,通过wait()使进程的执行与先前创建的一个进程的exit保持同步,通过brk()控制分配给一个进程的存储空间的大小,通过signal()控制进程对特别事件的响应,等等。
内存管理模块可用于控制存储分配。一旦系统没有足够多的内存空间供所有进程使用,内核就会在内存与二级缓存之间对进程进行迁移,以便所有的进程都能得到公平的执行机会。这里又会涉及内存管理的两个策略:对换与请求调页。对换进程也称为调度程序,因为它可用于调度进程的存储和分配,并且会影响CPU调度程序的操作。
调度程序模块把CPU分配给进程,该模块调度各进程依次运行,直到它们因等待资源而自愿放弃CPU,或者它们最近一次的运行时长超过了设定的时间量,从而造成内核抢占进程的问题(这时调度程序就会将最高优先级的合格进程投入运行)。当原来的进程成为最高优先级的合格进程时,它还会再次运行。
最后,硬件控制主要负责处理中断以及与机器通信。像磁盘或终端这样的设备是可以在进程执行时中断CPU的。如果出现这种情况,在中断处理完毕之后,内核可以恢复被中断的进程。中断不是由特殊的进程处理的,而是由内核中的特殊函数处理的,这些特殊函数是在当前运行的进程的上下文中被调用的。
Oracle体系结构与UNIX系统体系结构有很多共同点,因为Oracle体系结构的很多理念和思想来源于UNIX系统。Oracle体系结构可分为两大部分:进程子系统和文件(存储)子系统。数据库中比较重要的概念之一是事务处理,即通过DO-UNDO-REDO协议和两段锁协议来确保事务的ACID特性。Oracle内核在进程管理、内存管理、I/O等待等方面与UNIX内核的实现有不少共同点,因此深入理解Oracle离不开对以上概念的理解。
1.1.3 Oracle进程状态转换
数据库作为系统软件,既依赖操作系统又在很多方面独立于操作系统。在哪些方面独立呢?答案是在数据的并发访问、事务一致性、数据的逻辑读、小颗粒变更(日志)记录的跟踪等方面。这也是将其归类为系统软件而不是应用软件的原因之一。
在图1-3中,每个节点(矩形)表示一个进程可以在操作系统中的状态,比如运行或等待。每条边(有向虚线)表示从一种状态到另一种状态的转换。这个简化的进程状态图说明了进程在大多数现代时分操作系统中的主要状态。
图1-3 进程状态图
接下来,我们看看Oracle内核与操作系统内核之间的进程交互及状态变更过程。Oracle内核进程更多时候是在用户态模式下运行的。SQL解析、记录排序、逻辑读、字段类型转换等操作一般是在用户态模式下运行的。有两个事件可以导致进程从用户态模式转换到内核态模式,分别为系统调用和中断。接下来进一步介绍这两个事件。
1.系统调用转换
当处于用户态模式的进程进行系统调用时,它会转换到内核态模式。读取(read)和查询(select)是两个典型的系统调用。一旦进入内核态模式,进程就被赋予了特殊的权限,能够操作低级硬件组件和内存空间。在内核态模式下,进程可以操作I/O设备,例如套接字(socket)和磁盘驱动器。
部分系统调用可能会在设备上等待许多个CPU周期。在执行一次物理磁盘I/O操作所需的时间(几毫秒)内,CPU能够执行数百万条指令。在这种情况下,一次读取调用将花费足够执行几百万个CPU指令的时间。因此可以允许另一个进程在读取进程的等待时隙内,通过时分复用CPU的方式提高执行效率。例如,假设一个Oracle内核态进程要执行某种读取系统调用(比如从磁盘获取Oracle数据块)的操作,在向预期的“慢”磁盘设备发出请求后,读取系统调用的内核代码将此调用进程转换为睡眠状态,在睡眠状态下,该进程会等待一个中断信号(表明I/O操作已完成)。这种复用方式允许另一个处于就绪状态的进程充分利用原读取进程无法使用的CPU资源。也就是说,CPU的主频越快,进程间的切换效率越高。
当I/O设备通知处于睡眠状态的原读取进程的I/O操作准备好以进行进一步处理时,该进程会被唤醒,也就是说它会转换到就绪状态。当进程处于就绪状态时,它就有资格进行调度了。当调度器再次选择要执行的进程时,该进程会返回到内核态,执行原进程剩余的内核态模式代码,例如把从I/O通道获得的内容数据传输到内存中。读取进程中的最后一条指令会将控制权返回给调用程序。也就是说,Oracle内核进程将转换回用户态,并在这种状态下继续使用CPU,直到它下一次接收到中断指令或进行系统调用为止。进程退出本身就是一个系统调用过程,即exit(),因此即使应用程序已完成相关工作,系统调用和中断也是转换用户态的唯一方法。
2.中断转换
中断是一种机制,I/O外围设备或系统时钟可以通过这种机制异步中断CPU。大多数系统是这样配置的:系统时钟每0.01s发生一次中断,在接收到时钟中断时,系统中处于用户态的每个进程都会保存其上下文(进程正在执行的环境及数据)并执行操作系统调度程序。调度程序决定是让原进程继续运行还是让其他已就绪的进程抢占原进程的CPU资源。
抢占实质上是将一个进程直接从内核态发送到就绪态,这为其他就绪态的进程返回用户态扫清了道路,也是大多数现代操作系统实现时分复用的方式。任何处于就绪态的进程都要接受调度程序的调用并转换到内核态。
在讨论了图1-3所示的4种过程状态和7种转换方式之后,接下来将简要介绍更复杂的进程状态转换图。