Oracle Memory Management and HugePage

概述

大家好,我是魏兴华,你们可以叫我肉丝,我的英文名是Rose😊。在这篇文章中,我给大家介绍一些Oracle内存管理和大页的知识。Oracle发展这么多年,提供了多种的内存管理方式,从最早SGA、PGA手工管理,到9I版本出现的PGA的自动管理,到10G版本出现的SGA自动管理(ASMM),再到11G版本出现的memory自动管理(AMM),Oracle基本是在朝着智能化、傻瓜化、自动化的方向稳步前进着,对于初学Oracle的DBA来说,看到这些不同的内存管理方式一定心里有着不同程度的疑惑,例如:

Oracle有这么多内存分配的管理方式,我该使用哪一种?是使用11G版本推出的AMM管理方式,还是使用10G版本出现的ASMM管理方式?或者干脆使用最旧的手工方式管理内存?

我该为我的实例SGA,PGA分别设置多大呢?

我该为buffer cache,shared pool分配多大的内存空间?

什么情况下我该使用大页?为什么大页这个词最近几年这么容易听到?

有没有一些简单粗暴的算法来搞定这一切?

写这篇文章的初衷也来源于上面这些非常正常的疑问,如果这篇文章能给有这些疑惑的DBA朋友一些指导和帮助,那肉丝我也就真的功莫大焉了。

Memory构成

在一个运行着Oracle数据库的专用服务器上,内存基本上被以下内容所占用:

Kernel

OS Page Table

文件系统Cache

SGA

PGA

Oracle进程

其他进程(RMAN,非Oracle的进程等等)

严格来说OS Page Table也算是Kernel Memory部分的内容,由于本文后面会重点讲大页的内容,因此把OS Page Table这部分在图中独立了出来,以引起读者注意。必须要强调,Oracle不应该使用掉主机上的所有内存,过量的内存分配会诱发操作系统SWAP的产生,导致Oracle性能严重降低,在RAC环境下,内存不足还非常容易导致RAC节点的驱逐。对于Oracle两大块内存:SGA和PGA,DBA一定要仔细根据系统特点、业务使用特点做好规划和设计,以防出现OS内存不够用的情况。

内存自动化发展历程

我们首先了解一下Oracle内存发展的历程,基本上Oracle的内存管理在版本的演进过程中沿着越来越智能化、自动化、傻瓜化的方向前进。接触过MYSQL等数据库的DBA朋友应该比较清楚,这些数据库基本上都还需要DBA去决定每个内存组件的大小,而Oracle已经从9I版本开始就迈向智能化、自动化的过程了。下图是Oracle内存管理的一个演进图:

PGA 自动管理

Oracle是多进程的架构,这点区别于MYSQL,MYSQL是单进程多线程的架构,Oracle会为每一个用户连接创建一个独立的操作系统进程来为用户提供服务,这个进程叫做服务器进程或者影子进程,它像是用户的一个代理,来操作数据文件或者SGA内存,由于服务器进程的代码是Oracle公司开发的,所以Oracle公司完全相信这些进程或代码是安全的,因此这些进程可以直接操作数据文件和SGA内存,这些进程接受用户进程发送的指令,并完成相关的操作,并根据需要来给用户进程返回结果。

由于服务器进程是操作系统上的一个进程,因此它本身需要占用一些操作系统内存,除此之外,进程在对数据进行读取、排序、hash过程中,也会占用一定量的内存,在Oracle 9I版本之前,对于服务器进程的内存管理是由一些参数去控制的,以下参数代表每一个服务器进程可以使用的不同区域的内存大小(都是进程的私有内存区域):

SORT_AREA_SIZE

HASH_AREA_SIZE

BITMAP_MERGE_AREA_SIZE

CREATE_BITMAP_AREA_SIZE

例如,SORT_AREA_SIZE控制了每一个进程可用的排序区大小,HASH_AREA_SIZE参数控制了每一个进程可用的hash区大小,这些参数都有默认值,但是默认值是否合适,需要打上一个大大的问号,因为不同的任务对于PGA内存的不同区域有不同的要求,例如,如果是做排序操作,就对排序区内存要求较大,而对hash区没任何的要求。当然如果默认值不合适,DBA可以手工调整这些区域的大小。

Oracle 9I版本出现了PGA的自动管理,不再需要像9I之前版本需要设置一系列参数来控制PGA的使用,只需要设置PGA_AGGREGATE_TARGET为一个值,就可以控制所有的服务器进程的PGA使用量,至于每个服务器进程使用了多少排序区,hash区,都交给Oracle去控制。

一般情况下对于PGA的大量使用有如下几种操作:

hash 对于hash join操作,hash桶所占用的内存就在进程的私有PGA内存中,而不是在共享内存SGA中,如果使用PGA手工管理的话,可以通过HASH_AREA_SIZE参数来动态调整会话进行hash操作能够使用的内存量。

sort 对于排序操作,例如查询语句里的order by、创建索引的排序操作等占用的内存也在PGA中,如果使用PGA手工管理,可以通过SORT_AREA_SIZE参数动态调整会话排序操作可以使用的内存量。

parallel 并行操作简直可以说是PGA内存的杀手,每一个并行进程都能使用到最多2G的PGA内存,当然Oracle会确保所有的并行slave使用的PGA内存不能超过PGA_AGGREGATE_TARGET的一半。

