解析Oracle rowid系列二

解析Oracle rowid系列一里,我们简单探讨了Oracle rowid的格式及获取rowid的方法,在本篇中我们探讨的主题是:通过Oracle rowid真的可以唯一定位到表的一条记录吗?或者说在同一Oracle数据库中,rowid就一定唯一、没有重复的吗?
实验步骤:
1 构建cluster table并插入测试数据:

SQL> conn / as sysdba;
Connected.
SQL> select * from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
PL/SQL Release 11.2.0.1.0 - Production
CORE    11.2.0.1.0      Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production
SQL> conn hr/hr;
Connected.
SQL> create cluster dept_emp_cluster(department_id number(4));
Cluster created.
SQL> create index idx_dept_emp_cluster on cluster dept_emp_cluster;
Index created.
SQL> create table dept cluster dept_emp_cluster(department_id)
2  as select * from departments;
Table created.
SQL> create table emp cluster dept_emp_cluster(department_id)
2  as select * from employees;
Table created.
SQL> select count(*) from dept;
COUNT(*)
----------
27
SQL> select count(*) from emp;
COUNT(*)
----------
107

2 接下来我们分别查询emp、dept表中department_id=10的rowid:

SQL> select department_id,rowid from dept where department_id=10;
DEPARTMENT_ID ROWID
------------- ------------------
           10 AAAUB3AAEAAAAK3AAA
SQL> select department_id,rowid from emp where department_id=10;
DEPARTMENT_ID ROWID
------------- ------------------
           10 AAAUB3AAEAAAAK3AAA
SQL> select department_id,rowid,
  2  dbms_rowid.rowid_object(rowid) object_id,
  3  dbms_rowid.rowid_relative_fno(rowid)  file_id,
  4  dbms_rowid.rowid_block_number(rowid)  block_id,
  5  dbms_rowid.rowid_row_number(rowid)   num
  6  from dept where department_id=10;
DEPARTMENT_ID ROWID               OBJECT_ID    FILE_ID   BLOCK_ID        NUM
------------- ------------------ ---------- ---------- ---------- ----------
           10 AAAUB3AAEAAAAK3AAA      82039          4        695          0
SQL> select department_id,rowid,
  2  dbms_rowid.rowid_object(rowid) object_id,
  3  dbms_rowid.rowid_relative_fno(rowid)  file_id,
  4  dbms_rowid.rowid_block_number(rowid)  block_id,
  5  dbms_rowid.rowid_row_number(rowid)   num
  6  from emp where department_id=10;
DEPARTMENT_ID ROWID               OBJECT_ID    FILE_ID   BLOCK_ID        NUM
------------- ------------------ ---------- ---------- ---------- ----------
           10 AAAUB3AAEAAAAK3AAA      82039          4        695          0
SQL>

从上,我们看到对于dept、emp这两张不同的表,对于department_id=10的两条记录的rowid完全一样,均为AAAUB3AAEAAAAK3AAA
其中AAAUB3为数据库对象号,即转成10进制后位82309;数据文件号为AAE=4,位于第4号数据文件上;位于第4号文件的第AAAAK3=695个数据块上。
为什么会这样呢?不是说,rowid是唯一的吗,通过rowid可以唯一定位表里的一条记录吗?可是,现在却有两张完全不同的表中的rowid竟然完全重复?
3 原来,我们这个场景比较特殊,对象号为82309的对象是一个聚簇表,而dept,emp是位于该cluster下的。那么,emp、dept表中拥有完全重复的rowid也就不足为奇了。因为,这本身就是聚簇表的特征。Oracle的Cluster Table就是要将不同表中的数据放在同一个数据块中存放。关于Cluster Table我们将在后续探讨。

SQL> select object_name,object_id,object_type
  2  from user_objects where object_id='82039';
OBJECT_NAME           OBJECT_ID OBJECT_TYPE
-------------------- ---------- -------------------
DEPT_EMP_CLUSTER          82039 CLUSTER
SQL> select table_name,tablespace_name,cluster_name from user_tables
  2  where table_name in ('DEPT','EMP');
TABLE_NAME               TABLESPACE_NAME         CLUSTER_NAME
------------------------ ----------------------- --------------------
DEPT                     USERS                   DEPT_EMP_CLUSTER
EMP                      USERS                   DEPT_EMP_CLUSTER
SQL>

4 结论:对于普通的Heap Table,我们说rowid的确可以精确定位到表里的记录,并且任何两张表中得rowid绝对不会重复。而对于Cluster Table就不一样了,完全有可能会出现rowid重复的情况。

解析Oracle rowid系列一

搞Oracle的人,都知道表里有一个叫做rowid的伪列,也知道可以通过rowid来快速定位表里的记录,至少通过oracle的索引来快速查找表里的数据就是通过rowid来定位的。可是,我们真的对rowid很了解吗?至少,我之前存在了一些误区,甚至有点儿荒谬!在此,对rowid作一个全面解析,同大家分享,以飨网友。

1 Oracle rowid格式:

Oracle rowid format

rowid

OOOOOO

FFF

BBBBBB

RRR

说明

数据对象号

相对文件号

数据块号

行号

上述表格是Oracle 9i及以上版本数据库中的rowid格式:6位对象号+3位相对文件号+6位数据块号+3位行号,是一个18位的64进制值。这个18位的64进制值在数据库内却是以10个bytes合计80个bit的二进制数存放的,和我们直接看到的结果有所区别。这里的64进制和10进制的对应编码如下表:

