Oracle的知识点

2020/09/11

刚毕业出来工作的那几年,还会有公司使用oracle,但是oracle高成本的版权费和硬件维护成本,国内已经比较少公司使用oracle了

1、sql优化策略

要会看执行计划,看io的耗费,cpu的耗费,表的索引扫描还是全表扫描

1、尽量使用SELECT EXISTS 替代SELECT IN

2、select in 的两种优化

3、select in 、or、union的互转的性能比较

select * from article where article_category=2 or article_category=11
-- 执行时间 11.077
select * from article where article_category in (2,11)
-- 执行时间 11.285
select * from article where article_category=2
union all
select * from article where article_category=11
-- 执行时间 0.0264

4、分页索引的优化,时间倒序

create index IDX_WAREHOUSE_CT on T_WAREHOUSE(CREATED_DATE DESC); 
select * from (
    select t1.*,ROWNUM rn from (
        select prod_id,bar_code,created_date from warehouse order by created_date desc
    ) t1 where rownum<=10
)t2 where rn>0

5、INNER JOIN 与WHERE

尽量使用WHERE,前者是连接表结果,where是结果集查询,where 遵循最右匹配原则,与mysql的最左匹配原则相反

6、where 语句中选择最有效的表顺序,基础表放在最右边

2个表的时候,使用数据量小的表作为基础表

3个以上的表连接查询,需要选择交叉表作为基础表,交叉表是指那个被其他表所引用的表

2、insert append原理解析

解析

insert /*+ APPEND */  into test1 select * from dba_objects;
  • append 就是把数据追加到表的末尾,直接路径加载,速度比常规加载方式快是因为从HWM高水位的位置开始插入,会造成空间浪费(表中有空闲的位置,它不会插入数据,从而造成空间浪费)

  • 非归档模式下,只需append就能大量减少redo的产生;归档模式下,只有append+nologging才能大量减少redo。

  • insert /*+ append */ 时会对表加锁(排它锁),会阻塞表上的除了select以外所有DML语句,这就是所谓的enqueue等待。

    扩展:传统的DML在TM enqueue上使用模式3(row exclusive),其允许其他DML在相同的模式上获得TM enqueue。但是直接路径加载在TM enqueue使用模式6(exclusive),这使其他DML在直接路径加载期间将被阻塞。

insert into tab1 select * from tab2; 
commit;
-- 千万级别的数据插入产生arch归档日志文件会比较快

分清redo与undo

redo就是重做日志文件(redo log file),用来重做数据。Oracle维护着两类重做日志文件:在线(online)重做日志文件和归档(archived)重做日志文件。这两类重做日志文件都用于恢复;其主要目的是,万一实例失败或介质失败,它们能够恢复数据。 由于数据库缓冲,对磁盘数据的更新不是实时的,但是对redo日志的更新会在commit之后确切发生。 如果在事务提交之后,磁盘数据更新之前,系统发生故障,比如断电,系统重启之后会将那些已经写入redo,但是没有更新到磁盘的数据进行重做,这样系统就恢复到故障点之前了。 redo日志默认3组,循环写入,第一组(每个组所有成员同时写入同样的信息)满了,切换到第二个,第二个满了切换到第三个,当所有组都写满之后,日志进程再次开始写第一个,后面的数据覆盖前面的数据,即为非归档模式。 鉴于redo如此重要,需要将已写满的日志归档,即复制内容到其他地方,即开启归档日志模式,但是会影响系统性能。

undo就是回滚数据的信息,保存在undo表空间中,且包含在redo日志中。 当执行DML操作时,旧数据会写入undo中。 事务回滚,未提交时,rollback,把undo中的旧数据重新写回数据段中;已提交时,进行闪回(flashback)操作 ,大多都是基于undo数据实现的。读一致性:用户检索数据时,ORACLE总是使用户只能看到被提交过的数据(当前事务中的其他语句可以看到未提交的数据),或者特定时间点的数据(select语句时间点)。当某个用户在此查询点之后修改了数据,此查询读到这个数据时,就是通过在undo中读取来实现的。 oracle使用scn来实现读一致性,系统变化号(SCN)是一个数据结构,它定义了一个给定时刻提交的数据库版本,SCN可以被认为是oracle的逻辑时钟,每一次提交数值都要增加。

归档模式与非归档模式

Oracle数据库有联机重做日志,这个日志是记录对数据库所做的修改,比如插入,删除,更新数据等,对这些操作都会记录在联机重做日志里。一般数据库至少要有2个联机重做日志组。当一个联机重做日志组被写满的时候,就会发生日志切换,这时联机重做日志组2成为当前使用的日志,当联机重做日志组2写满的时候,又会发生日志切换,去写联机重做日志组1,就这样反复进行。 如果数据库处于非归档模式,联机日志在切换时就会丢弃. 而在归档模式下,当发生日志切换的时候,被切换的日志会进行归档。比如,当前在使用联机重做日志1,当1写满的时候,发生日志切换,开始写联机重做日志2,这时联机重做日志1的内容会被拷贝到另外一个指定的目录下。这个目录叫做归档目录,拷贝的文件叫归档重做日志。 数据库使用归档方式运行时才可以进行灾难性恢复。