现在Oracle的版本已经出到了12C,PGA的自动管理已经发展了很多个年头,如果是个人,也应该是一个非常成熟的小伙子了,甚至是位大叔了😃,绝大部分数据库操作完全没必要再去手工调整PGA的一些参数。不过,我们依然能从互联网上、论坛上看到有很多DBA对这种手工调整PGA的技术崇拜有加(我以前也是),确实在一些情况下,通过手工调整PGA的相关内存区,可以达到加速排序等一些操作的目的,但是如果需要操作的数据量非常的大,那这种调整往往是费时费力,甚至是徒劳的,因为对于一个进程的私有PGA内存来说,像sort,hash等P区域的内存分配是有限制的,现在11GR2的版本对于每个进程的PGA内存最大限制默认是2个G,且排序区可以使用的只有1个G,如果你的排序等操作需要的内存远远不止1,2个G,那么这种优化就非常的徒劳,甚至还可能变慢(肉丝亲身遭遇过变慢的案例😢)。是否使用PGA自动管理由参数WORKAREA_SIZE_POLICY控制,它的值可以为auto和manual,顾名思义,auto为PGA自动管理,manual为PGA手工管理,回到9I之前的使用方式。

这里肉丝提供几个大家可能会感兴趣的隐含参数,比如我上面提到了每个进程最大能使用的PGA不能超过2个G,通过修改隐含参数可以突破这个限制。 >肉丝在这里警戒各位,这些参数如果要在生产环境使用,请在你的数据库版本下做好测试。

以下为11GR2版本的情况,其他版本并未做测试:

_PGA_MAX_SIZE 每个进程的PGA的最大内存大小。默认值为:2147483648,2个G,单位为B。

_SMM_MAX_SIZE 每个进程的工作区的大小,默认值为1/2 _PGA_MAX_SIZE,1048576 ,单位KB,1GB,排序区、hash区都属于工作区的范围。64位系统下真实使用的排序区内存不能超过4GB。

_SMM_PX_MAX_SIZE 所有并行查询的SLAVE进程能够用到的PGA总量。默认值为 1/2 pga_aggregate_target,单位为KB,RAC环境下,每个节点都可以用到这么多内存。

以上全部为动态参数,可以在session/system级别来在线修改。

alter system set "_SMM_PX_MAX_SIZE"=10485760; 
alter system set "_SMM_MAX_SIZE"=1048576;
alter system set "_PGA_MAX_SIZE"=2147483648;

上面的参数调整后,一定要设置对应的pga_aggregate_target,否则以上调整可能会不起作用,建议设置为修改后的_SMM_PX_MAX_SIZE的值的两倍。

默认情况下,每个进程使用的排序区不能超过1G。由参数_SMM_MAX_SIZE(单位KB)控制,默认为_PGA_MAX_SIZE(单位B)的一半。 例如,并行度20创建索引,总共可以使用的排序区大小为20*1G=20G,但是同时还受参数_SMM_PX_MAX_SIZE的控制,所有的slave占用的内存不能超过_SMM_PX_MAX_SIZE的值(单位为KB),默认为pga_aggregate_target的一半。同时64位系统下,每个进程可以使用的排序空间不能超过4个G。所以即使把_SMM_MAX_SIZE调整大于4个G也没有用。

_SMM_PX_MAX_SIZE,所有并行查询的SLAVE进程能够用到的PGA总量。每个RAC 节点都可以用到这么多,限制的是本节点所有并行slave能够消耗的PGA。

如何为PGA_AGGREGATE_TARGET设置一个合理的值?

PGA_AGGREGATE_TARGET的设定经常是一个摸索的过程,这里给出官方的一个分配指导原则

PGA_AGGREGATE_TARGET = (TOTAL_MEM * 80%) * 20% for an OLTP system
PGA_AGGREGATE_TARGET = (TOTAL_MEM * 80%) * 50% for a DSS system

上面公式中的TOTAL_MEM * 80%代表着Oracle可以使用的所有内存为操作系统的80%,再根据不同类型业务的特点,OLTP系统,可以在此基础上分配20%的内存给PGA,DSS分析型系统,可以给出剩余内存的50%。这个只是一个指导的意见,具体情况要具体分析。

例如,你的OLTP系统上有成千上万个连接,那么你可以粗略的按照每个进程占用10M的内存来大体的计算一下PGA需要占用的内存空间,再者,假如你的系统不但连接数非常多,而且活跃的连接数也非常的多,那么你可以按照每个进程至少12M的内存来进行估算,更为重要的,系统中假如存在非常多的临时性的计算任务,那么要为PGA预留的内存就更多了。 例如,并行度设置为5创建索引,每个并行进程占用的PGA内存接近1个G:

select *  from 
(select PGA_USED_MEM/1024/1024,PGA_ALLOC_MEM/1024/1024,PGA_MAX_MEM/1024/1024 
from v$process order by 1 desc) where rownum<14;

PGA_USED_MEM/1024/1024 PGA_ALLOC_MEM/1024/1024 PGA_MAX_MEM/1024/1024
---------------------- ----------------------- ---------------------
        1043.70779          1044.39673        1044.39673
        904.724821          905.334227        905.334227
        851.350813          851.959227        851.959227
        804.526175          805.146727        805.146727
        589.681547          590.209227        637.584227
        27.0686626          27.5379925        27.5379925
        27.0686626          27.5379925        27.5379925