64进制-10进制转换表

64进制编码

A

B

Z

a

b

z

0

1

9

+

/

10进制值

0

1

25

26

27

51

52

53

61

62

63

2 那么这个18位的64进制值又是如何同80位的二进制数对应的呢?

其中,6位的对象号在数据库中是用32位二进制来存放的,也就意味着一个oracle数据库中最多可以有232个对象,即4G个对象;3位的相对文件号在数据库中是用10位二进制来存放的,也就意味着一个表空间中最多可以容纳210=1024去掉全0和全1个数据文件,即1022个数据文件;6位的数据块号用22位二进制来存放的,也就意味着一个数据文件最多可以包含222=4M个数据块;3位的行号在数据库中是用16位二进制来存放的,也就意味着一个数据块上最多可以容纳216=65536行记录。

3 我们已经对rowid有了基本认识,我们又该如何获取rowid呢?见下例

SQL> show user;
USER is "HR"
SQL> select employee_id,last_name,rowid from employees where employee_id=100;

EMPLOYEE_ID LAST_NAME                 ROWID
----------- ------------------------- ------------------
100 King                      AAAR5pAAFAAAADPAAA

SQL>

我们可以在查询列表中,把rowid伪列当做普通的字段来查询。上例中,AAAR5p就是HR.employees在数据库中的对象号,AAF表示相对文件号,AAAADP则表示数据块编号,最后的3个AAA则表示行号。当然,我们完全可以根据上述的转换表,将这些64进制值直接转成对应的10进制值。分别就是HR.employees表在数据库内的对象号是73321,而该表在数据库内部是存放在第5号文件(AAF=0*642+0*641+5*640=5)上的,employee_id=100的记录则是放在第5号文件的第207个数据块上的;最后的AAA表示该记录是位于第5号文件上的第207个数据块上的第1条记录。

4 除了上述方法,我们还可以通过数据库提供给我们的工具包来获取该信息:

SQL> select rowid,
2  dbms_rowid.rowid_object(rowid) object_id,
3  dbms_rowid.rowid_relative_fno(rowid)  file_id,
4  dbms_rowid.rowid_block_number(rowid)  block_id ,
5  dbms_rowid.rowid_row_number(rowid)   num ,
6  rowidtochar(rowid) from employees where employee_id=100
7  ;

ROWID               OBJECT_ID    FILE_ID   BLOCK_ID        NUM ROWIDTOCHAR(ROWID)
------------------ ---------- ---------- ---------- ---------- ------------------
AAAR5pAAFAAAADPAAA      73321          5        207          0 AAAR5pAAFAAAADPAAA

SQL>

很显然,我们通过调用系统的dbms_rowid包,获得的rowid信息同我们手工将64进制转成10进制得到的结果完全一致!

这是探讨Oracle rowid系列之一,后续将有系列二探讨Cluster table rowid、系列三探讨Bigfile tablespace rowid,敬请关注。

游泳卡免费赠送

近日来,厦门的天气不太正常,突然有点儿夏天的感觉,就想起之前在厦门佳健游泳馆办的那张会员卡,一再申请LP前去游泳未果,只好屁颠屁颠儿的一个人在上个周末游了两次,感觉挺舒服,缓解了疲劳,活动了颈椎。

只是,那张游泳卡是2011年11月30日就到期了,截止目前还有14次未用,估计自己是消费不完了。在此,向各位互联网的朋友们免费赠送出去,如果有需要的,可以及时回复我,或转至About页面获取我的联系方式。

友情提醒读者:

1 工作之余,加强锻炼身体,尤其是IT同行,长期坐立工作,易患职业病;

2 季节变换,天气变化,尤其是厦门这几天的温度变化明显,注意保暖;

3 如需要免费游泳卡的请从速,这个月30号就到期了,或者友情转发本文;

4 直接联系我就成,别不好意思,我很爽快的。

Oracle 11g Concepts 笔记3:什么是B树索引及其介绍

上一篇笔记里我们简单了解了Oracle索引的分类及唯一索引的特征,在本篇里我们简单了解什么是B-Tree Indexes及对其作一基本介绍。

B-trees, short for balanced trees, are the most common type of database index.顾名思义,B-Tree是一个平衡树的结构注意这里的B表示Balanced平衡的意思,而不是Binary二叉】,B树索引也是Oracle里最为常见的索引类型。B树索引里的数据是已经按照键字或者是被索引字段事先排好序存放的,默认是升序存放,也解释了为什么我们在创建索引的时候有可能会用到数据库的临时表空间的临时段。下面,看一幅B树索引的内部结构图:

b-tree-index

对于这幅B树存储结构图作以下几点介绍:

1 索引高度是指从根块到达叶子块时所遍历的数据块的个数,而索引层次=索引高度-1;本图中的索引的高度是3,索引层次等于2;通常,索引的高度是2或者3,即使表中有上百万条记录,也就意味着,从索引中定位一个键字只需要2或3次I/O,索引越高,性能越差;

2 B树索引包含两种数据块儿:分枝块(Branch Block)和叶子块(Leaf Block);

3 分枝块里存放指向下级分枝块(索引高度大于2,即有超过两层分枝块的情况)或者直接指向叶子块的指针(索引高度等于2,即层次为1的索引);

