在前面的2篇中分别介绍了IM的理论和实践操作:
Oracle 12c In-Memory Column Store 理论说明
http://www.cndba.cn/dave/article/233
Oracle 12c In-Memory Column Store 操作示例
http://www.cndba.cn/dave/article/234
1 IM 加载进程
In Memory内存区可分为两个子池:
1. 1M的pool,用来存储实际的column format数据。
2. 64k的pool,用来存储IM对象中的元数据(metadata)和事务日志(transaction journal)。
1M pool 中分配的内存块的大小为1M的整数倍,64K pool中分配的内存块大小均为64K。
每个container都有自己对应的pool:
SQL> show con_name NAME_COL_PLUS_PDB_CONTAINER ---------------------------------------------------------------------------- IM SQL> select * from v$inmemory_area; POOL ALLOC_BYTES USED_BYTES POPULATE_STATUS CON_ID -------------------------- ----------- ---------- -------------------------- ---------- 1MB POOL 166723584 0 DONE 4 64KB POOL 33554432 0 DONE 4 SQL> conn / as sysdba Connected. SQL> show con_name CON_NAME ------------------------------ CDB$ROOT SQL> select * from v$inmemory_area; POOL ALLOC_BYTES USED_BYTES POPULATE_STATUS CON_ID -------------------------- ----------- ---------- -------------------------- ---------- 1MB POOL 166723584 0 DONE 1 64KB POOL 33554432 0 DONE 1 1MB POOL 166723584 0 DONE 2 64KB POOL 33554432 0 DONE 2 1MB POOL 166723584 0 DONE 3 64KB POOL 33554432 0 DONE 3 1MB POOL 166723584 0 DONE 4 64KB POOL 33554432 0 DONE 4 SQL> select con_id,name from v$pdbs; CON_ID NAME ---------- ------------------------------ 2 PDB$SEED 3 PDB1 4 IM
管理这里CON_ID=1 的说明,请参考:
Oracle 12c 中CON_ID 值说明
http://www.cndba.cn/dave/article/235
这2个IM pool的大小由oracle 内部大小控制,大部分还是1MB pool,用来存放实际的数据。
IM对象的装载是由后台进程ora_w00*_完成的.
[root@Ora12c ~]# ps -ef|grep ora_w oracle 13270 1 0 11:48 ? 00:00:01 ora_w000_dave oracle 13272 1 0 11:48 ? 00:00:02 ora_w001_dave oracle 13651 1 0 12:05 ? 00:00:01 ora_w002_dave oracle 13654 1 0 12:05 ? 00:00:01 ora_w003_dave oracle 14154 1 0 13:00 ? 00:00:00 ora_w004_dave oracle 14217 1 0 13:05 ? 00:00:00 ora_w005_dave oracle 14713 1 0 14:00 ? 00:00:00 ora_w006_dave oracle 14779 1 0 14:05 ? 00:00:00 ora_w007_dave root 19756 19689 0 22:29 pts/0 00:00:00 grep ora_w [root@Ora12c ~]#
参数inmemory_max_populate_servers控制IM后台装载进程的个数, 其默认值取cpu_count/2 和PGA_AGGREGATE_TARGET/512M 中的最小值. 进程数越多,装载速度越快,但消耗的资源也更多.
但实际上,我们上面查看有7个ora_w00*进程,但参数值只是1:
SQL> show parameter cpu_count NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ cpu_count integer 2 SQL> show parameter inmemory_max_populate_servers NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ inmemory_max_populate_servers integer 1 重启DB: SQL> startup force ORACLE instance started. Total System Global Area 1627389952 bytes Fixed Size 2924976 bytes Variable Size 956304976 bytes Database Buffers 436207616 bytes Redo Buffers 13848576 bytes In-Memory Area 218103808 bytes Database mounted. Database opened. SQL> [root@Ora12c ~]# ps -ef|grep ora_w oracle 19961 1 0 22:43 ? 00:00:00 ora_w000_dave oracle 19963 1 0 22:43 ? 00:00:00 ora_w001_dave root 20112 19689 0 22:43 pts/0 00:00:00 grep ora_w
进程变成2个,所以这里测试的结果,可以理解,inmemory_max_populate_servers参数并不是对后台装载进程ora_w00*_的硬性限制,应该有一个动态增加的机制。 至少这点,与官方的描述,是有冲突的。
INMEMORY_MAX_POPULATE_SERVERS
specifies the maximum number of background populate servers to use for In-Memory Column Store (IM column store) population, so that these servers do not overload the rest of the system.
2 IMCU(In Memory Compression Units)
Oracle 数据文件由很多的extents组成,IM column store 也类似, 其由很多 In Memory Compression Units (IMCUs)组成。 IMCU 是列数据在IM 内存中分配块的大小。 每个worker 进程(ora_w00*_)分配自己的IMCU,然后加载数据到IMCU中。 在加载的过程中数据没有任何排序,加载的顺序和读取的row format一致。
当然,数据何时加载到IMCU中,由对象的优先级别控制,默认都是在第一次扫描的时候加载。
当访问IM区中的列数据时,统计信息中看到的consistent gets值也就是统计所访问IMCU的个数和所需访问metadata块的个数之和。
特定对象所分配IMCU的信息,可从视图V$IM_HEADER中查询。而其metadata块的信息可从视图V$IM_SMU_CHUNK和V$IM_SMU_HEAD查询。
当对象小于64KB时,即使启用了IM,也不会被加载到IM 内存,因为IM 内存的chunk 是1M,如果加载这64KB数据,也需要分配1M的内存空间,这样会浪费很大的内存空间。
但这并不是硬性限制,我们可以通过隐含参数,关闭这个限制: SQL> alter system set "_inmemory_small_segment_threshold" = 0 scope=both; 示例: SQL> create table hn(name varchar2(20)); Table created. SQL> select bytes/1024 from dba_segments where segment_name='HN'; no rows selected SQL> insert into hn values('anqing'); 1 row created. SQL> commit; Commit complete. SQL> select bytes/1024 from dba_segments where segment_name='HN'; BYTES/1024 ---------- 64 SQL> alter table hn inmemory; Table altered. SQL> select * from hn; NAME -------- anqing SQL> col owner for a10 SQL> col segment_name for a20 SQL> select v.owner, v.segment_name,v.bytes orig_size,v.inmemory_size in_mem_size,v.bytes / v.inmemory_size comp_ratio from v$im_segments v; OWNER SEGMENT_NAME ORIG_SIZE IN_MEM_SIZE COMP_RATIO ---------- -------------------- ---------- ----------- ---------- IM CNDBA_2 13631488 4325376 3.15151515 IM HN 65536 1179648 .055555556 IM CNDBA 13631488 1179648 11.5555556
这里的测试很有意思,HN表最小分配大小就是64k,并且也成功加载到IM了。 所以这个参数,用不到了。 仅作了解。
3 In-Memory Storage Index
为了进一步的减少数据的访问,IM column store 创建了In-Memory Storage Indexes。 对IM中的每个列,都会自动创建并维护索引,当查询该列时,可以根据该索引的信息,对数据进行过滤。 索引会跟踪每个IMCU中列的最大值和最小值。
在查询时,如果有谓词过滤,会先根据谓词的值来比对索引的值,因为索引记录列每个IMCU中的最大值和最小值,所以可以根据比较的结果判断是否需要扫描该IMCU,从而达到降低扫描的数量,提升性能。
在使用dictionary-based compression的情况下,对等值过滤,in 过滤,和范围过滤时,也可以使用每个IMCU上创建的metadata dictionary 进行过滤, 因为metadata dictionary 包含了IMCU中每列的不同值,Oracle 可以判断要查询的值是否存在该IMCU中,如果不在就不会扫描,这样也可以减少扫描IMCU的数量。
视图V$IM_HEADER记录了每个IMCU的内存地址,分配大小等信息:
SQL> select OBJD,IMCU_ADDR,ALLOCATED_LEN,USED_LEN from V$IM_HEADER; OBJD IMCU_ADDR ALLOCATED_LEN USED_LEN ---------- ---------------- ------------- ---------- 91801 0000000067000000 1048576 1019857 91836 0000000062FFFFE8 4194304 3255914 91838 0000000067100000 1048576 224
SQL> select IMCU_ADDR,ALLOCATED_LEN,USED_LEN from V$IM_HEADER where OBJD=(select object_id from dba_objects where object_name='HN') order by IMCU_ADDR; IMCU_ADDR ALLOCATED_LEN USED_LEN ---------------- ------------- ---------- 0000000067100000 1048576 224 视图V$IM_COL_CU记录了每个IMCU的记录条数,最小值,最大值等信息。 SQL> col MINIMUM_VALUE for a15 SQL> col MAXIMUM_VALUE for a15 SQL> select head_piece_address imcu_addr,column_number,dictionary_entries,utl_raw.cast_to_varchar2(MINIMUM_VALUE) minimum_value,utl_raw.cast_to_varchar2(MAXIMUM_VALUE) maximum_value from v$im_col_cu where objd=(select object_id from dba_objects where object_name='HN') and column_number=1 order by 1; IMCU_ADDR COLUMN_NUMBER DICTIONARY_ENTRIES MINIMUM_VALUE MAXIMUM_VALUE ---------------- ------------- ------------------ --------------- --------------- 0000000067100000 1 0 anqing anqing
我们这里查询的是HN表中IMCU数量,这里只有一条记录,最大值和最小值都是anqing。
4 SIMD Vector Processing
从IM区访问数据效率高,占用的CPU time很低,除了Storage Indexes有关之外,还与IM 使用的SIMD vector 处理有关。
当需要扫描IM 中的数据时,IM 使用SIMD(Single Instruction processing Multiple Data values)方式代替了传统的方式,即每次比较一条记录。 SIMD vector processing 允许先将加载多个列的值,然后在一个CPU 指令完成比较。
IM columnar format 经过特殊设计,使其能够最大数量的加载到CPU中vector registers 中,然后在一条CPU 指令内完成比较工作。 采用SIMD vector processing的方式,允许IM 每秒能扫描十亿条记录。
假设要查询SALES 表中所有PROMO_ID 等于9999的数据,并且SALES 表已经加载到IM中。 当开始查询时,会先加载8个PROMO_ID 列的值到CPU 上的SIMD register 中,然后在单个CPU 指令内完成比较。 如果满足比较的,就记录下来,不满足的,就丢弃。 一批完成之后,重新加载另外的8条记录进行比较,直到所有列都比较完成。
注意:这里是否是8条记录,取决于数据类型和数据压缩的使用,IM columnar format 本身已经过特殊设计,会每次载入最大的记录数。
5 Transaction Processing
当对象启用IM column store后,如果对row data 进行操作(DML),In-Memory Transaction Manager会自动维护buffer cache 和 IM 的事务一致性。
对于存放在IM column store 中的每个IMCU,IM 会自动创建并维护事务日志(transaction journal)。当DML 语句改变已经在IM中对象的某个row记录(buffer cache),IMCU中对应的该记录也会被标记为stale,并且会重新复制改row的新版本到事务日志。 但IMCU中就的值并不会背理解删除,而是要保证读事务的一致性,以及维护压缩数据。 当然这里的读一致性由Oracle SCN(System Change Number) 来保证,与IM 无关。
当下次查询更新的SCN 时,IMCU中已经被标记为stale状态的条目就需要从事务日志或者从基表(buffer cache)中进行恢复。
6 Repopulation
当一个IMCU中stale 状态的条目多了以后,就会导致IMCU查询变慢,因为当IMCU中stale 状态的条目达到一定阀值之后,Oracle 就会重新加载(repopulate)该IMCU。 该阀值由算法根据IMCU 访问频率和IMCU中stale 条目的数量来决定。
IMCU 访问的越频繁,或者IMCU中stale的条目越多,Repopulation的频率也就越高。 Repopulation 操作由后台的worker process(ora_w000_dave)在线完成,在操作期间所有的数据都可用。
除了上面讲的标准再加载算法,还有另外一个算法使用低优先级的后台进程来清除所有stale 条目。
IMCO (In-Memory Coordinator) 后台进程可以repopulation那些访问不频繁,stale 条目还没有达到阀值的IMCU. 通过IMCO的叫trickle repopulate。
[oracle@Ora12c bin]$ ps -ef|grep ora_imco oracle 19965 1 0 Nov06 ? 00:00:10 ora_imco_dave oracle 22932 20975 0 03:04 pts/0 00:00:00 grep ora_imco [oracle@Ora12c bin]$
IMCO 进程每隔2分钟会被唤醒一次,用来检查是否有需要进行repopulate的的IMCU. 当检测到满足条件的,就会唤醒worker process 来进行repopulate。
INMEMORY_TRICKLE_REPOPULATE_SERVERS_PERCENT 参数可以控制trickle repopulate 时,能够使用worker process(ora_w000_dave)的百分比。 当然百分比越高,消耗的CPU 资源也就越高,可以设置INMEMORY_TRICKLE_REPOPULATE_SERVERS_PERCENT参数为0,来禁用trickle repopulate。
7 IMPDP 与IM
在使用impdp的时候,可以设置TRANSFORM 选项:
TRANSFORM Metadata transform to apply to applicable objects. Valid keywords are: DISABLE_ARCHIVE_LOGGING, INMEMORY, INMEMORY_CLAUSE, LOB_STORAGE, OID, PCTSPACE, SEGMENT_ATTRIBUTES, STORAGE, and TABLE_COMPRESSION_CLAUSE. TRANSFORM=INMEMORY:y 继承IM导出对象属性 TRANSFORM=INMEMORY:n 不继承IM导出对象属性 TRANSFORM=INMEMORY_CLAUSE:string 修改IM导出对象关于IM的属性
8 IM相关的等待事件
查看IM 有关的等待事件:
SQL> col name for a50 SQL> select name from v$event_name where name like '%IM %'; NAME -------------------------------------------------- IM buffer busy enq: IM - contention for blr IM CU busy latch: IM area scb latch latch: IM area sb latch latch: IM seg hdr latch latch: IM emb latch enq: SY - IM populate by other session IM populate completion 9 rows selected.
9 IM相关的统计信息
SQL> col name for a80 SQL> select name from v$statname where name like 'IM %' order by 1; NAME -------------------------------------------------------------------------------- IM fetches by rowid from IMCU IM fetches by rowid from disk IM fetches by rowid from fetch list IM fetches by rowid from journal IM fetches by rowid row invalid in IMCU IM populate (faststart) CUs accumulated write time (ms) IM populate (faststart) CUs bytes read IM populate (faststart) CUs bytes written IM populate (faststart) CUs read IM populate (faststart) CUs read attempts …… IM zzzz spare8 IM zzzz spare9 198 rows selected.
10 查看IM中对象
该SQL 除了可以检查IM中对象的数量,也可以用来监控数据加载的进度。
set lines 1000 pages 1000 col owner for a15 col segment_name for a20 col inmemory_size for 99999999 col bytes for 99999999 col POPULATE_STATUS for a15 col INMEMORY_COMPRESSION for a20 select owner,segment_name, sum(inmemory_size)/1024/1024 as "Mem_Size", sum(bytes)/1024/1024 as "Disk_Size", sum(BYTES_NOT_POPULATED)/1024/1024 as "BYTES_NOT_POPULATED", POPULATE_STATUS from v$im_segments group by owner,segment_name, POPULATE_STATUS,INMEMORY_COMPRESSION,INMEMORY_PRIORITY order by 5 desc; OWNER SEGMENT_NAME Mem_Size Disk_Size BYTES_NOT_POPULATED POPULATE_STATUS --------------- -------------------- ---------- ---------- ------------------- --------------- IM CNDBA_2 4.125 13 0 COMPLETED IM HN 1.125 .0625 0 COMPLETED IM CNDBA 1.125 13 0 COMPLETED
11 IM 查询
当访问IM中的对象时,执行计划中会出现INMEMROY的关键字。 但是要注意,出现INMEMORY 不一定代表一定是从IM area中获取的数据。 该特性仅表示查询的对象已经启用了IM的功能,也可能还没有加载到IM area中,比如IM 空间不够的时候。
SQL> set timing on SQL> set autotrace traceonly SQL> select owner,object_name,status from cndba where object_id='888'; Elapsed: 00:00:00.10 Execution Plan ---------------------------------------------------------- Plan hash value: 3204404738 ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 42 | 28 (4)| 00:00:01 | |* 1 | TABLE ACCESS INMEMORY FULL| CNDBA | 1 | 42 | 28 (4)| 00:00:01 | ------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - inmemory("OBJECT_ID"=888) filter("OBJECT_ID"=888) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 3 consistent gets 0 physical reads 0 redo size 704 bytes sent via SQL*Net to client 552 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed 如果不想使用IM query,也可以禁用: SQL> ALTER SESSION set inmemory_query =disable; Session altered. Elapsed: 00:00:00.00 SQL> set autot traceonly SQL> select owner,object_name,status from cndba where object_id='888'; Elapsed: 00:00:00.02 Execution Plan ---------------------------------------------------------- Plan hash value: 3204404738 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 42 | 426 (1)| 00:00:01 | |* 1 | TABLE ACCESS FULL| CNDBA | 1 | 42 | 426 (1)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_ID"=888) Statistics ---------------------------------------------------------- 5 recursive calls 0 db block gets 1537 consistent gets 1527 physical reads 0 redo size 704 bytes sent via SQL*Net to client 552 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
关于IM的理论,实践和进一步的理论说明,整理完毕,花了4天时间,系统的过了一遍,整理了3篇Blog。 IM的第一次学习结束。 一句话,脑力劳动比体力劳动更累,尤其是自己研究和实验的时候。
版权声明:本文为博主原创文章,未经博主允许不得转载。