所以你在为系统规划PGA内存时不要忘了这些临时性任务需要占用的内存。他们可能是很大的一块哦。
该为数据库分配多少的PGA内存,除了把连接数的多少这个指标作为一个考量因素外,还需要关注活跃连接数的多少,这是因为很多系统连接数虽然非常的多,但是去数据库里一统计发现,绝大部分的连接已经几个小时甚至几天都没活跃过了,这往往是应用程序的连接池不加以管理的结果(也可能是其他原因)。对于不活跃的连接,Oracle每个进程的PGA占用不会太大,按照10M计算是个合理安全的值。AIX下可以大一些,按照每个15M-20M计算。

这里再提供一种估算每个空闲的服务器进程占用OS内存的方法。 首先通过操作系统命令free -m查看一下当前OS剩余的内存,69590M

echo 3 > /proc/sys/vm/drop_caches

#free -m
             total       used       free     shared    buffers     cached
Mem:        128923      69590      59332          0          4        227
-/+ buffers/cache:      69358      59564
Swap:        15999       1276      14723

然后创建2000个连接

test.sql
declare
L_N number;
begin
dbms_lock.sleep(600);
end;
/

#! /bin/sh
. /home/Oracle/.bash_profile
step=1
while [ $step -lt 2000 ]
do
nohup sqlplus test/test @test.sql &
let "step+=1"
echo $step
done

再次查看剩余的操作系统内存,通过没创建连接之前的剩余内存减去创建完2000个连接之后的剩余内存可以估算出每个进程占用的内存大约为(59332-44548)/ 2000=7M

free -m
             total       used       free     shared    buffers     cached
Mem:        128923      84374      44548          0         11        263
-/+ buffers/cache:      84099      44823
Swap:        15999       1276      14723

读者需要牢记,减少的7M内存中,绝大部分都是进程本身占用的,只有1-2M的内存是PGA占用的。因此上面给大家推荐的值是10-12M,也就是给进程预留出一些PGA的内存来。

确认一个空闲进程占用的真正PGA内存有多大,可以通过v$process视图中PGA_ALLOC_MEM字段来获得,如下:只有1.49M。但是就像上面已经提到的,这个进程从操作系统层面看却占用了7M左右的内存。

select round(PGA_ALLOC_MEM/1024/1024,2) from v$process where spid=5553;

ROUND(PGA_ALLOC_MEM/1024/1024,2)
--------------------------------
                1.49

pga_aggregate_target参数指定的值并不是一个硬限制,直到Oracle 12C才提供了一个参数来强制限制PGA的使用量。如果读者不知道该为自己系统的PGA设置一个什么样的值,可以通过视图vpgastat中的maximumPGAallocated,totalPGAallocated值作为参考,前者代表自实例启动以来最大的PGA使用峰值,后者代表当前PGA的使用量,不过这个视图最大的缺点是不能看到各个时间段的PGA总体使用情况,这里肉丝给大家提供一个更好的视图来作为参考vsysmetric_history

select begin_time,end_time,value  
from v$sysmetric_history
 where metric_name ='Total PGA Allocated';

BEGIN_TIME          END_TIME                 VALUE
------------------- ------------------- ----------
2016-04-08 11:31:21 2016-04-08 11:32:21  323746816
2016-04-08 11:30:20 2016-04-08 11:31:21  323746816
2016-04-08 11:29:21 2016-04-08 11:30:20  328404992
2016-04-08 11:28:21 2016-04-08 11:29:21  323746816
2016-04-08 11:27:21 2016-04-08 11:28:21  323746816
2016-04-08 11:26:20 2016-04-08 11:27:21  323746816
2016-04-08 11:25:21 2016-04-08 11:26:20  323746816
2016-04-08 11:24:21 2016-04-08 11:25:21  326611968
2016-04-08 11:23:21 2016-04-08 11:24:21  323746816

上面查询的输出代表了各个时间段的,PGA内存的使用量,结果集并没有完全列出来,读者可以根据PGA各个时间段的使用量来更加精准的去为自己系统的PGA如何做设定做决策。设定PGA的过程是一个循序渐进的过程。再次强调,一个进程占用的内存除了PGA之外,进程本身也会占用内存,这点上面我们已经讨论过。

按照Oracle 2015年OOW上的一份PPT提到,12C之前版本,PGA最多可用的内存可达到PGA_AGGREGATE_TARGET设定值的三倍,这里你听一下就行了,不必当真。

如果生产环境真的遭遇了PGA严重使用过量的情况,可以通过Event 10261 来限制某个/所有进程PGA的使用,level后面值的单位为KB。

alter session set events '10261 trace name context forever, level 100000';

一旦进程超出PGA的设定配额,会被后台进程杀掉并报错,不同的版本可能报错的信息不一样:

For 11.2.0.4 and 12.1.0.1+, ORA-10260
For 11.1 - 11.2.0.3, ORA-600 [723]
12C PGA_AGGREGATE_LIMIT

12C之前的版本对于PGA的使用限制并没有一个硬限制,这个可能会导致一些问题,比如不加以限制后可能会导致OS SWAP的问题,一旦出现SWAP会导致Oracle性能的急剧下降,甚至导致DOWN机,我曾经遭遇过的一个案例是出现SWAP后,LGWR进程本身的内存出现了SWAP,数据库系统的表现是几乎完全HANG死,最后没办法只能重启解决问题。

