扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
在日常基于数据库应用的开发过程中,我们经常需要对多个表或者数据源进行关联查询而得出我们需要的结果集。那么Oracle到底存在着哪几种连接方式?优化器内部又是怎样处理这些连接的?哪种连接方式又是适合哪种查询需求的?只有对这些问题有了清晰的理解后,我们才能针对特定的查询需求选择合适的连接方式,开发出健壮的数据库应用程序。选择合适的表连接方法对SQL语句运行的性能有着至关重要的影响。下面我们就Oracle常用的一些连接方法及适用情景做一个简单的介绍。
10年积累的成都网站设计、网站建设、外贸网站建设经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站设计后付款的网站建设流程,更有增城免费网站建设让你可以放心的选择与我们合作。
3.1 嵌套循环连接(nested loop)
嵌套循环连接的工作方式是这样的:
1、 Oracle首先选择一张表作为连接的驱动表,这张表也称为外部表(Outer Table)。由驱动表进行驱动连接的表或数据源称为内部表(Inner Table)。
2、 提取驱动表中符合条件的记录,与被驱动表的连接列进行关联查询符合条件的记录。在这个过程中,Oracle首先提取驱动表中符合条件的第一条记录,再与内部表的连接列进行关联查询相应的记录行。在关联查询的过程中,Oracle会持续提取驱动表中其他符合条件的记录与内部表关联查询。这两个过程是并行进行的,因此嵌套循环连接返回前几条记录的速度是非常快的。在这里需要说明的是,由于Oracle最小的IO单位为单个数据块,因此在这个过程中Oracle会首先提取驱动表中符合条件的单个数据块中的所有行,再与内部表进行关联连接查询的,然后提取下一个数据块中的记录持续地循环连接下去。当然,如果单行记录跨越多个数据块的话,就是一次单条记录进行关联查询的。
3、 嵌套循环连接的过程如下所示:
Nested loop
Outer loop
Inner loop
我们可以看出这里面存在着两个循环,一个是外部循环,提取驱动表中符合条件的每条记录。另外一个是内部循环,根据外循环中提取的每条记录对内部表进行连接查询相应的记录。由于这两个循环是嵌套进行的,故此种连接方法称为嵌套循环连接。
嵌套循环连接适用于查询的选择性强、约束性高并且仅返回小部分记录的结果集。通常要求驱动表的记录(符合条件的记录,通常通过高效的索引访问)较少,且被驱动表连接列有唯一索引或者选择性强的非唯一索引时,嵌套循环连接的效率是比较高的。
嵌套循环连接驱动表的选择也是连接中需要着重注意的一点,有一个常见的误区是驱动表要选择小表,其实这是不对的。假如有两张表A、B关联查询,A表有1000000条记录,B表有10000条记录,但是A表过滤出来的记录只有10条,这时候显然用A表当做驱动表是比较合适的。因此驱动表是由过滤条件限制返回记录最少的那张表,而不是根据表的大小来选择的。
在外连接查询中,如果走嵌套循环连接的话,那么驱动表必然是没有符合条件关联的那张表,也就是后面不加(+)的那张表。这是由于外连接需要提取可能另一张表没符合条件的记录,因此驱动表需要是那张我们要返回所有符合条件记录的表。比如下面这个查询,
嵌套循环连接返回前几行的记录是非常快的,这是因为使用了嵌套循环后,不需要等到全部循环结束再返回结果集,而是不断地将查询出来的结果集返回。在这种情况下,终端用户将会快速地得到返回的首批记录,且同时等待Oracle内部处理其他记录并返回。如果查询的驱动表的记录数非常多,或者被驱动表的连接列上无索引或索引不是高度可选的情况,嵌套循环连接的效率是非常低的
-- 删除原表
drop table t1;
-- 建立测试表
create table t1(
f1 varchar2(10),
f2 varchar2(1000)
)
tablespace CTL
pctfree 98;
-- 填充测试内容
insert into t1(f1,f2)
select rownum, lpad(rownum,700,'0')
from dba_tables a, dba_tab_cols b
where a.owner = b.owner
and rownum 10000;
commit;
-- 检查测试内容格式
select sys.dbms_rowid.rowid_block_number(rowid), f1, f2 from t1;
-- 每条记录都存储在单独的数据块中
select count( distinct sys.dbms_rowid.rowid_block_number(rowid)) from t1;
/*
用同样的方式建立表t2
*/
-- 删除原表
drop table t2;
-- 建立测试表
create table t2(
f1 varchar2(10),
f2 varchar2(1000)
)
tablespace CTL
pctfree 98;
-- 填充测试内容
insert into t2(f1,f2)
select rownum * 10, lpad(rownum * 10,700,'0')
from dba_tables a, dba_tab_cols b
where a.owner = b.owner
and rownum 1000;
commit;
-- 检查测试内容格式
select sys.dbms_rowid.rowid_block_number(rowid), f1, f2 from t2;
-- 每条记录都存储在单独的数据块中
select count( distinct sys.dbms_rowid.rowid_block_number(rowid)) from t2;
create index ind_t1_f1 on t1(f1);
create index ind_t2_f1 on t2(f1);
--首先我们来看使用nested loop关联方式, 不同表作为驱动时的情况.
1, 表t2作为驱动表
select /*+ ordered use_nl(t1 , t2)*/
t1.f1, t2.f1
from ctl.t2 t2,ctl.t1 t1
where t1.f1 = t2.f1
and t1.f1 1000;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=84 Card=4 Bytes=56)
1 0 NESTED LOOPS (Cost=84 Card=4 Bytes=56)
2 1 TABLE ACCESS (FULL) OF 'T2' (Cost=2 Card=82 Bytes=574)
3 1 INDEX (RANGE SCAN) OF 'IND_T1_F1' (NON-UNIQUE) (Cost=1 C
ard=1 Bytes=7)
Cost = outer access cost + (inner access cost * outer cardinality)
Cost = 2 + 1 * 82 = 84;
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
2020 consistent gets
23 physical reads
0 redo size
2650 bytes sent via SQL*Net to client
721 bytes received via SQL*Net from client
8 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
99 rows processed
2, t1作为驱动表
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=4 Bytes=56)
1 0 NESTED LOOPS (Cost=6 Card=4 Bytes=56)
2 1 TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=4 Bytes=28)
3 1 INDEX (RANGE SCAN) OF 'IND_T2_F1' (NON-UNIQUE) (Cost=1 C
ard=1 Bytes=7)
Cost = outer access cost + (inner access cost * outer cardinality)
Cost = 2 + 1 * 4 = 84;
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
11123 consistent gets
3 physical reads
0 redo size
2650 bytes sent via SQL*Net to client
721 bytes received via SQL*Net from client
8 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
99 rows processed
3.2, 哈希连接(hash join)
哈希连接分为两个阶段,如下。
1、 构建阶段:优化器首先选择一张小表做为驱动表,运用哈希函数对连接列进行计算产生一张哈希表。通常这个步骤是在内存(hash_area_size)里面进行的,因此运算很快。
2、 探测阶段:优化器对被驱动表的连接列运用同样的哈希函数计算得到的结果与前面形成的哈希表进行探测返回符合条件的记录。这个阶段中如果被驱动表的连接列的值没有与驱动表连接列的值相等的话,那么这些记录将会被丢弃而不进行探测
哈希连接比较适用于返回大数据量结果集的连接。
使用哈希连接必须是在CBO模式下,参数hash_join_enabled设置为true,
哈希连接只适用于等值连接。从Oracle9i开始,哈希连接由于其良好的性能渐渐取代了原来的排序合并连接。
SQL select /*+ ordered use_hash(t1 , t2) */
t1.f1, t2.f1
from ctl.t1 t1,ctl.t2 t2
where t1.f1 = t2.f1 2 3 4 ;
999 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=82 Bytes=1148
)
1 0 HASH JOIN (Cost=5 Card=82 Bytes=1148)
2 1 TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=82 Bytes=574)
3 1 TABLE ACCESS (FULL) OF 'T2' (Cost=2 Card=82 Bytes=574)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
11113 consistent gets
0 physical reads
0 redo size
23590 bytes sent via SQL*Net to client
1381 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
999 rows processed
3.3, 排序合并连接(merge join)
排序合并连接的方法非常简单。在排序合并连接中是没有驱动表的概念的,两个互相连接的表按连接列的值先排序,排序完后形成的结果集再互相进行合并连接提取符合条件的记录。相比嵌套循环连接,排序合并连接比较适用于返回大数据量的结果。
排序合并连接在数据表预先排序好的情况下效率是非常高的,也比较适用于非等值连接的情况,比如、=、=等情况下的连接(哈希连接只适用于等值连接)。由于Oracle中排序操作的开销是非常消耗资源的,当结果集很大时排序合并连接的性能很差,于是Oracle在7.3之后推出了新的连接方式——哈希连接。
1, rbo模式;
2, 不等于关联( = = )
3, hash_join_enabled = false;
4, 数据源已排序
select t.出勤天数,t.应到天数,round(t.出勤天数/t.应到天数,4)*100||'%' 出勤率
from
(select count(login_time) 出勤天数,:countday 应到天数
from data_sign,sys_user,sys_dept
where sys_user.dept_id = sys_dept.dept_id and sys_user.user_id = data_sign.user_id and dept_name = :dept_name and to_char(login_time,'mm') =:month
and to_char(login_time,'yyyy') =:yyyy) t
这样用一下
数据查询 是数据库操作中最主要的功能之一;有时候数据库查询性能的好坏 直接关系到数据库的运行效率 关系到数据库的选型 下面笔者不谈大道理 只是对其中对一些平时大家容易忽略的查询小技巧做一些总结 或许大家可能正在为此犯愁呢?
第一个技巧 利用连接符连接多个字段
如在员工基本信息表中 有员工姓名 员工职位 出身日期等等 如果现在视图中这三个字段显示在同一个字段中 并且中间有分割符 如我现在想显示的结果为 经理Victor出身于 年 月 日 这该如何处理呢?其实 这是比较简单的 我们可以在Select查询语句中 利用连接符把这些字段连接起来
如可以这么写查询语句
SELECT员工职位 || ||员工姓名|| 出身于 ||出身日期 as 员工出身信息 FROM 员工基本信息表;
通过这条语句就可以实现如上的需求 也就是说 我们在平时查询中 可以利用||连接符把一些相关的字段连接起来 这在报表视图中非常的有用 如笔者以前在设计图书馆管理系统的时候 在书的基本信息处有图书的出版社 出版序列号等等内容 但是 有时会在打印报表的时候 需要把这些字段合并成一个字段打印 为此 就需要利用这个连接符把这些字段连接起来 而且 利用连接符还可以在字段中间加入一些说明性的文字 以方便大家阅读 如上面我在员工职位与员工姓名之间加入了空格;并且在员工姓名与出身日期之间加入了出身于几个注释性的文字 这些功能看起来比较小 但是却可以大大的提高内容的可读性 这也是我们在数据库设计过程中需要关注的一个内容
总之 令后采用连接符 可以提高我们报表的可读性于灵活性
第二个技巧 取消重复的行
如在人事管理系统中 有员工基本信息基本表 在这张表中 可能会有部门 职位 员工姓名 身份证件号码等字段 若查询这些内容 可能不会有重复的行 但是 我若想知道 在公司内部设置了哪些部门与职位的时候 并且这些部门与职位配置了相关人员 此时 又该如何查询呢?
若我现在直接查询部门表 其可以知道系统中具体设置了哪些部门与职位 但是 很有可能这些部门或者职位由于人事变动的关系 现在已经没有人了 所以 这里查询出来的是所有的部门与职位信息 而不能够保证这个部门或者职位一定有职员存在 也就是说 这不能够满足于我们上面的要求
若我现在直接从员工信息表中查询 虽然可以保证所查询出来的部门与职位信息 一定有员工信息的存在 但是 此时查询出来的部门与职位信息会有重复的行 如采购部门分工合作 可能会有采购采购小组长 此时 在查询出来的部门与职位的信息中 就会有三条重复的记录
所以 以上两种处理方式 都不能够百分之百的满足企业用户的需求 此时 我们其实可以利用一个DISTINCT函数 来消除其中查询出来的重复行
如我们可以利用SELECT DISTINCT 部门信息 职位信息 FROM 员工基本信息表 通过这条加了DISTINCT约束的查询语句 不但可以查询出所有有员工的职位与部门信息 而且 会把重复的记录过滤掉 从而提高可阅读性
所以 在数据库设计过程中 特别是在查询语句的使用中 这个函数特别有用
第三个技巧 勤用WHERE语句
我们都知道 数据库查询效率高不高 是我们评价数据库设计好坏的一个重要标准 毋庸置疑 在数据库查询中勤用Where条件语句 是提高数据库查询性能的一个很重要的手段之一 特别是在设计到比较大的表中查询符合条件的记录过程中 利用WHERE条件语句加以限制 可以大幅度的提高查询的响应速度
如在图书馆管理系统中 现在有人想查询 注册会计师 辅导用书的时候 虽然不在书的类别或者名称中输入 注册会计师 先查询出全部的纪录 然后再一条条的看是否有相关的书籍信息 也是可行的 但是 这么处理的话 一方面系统响应的速度会非常的慢 因为里面记录很多 另一方面 查询的结果看起来也会非常的头疼
其实 我们只需要在查询中加入一些查询的参数 利用Where条件语句加以限制 则即可以提高数据库响应的速度 也可以找出最符合用户需求的数据
另外 我也接触过一些在Oracle数据库上设计的平台型管理软件 他们可以自定义相关的报表 在报表设计中 只要用户在前台设计平台中 选中 大表查询 的话 则这个平台会在生成报表的时候 自动应用Where条件语句 以提高前台系统从数据库查询数据的效率
所以 笔者认为在Oracle数据库系统设计中 要勤于使用Where语句 利用Where语句来提高数据库查询的效率
第四个技巧 灵活使用COUNT函数
在查询处理的时候 COUNT函数可以说是我们应用的比较多的函数之一 如我们有时候需要统计员工的人数 统计图书的种类数的时候 都需要使用到这个函数 不过 这个函数很多人可能会用 但是到灵活应用的地步 还是有一点差距
下面笔者就COUNT函数的一些应用技巧谈谈自己的心得
一是要灵活放置COUNT函数的位置 因为利用COUNT函数统计记录数的时候 是会考虑空行的记录的 如在数据表中一般有序列字段与其它的有意义字段两类 有时候可能序列字段中有内容而其它字段中没有内容 则在利用COUNT函数统计记录数量的时候 会把这个空记录也考虑进去 很明显 则就会发生统计的错误 所以 这个COUNT函数该放在哪个位置上 还是比较讲究的 一般的话 笔者试建议不要放在序列号字段上 而要放在一些关键的实体字段中 如统计员工人数的时候 则就可以放在员工姓名或者编号上等等
二是灵活跟其它函数搭配使用 如在上面的例子中 笔者谈到有时候用户需要知道现在有员工编制的部门与职位有哪一些 我们可以利用DISTINCT函数来找出具体的部门 但是 我现在只想知道有编制的部门与职位具体有多少 此时 我们也可以利用COUNT 与DISTINCT函数结合应用 找出我们所需要的数据 在COUNT函数中 可以指定ALL与DISTINCT选项 默认的情况下 是ALL选项 表示统计所有的行 其中也包括重复的行 而DISTINCT就表示只统计不重复的行 可见 COUNT函数跟其它函数搭配使用的话 可以简化我们的查询语句 提高查询效率
第五个技巧 只查询时必须的字段
有时候 用户不同的查询需求都要用到同一张表 如在员工信息表中包含了很多内容 有时候用户想要知道正式员工有多少;管理层员工有多少;生产线员工又有哪些;或者想知道合同即将到期的员工有哪些 为此 就遇到一个问题 因为这些内容基本上都是在同一张表中 那是在同一个视图中实现 而是根据需求不同 设计不同的视图呢?
若单从技术上考虑 两这都是可以实现的 不会有多大的难度 但是 若是从数据库性能上考虑在 则还是采用不同的视图来实现不同的需求为好
一方面 若从安全方面讲 则可以根据不同的视图来控制相关的访问权限 可见 把视图细化 在权限控制上则会更加的灵活
lishixinzhi/Article/program/Oracle/201311/17049
查看那些库的话必须用dba权限登录。登陆后可以通过下面的方法来查看。
一、查看有哪些库,你的库理解不准确,应该准确来说是表空间,可以通过下面的命令来实现
SELECT Total.name "Tablespace Name",
Free_space, (total_space-Free_space) Used_space, total_space
FROM
(select tablespace_name, sum(bytes/1024/1024) Free_Space
from sys.dba_free_space
group by tablespace_name
) Free,
(select b.name, sum(bytes/1024/1024) TOTAL_SPACE
from sys.v_$datafile a, sys.v_$tablespace B
where a.ts# = b.ts#
group by b.name
) Total
WHERE Free.Tablespace_name = Total.name
二、查看有哪些表。
select table_name from dba_tables where owner='A';
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流