在理解Oracle的读写操作机制的基础上,解决各种常见的IO问题
扫描二维码
随时随地手机看文章
数据库的作用就是实现对数据的管理和查询。任何一个数据库系统,必然存在对数据的大量读或者写或者两中操作都大量存在。IO问题也往往是导致数据库性能问题的重要原因。在这篇文章中,主要帮助大家在理解Oracle的读写操作机制的基础上,灵活解决遇到的各种常见的IO问题。
1 Oracle中IO的产生
IO当然包括了读、写两部分,先介绍Oracle中写操作的产生。
1.1 写
介绍写操作之前,先简单的看下Oracle的物理结构:oracle的物理文件包括以下三种文件:控制文件(Control Files)、重做日志文件(Redo Log Files)、数据文件(datafiles)。而数据文件中,根据功能的不同,还可以分为系统数据文件、临时空间文件、回滚段文件和用户数据文件。另外,如果数据库的Archive Log模式被激活,还存在归档日志文件。Oracle的IO产生,就是对这些文件的数据读、写操作。下面再详细看下几种主要写操作的产生及其过程。
1.1.1 控制文件
控制文件中记录了整个数据库的物理结构信息,如数据库名字、数据文件及日志文件名字和位置、事件戳信息等等。任何数据库的结构变化(如果创建新的数据文件)都会引起Oracle修改控制文件。同时控制文件还记录系统和各个数据文件的SCN(System Change Number,关于SCN可以参见文章《Oracle SCN机制详解》)信息,以用于数据恢复,因此数据文件上的SCN变化后,Oracle也会相应修改控制文件上的SCN信息。
1.1.2 用户数据修改
由于内存的读写效率比磁盘的读写效率高万倍,因此,为了降低IO wait,oracle会将数据cache在内存(Buffer Cache,对Buffer Cache的详细介绍可以参见《Oracle内存全面分析》)中,对数据的读写尽量在内存中完成。当Buffer Cache中的数据缓存块被修改过了,它就被标记为“脏”数据。根据LRU(Least Recently Used)算法,如果一个数据块最近很少被使用,它就称为“冷”数据块。进程DBWn(系统中可以存在多个DBW进程,n为序号)负责将“冷”的“脏”数据写入数据文件中去。DBWn进程会在以下两种情况下将“脏”数据写入磁盘中去:
当服务进程扫描一定数量(阀值)的Buffer Cache后还没有找到干净、可重用的缓存块后,它会通知DBWn进程将“脏”数据写入文件中去,以释放出空闲缓存;当发生检查点(Checkpoint)时。1.1.3 Redo Log
在非直接写(Direct Write)的情况下,事务中的写操作都会产生Redo Log,作为数据块异常关闭时的恢复记录。同样,和写用户数据类似,Redo Log也不会被直接写入Redo Log文件,而是先写入Log Buffer中。
Log Buffer是一个可以循环重用的缓存区。LGWR进程负责将Log Buffer中的记录写入Redo Log File中去。一旦Log Buffer中的条目被写入了Redo Log文件中,就可以被重用了。
为了保证事务尽快获得Log Buffer,LGWR进程一般会尽快将Log Buffer中的数据写入Redo Log文件中去。在以下几种情况下,LGWR回将一个连续的Log Buffer写入Redo Log文件中去:
当一个事务提交(COMMIT)时;每3秒钟写一次Log Buffer;当Log Buffer到达1/3满时;当DBWn进程将“脏”数据写入磁盘时;1.1.4 Archive Log
当据库的Archive Log模式被激活后,所有Redo Log数据都会被写入Archive Log文件中以便日后进行恢复。当发生日志组切换时,ARCn(Archive进程,可以存在多个)进程就会Redo Log文件拷贝到指定存储目录中去,成为Archive Log文件。
1.1.5 临时表空间
当Oracle在执行一些SQL时,会需要一些临时空间来存储执行语句时产生的中间数据。这些临时空间由Oracle从指定的临时表空间中分配给进程。主要有三种情况会占用临时空间:临时表/索引操作、排序和临时LOB操作。
临时表/索引
在会话中,当第一次对临时表进行INSERT(包括CTAS)时,Oracle会从临时表空间中为临时表及其索引分配临时空间一存储数据。
排序
任何会使用到排序的操作,包括JOIN、创建(重建)INDEX、ORDER BY、聚合计算(GROUP BY)以及统计数据收集,都可能使用到临时表空间。
排序操作首先会选择在内存中的Sort Area进行(Sort In Memory),一旦Sort Area不足,则会使用临时空间进行排序操作(Sort In Disk)。看以下例子:
临时LOB对象
LOB对象包括BLOB、CLOB、NCLOB、和BFILE。在PLSQL程序块中,如果定义了LOB变量,则这些LOB变量就是临时LOB对象。临时LOB对象被创建在临时表空间上,直到LOB数据被释放,或者会话结束。
1.1.6 回滚段
我们知道,一个事务在未被提交前,其做的任何修改都是可以被回滚(Rollback)的。这些回滚数据就被放到回滚段(Rollback Segment)上。此外,一致性读(Read Consistency)、数据库恢复(Recover)都会用到回滚段。
任何数据块的修改都会被记录在回滚段中,甚至Redo Log也会产生回滚记录。当任何一个非只读(只有查询)的事务开始时,oracle会自动为其指定下一个可用的回滚段。事务中任何数据变化都被写入回滚段中。如果事务回滚,oracle根据回滚段中的回滚记录将buffer cache中的“脏”数据恢复,释放回滚段空间。当事务被提交,由于要保证一致性读,oracle并不会立即释放回滚段中的数据,而是会保留一段时间。
1.1.7 Direct-Path Insert
这里,我们还要介绍一种特殊的写操作——Direct-Path Insert(直接路径插入)。Direct-Path Insert通过直接在表中已存在的数据后面添加数据,直接将数据写入数据文件中,而忽略掉了Buffer Cache。
我们前面提到,为了能在意外时恢复数据,每一个数据修改都会被记录到Redo Log中。然而,由于Redo Log需要写入到物理文件中去,是一个比较消耗性能的操作。为了提高性能,我们在批量写入数据时就可以通过Direct-Path Insert的指定NOLOGING的方式来避免写Redo Log。
有多种方法可以指定Direct-Path Insert:CTAS(CREATE TABLE AS SELECT);SQL*Loader指定Direct参数;在语句中指定APPEND提示。
1.2 读 1.2.1 物理读
产生物理读主要有以下几种情况:
第一次读取
当数据块第一次被读取到,Oracle会先将其从磁盘上读入Buffer Cache中,并将他们放在LRU(Last Recently Used)链表的MRU(Most Recently Used)端。再次访问数据块时就可以直接从Buffer Cache中读取、修改了。看以下例子:
数据块被重新读入Buffer Cache
如果有新的数据需要被读入Buffer Cache中,而Buffer Cache又没有足够的空闲空间,Oracle就根据LRU算法将LRU链表中LRU端的数据置换出去。当这些数据被再次访问到时,需要重新从磁盘读入。
全表扫描
当发生全表扫描(Full Table Scan)时,用户进程读取表的数据块,并将他们放在LRU链表的LRU端(和上面不同,不是放在MRU端)。这样做的目的是为了使全表扫描的数据尽快被移出。因为全表扫描一般发生的频率较低,并且全表扫描的数据块大部分在以后都不会被经常使用到。
而如果你希望全表扫描的数据能被cache住,使之在扫描时放在MRU端,可以通过在创建或修改表(或簇)时,指定CACHE参数。
1.2.2 逻辑读
逻辑读指的就是从(或者视图从)Buffer Cache中读取数据块。按照访问数据块的模式不同,可以分为即时读(Current Read)和一致性读(Consistent Read)。注意:逻辑IO只有逻辑读,没有逻辑写。
即时读
即时读即读取数据块当前的最新数据。任何时候在Buffer Cache中都只有一份当前数据块。即时读通常发生在对数据进行修改、删除操作时。这时,进程会给数据加上行级锁,并且标识数据为“脏”数据。
一致性读
Oracle是一个多用户系统。当一个会话开始读取数据还未结束读取之前,可能会有其他会话修改它将要读取的数据。如果会话读取到修改后的数据,就会造成数据的不一致。一致性读就是为了保证数据的一致性。在Buffer Cache中的数据块上都会有最后一次修改数据块时的SCN。如果一个事务需要修改数据块中数据,会先在回滚段中保存一份修改前数据和SCN的数据块,然后再更新Buffer Cache中的数据块的数据及其SCN,并标识其为“脏”数据。当其他进程读取数据块时,会先比较数据块上的SCN和自己的SCN。如果数据块上的SCN小于等于进程本身的SCN,则直接读取数据块上的数据;如果数据块上的SCN大于进程本身的SCN,则会从回滚段中找出修改前的数据块读取数据。通常,普通查询都是一致性读。
1.2.3 查找数据
在一个查询操作中,大量的读操作都产生于数据的查找过程中。减少查找过程是我们优化IO性能问题的重要目标。
下面介绍几种主要的数据查找方式。
Full Table Scan
当查询条件无法命中任何索引、或者扫描索引的代价大于全表扫描代价的某一比例时(由参数optimizer_index_cost_adj设定),Oracle会采用全表扫描的方式查找数据。当发生全表扫描时,Oracle会自下向上一次读取一定数量(由参数db_file_multiblock_read_count设定)的数据块,一直读取到高水位标志(HWM,High Water Mark)下。Full Table Scan会引起db file scattered read事件。
INDEX UNIQUE SCAN
全表扫描查找数据的效率是非常低的。而索引能大幅提高查找效率。普通索引的数据结构是B-Tree,树的叶子节点中包含数据的ROWID,指向数据记录,同时还有指针指向前一个/后一个叶子节点。索引扫描每次读取一个数据块,索引扫描是“连续的”(Sequential)。当索引为UNIQUE索引时,每个叶子节点只会指向一条数据。如果Oracle能预知扫描结果只有0或1条记录时,会采用INDEX UNIQUE SCAN。当对Unique Index中的所有字段进行完全匹配时,会发生INDEX UNIQUE SCAN。
INDEX UNIQUE SCAN的查找过程如下:
从数的根节点数据块开始查找;查找根节点块中所有key值中大于或等于要查找的值的最小key值;如果key值大于查找值,则继续查找这个key值之前一个key值所指向的子节点数据块;如果key值等于查找值,则继续查找这个key值所指向的子节点数据块;如果没有key值大于或等于查找值,则继续查找最大key值所指向的子节点数据块;如果继续查找的节点数据块是数一个分支节点,则重复2~4步;如果查找的节点是叶子节点数据块,则在数据块中查找等于查找值的key值;如果找到相等的key值,则返回数据和ROWID;如果没找到相等的key值,则说明没有符合条件的数据,返回NULL。INDEX RANGE SCAN
如果通过索引查找数据时,Oracle认为会返回数据可能会大于1,会进行INDEX RANGE SCAN,例如Unique Index中字段不完全匹配查找时、非Unique Index查找时。
INDEX RANGE SCAN分为闭包(有前后查找边界)和非闭包(只有一边或者没有边界)。返回数据会依据索引增序排序,多个相同值则会按照ROWID的增序排序。
闭包条件下的INDEX RANGE SCAN的查找过程如下:
从数的根节点数据块开始查找;查找根节点块中所有key值中大于或等于要查找的起始值的最小key值;如果key值大于起始值,则继续查找这个key值之前一个key值所指向的子节点数据块;如果key值等于起始值,则继续查找这个key值所指向的子节点数据块;如果没有key值大于或等于起始值,则继续查找最大key值所指向的子节点数据块;如果继续查找的节点数据块是数一个分支节点,则重复2~4步;如果查找的节点是叶子节点数据块,则在数据块中大于或等于要查找的起始值的最小key值;如果Key值小于或等于结束值,则:如果所有Key字段都符合WHERE字句中的查找条件,则返回数据和ROWID;否则继续查找当前叶子节点所指向的右边的叶子节点。
INDEX UNIQUE SCAN和INDEX RANGE SCAN都会引起db file sequential read事件。
TABLE ACCESS BY INDEX ROWID
当发生索引扫描时,如果需要返回的字段都在索引上,则直接返回索引上的数据,而如果还需要返回非索引上的字段的值,Oracle则需要根据从索引上查找的ROWID到对应的数据块上取回数据,这时就是TABLE ACCESS BY INDEX ROWID。
INDEX FAST FULL SCAN & INDEX FULL SCAN
索引快速全扫描和全表扫描类似,一次读取db_file_multiblock_read_count个数据块来描所有索引的叶子节点。INDEX FAST FULL SCAN和其他索引扫描不同,它不会从树的根节点开始读取,而是直接扫描所有叶子节点;也不会一次读取一个数据块,而是一次读取db_file_multiblock_read_count个数据块。INDEX FAST FULL SCAN会引起db file scattered read事件。
在某些情况下,如db_file_multiblock_read_count值过小、强制使用索引扫描时,会发生INDEX FULL SCAN。INDEX FULL SCAN和INDEX FAST FULL SCAN不同,它是一种索引扫描,按照B-Tree的查找法从树的根节点开始扫描,遍历整棵树,并且一次读取一个数据块。它会引起db file sequential read事件。
2 IO系统的设计和配置
要控制好数据库的整体IO性能,在规划数据库架构时就需要做好IO系统的设计和配置。例如,将对IO要求不同的文件放置在不同的存储设备上;规划数据文件的分布、均衡IO负担等。
2.1 OS和存储相关
IO性能是直接和操作系统已经硬件性能相关的。如果能利用操作系统的一些高级IO特性,或者采用更高速的磁盘设备,能大大提高IO性能。下面介绍一些OS的IO配置、不同的磁盘硬件设备以及存储技术。
2.1.1 文件系统(File System)和裸设备(Raw Device)
我们知道,内存的读写效率比磁盘高近万倍,因此Oracle在内存中开辟了一片区域,称为Buffer Cache,使数据的读写尽量在Buffer Cache中完成。同样,在文件系统中,操作系统为了提高读写效率,也会为文件系统开辟一块Buffer Cache用于读写数据的缓存。这样,Oracle的数据会被缓存2次。为了避免OS的这次缓存,我们可以采用裸设备做为数据文件的存储设备。裸设备,也称为裸分区(Raw Partition),它是一个没有被加载(Mount)到操作系统的文件系统上、也没有加载到Oracle集群文件系统(OCFS Oracle Cluster File System)的磁盘分区,它通过字符设备驱动来访问。裸设备的文件读写不由操作系统控制,而是由应用程序(如Oracle RDBMS)直接控制。
2.1.2 IO方式
OS和文件系统对IO的控制存在多种方式,不同的IO方式下对于数据库的IO性能影响也不同。
2.1.2.1 Direct IO & Concurrent IO
除了裸设备,某些文件系统可以支持Direct IO,以避开读写缓冲。如果要使用Direct IO,需要指定Oracle参数“filesystemio_options”来设置支持Direct IO。但是要注意,不同OS中的不同文件系统对Direct IO的支持也不同:
Windows 在windows中不需要做特别设置可以直接使用Direct IO;AIX 在AIX中,JFS文件系统需要通过设置“filesystemio_options”为“SETALL”或者“DIRECTIO”来支持Direct IO;LINUX Linux在内核版本为2.4.9以上才支持Direct IO。NFS或者OCFS文件系统支持Direct IO。需要设置“filesystemio_options”为“SETALL”或者“DIRECTIO”;Solaris Solaris需要在操作系统中设置“forcedirectio”选项,并设置“filesystemio_options”为“SETALL”或者“DIRECTIO”。
参数“filesystemio_options”支持4种值:
ASYNCH: 使Oracle支持文件的异步(Asynchronous)IO;DIRECTIO:使Oracle支持文件的Direct IO;SETALL:使Oracle同时支持文件的Asynchronous IO和Direct IO;NONE:使Oracle关闭对Asynchronous IO和Direct IO的支持。
在AIX的JFS2文件系统上,如果“filesystemio_options”为“SETALL”,则会支持Concurrent IO。CIO比DIO的性能更高,因为JFS2的CIO支持多个进程同时对一个文件进行读写。
2.1.2.2 Asynchronous IO & Synchronous IO
通常,用的比较多的IO模型是同步IO(Synchronous IO)。在这种模式下,当请求发出之后,应用程序就会阻塞,直到请求满足为止。这种模式最大好处就是调用应用程序在等待 I/O 请求完成时不需要使用CPU资源。但是,对于一些强调高响应速度的程序(如DB)来说,希望这种等待时间越短越好,我们这时就可以考虑采用异步IO(Asynchronous IO)模式。异步IO模式下,进程发出IO请求后无需等待IO完成,可以去处理其它事情;IO请求被放入一个队列中,一旦IO完成,系统会发出信号通知进程。
异步IO可以使需要大量写的Oracle进程(如DBWn进程)将IO请求队列化,以充分利用硬件的IO带宽,从而使它们能最大程度实现并行处理。异步IO还可以使那些需要进行大量计算的操作(如排序)在它们发出IO请求前预先从磁盘取出数据,以使IO和计算并行处理。
确认操作系统已经设置支持AIO后,还需要设置Oracle初始化参数"DISK_ASYNCH_IO"为“true”以支持异步IO。
2.1.3 负载均衡及条带化(Striping)
当多个进程同时访问一个磁盘时,会出现磁盘冲突。大多数磁盘系统都对访问次数(每秒的IO操作)和数据传输率(每秒传输的数据量)有限制。当达到这些限制时,后面要访问磁盘的进程就需要等待,这时就是所谓的磁盘冲突。
避免磁盘冲突是优化IO性能的一个目标,这就需要将一个热点磁盘上的IO访问负载分担到其他可用磁盘上,也就是IO负载均衡。在一些成熟的磁盘负载均衡技术出现之前,DBA需要了解/预测各系统的IO负载量,通过手工配置每个数据到不同存放位置以分担IO负载来达到负载均衡的目的。
条带化技术就是将数据分成很多小部分并把他们分别存储到不同磁盘上的不同文件中去。这就能使多个进程同时访问数据的多个不同部分而不会造成磁盘冲突。很多操作系统、磁盘设备供应商、各种第三方软件都能做到条带化。通过条带化,DBA可以很轻松的做到IO负载均衡而无需去手工配置。
2.1.4 RAID
RAID的全称是独立磁盘冗余阵列(Redundant Array of Independent Disks)。它通过将多个相对比较便宜的磁盘组合起来,并相互连接,同时都连到一个或多个计算机上,以组成一个磁盘组,使其性能和容量达到或超过一个价格更昂贵的大型磁盘。RAID分为6级。
RAID-0
RAID-0只提供纯粹的条带化(Stripping)。条带可以使一个大文件被多个磁盘控制器同时访问,因此支持对数据的并发访问。RAID-0不提供数据冗余和奇偶保护,它只关注性能。如果RAID-0中任何一个磁盘出错,整个数据库都会崩溃。
RAID-1
RAID-1提供磁盘镜像(Disk Mirror)。在RAID-1中,所有数据都会被写入两个独立的磁盘中,以实现对数据的冗余保护。两块磁盘的数据是同时写入的,以保证其速度不会低于写入单独磁盘的速度。RAID-1实现了数据的完全冗余,它提供了所有RAID级别中最安全可靠的数据保护。在这种模式下,写的性能下降了,但读的性能被提升了。此外,RAID-1也是最占用磁盘空间的模式
RAID 0+1
RAID-0能提供更好的性能,RAID-1提供最佳的数据保护。如果把两者结合在一起就能同时提供高性能和数据保护,但是也会同时提高磁盘阵列造价。
RAID-3
在RAID-3中,会有一块专门的磁盘驱动被用作存储错误修正或者奇偶校验数据。而其他的磁盘驱动则被条带化。RAID-3的并行处理能力比较低,它适合于主要是读操作的系统(如决策分析系统 DSS,但是DSS会存在大量复杂查询,需要做JOIN,同样也会存在一些临时的写操作),不适合存在大量写操作的系统(OLTP)。
RAID-5
RAID-5不做全磁盘镜像,但它会对每一个写操作做奇偶校验计算并写入奇偶校验数据。奇偶校验磁盘避免了像RAID-1那样完全重复写数据。当一个磁盘失效,校验数据被用来重建数据,从而保证系统不会崩溃。为避免磁盘瓶颈,奇偶校验和数据都会被分布到阵列中的各个磁盘。尽管读的效率提高了,但是RAID-5需要为每个写操作做奇偶校验,因此它的写的效率很差。
RAID-S
RAID-S是EMC公司的RAID-5的实施方案,它和纯粹的RAID-5存在以下区别:
(1) 它条带化奇偶校验,但不条带化数据;
(2) 它与一个带有写缓存的异步硬件环境合并。
这个缓存主要是一种延迟写的机制,因此它能让系统在相对不忙的时候计算和写奇偶校验信息。
RAID-7
RAID-7也同样引入了缓存机制,这个缓存是被一个内嵌式操作系统控制。但是,RAID-7中数据是被条带化的,而奇偶校验不被条带化。奇偶校验信息被存放着一个或者多个专门的磁盘上。
2.1.5 SAN
SAN(Storage Area Network,存储区域网)是一个高速的子网,这个子网中的设备可以从你的主网卸载流量。通常SAN由RAID阵列连接光纤通道(Fibre Channel)组成,SAN和服务器和客户机的数据通信通过SCSI命令而非TCP/IP,数据处理是“块级”(block level)。
SAN通过特定的互连方式连接的若干台存储服务器组成一个单独的数据网络,提供企业级的数据存储服务。 SAN是一种特殊的高速网络,连接网络服务器和诸如大磁盘阵列或备份磁带库的存储设备,SAN置于LAN之下,而不涉及LAN。利用SAN,不仅可以提供大容量的存储数据,而且地域上可以分散,并缓解了大量数据传输对于局域网的影响。SAN的结构允许任何服务器连接到任何存储阵列,不管数据置放在哪里,服务器都可直接存取所需的数据。
2.1.6 NAS
NAS是Network Attached Storage(网络附加存储)的简称。在NAS存储结构中,存储系统不再通过I/O总线附属于某个服务器或客户机,而直接通过网络接口与网络直接相连,由用户通过网络访问。它是连接到一个计算机网络的文件层的数据存储,它可以为不同网络客户端提供数据存储服务。NAS的硬件与传统的专用文件服务器相似。它们的不同点在于软件端。NAS中的操作系统和其他软件只提供数据存储、数据访问功能,以及对这些功能的管理。与传统以服务器为中心的存储系统相比,数据不再通过服务器内存转发,直接在客户机和存储设备间传送,服务器仅起控制管理的作用。
2.2 IO配置
在借助各种成熟的存储技术的基础上,合理配置系统的IO分布及系统IO配置能大量减少系统在生产运行中出现IO性能及相关问题的几率。当然,这些配置是我们在布置数据库系统时初始建议,对于复杂的系统来说,很多配置(如一些存储相关的参数)是需要根据系统的运行状况进行调优的。
在数据库系统中,如果某个文件或者某块磁盘上存在远远高于其他文件或磁盘的大量IO访问,我们就称这个文件或磁盘为热点文件/磁盘。我们在做IO规划时的一个重要目标就是要消除系统中热点文件/磁盘的存在,使整个系统的IO负载相对平衡。
2.2.1 条带化的设置
由于现在的存储技术成熟、成本降低,大多数系统都采用条带化来实现系统的IO负载分担。如果操作系统有LVM(Logical Volume Manager逻辑卷管理器)软件或者硬件条带设备,我们就可以利用这些攻击来分布IO负载。当使用LVM或者硬件条带时,决定因素是条带深度(stripe depth)和条带宽度(stripe width):
条带深度指的是条带的大小,也叫条带单元;条带宽度指的是条带深度的产量或者一个条带集中的驱动数;
需要根据系统的IO要求来合理的选择这些数据。对于Oracle数据库系统来数,比较合理的条带深度是从256K到1M。下面分析影响条带深度和条带宽度的影响因素。
2.2.1.1 条带深度
为了提高IO效率,我们要尽量使一次逻辑IO请求由一块磁盘的一次物理IO请求。因而影响条带的一个重要因素就是一次逻辑IO请求的大小。
此外,系统中IO的并发度不同我们对条带的配置要求也不同。例如,在高并发度且IO请求的大小都比较小的情况下,我们希望一块磁盘能同时响应多个IO操作;而在那些存在大IO请求的低并发度系统中,我们可能就需要多块磁盘同时响应一个IO请求。无论是一个磁盘还是多个磁盘响应IO请求,我们的一个原则是让一次逻辑IO能被一次处理完成。
下面先看下影响IO大小的操作系统和Oracle的相关参数:
db_block_size:Oracle中的数据块大小,也决定了Oracle一次单个IO请求中的数据块的大小;db_file_multiblock_read_count:在多数据块读时,一次读取数据块的数量,它和参数db_block_size一起决定了一次多数据块读的大小,它们的乘积不能大于操作系统的最大IO大小;操作系统的数据块大小:这个参数决定拉Redo Log和Archive Log操作时的数据块大小,对于大多数Unix系统来说,该值为512K;最大操作系统IO大小:决定了一次单个的IO操作的IO大小的上限,对于大多数Unix系统来说,由参数max_io_size设置;sort_area_size:内存中sort area的大小,也决定了并发排序操作时的IO大小;hash_area_size:内存中hash area的大小,也决定了哈希操作的IO大小。
其中,前面两个是最关键的两个参数。
在OLTP系统中,会存在大量小的并发的IO请求。这时就需要考虑选择比较大的条带深度。使条带深度大于IO大小就称为粗粒度条带(Coarse Grain Striping)。在高并行度系统中,条带深度为(n * db_block_size),其中n为大于1的整数。
通过粗粒度条带能实现最大的IO吞吐量(一次物理IO可以同时响应多个并发的逻辑IO)。大的条带深度能够使像全表扫描那样的多数据块读操作由一个磁盘驱动来响应,并提高多数据块读操作的性能。
在低并发度的DSS系统中,由于IO请求比较序列化,为了避免出现热点磁盘,我们需要避免逻辑IO之由一块磁盘处理。这是,粗粒度条带就不适合了。我们选择小的条带深度,使一个逻辑IO分布到多个磁盘上,从而实现IO的负载均衡。这就叫细粒度条带。条带深度的大小为(n * db_block_size),其中n为小于多数据块读参数(db_file_multiblock_read_count)大小的整数。
另外,IO过程中,你无法保证Oracle数据块的边界能和条带单元的大小对齐。如果条带深度大小和Oracle数据块大小完全相同,而它们的边界没有对齐的话,那么就会存在大量一个单独的IO请求被两块磁盘来完成。
在OLTP系统中,为了避免一个逻辑IO请求被多个物理IO操作完成,条带宽度就需要设置为两倍或者两倍以上于Oracle数据块大小。例如,如果条带深度是IO大小的N倍,对于大量并发IO请求,我们可以保证最少有(N-1)/ N的请求是由一块磁盘来完成。
2.2.1.2 条带宽度
正如我们前面所述,无论是一个还是多个磁盘响应一个逻辑IO,我们都要求IO能被一次处理。因而在确定了条带深度的基础上,我们需要保证条带宽度 >= IO请求的大小 / 条带深度。
此外,考虑到以后系统容量的扩充,我们也需要规划好条带宽度。
如今大多数LVM都支持在线动态增加磁盘。也就是在磁盘容量不足时,我们可以随时将新磁盘加入到一个已经使用的逻辑卷中。这样的话,我们在设置逻辑卷时就可以简单地将所有磁盘都归入到一个卷中去。
但是,有些LVM可能还不支持动态增加磁盘。这时我们就需要考虑以后的容量扩充对IO均衡的影响了。因为你新增加的磁盘无法加入原有卷,而需要组成一个新的卷。但一般扩充的容量和原有容量比较相对比较小,如果原有卷的条带宽度比较大的话,新增加的卷的条带宽度无法达到其大小,这样就会使新、旧卷之间出现IO失衡。
例如,一个系统的初始配置是一个包含64块磁盘、每块磁盘大小为16G的单一逻辑卷。磁盘总的大小是1T。随着数据库的数据增长,需要增加80G的空间。我们把新增加的5个16G磁盘再组成一个逻辑卷。这样就会导致两个卷上的IO失衡。为了避免这种情况。我们可以将原有磁盘配置成每个条带宽度为8个磁盘的8个逻辑卷,这样在新增加磁盘时可以也增加为8个磁盘的新卷。但必须要保证8个磁盘的条带宽度能够支持系统的每秒IO吞吐量。
如果你的条带宽度设置得比较小,就需要估算出你的各个数据库文件的IO负载,并根据负载量不同将他们分别部署到不同卷上一分担IO负载。
2.2.2 人工条带
如果系统不支持LVM或者硬件条带,IO负载就必须由DBA根据数据库文件的IO负载不同手工将他们分散到各个磁盘上去以保证整个系统的IO负载均衡。
有许多DBA会将哪些使用频率非常高的表和它的索引分开存储。但实际上这种做法并不正确。在一个事务中,索引会先被读取到然后再读取表,它们的IO操作是有前后顺序的,因此索引和表存储在同一个磁盘上是没有冲突的。仅仅因为一个数据文件即包含了索引又包含了数据表而将它分割是不可取的。我们需要根据文件上的IO负载是否已经影响到了数据库的性能来决定是否将数据文件分割。
为了正确分布文件,我们首先必须先了解各个数据库文件的IO负载需求以及IO系统的处理能力。鉴定出每个文件的IO吞吐量。找出哪些文件的IO吞吐率最高而哪些IO量很少,将它们分散分布到所有磁盘上去以平衡IO吞吐率。
如果你不了解或者无法预计文件的IO负载,就只能先估计他们的IO负载来规划文件分布,在系统运行过程中再做调整。
2.2.3 文件分离
无论是采用操作系统条带化还是手工IO分布方式,如果IO系统或者IO规划布置无法满足IO吞吐率的要求,我们就需要考虑将高IO吞吐率的文件和其他文件分离。我们可以在存储规划阶段或者系统运行阶段找出那样的文件。
除了IO吞吐率,在决定是否分割文件时,我们还需要考虑可恢复性以及数据容量扩张问题。
但是在分割文件之前,一定要确认存在IO瓶颈,然后再根据产生IO瓶颈的数据定位到存在高IO吞吐率的文件(热点文件)。
2.2.3.1 表、索引和临时表空间
如果具有高IO吞吐率的数据文件属于包含表和索引的表空间,我们就需要找出这些文件的IO是否可以通过SQL语句调优或者优化应用程序来降低。
如果具有高IO吞吐率的数据文件属于临时表空间,那我们就需要检查是否可以通过避免或调优SQL语句的排序操作来降低IO。
经过应用调优后,如果IO分布仍然无法满足IO吞吐的要求,我们就需要考虑分离高IO吞吐率的数据文件了。
2.2.3.2 Redo Log文件
如果具有高IO吞吐率的文件是Redo Log文件,则需要考虑将Redo Log文件与其他文件分离,可以通过以下配置来实现:
将所有Redo Log文件放到没有任何其他文件的磁盘上去。考虑到可恢复性,需要将一个Redo Log组中的成员文件分别放到不同的物理磁盘上去;将每个Redo Log组放到一个没有任何其他文件的单独磁盘上;通过操作系统条带化工具,将Redo Log文件条带化分布到多个磁盘上去;不要将Redo Log文件放到RAID 5上去
Redo Log文件是由LGWR进程序列化的写入的。如果在同一个磁盘上不存在并发的其他IO操作,写入效率就更高。我们需要确认已经没有其他优化调整空间再考虑分割Redo Log文件。如果系统支持AIO但还没有激活该特性,可以考虑激活AIO看是否能解决Redo Log的IO性能瓶颈。
2.2.3.3 归档Redo Log
如果归档变慢,我们也许可以通过使LGWR的写操作与Archive进程的读操作分离来避免LGWR进程鱼Archive进程直接的IO冲突。我们可以同交替成组存放Redo Log文件来实现。
例如,我们有四组Redo Log,每组包含两个Log文件:(A1,A2)、(B1,B2)、(C1,C2)、(D1,D2)。我们就可以以下面这种存放方式将它们分布存储到四个磁盘上去来实现磁盘分离访问:(A1,C1)、(A2、C2)、(B1,D1)、(B2,D2)。
当LGWR进程做日志切换时,如从A组切换到B组,LGWR开始向B组写Redo Log(第三、四块磁盘),而Archive进程则从B组读取数据(第一、二块磁盘)写入归档文件中去,他们分别访问的是不同磁盘,因而避免了IO冲突。
2.3 三种简单的配置方法
这里给出三种简单的操作系统IO配置的例子,包括如何简单地计算来决定磁盘的拓扑结构、条带深度等等。
2.3.1 将所有文件条带化到所有磁盘上去
IO配置最简单的方法就是建立一个大的逻辑卷,将所有磁盘都条带化到这个卷中去。考虑到可恢复性,这个卷需要被镜像(RAID 1)。每个磁盘的条带深度必须大于频繁执行的IO操作的最大IO大小。这种配置对大多数情况都能提供足够的性能支持。
2.3.2 将归档日志放到另外的磁盘上去
在归档模式下,如果归档文件也和其他文件放在同一个条带化的卷中,那么当归档进程对Redo Log进行归档时,会大大增加磁盘的IO负载。将归档日志转移到其他磁盘上有如下好处:
归档进程效率提高;当归档时,其他进程受到归档进程的影响
归档日志的磁盘数由归档日志产生的频率以及归档存储容量决定。
2.3.3 将Redo Log文件放到另外的磁盘上去
在更新非常频繁的OLTP系统中,Redo Log的写操作非常频繁。将Redo Log文件转移到其他磁盘上可以有如下好处:
写Redo Log的读写效率最高,因而事务的执行也能获得最佳性能;写Redo Log操作不会影响任何其他IO操作
Redo Log的磁盘数量有Redo Log的大小决定。由于现在的磁盘容量都非常大,通常配置两个磁盘(如果做镜像则需要四块)就足够了。并且,根据我们前面的分析,将Redo Log文件交互的存放到两块磁盘上去能避免LGWR进程的写操作与ARCH进程的读操作之间的IO冲突。
3 Oracle中的IO问题及其解决思路
对于负载偏重点不同,我们可以简单的将数据库系统分为CPU负载系统(CPU Bound System)和IO负载系统(IO Bound System)。顾名思义,CPU负载系统的资源瓶颈在于CPU,而IO负载系统的瓶颈在于磁盘IO。
我们可以通过操作系统的一些命令来确认一个系统是否是存在IO负载。在UNIX下,可以使用"iostat"或者"sar -d"来看系统的IO情况;在windows下,可以通过系统的性能监视器查看,但由于性能监控器中看到的IO是静态的IO总量信息,并不直观,因此也可以用本站的TopShow工具来查看实时的IO信息。
在UNIX系统下,发现CPU IDLE很低并不一定代表这是一个CPU负载系统。一个IO负载系统在表面上看CPU的IDLE值也可能很低:
oracle@db01:/export/home/oracle> sar -u 1 10
HP-UX hkhpdv45 B.11.23 U ia64 10/24/07
09:43:05 %usr %sys %wio %idle
09:43:06 43 25 30 1
09:43:07 44 36 19 1
09:43:08 23 27 44 6
09:43:09 12 37 50 1
09:43:10 1036 51 3
09:43:11 15 34 42 9
09:43:12 18 36 44 3
09:43:13 17 35 46 2
09:43:14 12 32 52 4
09:43:15 12 31 56 1
Average 21 33 43 3
我们可以注意到,实际上WIO是引起CPU IDLE过低的主要原因。WIO是当一个进程需要运行或已经运行后,因为需要等待IO事件而被阻塞了。事实上CPU是处于IDLE状态(在某些系统中,已经将WIO取消并归为IDLE),真正的原因是系统中存在IO瓶颈。
通过iostat或者sar -d我们可以找出存在IO瓶颈的磁盘设备,如果该磁盘设备是用于Oracle 数据库存储文件的,我们可以判断出是数据库存在IO问题。在windows下,可以通过TopShow来找出哪个进程正在进行大量IO传输,如果是Oracle进程,也可以判断为是数据库存在IO问题。
确认系统存在IO问题后,我们就需要定位到底是什么引起的IO问题,该采取什么措施来解决问题。根据我们前面的介绍,Oracle中存在各种IO,要定位IO,最好的工具是statspack(在10g以后,可以用AWR)。通过statspack report的Top 5 Events,我们可以看到对系统系能影响最大的5个等待event,而不同的IO问题会对应不同Event,所以,我们可以根据这些event采取不同的措施来解决IO问题。下面是一个典型的IO负载系统的Top 5 Event:
Top 5 Timed Events
~~~~~~~~~~~~~~~~~~ % Total
Event Waits Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
db file sequential read 70,575,969 344,200 53.34
db file scattered read 11,240,748 163,242 25.30
log file sync 657,241 36,363 5.64
CPU time 35,290 5.47
log file parallel write 833,799 20,767 3.22
可以看到,前两个时间“db file sequential read”和“db file scattered read”分别占了总等待时间的53.34%和25.30%,而我们前面提到这两个事件分别是由索引扫面和全表扫面(或快速索引扫面)引起的,因此,能解决索引扫面问题和全表扫面问题就能解决这个系统的IO瓶颈。
IO问题到底对CPU有多大影响呢?我们用以上例子中的数据分析一下。从等待时间统计数据中,我们看到的是时间在总等待时间中所占的比例。而系统的“总响应时间 ”= “等待时间 ”+ “CPU工作时间”(注意,上面Top 5事件中的“CPU Time”不是指CPU的工作时间,而是指CPU的等待时间)。“CPU工作时间”的数据我们可以在“Instance Activities Stats for DB”这一分类统计数据中找到:
Statistic Total per Second per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session 17,136,868 396.7 15.5
先计算出“总等待时间” = 344,200 * 100% / 53.34% = 645,294s
“总响应时间” = “总等待时间” + “CPU工作时间” = 645,294 + 17,136,868 = 17,782,162s
我们可以算出“CPU工作时间”、“db file sequential read”和“db file scattered read”分别在“总响应时间中所占的比例为:
CPU工作时间 = 17,136,868 / 17,782,162 = 96.4%
“db file sequential read” = 344,200 / 17,782,162 = 1.9%
“db file scattered read” = 163,242 / 17,782,162 = 0.9%
可见,IO事件所引起的等待时间在总响应时间所占比例并不大。因此,我们在做系统优化之前先分析系统是CPU负载系统还是IO负载系统对于我们的优化方向和最终的优化效果起很大的作用。
以下事件是可能由IO问题引起的等待事件,在IO负载系统中,我们要特别关注这些事件:
与数据文件相关的IO事件
'db file sequential read'
'db file scattered read'
'db file parallel read'
'direct path read'
'direct path write'
'direct path read (lob)'
'direct path write (lob)'
与控制文件相关的IO事件
'control file parallel write'
'control file sequential read'
'control file single write'
与Redo日志相关的IO事件
'log file parallel write'
'log file sync'
'log file sequential read'
'log file single write'
'switch logfile command'
'log file switch completion'
'log file switch (clearing log file)'
'log file switch (checkpoint incomplete)'
'log switch/archive'
'log file switch (archiving needed)'
与Buffer Cache相关的IO事件
'db file parallel write'
'db file single write'
'write complete waits'
'free buffer waits'
下面我们就分别介绍如何解决IO问题。
3.1 IO调优的思路及常用手段
通过对statspack或者awr报告的分析,我们可以得知是那些IO相关事件引起的IO问题。针对不同的事件,可以采取不同的分析、处理方法。而有一些通用的方法并不是针对特定的事件的。我们这里先介绍一下这些方法。
3.1.1 通过SQL调优来减少IO请求
一个没有任何用户SQL的数据库几乎不产生任何IO。基本上数据库所有的IO都是直接或间接由用户提交的SQL所导致的。这意味着我们可以通过控制单个SQL产生的IO来降低数据库总的IO请求。而通过SQL调优来降低SQL查询计划中的IO操作次数则是降低SQL产生IO的最好方法。数据库的性能问题通常是由少数几个SQL语句所导致的,它们产生了大量IO导致了整个数据库的性能下降。优化几条问题语句往往就能解决整个数据库的IO性能问题。
从Oracle 10g开始,ADDM能够自动检测出问题语句,同时,再通过查询优化建议器能够自动优化语句并降低它们对IO的消耗。关于ADDM和查询优化建议器可以参考文章《Oracle 10G 新特性——ADDM和查询优化建议器》。
3.1.2 通过调整实例参数来减少IO请求
在这种方法中,主要有两种途径来实现对IO的优化。
使用内存缓存来减少IO
通过一些内存缓存,如Buffer Cache、Log Buffer、Sort Area,可以降低数据库对IO的请求。
当Buffer Cache被增大到一定大小时,绝大多数结果可以直接从缓存中获取到,而无需从磁盘上读取了。而在进行排序操作时,如果Sort Area足够大,排序过程中产生的临时数据可以直接放在内存中,而无需占用临时表空间了。
调整multiblock IO(多数据块IO)的大小
控制Multiblock IO的参数叫DB_FILE_MULTIBLOCK_READ_COUNT,它控制在多数据块读时一次读入数据块的次数。适当增加这个参数大小,能够提高多数据块操作(如全表扫描)的IO效率。例如,读取100M数据,如果每次读取1M一共读取100次的效率就比每次读取100K一共读取1000次更快。但是这个数字达到一定大小后,再增加就作用不大了:每次10M一共读100次来读取1G的数据的效率和单独一次读取1G数据的效率是没有多大区别的。这是因为IO效率受到2个因素的影响:IO建立时间和IO传输时间。
IO建立时间对于不同IO大小来说都是相同的,它决定了对小IO的总的IO时间,增大Multiblock IO大小可以减少IO建立时间;
IO传输时间与IO大小是成正比的,在小IO时,IO传输时间一般比IO建立时间少,但对于大IO操作来说,IO传输时间决定了总的IO时间。因此Multiblock IO大小增大到一定大小时,它对总的IO时间影响就不大了。
3.1.3 在操作系统层面优化IO
如我们前面所介绍的,利用一些操作系统提供的提升IO性能的特性,如文件系统的异步IO、Direct IO等来优化数据库系统的IO性能。另外一种方法就是增加每次传输的最大IO大小的限制(大多数Unix系统中,由参数max_io_size控制)。
3.1.4 通过Oracle ASM实现对IO的负载均衡
ASM(Automatic Storage Manager自动存储管理)是从Oracle 10g开始引入的。它是一个建立在数据库内核中的文件系统和卷管理器。它能自动将IO负载均衡到所有可用的磁盘启动器上去,一避免“热区”。ASM能防止碎片,因此无需重建数据来回收空间。数据被均衡分布到所有硬盘上。
3.1.5 通过条带化、RAID、SAN或者NAS实现对IO的负载均衡
这个方法通过一些成熟的存储技术,如条带化、RAID、SAN和NAS,来将数据库IO分布到多个可用的物理磁盘实现负载均衡,以避免在还存在空闲可用磁盘时出现的磁盘争用和IO瓶颈问题。
关于这几种存储技术,我们文章的前面部分都有做介绍。
3.1.6 通过手工布置数据库文件到不同的文件系统、控制器和物理设备上来重新分布数据库IO
当数据库系统中缺乏以上各种存储技术手段时,我们可以考虑使用这种方式。这样做的目的是使数据库的IO得到均匀分布,从而避免在还有空闲磁盘时出现磁盘争用和IO瓶颈问题。当然这种手工分布IO方法是无法达到以上的自动分布IO的效果的。
3.1.7 其他手段
系统中总会存在一些IO是无法消除或降低的。如果采用以上手段还不能满足IO性能要求的话,可以考虑这两种方法:
将老数据移除你的生产数据库(Housekeep)采用更多、更快的硬件3.2 数据文件相关的IO事件
数据库系统中的大多数的IO请求都是针对数据文件的。因此大多数情况下,与数据文件相关的IO事件是引起系统IO性能的主要原因。