对于这个新特性的使用是通过参数PGA_AGGREGATE_LIMIT来限制PGA的使用上线,它是一个推导参数,在使用ASMM情况下,取以下的最大值

2GB
200% PGA_AGGREGATE_TARGET
3MB* PROCESSES

在使用AMM情况下,肉丝还未找到PGA_AGGREGATE_LIMIT取值的规律,如果你知道,请告诉我哦。

MSMM

10G前SGA的管理是通过手工设置一系列的参数来实现的,例如重要的参数有以下几个:

buffer_cache_size
shared_pool_size
large_pool_size
stream_pool_size
log_buffer

SGA是一大块内存的统称,由上面列出的组件组成,10G之前的版本,DBA需要对SGA的各个内存区域进行手工设置,这可能对一个DBA无形中要求变得非常的高,需要了解业务,了解热点数据量,了解SQL的使用方式,是否存在大量不同文本的SQL等等来决策来给每个区域分配多少内存。使用过这个版本的DBA基本都遭遇过一个经典的报错:ORA-04031,一般是由于shared pool内存不够导致,这个不够的原因可能有很多,共享池内存的严重碎片化,大量的硬解析,巨大的PL/SQL文本等等都可能会导致这个问题。下面我们来看下,手工管理SGA情况下,如何考虑为buffer cache和shared pool这两个最重要的内存组件分配内存。

Buffer Cache

Buffer Cache俗称数据块高速缓存,是用来缓存数据块的拷贝的,Oracle通过改进后的LRU算法以期待这些数据块后续被高效的重用。服务器进程从磁盘读入的数据就放在这块内存中,修改数据时,也是在这个区域对数据进行修改。并没有一个给buffer cache分配内存的黄金法则,还是要根据操作系统的内存大小,业务的热点数据量、业务的类型等因素来决定给buffer cache分配多大的内存大小。我们先看下如何决定整个SGA的大小,对于SGA的分配原则,官方建议如下:

SGA = (TOTAL_MEM * 80%) * 80% for an OLTP system
SGA = (TOTAL_MEM * 80%) * 50% for a DSS system

上面的公式格式跟PGA的公式大同小异,刨去20%内存给操作系统之后,分为了不同的系统类型,基本上是OLTP系统,大部分的内存给buffer cache,DSS类的系统,buffer cache没那么重要,可以给到50%甚至更低。 至于SGA中多少可以分配给buffer_cache,要看实际情况。DBA可以通过查看buffer cache的命中率来粗略了解是否给buffer cache分配了足够的内存。

但是熟悉我的朋友都知道,我是OWI方法论的践行者,对于命中率的崇拜在Oracle里已然不应该再存在,但是命中率作为一种性能诊断的辅助手段依然具有它的价值。如果系统的SQL都足够优化后,命中率不高,一定程度上意味着可能buffer cache给小了或者你的业务访问的数据非常的离散,热点数量太少。这里肉丝给出的优化思路是自顶向下,命中率低是一个表象,最上层的应用是不是调用了大量不需要调用的SQL,这些SQL是不是缺少索引,SQL都足够优化后,DBA是不是考虑加大buffer cache来提升性能,加大之后还是不行,是不是要考虑增加系统内存来进一步提高buffer cache的大小,最后是不是需要为数据库多加几个盘或者使用SSD来提高性能。

采用自顶向下的分析方法有利于真正发现问题所在而且以最低的代价解决问题,如果仅仅是一个SQL由于缺少索引而导致整个数据库系统缓慢,DBA直接就去加盘扩容IO,那么最终会发现花费了这么大的代价,治标不治本,问题依然没有解决。上面给出的公式并不适用于所有情况,特别是OLTP系统,如果进程数非常的多,那么需要进一步降低SGA占用内存的比例,以预留出更多内存给PGA使用。

Shared Pool

Oracle占用量最大的两块内存除了buffer cache区就是Shared Pool的内存了,它的结构非常的复杂,而且由于要缓存SQL代码这种非标准大小的文本,经常会产生大量的碎片化内存,shared pool总体上包含了两大部分,一块是library cache区,用来缓存SQL、PL/SQL代码,保存它们的执行计划,提高SQL的解析效率,如果你的应用代码从来不使用绑定变量,那么这一块的内存对你来说是一个很大的负担,但是Oracle里是无法关闭library cache区的,因此对于OLTP系统,请确保SQL都使用了绑定变量。

第二大块区域是row cache区,用来缓存数据库的数据字典,由于保存在里面的信息是以行的形式存在,因此叫row cache。对于这一块的内存,依据数据库中元信息(metadata)的多少而决定,如果数据库中有几十万的对象,那么这一块的内存就会占用比较大,同时表上的很多列都有直方图信息,也会导致这一区的内存占用比较大。在一个稳定的系统中,这一区域的内存基本上是静态的,Oracle中几乎没有操作会频繁修改row cache区。

有个例外情况是没有cache属性的sequence,如果这种sequence调用频繁,就会触发频繁的修改sequence的属性值,进而可能会产生row cache lock的一些等待,优化的办法是为每一个sequence设置足够的cache值。 >如果应用程序没有使用绑定变量,而且难以修改,可以通过设置cursor_sharing为force来尝试解决问题。