4 叶子块,就是位于B树结构里最底层的数据块。叶子块里存放的是索引条目,即索引关键字和rowid,rowid用来精确定位表里的记录;索引条目都是按照索引关键字+rowid已经排好序存放的;同一个叶子块里的索引条目同时又和左右兄弟条目形成链表,并且是一个双向链表;

5 B树索引的所有叶子块一定位于同一层上,这是由B树的数据结构定义的。因此,从根块到达任何一个叶子块的遍历代价都是相同的;

Oracle 11g Concepts 笔记2:什么是唯一、非唯一索引及索引分类

从Oracle索引的特征上,我们可以简单的把索引分为Unique Indexes and Nonunique Indexes,即唯一索引和非唯一索引。

1      对于唯一索引,唯一索引确保被索引的字段或多个联合字段在表中绝对不会有重复值;通常,我们在建表时,创建唯一约束或者主键约束,再或者建表以后给表添加唯一、主键约束时,Oracle会自动在主键、唯一约束的字段上创建唯一索引,并且索引的名字跟约束的名字一样,如:

SQL> create table tt(id1 number primary key,id2 number,id3 number);
Table created.
SQL> select index_name,table_name,uniqueness from user_indexes where table_name='TT';
INDEX_NAME                     TABLE_NAME                     UNIQUENESS
------------------------------ ------------------------------ ----------
SYS_C0015420                   TT                             UNIQUE
SQL> select table_name,constraint_name,index_name,constraint_type
2  from user_constraints
3  where table_name='TT';
TABLE_NAME     CONSTRAINT_NAME  INDEX_NAME    CONSTRAINT_TYPE
-------------- ---------------- ------------- ---------------
TT             SYS_C0015420     SYS_C0015420  P

我们在建测试表tt时在id1字段上创建了一个主键约束,当然该约束名字SYS_C0015420是Oracle自动分配给我们的,同时我们也看到tt表上有1个名为SYS_C0015420的索引。
接下来,在id2字段上添加一个唯一约束,并且手动指定名字:

SQL> alter table tt add constraint tt_id2_uk unique(id2);
Table altered.
SQL> select table_name,constraint_name,index_name,constraint_type
2  from user_constraints
3  where table_name='TT';
TABLE_NAME                     CONSTRAINT_NAME                INDEX_NAME                     C
------------------------------ ------------------------------ ------------------------------ -
TT                             SYS_C0015420                   SYS_C0015420                   P
TT                             TT_ID2_UK                      TT_ID2_UK                      U
SQL>  select index_name,table_name,uniqueness from user_indexes where table_name='TT';
INDEX_NAME                     TABLE_NAME                     UNIQUENES
------------------------------ ------------------------------ ---------
TT_ID2_UK                      TT                             UNIQUE
SYS_C0015420                   TT                             UNIQUE
SQL>

我们发现,这tt表上的两个索引都是唯一索引,且名字都等同于各自对应的约束名字。
然后,在该表上创建一个索引:

SQL> create index idx_tt_id3 on tt(id3);
Index created.
SQL> select index_name,table_name,uniqueness from user_indexes where table_name='TT';
INDEX_NAME                     TABLE_NAME                     UNIQUENES
------------------------------ ------------------------------ ---------
IDX_TT_ID3                     TT                             NONUNIQUE
TT_ID2_UK                      TT                             UNIQUE
SYS_C0015420                   TT                             UNIQUE
SQL>

此时,发现该索引IDX_TT_ID3是一个非唯一索引,我们也可以使用create unique index…语句显示创建一个唯一索引。
就唯一索引存储而言,每一个rowid会唯一对应表中的一条记录,在索引的叶子块儿中,数据按照索引字段升序存放。
2     对于非唯一索引,允许被索引的字段有重复值。并且,非唯一索引的存放有别于唯一索引,它的数据在索引叶子块儿中是按照索引字段和rowid进行升序存放的。
3    在Oracle数据库中,索引分类有以下几种:

  • B树索引 B-tree indexes;B树索引又可分为以下子类:
    • 索引组织表Index-organized tables
    • 反转索引Reverse key indexes
    • 降序索引Descending indexes
    • B树聚簇索引B-tree cluster indexes
  • 位图和位图联合索引Bitmap and bitmap join indexes
  • 基于函数的索引Function-based indexes
  • 应用域索引Application domain indexes

4    关于Oracle的索引还有更多知识需要了解和探索,下篇继续,To Be Continued…

WordPress恢复及建站记