区别

  • 非归档模式只能做冷备份,并且恢复时只能做完全备份.最近一次完全备份到系统出错期间的数据不能恢复.
  • 归档模式可以做热备份,并且可以做增量备份,可以做部分恢复.

3、快速插入大量数据

当需要对一个非常大的表INSERT的时候,会消耗非常多的资源,因为update表的时候,oracle需要生成 redo log和undo log;此时最好的解决办法是用insert, 并且将表设置为nologging;当把表设为nologging后,并且使用的insert时,速度是最快的,这个时候oracle只会生成最低限度的必须的redo log,而没有一点undo信息。如果有可能将index也删除,重建。

1、alter table nologging;

a、查询当前数据库的归档状态:

select name,log_mode from v$database; # 默认为 NOARCHIVELOG 非归档

b、nologging在归档模式下有效,非归档模式nologging不起什么作用

c、为了提高插入的速度,我们可以对表关闭写log功能。 SQL 如下:

alter table table_name NOLOGGING; 
-- 插入/修改,完数据后,再修改表写日志:  
alter table table_name LOGGING;

d、没有写log, 速度会块很多,但是也增加了风险,如果出现问题就不能恢复。

create table table_name nologging as (select * from ...);

2、drop掉索引约束之类的,待insert完成后再建索引和约束。;

3、使用直接插入的方式

insert/*+append+*/into tb_name select colnam1,colname2 from table_name;    

4、并行 parallel

insert into tab1 select /*+ parallel */ * from tab2; 
commit;
-- 对于select之后的语句是全表扫描的情况,我们可以加parallel的hint来提高其并发,这里需要注意的是最大并发度受到初始化参数parallel_max_servers的限制,并发的进程可以通过v$px_session查看,或者ps -ef |grep ora_p查看。

alter session enable parallel dml; 
insert /*+ parallel */ into tab1 select * from tab2; 
commit;
-- 与上面的直接插入相反(单线程与多线程)

4、DML与锁

delete与truncate的区别

  • delete会锁定行,truncate会锁定整张表

  • delete不影响表所占用的extent,HWM高水位线保持不变,

    truncate默认会将占用空间会释放到minextents(除非使用reuse storage),truncate会将高水线复位回到最开始。

锁类型

  • 排他锁,只有一个事务在某个时间点持有某个资源

  • 共享锁 ,多个事务同时持有某个资源

行锁原理

行级锁的信息是置于数据块block中的,如果要修改某一条记录的值,就会访问相应的block块,并且分配一个ITL,然后通过rowid访问rowpiece header 行头信息,如果第二个字节lock byte 为0,则将其改为分配的ITL slot number 槽号码,lock byte只占用1个字节,最大值为255,这也是为什么maxtrans最大为255。另外一个事务如果也想要修改数据,就会发现lock byte不为0,如果第一个事务还没有结束,则第二个事务进入enqueue(入队列相对就是dequeue出队列)等待,也就是transaction enqueue。

当一行被修改时,oracle通过回滚段undo空间提供数据的一致性读。一个DML操作,会对表和行加锁,也就是v$lock中的TM锁(表锁)和TX锁(行锁),

  • TM锁,也就是 row-X (SX)锁,保证在事务结束之前,表的结构不会被更改。多个事务可以同时持有相同表的sx锁。
  • TX锁,行排他锁。

v$lock.LMODE字段中的数字对应的锁类型

0 -none

1 -null (NULL)

2 -row-S (SS)

3 -row-X (SX)

4 -share (S)

5 -S/Row-X (SSX)

6 -exclusive (X)

5、阻塞

insert阻塞

普通 insert

insert操作会对表加3级rx锁,和行排他锁,但是一般不会发生阻塞,因为读一致性的关系,在没提交之前只有当前session才可以操作新插入的行,对于其他事务来说 新增的记录是不可见的。

append insert 直接路径加载

通过v$lock 可用看到它会给表和行都会加6级的排他锁,从而阻塞该表除了select外的所有DML操作,直到insert commit。例子:

-- append插入
insert /*+ append_values */ into tun2_tab values (1) ;
-- 查看锁情况
select sid , type , lmode , request , block from v$lock where sid = (select sidfrom v$mystat where rownum<2) 
SID  TYPE  LMODE   REQUEST    BLOCK
-------------- ---------- ---------- ----------
  22 AE            4          0          0
  22 TM            6          0          0
  22 TX            6          0         0

-- 另一个session会话 
update tun2_tab set id=3 ;
waiting...
-- 查看锁情况
select sid , type , id1 , lmode , request , block
from v$lock l
where sid in (select session_id from v$locked_object)
and type in ('TM', 'TX')
order by 1 ;

SID   TYPE        ID1     LMODE    REQUEST      BLOCK
-------------- ---------- ---------- ---------- ----------
22 TM        82618          6          0          1        --session1 包含了表6级锁,它正在阻塞其他的事务
22 TX       524296          6          0          0
24 TM        82618          0          3          0        --session2 它正在请求表的3级锁。