这里肉丝再讲一个亲身经历的案例,老DBA嘛,可能技能已经不多,但故事还是有的😄,曾经帮一位客户解决了一个free buffer waits的案例,这个等待事件的出现一般说明buffer cache过小,或者全表扫描、写入操作比较多、写脏数据比较慢,从AWR报告看,free buffer waits排在TOP EVENT的第一名,占用的DB TIME已达到了百分之七八十,而且从awr报告中发现客户的shared pool内存占用已经达到了接近50G,而分析数据库一个月之前的AWR报告,shared pool的内存只有3个G左右的大小,基本上可以认定为shared pool内存占用过大,而导致buffer cache不够用,进而出现了free buffer waits等待事件,经过跟客户沟通后知道,以前一直比较稳定,最近做过的大的变更是给数据库使用了oracle 的flahcache,通过MOS上搜索flashcache关键字最终发现,在11.2.0.4,Oracle RAC如果使用了flash cache,那么会在shared pool 中分配额外的内存存放GCS资源。多出的内存为:208 bytes * the total number of (flash) buffer blocks。因为这里提醒广大DBA朋友,如果你打算使用Oracle的FlashCache那么请为它预留出足够的shared pool内存。

ASMM

10G 版本Oracle推出了ASMM,自动SGA管理,它的出现一定程度上帮助DBA解决了管理SGA的问题,通过设置参数sga_target为非0值来启用ASMM。但是在10GR1,包括10GR2的早期版本,ASMM还不够成熟存在比较多的BUG,导致了比较多的问题,所以当时DBA在一些核心的生产环境,还是沿用了9I时候的手工SGA管理。

自动SGA管理,不再需要为每个内存组件设定值,当然如果你设置sga_target的同时,设置了db_cache_size这些参数,那么db_cache_size这些参数值会作为最小值要求。

如果sga_target设置为0,memory_target也为0,则回归到传统的MSMM管理。通过使用ASMM可以很大程度上解决上面提到的ORA-04031的错误。在使用了ASMM后,去查看spfile文件,会发现多了一些双下划线打头的内存参数如__db_cache_size,这些内存参数是Oracle实例运行过程中动态产生固化到spfile中的,如果实例运行的时间足够长,这些参数的值被固话后,相当于有了一个基于自身环境的最佳实践参数,数据库实例重启后,会沿用固化在spfile中的这些参数值。

11G版本即使sga_target设置为0,memory_target也设置为0,也可能会发现SGA的pool之间Granule的移动,这是11G的新Linux特性,通过参数_memory_imm_mode_without_autosga来控制。 >

Granule

ASMM技术的实现,内部是通过Granule在内存组件之间移动来实现。这里对Granule做一点解释,Oracle 10G引入的自动共享内存管理,目的是为了解决下面的问题,“我应该把db_cache_size设置为多大?我应该把shared_pool_size设置多大?

那么10G以后,Oracle给出的答案是,“你不用再考虑这个问题了,我通过算法帮你搞定“。简单来说,就是通过比较通过增加数据块缓存减少的磁盘IO时间与通过增加共享池优化而节省的时间,通过比对,来得出是否需要在两者间来移动内存。为了让内存在db_cache_size和shared_pool_size之间高效移动,Oracle在9I版本中重新构建了SGA,也就是使用固定大小的内存块-Granule,Granule会随着操作系统、Oracle版本以及SGA大小的不同而不同,读者可以通过如下的SQL语句来查看当前实例的Granule的大小:

sys@OLTP6>select bytes from v$sgainfo
  2  where name ='Granule Size';

     BYTES
----------
  33554432
in Oracle Database 11.2.0.2 indicates that the granule size increases from 64MB to 128MB when the SGA_TARGET parameter is set to 1 byte greater than 32G, and jumps to 256MB when the SGA_TARGET parameter is set to 1 byte greater than 64G. A granule size of 32MB is possible when the SGA_TARGET was set to a value between 8G + 1 byte to 16G.

AMM

有了PGA的自动管理,SGA的自动管理,Oracle 这个强迫症患者终于在11G这个版本推出了AMM,自动内存管理,由于使用了AMM后就不能使用大页,因此这个功能其实一直没被广泛使用,通过这个功能DBA只需要设置memory_target一个参数就可以完成整个数据库实例内存的配置,就像ASMM导致了一些双下划线隐含参数的产生,AMM同样导致了一些双下划线隐含参数的产生,例如:

__pga_aggregate_target,__sga_target。而且在11.2.0.2版本之前,DBCA建库默认就是AMM管理(有些时候Oracle胆子真的是很大),11.2.0.3以后开始DBCA建库时会检测操作系统内存大小,大于4G内存默认为ASMM,小于4G内存默认为AMM。同样如果你设置AMM后,也设置了SGA_TARGET等参数,那么这些参数会作为最小值要求。

AMM最大的问题是不能使用大页内存。关于使用大页的重要性会在本文后面部分详细介绍。 >Doc 749851.1 > > Doc 1453227.1.

11G后由于出现了AMM,如果你做DBA时间足够长,你一定遇到过下面这个错误

ORA-00845: MEMORY_TARGET not supported on this system