前段时间,经过几番折腾终于将个人Blog,OracleOnLinux.cn从以前的备份数据中完全恢复过来,历经将长达半年的临时无法访问后,终于可以正常访问了。 工作之余,抽点儿时间简单记录这次恢复经过及Blog建站过程。 首先记录建站过程: 2008年10月3日花1元RMB,以个人名义成功在紫田网络注册域名 www.OracleOnLinux.cn,至今一直生效,并准备长期使用下去; 2009年10月9日,在好友Jia GuoQing免费提供空间及相关技术的大力支持下,成功搭建WordPress个人Blog,第一篇系统日志Hello World面世,此时离域名生效日期已经1年有余; 期间,由于个人懒惰+不勤奋+不坚持等种种原因,博客更新频率颇低,站点流量徘徊不前; 2011年5月25日,在空间服务器崩溃前发表最后一篇博文:记一次10g RAC收缩表空间; 之后的某一天,国庆兄告之我,空间服务器硬盘故障,导致数据丢失,好在之前用PHPMyAdmin对后台的MySQL数据库做了一次完全备份,生成了一份SQL的dump文件,身为Oracle DBA的我想,只要有有效的备份,恢复起来应该不难; 2011年6月,忙碌,顾不得恢复博客,博客无法访问; 2011年7月,兼职,依然没心思恢复,继续无法访问; 2011年8月,懒惰,更懒得理博客,还是无法访问; 2011年9月,借口,不想恢复,博客访问不了; 2011年10月,NND,还有什么借口和推辞,实在说不下去忍不下去看不过去了,就向国庆兄拿到了当初备份的文件,是一份采用gzip压缩过的压缩包文件alldata.sql.gz,使用gunzip alldata.sql.gz 解压缩该备份文件,并尝试单独恢复数据库; 然后,分享这次恢复过程中遇到的问题及解决方法: 1备份文件过大,包含当时所有后台其它数据库,gunzip解压之后的alldata.sql SQL文本文件信息如下:

[root@oracle11g ~]# ll -h

total 1.2G

drwxr-xr-x 4 root root 4.0K Aug 18 09:42 10gRAC_Media

drwxr-xr-x 7 root root 4.0K Sep 30 15:57 11gRAC_Media

-rw-r--r-- 1 root root 1.2G Oct 18 13:38 alldata.sql

-rw------- 1 root root 1.4K Jan 27  2011 anaconda-ks.cfg

drwxr-xr-x 3 root root 4.0K Jan 29  2011 asm

drwxr-xr-x 2 root root 4.0K Jan 27  2011 Desktop

-rw-r--r-- 1 root root  37K Jan 27  2011 install.log

-rw-r--r-- 1 root root 3.6K Jan 27  2011 install.log.syslog

drwxr-xr-x 3 root root 4.0K Jan 29  2011 linux_image

[root@oracle11g ~]# wc -l alldata.sql

2930681 alldata.sql

[root@oracle11g ~]# wc alldata.sql

2930681   86537367 1194843806 alldata.sql

[root@oracle11g ~]#

备份文件达到1.2GB,计2930681行,要从一个这样的将近300万行的文件中恢复数据,让我何从下手,情何以堪呢?

[root@oracle11g ~]# ll -h
total 1.2G
drwxr-xr-x 4 root root 4.0K Aug 18 09:42 10gRAC_Media
drwxr-xr-x 7 root root 4.0K Sep 30 15:57 11gRAC_Media
-rw-r–r– 1 root root 1.2G Oct 18 13:38 alldata.sql
-rw——- 1 root root 1.4K Jan 27  2011 anaconda-ks.cfg
drwxr-xr-x 3 root root 4.0K Jan 29  2011 asm
drwxr-xr-x 2 root root 4.0K Jan 27  2011 Desktop
-rw-r–r– 1 root root  37K Jan 27  2011 install.log
-rw-r–r– 1 root root 3.6K Jan 27  2011 install.log.syslog
drwxr-xr-x 3 root root 4.0K Jan 29  2011 linux_image
[root@oracle11g ~]# wc -l alldata.sql
2930681 alldata.sql

2考虑采用SQLyog Enterprise这个MySQL GUI工具直接在本地恢复数据库,因为该图形工具提供一个直接从SQL Dump文件中恢复数据库的功能,的确好用; 3上述工作进行的还算顺利,可是恢复到MySQL自带的系统数据库information_schema时就报错,最为要命的是我blog用到的名为linuxor这个数据库还没恢复出来,该怎么办呢? 4怎么才能让MySQL GUI工具跳过系统自带的数据库information_schema,或者直接让MySQL GUI工具只恢复我所需要的linuxor这个数据库岂不更简单?于是,欲用Vim直接编辑这个大文件…… 5尝试使用Vim编辑器或其它文本编辑器编辑这个硕大的近300万行的文件时均无任何结果时,不得已,见招拆招考虑通过从解压缩出来的原始备份文件alldata.sql中抽取linuxor这个数据库相关的备份信息,如果能顺利抽取出来的话,那么我想,单独恢复linuxor这个数据库就不成问题了; 6接下来,使用split命令在Unix、Linux平台下拆分alldata.sql文件,我当时使用的命令如下,具体用法可自行Google之:

[root@oracle11g ~]# split -500000 alldata.sql

[root@oracle11g ~]# ll -h

total 1.8G

drwxr-xr-x 4 root root 4.0K Aug 18 09:42 10gRAC_Media

drwxr-xr-x 7 root root 4.0K Sep 30 15:57 11gRAC_Media

-rw-r--r-- 1 root root 1.2G Oct 18 13:38 alldata.sql

-rw------- 1 root root 1.4K Jan 27  2011 anaconda-ks.cfg

drwxr-xr-x 3 root root 4.0K Jan 29  2011 asm

drwxr-xr-x 2 root root 4.0K Jan 27  2011 Desktop

-rw-r--r-- 1 root root  37K Jan 27  2011 install.log

-rw-r--r-- 1 root root 3.6K Jan 27  2011 install.log.syslog

drwxr-xr-x 3 root root 4.0K Jan 29  2011 linux_image

-rw-r--r-- 1 root root 111M Nov  1 16:36 xaa