主键和唯一键引发的阻塞

-- 添加主键
alter table tun2_tab add primary key (id) ;

-- session1 session_id=22: 插入数据
insert into tun2_tab values (1) ;
1 rowcreated
 
-- session1 session_id=24: 插入数据, 同一个主键值会发生阻塞,不同主键值不会
insert into tun2_tab values (1) ;
waiting...

-- 查看锁情况
select sid , type , id1 , lmode , request , block
from v$lock l
where sid in (select session_id from v$locked_object)
and type in ('TM', 'TX')
order by 1 ;

SID TYPE        ID1     LMODE    REQUEST      BLOCK
  22 TM        82618          3          0          0
  22 TX       196635          6          0          1        
  24 TX        65548          6          0          0
  24 TM        82618          3          0          0
  24 TX       196635          0          4          0

select sid,seq#,event from v$session_wait where sid= 24 ;
SID  SEQ# EVENT
24   104 enq: TX - row lock contention

-- 因为在拥有primary key列上插入了相同的值,第二个session除了持有自己本事务的6级排他锁之外,还在请求一个4级行的共享锁。这里发生了阻塞。如果第一个session 提交 。第二个session会报错。

update/delete阻塞

对表加3级的共享锁TM,对修改/删除的行加6级的排他锁TX,delete\update相同的行,都会发生阻塞

-- session1 session_id=22
delete from tun2_tab where id =2 ;
1 rowdeleted

-- session2 session_id=18,
update tun2_tab set name ='dexter' where id>1 ;
waiting...

-- session3 session_id=9
delete tun2_tab where id = 3 ; 
1 rowdeleted

-- 查看锁情况
SID TYPE    ID1     LMODE    REQUEST      BLOCK
9 TX       393228          6          0          0
9 TM        82618          3         0          0
18 TX       131089          0          6          0
18 TM        82618          3          0          0
22 TX       131089          6          0          1
22 TM        82618          3          0         0
-- 发现 session2在请求6级排他锁,它还没来得及对任何记录加锁,就已经进入了等待中。所以session3能成功执行,并对id=3的行加了排他锁。即使session1提交了,session2会仍然等待session3的提交

稍微修改以下删除的id的顺序,就

-- session1session_id=144:
delete from tun2_tab where id =3 ;
1 rowdeleted.

-- session2session_id=18:
update tun2_tab set name ='dexter' whereid>1 ;
waiting..

-- session3session_id=9:
delete tun2_tab where id = 2 ;
waiting..

-- 查看锁的情况
SID TYPE        ID1     LMODE    REQUEST      BLOCK
9 TX       196635          0          6          0
9 TM        82618          3          0          0
18 TX       196635          6          0          1
18 TM        82618          3          0         0
18 TX       458767          0          6          0
144 TM        82618          3          0          0
144 TX       458767          6          0          1
-- 可以发现session2 先获取了ID=2的行锁,然后等待ID=3的行锁,所以造成了session3的阻塞等待

ITL引起的阻塞

当block中没有多余的空间来添加ITL entry的时候,就会发生阻塞。通常情况下不会发生这种情况。

解决办法:设置表的inittrans 参数为合理值。

bitmap index引起的阻塞

-- 创建表
create table tb_bitmap_test (id number , gender varchar2(1)) 

-- 插入数据
insert into tb_bitmap_test select level , 'F'from dual connect by level <= 3;
3 rowscreated.

insert into tb_bitmap_test select level , 'M'from dual connect by level <= 2;
2 rowscreated.

-- 创建位图index
create bitmap index tb_bitmap_test_btidx1 on tb_bitmap_test(gender) ;

-- session1 session_id=144:
update tb_bitmap_test set gender='M' where id=1 and gender='F' ;
1 rowupdated.
-- 因为有了Bitmap索引,所以这个操作会索引表中所有gender=’M’和‘F’的记录,并且会阻塞相关的DML操作-- session2 session_id=18:
delete tb_bitmap_test where gender='M' and id = 1;
waiting...
-- 发生阻塞

-- session3 session_id=9 :
insert into tb_bitmap_test values (1,'S') ;
1 rowcreated.
-- 只要gender的值不等于M或者F即可顺利执行
-- 查看锁的情况
SID TYPE        ID1     LMODE    REQUEST      BLOCK
   9 TM        82847          3          0          0
   9 TX       196626          6          0         0
   18 TX       327710          6          0          0
   18 TM        82847          3          0          0
   18 TX       589854          0          4          0
 144 TX       589854          6          0          1
 144 TM        82847          3          0          0
 
-- 发现不管gender是M 还是F,只要是涉及到这两个值的dml操作都会进入等待,因为session1  锁住了整个bitmap segment,
-- 但是只要gender的值不是M或者F就可以顺利执行,所以session3 没有被阻塞。

参考

https://blog.csdn.net/xiaobluesky/article/details/50494101

https://www.cnblogs.com/dongchao3312/p/12843870.html

https://www.cnblogs.com/jxldjsn/p/10815944.html

Post Directory