这个错误给人莫名其妙的感觉,“MEMORY_TARGET特性不被支持”,其实不是特性不被支持,这是由于AMM使用的是操作系统的共享内存文件系统,位于/dev/shm下,如果配置的内存文件系统比较小,小于了memory_target的值就会报错,一般在Linux主流的操作系统上,这个内存共享文件系统的值是内存大小的一半,如果读者遭遇了这个问题,要不去调小memory_target的参数值,要不通过如下办法去修改这个共享内存文件系统的大小:

# umount tmpfs
# mount -t tmpfs shmfs -o size=1200m /dev/shm

编辑/etc/fstab文件


tmpfs /dev/shm tmpfs size=1200m 0 0

这里指出一点,11G ASM默认会使用AMM,官方强烈建议不要修改这个默认行为。ASM里要求MEMORY_TARGET的最小值为256M。如果设置了低于256M的值,Oracle会自动增加内存到256M。

对于ASM的发布版:11.2.0.3/11.2.0.4/12.1,Oracle强烈建议把memory_target设置为1536M,这个值被证明在绝大部分的环境里都是足够的。

使用了ASM后,数据库实例的shared_pool内存需要重新评估计算,如下公式是在正常shared pool的基础上需要额外增加的大小:

SELECT SUM(bytes)/(1024*1024*1024) FROM V$DATAFILE;

SELECT SUM(bytes)/(1024*1024*1024) FROM V$LOGFILE a, V$LOG b
WHERE a.group#=b.group#;

SELECT SUM(bytes)/(1024*1024*1024) FROM V$TEMPFILE
WHERE status='ONLINE';

如果ASM磁盘组是external redundancy,需要在2MB基础上,每100G的空间,增加1MB。

如果ASM磁盘组是normal redundancy,需要在4MB基础上,每50G的空间,增加1MB。

如果ASM磁盘组是high redundancy,需要在6MB基础上,每33G的空间,增加1MB。

ASM & Shared Pool (ORA-4031) (文档 ID 437924.1)

大页

对于类Linux系统,CPU必须把虚拟地址转换程物理内存地址才能真正访问内存。

为了提高这个转换效率,CPU会缓存最近的虚拟内存地址和物理内存地址的映射关系,并保存在一个由CPU维护的映射表中,为了尽量提高内存的访问速度,需要在映射表中保存尽量多的映射关系。

这个映射表在Linux中每个进程都要持有一份,如果映射表太大,就会大大降低CPU的TLB命中率,主流的Linux操作系统,默认页的大小是4K,对于大内存来说,这会产生非常多的page table entries,上面已经提到,Linux下页表不是共享的,每个进程都有自己的页表,现在随便一个主机的内存都配置的是几十个G,几百个G,甚至上T。

如果在上面跑Oracle不使用大页,基本上是找死,因为Oracle是多进程架构的,每一个连接都是一个独占的进程,大内存+多进程+不使用大页=灾难,肉丝在8年的DBA生涯里,至少帮助不下5个客户处理过由于没有使用大页而导致的系统故障,而这5个客户都是近三四年遇到的,为什么大页这个词提前个三五年并没有被频繁提起,而当下,大页这个词在各种技术大会,最佳实践中成为热门词汇?这是因为最近几年是Linux系统被大量普及应用的几年,也是大内存遍地开花的几年,而且现在一个数据库系统动不动就是几百上千个连接,这些都促成了大页被越来越多的被关注到。

大页的好处

我们来看一下使用了大页的好处:

少的page table entries,减少页表内存

pin住SGA,没有page out

提高TLB命中率,减少内核cpu消耗

在没有使用大页的系统上,经常可能会发现几十上百G的页表,严重情况下,系统CPU的sys部分的消耗非常大,这些都是没使用大页的情况下的一些症状。

大页的特点/缺点

要预先分配

不够灵活,甚至需要重启主机

如果分配过多,会造成浪费,不能被其他程序使用。

大页的分配方法

通过在文件/etc/sysctl.cnf中增加vm.nr_hugepages参数来为大页设定一个合理的值,值的单位为2MB。或者通过echo 一个值到/proc/sys/vm/nr_hugepages中也可以临时性的对大页进行设定。 至于应该为大页设定多大的值,这个要根据系统SGA的配置来定,一般建议大页的总占用量大于系统上所有SGA总和+2GB。

HugePages on Oracle Linux 64-bit (文档 ID 361468.1),AIX页表共享,一般不用设置大页。

大页的原理

以下的内容是基于32位的系统,4K的内存页大小做出的计算:

1)目录表,用来存放页表的位置,共包含1024个目录entry,每个目录entry指向一个页表位置,每个目录entry,4b大小,目录表共4b*1024=4K大小

2)页表,用来存放物理地址页的起始地址,每个页表entry也是4b大小,每个页表共1024个页表entry,因此一个页表的大小也是4K,共1024个页表,因此页表的最大大小是1024*4K=4M大小

3)每个页表entry指向的是4K的物理内存页,因此页表一共可以指向的物理内存大小为:1024(页表数)1024(每个页表的entry数)4K(一个页表entry代表的页大小)=4G

4)操作系统将虚拟地址映射为物理地址时,将虚拟地址的31-22这10位用于从目录表中索引到1024个页表中的一个,将虚拟地址的12-21这10位用于从页表中索引到1024个页表entry中的一个。

从这个页表entry中获得物理内存页的起始地址,然后将虚拟地址的0-12位用作4KB内存页中的偏移量,那么物理内存页起始地址加上偏移量就是进出所需要访问的物理内存地址。