-rw-r--r-- 1 root root 107M Nov  1 16:37 xab

-rw-r--r-- 1 root root 454M Nov  1 16:44 xac

-rw-r--r-- 1 root root  90M Nov  1 16:45 xad

-rw-r--r-- 1 root root 227M Nov  1 16:47 xae

-rw-r--r-- 1 root root 153M Nov  1 16:47 xaf

[root@oracle11g ~]#

该命令可将alldata.sql文本文件拆分成每个500000行的小文本文件,考虑到备份文件接近300万行,这样,就会生成6个xaa、xab、xac、xad、xae、xaf相对较小的文件。 7然后,使用grep命令从每个拆分出来的小文件中过滤'CREATE DATABASE `linuxor`',前提是我的确知道该备份文件中,肯定只有一个名为linuxor的数据库;

[root@oracle11g ~]# grep 'CREATE DATABASE `linuxor`' xaa

[root@oracle11g ~]# grep 'CREATE DATABASE `linuxor`' xab

[root@oracle11g ~]# grep 'CREATE DATABASE `linuxor`' xac

[root@oracle11g ~]# grep 'CREATE DATABASE `linuxor`' xad

[root@oracle11g ~]# grep 'CREATE DATABASE `linuxor`' xae

CREATE DATABASE `linuxor` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

[root@oracle11g ~]# grep 'CREATE DATABASE `linuxor`' xaf

8定位到xae这个拆分文件里包含'CREATE DATABASE `linuxor`'关键字且其它几个拆分文件xaa、xab、xac、xad、xaf中均无'CREATE DATABASE `linuxor`'关键字相关信息后,就基本可以断定出我需要的linuxor的数据库信息就只位于xae该文件中了; 9在确定了xae文件后,就可以直接使用Vim编辑器编辑这个文件了,掐头去尾一番后,xae这个文件中只剩下linuxor这个数据库的备份信息了;

[root@oracle11g ~]# wc -l xae

4954 xae

正是这个仅仅只有4954行的文件才是我真正想要的啊!!! 10最后重命名xae为xae.sql文件,在MySQL GUI工具中,导入我的xae.sql文件,并且无任何错误后,至此,后台数据库完全恢复成功!!!!

Enter password: ******

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 14

Server version: 5.1.58-community MySQL Community Server (GPL)

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

This software comes with ABSOLUTELY NO WARRANTY. This is free software,

and you are welcome to modify and redistribute it under the GPL v2 license

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;

+--------------------+

| Database           |

+--------------------+

| information_schema |

| linuxor            |

| mysql              |

| test               |

+--------------------+

4 rows in set (0.00 sec)

mysql>

11接下来,做一些收尾工作,将xae.sql上传到空间服务器。创建MySQL数据库,安装配置WordPress,然后在PHPMyAdmin中删除WordPress数据库中的所有表,并重新将xae.sql中的数据导入数据库中。这样一来,我所有以前的blog内容尽收眼底全部找回,万岁! 后记: 1当初注册OracleOnLinux.cn这个域名时,是希望通过写Blog的形式督促自己把学习、工作中遇到的关于Linux上的Oracle相关的案例记录下来,一来可以鞭策自己,二来可以同大家分享知识,分享快乐。现在,看来自己做得不够好,需要戒除懒惰的恶习,勤能补拙,多读书多写多思考,最重要的是要分享; 2从这次Blog数据库的恢复经历可以看出来,对于任何情况下的重要数据库都应该备份,否则无从恢复,没有有效的备份,任何DBA都只能有心无力回天乏术了; 3如果广大网友对WordPress搭建博客有兴趣或需要帮助的话,可及时回复评论与我沟通,具体联系方式可以查看About页面,同时感谢大家光临本站点;

Oracle 11g Concepts 笔记1:联合索引的创建及注意事项

众所周知,每一位Oracle数据库技术牛人,每一名Oracle DBA在谈到自己的成长历程或者分享学习经验时,都会强调阅读Oracle的官方文档的重要性和必要性。而Oracle提供的官方文档数量之多,范围之广让很多初学者都望而却步,尤其是对于英语基础不好的网友更是头痛不已。其实,就我个人而言,也是经历过诸多痛苦的。事实上,我想但凡每一位认认真真地阅读过官方文档的过来人都或多或少的会有同感。既然Oracle提供给我们的学习文档如此之多,那么我们又该选择哪些文档入手呢?

我的建议是,优先阅读也是最应该阅读 Oracle Database Concepts这本书,因为这本书涵盖了Oracle数据库的全部基本概念,多读此书,收获颇多。记得当初08年的时候,自学Oracle,备考Oracle 10g OCA时,自己坚持着把10g 版本的 Oracle Concepts 10g Release 2前2/3部分反复阅读了几次,得以对Oracle数据库算是有个基本了解。

本系列文章将以笔记的形式记录自己在阅读和学习Oracle 11gR2 Database Concepts 【文档编号为E16508-05】的心得和体会。一方面算是对自己的知识总结和记录,另一方面也希望这一系列的文章能够对广大Oracle数据库技术爱好者带来帮助。当然,如果有理解的不对或者是纰漏之处,还恳请广大网友及时回复给予指正。 好了,废话不再赘述。

Oracle 11gR2 Database Concepts第3章Page 57讲到Indexes and Index-Organized Tables,其中Page 59讲到Composite Indexes。