由于32位操作系统不会出现4M的页表,因为一个进程不能使用到4GB的内存空间,有些空间被保留使用,比如用来做操作系统内核的内存。而且页表entry的创建出现在进程访问到一块内存的时候,而不是一开始就创建。

页表内存计算

在32位系统下,一个进程访问1GB的内存,会产生1M的页表,如果是在64位系统,将会增大到2M。 很容易推算,如果一个SGA设置为60G,有1500个Oracle用户进程,64位Linux的系统上,最大的页表占用内存为:6021500/1024=175G,是的,你没看错,是175G!但是实际情况看到的页表占用可能没有这么大,打个百分之四五十的折扣,这是因为只有服务器进程访问到SGA的特定区域后,进程才需要把这一块对应的页表项加入到自己的页表中。

11.2.0.3版本

11.2.0.3版本之前,如果分配的大页数量不足,那么Oracle启动过程中不会使用大页,转而使用小页,但是在11.2.0.3版本后,Oracle在启动时同样会尝试使用大页,如果大页的数量不够,那么会把当前配置的大页使用完,不够的部分再从小页中去获取,这一行为实际上是通过Oracle的一个参数来控制USE_LARGE_PAGES,后面会对这个参数做详细解释。通过数据库实例的alert文件可以清楚的看到这一情况的发生:

****************** Large Pages Information *****************

Total Shared Global Region in Large Pages = 1024 MB (85%)

Large Pages used by this instance: 512 (1024 MB)
Large Pages unused system wide = 0 (0 KB) (alloc incr 16 MB)
Large Pages configured system wide = 512 (1024 MB)
Large Page size = 2048 KB

RECOMMENDATION:
  Total Shared Global Region size is 1202 MB. For optimal performance,
  prior to the next instance restart increase the number
  of unused Large Pages by atleast 89 2048 KB Large Pages (178 MB)
  system wide to get 100% of the Shared
  Global Region allocated with Large pages
***********************************************************

Total Shared Global Region in Large Pages = 1024 MB (85%),代表只有85%的SGA被放在了大页中。RECOMMENDATION部分,建议你至少增加89个大页来让SGA完全放到大页中。

USE_LARGE_PAGES

这个参数用来控制数据库实例启动时,对于大页内存的使用行为。有3个值在11.2.0.3版本之前,

11.2.0.3版本多了一个值auto:
true 默认值,尽可能使用大页,有可能有一些页在大页中,有一些在小页中,这种情况下通过操作系统命令ipcs -ma可能会看到内存分段(内存分段可能有多重情况都会导致,例如开启numa也可能会导致内存分段):

false 不使用大页

only 选项代表强制使用大页,否则无法启动

auto (11.2.0.3)在实例启动时,通过后台进程dism echo xxx > /proc/sys/vm/nr_hugepages 来尽可能的使用大页

以下代码为在参数设置为auto情况下alert的输出:

Starting Oracle instance (normal)
DISM started, OS id=11969
****************** Large Pages Information *****************
Parameter use_large_pages = AUTO

Total Shared Global Region in Large Pages = 2514 MB (100%)

Large Pages used by this instance: 1257 (2514 MB)
Large Pages unused system wide = 0 (0 KB) (alloc incr 16 MB)
Large Pages configured system wide = 1257 (2514 MB)
Large Page size = 2048 KB
Time taken to allocate Large Pages = 0.130804 sec
***********************************************************
LICENSE_MAX_SESSION = 0

ls -l oradism
-rwsr-x--- 1 root oinstall 109247 Mar  3 11:34 oradism

可以看到启动实例过程中,优先启动了DISM进程,通过这个进程来自动完成大页的配置。$Oracle_HOME/bin/oradism的权限也是root权限,因为如果是grid权限不能完成大页的配置echo xxx > /proc/sys/vm/nr_hugepages。

Transparent Hugepage

从RedHat6, RedHat7, OL6, OL7 SLES11 和 UEK2 kernels开始transparent hugepage被默认开启,它允许大页做动态的分配,而不是系统启动后就分配好,根据Oracle MOS DOC:1557478.1,transparent hugepage导致了很多的问题,建议将其关闭 查看是否启用了transparent hugepage cat /sys/kernel/mm/transparent_hugepage/enabled [always] never []内的值是当前启用的值,上面的输出说明启用了transparent hugepage。 可以通过 /etc/grub.conf 文件来关闭transparent hugepage。

title Oracle Linux Server (2.6.32-300.25.1.el6uek.x86_64)
   root (hd0,0)
   kernel /vmlinuz-2.6.32-300.25.1.el6uek.x86_64 
   ro root=LABEL=/ transparent_hugepage=never
   initrd /initramfs-2.6.32-300.25.1.el6uek.x86_64.img

通过增加关键字transparent_hugepage=never来讲transparent hugepage关闭。 也可以通过开机启动后,echo相应的值到对应的文件来动态关闭transparent hugepage。

rc.local
echo never > /sys/kernel/mm/redhat_transparent_hugepage/defrag
echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled

OS层面查看大页使用情况

cat /proc/meminfo
HugePages_Total: 43000
HugePages_Free: 29493
HugePages_Rsvd: 23550
Hugepagesize: 2048 kB

HugePages_Total为所分配的页面数目,和Hugepagesize相乘后得到所分配的内存大小。43000*2/1024大约为84GB。

HugePages_Free为从来没有被使用过的Hugepages数目。即使Oracle sga已经分配了这部分内存,但是如果没有实际写入,那么看到的还是Free的。这是很容易误解的地方。

HugePages_Rsvd为已经被分配预留但是还没有使用的page数目。在Oracle刚刚启动时,大部分内存应该都是Reserved并且Free的,随着Oracle SGA的使用,Reserved和Free都会不断的降低。

HugePages_Free – HugePages_Rsvd 这部分是没有被使用到的内存,如果没有其他的Oracle instance,这部分内存也许永远都不会被使用到,也就是被浪费了。在该系统上有11.5GB的内存被浪费了。

最佳实践

对于Oracle来说,这是一个最佳实践泛滥的时代,所有的最佳实践其实都有特定的应用场景,而不是放之四海皆准,但是当今信息爆炸时代(数据库种类爆炸时代),很多DBA没有那么多的耐心去专注学习一门数据库,只是拿来主义从网上找出一些最佳实践来,不管三七二十一就用到自己的环境中(甚至是生产环境中),一定程度上来说,崇拜最佳实践是懒惰的结果。

针对于Oracle的内存分配方式,肉丝给大家推荐在核心系统上: 1.使用手工的SGA管理,这种管理方式已经非常的成熟,但是这种方式对DBA要求较高,一定程度上需要了解业务特点。 2.如果第一种方法你感觉到比较难,那么使用ASMM,但是为buffer cache,shared pool设定最小值的方式,这种方式是我非常推荐的方式。

至于AMM这种内存分配方式,由于不能使用大页,建议不要去用。

具体到内存如何分配,针对是OLTP场景,还是OLAP场景,有着不同的内存分配原则,即使是OLTP场景,针对进程数的多少,并发数的多少,SQL语句的特点,都会导致产生不同的内存分配方法。作为DBA要掌握基本的Oracle内存使用原理,然后根据实际情况去根据业务自身特点分配内存。

不管是OLTP系统或者OLAP系统,一般都建议至少为操作系统预留出20%的内存空间,这些内存空间用于kernal、page table以及一些其他的进程需要,例如rman备份,文件系统缓存。这个也是Oracle官方的建议。

把这一部分的内存刨去后,针对OLTP系统,可以考虑先尝试预留出20%的内存给PGA使用,80%的内存给SGA使用。如果系统上进程数比较多,特别是同时活跃的进程数比较多,需要给PGA预留出更多的内存,可以按照每个进程12M的PGA占用进行计算。

针对OLAP系统,刨去操作系统的预留内存后,可以考虑预留出50%的内存给PGA使用,50%的内存给SGA使用。毕竟在纯OLAP下,buffer cache其实不需要那么大的内存空间。

PGA_AGGREGATE_TARGET = (TOTAL_MEM * 80%) * 20% for an OLTP system
PGA_AGGREGATE_TARGET = (TOTAL_MEM * 80%) * 50% for a DSS system

SGA_TARGET = (TOTAL_MEM * 80%) * 80% for an OLTP system
SGA_TARGET = (TOTAL_MEM * 80%) * 50% for a DSS system

SGA、PGA分配好后,要考虑到是否需要为buffer cache、shared pool来手工设定一个值,就像上文已经提到过的,可以参考buffer cache 的命中率,Library Hit的命中率作为一个辅助参考.如果命中率较低,有可能是内存分配的不够合理。

当然你还需要借助于像AWR报告这样的工具,根据SQL的执行计划等内容来进一步的做诊断,比如命中率低的原因可能不是分配的buffer cache小,而是存在大量的低效的全表扫描,进而导致命中率低,这样的话,需要优化的就是SQL,而不是继续加大buffer cache。再比如,如果你发现Library cache Hit比较低,可能并不是shared pool比较小,还可能是系统的SQL存在大量未使用绑定变量的情况,导致硬解析过重。

优化经常是个系统工程,不能一蹴而就,特别像Oracle是一个很庞大的复杂系统,对于问题的出现,更是要仔细推敲,不能轻易的下结论。随着做DBA时间越来越长,你也会越来越懂TOM的一句话:It depends。

关于作者

魏兴华,沃趣科技高级技术专家,主要参与公司一体机产品、监控产品、容灾产品、DBaaS平台的研发和设计。曾就职于东软集团,阿里巴巴集团,Oracle ACE组成员,DBGEEK 用户组发起人,ITPUB认证博客专家,ACOUG、SHOUG核心成员。曾在Oracle技术嘉年华、ORCL-CON、YY分享平台等公开场合 多次做过数据库技术专题分享。对Oracle 并行机制、数据库异常恢复方法、ASM等有深入的研究,人称”Oracle Internal达人”,对企业数据库架构设计、故障恢复、高并发下数据库性能调优有丰富的经验,擅长从等待时间角度分析解决数据库性能问题,是OWI方法论的提倡者和践行者。

其它白皮书

SQL MONITOR: http://pan.baidu.com/s/1gfO2DtL

Parallel原理实现: http://pan.baidu.com/s/1i5xun9F

12C IN-MEMORY: http://pan.baidu.com/s/1ge7r1oZ

发表评论

电子邮件地址不会被公开。 必填项已用*标注