首先,我们创建测试表,并在该表上创建联合索引:


SQL> conn / as sysdba
Connected.
SQL> select * from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
PL/SQL Release 11.2.0.1.0 - Production
CORE    11.2.0.1.0      Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production 

SQL> conn hr/hr

Connected.

SQL> create table t(id1 number,id2 number,name varchar2(10));

Table created.

SQL> create index idx_t_id1_id2 on t(id1,id2);

Index created.

SQL> insert into t values(1,1,'oracle');

1 row created.

SQL> insert into t values(2,2,'on');

1 row created.

SQL> insert into t values(3,2,'linux');

1 row created.

SQL> commit;

Commit complete.

SQL> select * from t;

ID1        ID2 NAME

---------- ---------- ----------

1          1 oracle

2          2 on

3          2 linux

然后,查看执行计划1:

SQL> set autot trace exp

SQL> select * from t where id1=1;

Execution Plan

----------------------------------------------------------

Plan hash value: 1660670018

---------------------------------------------------------------------------------------------

| Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |               |     1 |    33 |     1   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| T             |     1 |    33 |     1   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | IDX_T_ID1_ID2 |     1 |       |     1   (0)| 00:00:01 |

---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access("ID1"=1)

Note

-----

- dynamic sampling used for this statement (level=2)

SQL>

发现该语句走索引。

查看执行计划2:

SQL> select * from t where id2=1;

Execution Plan

----------------------------------------------------------

Plan hash value: 1601196873

--------------------------------------------------------------------------

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      |     1 |    33 |     3   (0)| 00:00:01 |

|*  1 |  TABLE ACCESS FULL| T    |     1 |    33 |     3   (0)| 00:00:01 |

--------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter("ID2"=1)

Note

-----

- dynamic sampling used for this statement (level=2)

SQL>

SQL> select * from t where id2=2;

Execution Plan

----------------------------------------------------------

Plan hash value: 1601196873

--------------------------------------------------------------------------

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      |     2 |    66 |     3   (0)| 00:00:01 |

|*  1 |  TABLE ACCESS FULL| T    |     2 |    66 |     3   (0)| 00:00:01 |

--------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter("ID2"=2)

Note

-----

- dynamic sampling used for this statement (level=2)

SQL>

发现该语句走的全表扫描,而没走索引,id2字段上创建了联合索引,为什么不走索引呢?

查看执行计划3:

SQL> select * from t where id1=3 and id2=2;

Execution Plan

----------------------------------------------------------

Plan hash value: 1660670018

---------------------------------------------------------------------------------------------

| Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |               |     1 |    33 |     1   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| T             |     1 |    33 |     1   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | IDX_T_ID1_ID2 |     1 |       |     1   (0)| 00:00:01 |

---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access("ID1"=3 AND "ID2"=2)

Note

-----

- dynamic sampling used for this statement (level=2)

SQL> select * from t where id2=2 and id1=3;

Execution Plan

----------------------------------------------------------

Plan hash value: 1660670018

---------------------------------------------------------------------------------------------

| Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |               |     1 |    33 |     1   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| T             |     1 |    33 |     1   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | IDX_T_ID1_ID2 |     1 |       |     1   (0)| 00:00:01 |

---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access("ID1"=3 AND "ID2"=2)

Note

-----

- dynamic sampling used for this statement (level=2)

SQL>

Where从句中包含联合索引的所有字段时,优化器都会选择走索引,不论是将id1放前还是id2放前。

查看执行计划4:

SQL> select * from t where id1=2 and name='on';

Execution Plan

----------------------------------------------------------

Plan hash value: 1660670018

---------------------------------------------------------------------------------------------

| Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |               |     1 |    33 |     1   (0)| 00:00:01 |

|*  1 |  TABLE ACCESS BY INDEX ROWID| T             |     1 |    33 |     1   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | IDX_T_ID1_ID2 |     1 |       |     1   (0)| 00:00:01 |

---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter("NAME"='on')

2 - access("ID1"=2)

Note

-----

- dynamic sampling used for this statement (level=2)

SQL> select * from t where id2=2 and name='on';

Execution Plan

----------------------------------------------------------

Plan hash value: 1601196873

--------------------------------------------------------------------------

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      |     1 |    33 |     3   (0)| 00:00:01 |

|*  1 |  TABLE ACCESS FULL| T    |     1 |    33 |     3   (0)| 00:00:01 |

--------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter("ID2"=2 AND "NAME"='on')

Note

-----

- dynamic sampling used for this statement (level=2)

SQL>

看到,第一条SQL语句(id1=2 and name=’on’)走了索引,而第二条SQL语句(id2=2 and name=’on’)走的却是全表扫描,似乎更奇怪?

最后,我们可以得出以下结论:

1 对于联合索引,在Where限制条件中出现所有被索引的列时,优化器会选择走索引(上述执行计划3);

2 对于联合索引,在Where限制条件中出现联合索引中前导的列,即创建联合索引时前导的列时,优化器会选择走索引(执行计划1,2,4中,凡是有id1作为限制条件时都会走索引,而将id2作为限制条件时均不走索引,而走全表扫描);

3 对于创建联合索引时,应考虑被索引字段的优先顺序,应将经常作为限制条件的字段放在首位;重复值少,即高基数(high-cardinaltiy)的列往前靠,而重复值多的字段往后靠;

4 对于同一张表,可以创建多个不同的联合索引,前提是,被索引的字段的个数、顺序不能一样,否则报错,ORA-01408如:

SQL> create index idx1_t_id1_id2 on t(id1,id2);

create index idx1_t_id1_id2 on t(id1,id2)

*

ERROR at line 1:

ORA-01408: such column list already indexed

SQL> create index idx1_t_id1_id2 on t(id2,id1);

Index created.

SQL>

5 联合索引及前导列的解释:

A composite index, also called a concatenated index,is an index on multiple columns in a table.
A leading portion of an index is a set of one or more columns that were specified first and consecutively in the list of columns in the CREATE INDEX statement that created the index.

Consider this CREATE INDEX statement:

CREATE INDEX comp_ind ON tab1(x, y, z);

These combinations of columns are leading portions of the index: x, xy, and xyz.

These combinations of columns are not leading portions of the index: yz, y, and z.

A SQL statement can use an access path involving a composite index if the statement contains constructs that use a leading portion of the index.

iPod Nano 6入手初体验

经过长达两个多月的等待,终于收到了这份迟来的小礼物,iPod Nano 6 8GB。虽然来的有些迟,甚至因为新加坡那边儿投递的原因差点儿导致与这款小巧却功能强大的MP3失之交臂,但是收到快递包裹的那一刻,还是有一点儿小兴奋的。

话说这个免费礼物的由来,7月份的时候,接到365信息科技大讲堂的邮件邀请,于2011年7月20日参加了甲骨文网上讲座 “最高的数据库性能和快速 100% 的投资回报率”,会后成为众多参与者中的幸运儿。

初入手时,精致而又环保的包装,着实给用户一个完美的体验,尤其是那个设计别有用心的卡片式基本使用说明手册。拆解开来,银白色的机身,延续苹果产品的主打色系。据称,苹果的这款机器投入生产时白黑比是6:1,而市场反应是,消费者的喜好却是白黑比1:6,黑色系的机器颇受消费者的青睐。个人而言,白也好,黑也罢,好用就行,当然更不能颠倒黑白。

接下来,第一件事就是阅读手册,登录苹果官网,获取更多的用户指南。

第二,连接USB接口进行充电,在充电的过程中,顺便安装配置iTunes。这里出了一个小差错,充电大概10分钟左右,误操作断开了iPod Nano,导致充电结束。苹果官方申明初次充电要到2-3小时左右,担心会不会出什么问题,以后电池会不会都充不满?没辙呀,自己是个土包子,从未拥有过苹果的任何产品,使用经验更几乎为零。在此,向各位请教,为什么现在用USB连接线给Nano充电都充不满?是否需要购买iPod 专属充电器?

第三,使用iTunes同步数据,这个应用程序的功能比较强大,就是要注册用户的那个玩意儿比较繁琐,貌似要Visa信用卡号之类的,目前还未注册成功。

第四,Nano的收音机功能很好用,可以迅速搜索当地的所有无线电台。

第五,健身功能很好用,可以记录步行、跑步的数据,并且可以同步到Nike +。

第六,时钟不错,好用。

第七,MP3播放功能和效果自是不用说了,超牛逼。

第八,感谢365信息科技大讲堂,那次会议不仅给予我Oracle相关的知识,还赠送我这个可爱的小礼物。当然还要感谢自己对于Oracle数据库技术的热爱,看来选择从业Oracle DBA还是非常不错的,至少有Nano可以免费玩儿。欢迎Nano网友回复交流用户经验及Oracle数据库相关技术经验,谢谢!

最后,附上邮件确认函一封:

From: Oracle APAC Responses Team <response@oracleevent.com>
Date: Fri, 22 Jul 2011 07:44:40 +0100
Subject: 甲骨文网上讲座: 祝贺你, 您已成为甲骨文网上讲座的iPod Nano幸运儿
To: “3dian14@gmail.com” <3dian14@gmail.com>

Dear黄伟,

谢谢您出席2011 年 7 月 20 日, 星期三的甲骨文网上讲座 “最高的数据库性能和快速 100% 的投资回报率”

您已成为甲骨文网上讲座的iPod Nano幸运儿!

请您确认以下的地址:
厦门火炬高新区软件园二期望海路29#吉联研发大楼, 厦门, 福建 361006, China
(Xiamen Torch Hi-tech Software Park two WangHai 29 # Kyrgyzstan joint
R & D Building, Xiamen, Fujian 361006, China)

我们期待您的答复。

The Oracle Corporation

记一次10g RAC 收缩表空间

这是公司一套10g RAC双节点数据库,近期业务量增长比较大,导致硬盘空间有些吃紧,在新的存储添加之前。我想到的是收缩表空间,缓解存储。做一简单记录如下:
① 操作系统版本:

[root@oracle-rac2 bin]# su - oracle
[oracle@oracle-rac2 ~]$ uname -a
Linux oracle-rac2.gillion.com.cn 2.6.18-194.0.0.0.3.el5 #1 SMP Mon Mar 29 18:14:09 EDT 2010 x86_64 x86_64 x86_64 GNU/Linux
[oracle@oracle-rac2 ~]$ uname -r
2.6.18-194.0.0.0.3.el5

②数据库版本:

[oracle@oracle-rac2 ~]$ . oraenv
ORACLE_SID = [glndb2] ? glndb
[oracle@oracle-rac2 ~]$ export ORACLE_SID=glndb2
[oracle@oracle-rac2 ~]$ sqlplus

SQL*Plus: Release 10.2.0.5.0 - Production on Wed May 25 09:32:03 2011

Copyright (c) 1982, 2010, Oracle.  All Rights Reserved.

SQL> conn / as sysdba;
Connected.
SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.5.0 - 64bi
PL/SQL Release 10.2.0.5.0 - Production
CORE    10.2.0.5.0      Production
TNS for Linux: Version 10.2.0.5.0 - Production
NLSRTL Version 10.2.0.5.0 - Production

SQL>

③ 采用ASM存储,收缩表空间之前,存储信息如下:

SQL> select name,block_size,state,type,total_mb,free_mb from v$asm_diskgroup;

NAME                           BLOCK_SIZE STATE       TYPE     TOTAL_MB    FREE_MB
------------------------------ ---------- ----------- ------ ---------- ----------
ORADATA                              4096 MOUNTED     EXTERN     262138      61364

SQL> select mount_status,header_status ,state,redundancy ,total_mb,free_mb ,name ,path from v$asm_disk;

MOUNT_S HEADER_STATU STATE    REDUNDA   TOTAL_MB    FREE_MB NAME                           PATH
------- ------------ -------- ------- ---------- ---------- ------------------------------ ----------
CACHED  MEMBER       NORMAL   UNKNOWN     262138      61364 ORADATA_0000                   /dev/oradata1

④ 表空间FR7_SA_DATA在收缩之前占用的空间如下:

SQL> select file_name,tablespace_name,bytes/1024/1024 M from dba_data_files where tablespace_name='FR7SA_DATA';

FILE_NAME
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TABLESPACE_NAME                         M
------------------------------ ----------
+ORADATA/glndb/datafile/fr7sa_data.425.751556425
FR7SA_DATA                           4996
接下来,采用如下命令resize该表空间下的数据文件:
SQL> alter database datafile '+ORADATA/glndb/datafile/fr7sa_data.425.751556425' resize 3325M;

Database altered.
收缩之后,该数据文件大小为3325M,大约释放了1670M的空间。
SQL> select file_name,tablespace_name,bytes/1024/1024 M from dba_data_files where tablespace_name='FR7SA_DATA';

FILE_NAME
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TABLESPACE_NAME                         M
------------------------------ ----------
+ORADATA/glndb/datafile/fr7sa_data.425.751556425
FR7SA_DATA                           3325

SQL>

⑤ 最后,再查看ASM磁盘、磁盘组的存储信息:

SQL>select name,block_size,state,type,total_mb,free_mb from v$asm_diskgroup;

NAME                           BLOCK_SIZE STATE       TYPE     TOTAL_MB    FREE_MB
------------------------------ ---------- ----------- ------ ---------- ----------
ORADATA                              4096 MOUNTED     EXTERN     262138      63035

SQL>select mount_status,header_status ,state,redundancy ,total_mb,free_mb ,name ,path from v$asm_disk;

MOUNT_S HEADER_STATU STATE    REDUNDA   TOTAL_MB    FREE_MB NAME                           PATH
------- ------------ -------- ------- ---------- ---------- ------------------------------ ----------
CACHED  MEMBER       NORMAL   UNKNOWN     262138      63035 ORADATA_0000                   /dev/oradata1

⑥ 至此,存储回收了大概1.6G。附上:收缩表空间的脚本。

col file# for 999
col name for a50
col resizecmd for a100

select a.file#,a.name,a.bytes/1024/1024 CurrentMB,
       ceil(HWM * a.block_size)/1024/1024 ResizeTo,
       (a.bytes - HWM * a.block_size)/1024/1024 ReleaseMB,
       'alter database datafile '''||a.name||''' resize '||
       ceil(HWM * a.block_size/1024/1024) || 'M;' ResizeCMD
from v$datafile a,
     (select file_id,max(block_id+blocks-1) HWM
       from dba_extents
       group by file_id) b
where a.file# = b.file_id(+)
and (a.bytes - HWM *block_size)>0
order by 5

如果只是想对某个表个间的datafile resize,可采用:
select a.file#,a.name,a.bytes/1024/1024 CurrentMB,
       ceil(HWM * a.block_size)/1024/1024 ResizeTo,
       (a.bytes - HWM * a.block_size)/1024/1024 ReleaseMB,
       'alter database datafile '''||a.name||''' resize '||
       ceil(HWM * a.block_size/1024/1024) || 'M;' ResizeCMD
from v$datafile a,
     (select file_id,max(block_id+blocks-1) HWM
       from dba_extents where file_id in
              (select b.file#  From v$tablespace a ,v$datafile b
                where a.ts#=b.ts# and a.name='MP2000')
       group by file_id) b
where a.file# = b.file_id(+)
and (a.bytes - HWM *block_size)>0
order by 5

计划tempfile可以resize的空间.on apply that have only one tempfile

select 'alter database tempfile '''||a.name ||''' reize '||b.siz||'M;' from v$tempfile a,
     (select tmsize.maxblk*bk.value/1024/1024 siz from
         (select nvl(max(segblk#),128) maxblk  from v$sort_usage) tmsize,
         (select value From v$parameter where name = 'db_block_size') bk) b
生产环境,需慎重使用上述脚本。