继上次删除分区表的分区遇到ORA-01502错误后[详细见链接:Oracle分区表删除分区引发错误ORA-01502: 索引或这类索引的分区处于不可用状态],最近在split分区的时候又遇到了这个问题。这里记录一下该问题是如何产生的,以及如何去解决。

(一)目的

在生产中,我们的大多数分区表都是按照时间分区的,最常见的是按周或按月分区,对于我们DBA来说,对表分区的创建与删除都非常好管理,我在2018年10月会将所有表的分区创建到2019年12月,这样2019年的数据就会进入各个对应月份的分区。

  但是也有小部分分区表是按照其它来分区,例如,事物交易编号等,我们将10万个交易信息存放在一个分区,对于业务,这样创建分区是合理的,但是存在一定的隐患,每天的交易量是动态变化的,有可能3天使用完1个分区,也有可能1天就使用完一个分区,那么分区什么时候使用完我们是不得而知的。对于这种情况,我会为这类分区表添加max分区,从而保证当数据溢出了我们创建的分区时,会进入到max分区里面。分区表大致形式如下(需要说明的是,实际分区表的分区非常大,这里是为了模拟事故创建的小表):

图1.表栏位信息

图2.表分区情况

(二)事故起因

  在上周,由于交易量非常大,发现part_max分区已经开始进入数据了,并且进入的数据量还不小,有大概3个partition的数据。担心大量数据进入part_max分区引起业务查询缓慢,于是决定实施split part_max分区,split执行的语句为:

ALTER TABLE test01 SPLIT PARTITION part_max AT(1000) INTO(PARTITION part_1000,PARTITION part_max);
ALTER TABLE test01 SPLIT PARTITION part_max AT(1100) INTO(PARTITION part_1100,PARTITION part_max);
ALTER TABLE test01 SPLIT PARTITION part_max AT(1200) INTO(PARTITION part_1200,PARTITION part_max);
ALTER TABLE test01 SPLIT PARTITION part_max AT(1300) INTO(PARTITION part_1300,PARTITION part_max);

通过以上操作,将part_max分区的数据分离到part_1000,part_1100,part_1200,part_1300里面,从而减小part_max数据量。

在执行操作后,过了几分钟,业务方面出现了2个问题:

问题1:与该表相关的查询变得非常缓慢;

问题2:数据插入更新报出了大量的“ORA-01502”错误

(三)当时的解决方案

  结合上次出现ORA-01502错误的经历,立马断定是索引出现问题了。查看索引,果然一部分新分区的局部分区索引失效了。立马删除索引,新建索引,将业务给启动起来。

  现在回想起来,解决问题的方式略有不妥。出问题的表size非常的大,有150多GB,创建一个局部分区索引大概需要2.5小时,还好是一部分非关键业务,否则都不知道如何处理。

(四)查找原因&实验验证

回想了自己当天所做的操作,仅仅对这些表进行了split。那么是不是split引起索引失效呢?我们通过实验验证一下。

STEP1:建测试表。创建sales表,以transactionId(交易ID)来分区

create table sales
(
transactionId number,
goodsId number,
goodsName varchar2(30),
saleTimekey date,
goodsdescrip varchar2(100)
)
partition by range(transactionId)
(
partition part_100 values less than(100),
partition part_200 values less than(200),
partition part_300 values less than(300),
partition part_400 values less than(400),
partition part_500 values less than(500),
partition part_600 values less than(600),
partition part_700 values less than(700),
partition part_800 values less than(800),
partition part_900 values less than(900),
partition part_max values less than(maxvalue)
);

STEP2:创建主键约束和局部分区索引。

--6.1 创建主键约束,主键约束会引入唯一性索引
alter table sales add constraint pk_sales_transactionId
primary key(transactionId) using index local online tablespace users;
--6.2 创建普通的局部分区索引
create index lijiaman.goodsId on sales(goodsId) local online tablespace users;

STEP3:创建一个自增长序列。该序列用来模拟交易ID的自增长情况

create sequence sq_transactionId
start with 1
increment by 1
maxvalue 100000000
nocache;

STEP4:创建一个procedure,用来模拟数据插入

--3.1 创建异常捕获表
--该表用于捕获数据插入异常时的异常信息
--drop table sale_exception;
create table sale_exception
(
timekey date,
errcode varchar2(50),
errmess varchar2(500)
); --3.2创建插入sales表的pl/sql程序
create or replace procedure p_sales is
v_sqlcode number;
v_sqlerrm varchar2(4000);
begin
insert into sales
(transactionId, goodsId, goodsName, saleTimekey, goodsdescrip)
values
(sq_transactionId.Nextval,
(select round(dbms_random.value(10000, 100000000)) from dual),
(select dbms_random.string('a', 25) from dual),
sysdate,
(select dbms_random.string('a', 85) from dual));
commit;
exception
when others then
rollback;
v_sqlcode := sqlcode;
v_sqlerrm := substr(sqlerrm,1,100);
insert into sale_exception values(sysdate,v_sqlcode,v_sqlerrm);
commit;
end p_sales;

STEP5:创建job,定时向sales表插入数据。(多次执行,可以创建多个job向表里插入数据,这里我执行了10次,即由10个job每隔5s向sales表里面插入数据)

declare
job1 number;
begin
sys.dbms_job.submit(job => job1,
what => 'p_sales;',
next_date => sysdate,
interval => 'sysdate + 5/(1440*60)'); --每隔5s向sales表插入一笔随机数据
commit;
end;
/

STEP6:查看sales表的数据信息。查看sales表的数据及各个分区的数据

select count(*) from sales;
select count(*) from sales partition(part_100);
select count(*) from sales partition(part_200);
select count(*) from sales partition(part_300);
select count(*) from sales partition(part_400);
select count(*) from sales partition(part_500);
select count(*) from sales partition(part_600);
select count(*) from sales partition(part_700);
select count(*) from sales partition(part_800);
select count(*) from sales partition(part_900);
select count(*) from sales partition(part_max);

STEP7:确认索引的状态

查看dba_indexes,发现index状态为N/A:

SQL> select owner,table_name,index_name,uniqueness,status from dba_indexes i
2 where i.owner = 'LIJIAMAN' and i.table_name = 'SALES'; OWNER TABLE_NAME INDEX_NAME UNIQUENESS STATUS
------------------------------ ------------------------------ ------------------------------ ---------- --------
LIJIAMAN SALES PK_SALES_TRANSACTIONID UNIQUE N/A
LIJIAMAN SALES GOODSID NONUNIQUE N/A

分区索引状态需要从dba_ind_partitions查看:

SQL> select index_owner,index_name,partition_name,status from dba_ind_partitions i
2 where index_owner = 'LIJIAMAN' and index_name in('PK_SALES_TRANSACTIONID','GOODSID'); INDEX_OWNER INDEX_NAME PARTITION_NAME STATUS
------------------------------ ------------------------------ ------------------------------ --------
LIJIAMAN GOODSID PART_100 USABLE
LIJIAMAN GOODSID PART_200 USABLE
LIJIAMAN GOODSID PART_300 USABLE
LIJIAMAN GOODSID PART_400 USABLE
LIJIAMAN GOODSID PART_500 USABLE
LIJIAMAN GOODSID PART_600 USABLE
LIJIAMAN GOODSID PART_700 USABLE
LIJIAMAN GOODSID PART_800 USABLE
LIJIAMAN GOODSID PART_900 USABLE
LIJIAMAN GOODSID PART_MAX USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_100 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_200 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_300 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_400 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_500 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_600 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_700 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_800 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_900 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_MAX USABLE
20 rows selected

通过最后的STATUS列,可以看到所有局部分区索引都是可用的。

STEP8:再次查看各分区的数据量

SQL> select count(*) from sales;          --整个表有1244笔数据

  COUNT(*)
----------
1244 SQL> select count(*) from sales partition(part_max); --part_max分区有375笔数据 COUNT(*)
----------
375

STEP9:执行split分区操作

在上一步,max分区已经有375笔数据了,如果按照100大小作为一个分区,那么数据可以存放到4个分区里面。执行split分区操作。

alter table sales split partition part_max at (1000) into (partition part_1000,partition part_max);
alter table sales split partition part_max at (1100) into (partition part_1100,partition part_max);
alter table sales split partition part_max at (1200) into (partition part_1200,partition part_max);
alter table sales split partition part_max at (1300) into (partition part_1300,partition part_max);
alter table sales split partition part_max at (1400) into (partition part_1400,partition part_max);
alter table sales split partition part_max at (1500) into (partition part_1500,partition part_max);

STEP10:再次执行step7,查看分区索引的状态

SQL> select owner,table_name,index_name,uniqueness,status from dba_indexes i
2 where i.owner = 'LIJIAMAN' and i.table_name = 'SALES'; OWNER TABLE_NAME INDEX_NAME UNIQUENESS STATUS
------------------------------ ------------------------------ ------------------------------ ---------- --------
LIJIAMAN SALES PK_SALES_TRANSACTIONID UNIQUE N/A
LIJIAMAN SALES GOODSID NONUNIQUE N/A

查看各个索引分区的状态:

14:44:42 SQL> select index_owner,index_name,partition_name,status from dba_ind_partitions i
2 where index_owner = 'LIJIAMAN' and index_name in('PK_SALES_TRANSACTIONID','GOODSID'); INDEX_OWNER INDEX_NAME PARTITION_NAME STATUS
------------------------------ ------------------------------ ------------------------------ --------
LIJIAMAN GOODSID PART_100 USABLE
LIJIAMAN GOODSID PART_1000 UNUSABLE
LIJIAMAN GOODSID PART_1100 UNUSABLE
LIJIAMAN GOODSID PART_1200 UNUSABLE
LIJIAMAN GOODSID PART_1300 UNUSABLE
LIJIAMAN GOODSID PART_1400 UNUSABLE
LIJIAMAN GOODSID PART_1500 USABLE
LIJIAMAN GOODSID PART_200 USABLE
LIJIAMAN GOODSID PART_300 USABLE
LIJIAMAN GOODSID PART_400 USABLE
LIJIAMAN GOODSID PART_500 USABLE
LIJIAMAN GOODSID PART_600 USABLE
LIJIAMAN GOODSID PART_700 USABLE
LIJIAMAN GOODSID PART_800 USABLE
LIJIAMAN GOODSID PART_900 USABLE
LIJIAMAN GOODSID PART_MAX USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_100 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_1000 UNUSABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_1100 UNUSABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_1200 UNUSABLE
INDEX_OWNER INDEX_NAME PARTITION_NAME STATUS
------------------------------ ------------------------------ ------------------------------ --------
LIJIAMAN PK_SALES_TRANSACTIONID PART_1300 UNUSABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_1400 UNUSABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_1500 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_200 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_300 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_400 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_500 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_600 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_700 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_800 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_900 USABLE
LIJIAMAN PK_SALES_TRANSACTIONID PART_MAX USABLE
32 rows selected

从上面可以看到,2个索引的某些分区变为“UNUSABLE”状态,这些状态的索引都是新split出来的,但是并不包括全部,如part_1500分区的索引是可用的。上面的索引失效会引起2个问题:

问题1:在查询失效索引相关的分区时,由于索引不可用,查询速度会非常慢;

问题2:由于存在主键约束(带有唯一性索性),在失效索引相关的分区上,数据DML时会引发ORA-01502错误。我们可以从异常捕获表sales_exception查看异常信息:

这就明白了,为什么在split分区表后,生产系统中会出现以上2中情况。

小结:什么情况下split会引起index失效?

在测试时,发现在做split后,新split出来的分区,有的相关分区索引失效,而有的分区索引则不会失效。至于为什么会出现这种情况,个人认为是和segment的分裂有关,part_max段在split后,一个表segment分裂为多个,同样,对应的索引segment也分裂为多个。分裂后,如果一个index分区存放了所有分裂出来的数据,则索引分区与表分区依然可以对应;如果一个index分区存放不下所有数据,则会导致存在数据的索引分区与表分区数据对应不上,索引失效;如果是新分离出来的分区没有数据,则索引与表依然对应。

经过测试,发现规律:

1.part_max没有数据时,split操作不会引起local index失效;

2.part_max有数据:

--split出来的第一个分区【可以存放】part_max里面的全部数据,split后part_max为空,则split 【不会】  引起索引失效;

--split出来的第一个分区【不能够存放】part_max里面的数据,但是后续的分区可以存放下part_max的数据,split后part_max为空,split 【会】  引起索引失效。失效的索引为:新splits出来的有数据的分区,没有数据的分区不会失效,part_max同样不会失效;

--split出来的全部分区【不能够存放】part_max里面的全部数据,split后part_max不为空,split 【会】  引起索引失效。失效的索引为:新split的全部索引和part_max;

图3.split表分区索引失效梳理

(五)如何对应

方案一:重建不可用的索引

SQL> ALTER INDEX [schema.]index_name REBUILD PARTITION partition_name [ONLINE];

我在出问题时重建了整个表的索引,没想到可以重建单个分区的索引。

方法小结:

优点:在部分分区的local index不可用后,使用该方法可以快速重建,快速恢复业务;

缺点:用到这种方法,说明部分local index已经不可用,业务已经出现上面2个问题。

方案二:在split时添加update indexes选项

SQL> ALTER TABLE [schema.]table_name SPLIT PARTITION partition_name AT (part_values) INTO (PARTITION part_values, PARTITION part_max) update indexes;

对于这种方法,个人最关心的问题是:

1.会不会导致local index失效;

2.如果不会导致locl index失效,在进行split时,是否存在锁,导致DML失败。

经过测试(测试表有2个分区,我们对其中一个分区进行split,该分区数据量有2GB,22800000行数据),发现在进行split时会产生TX锁,split持续了90s。在这期间DML操作hang住。查看local index的状态,未出现不可用的索引。

方法小结:

优点:不会造成local index不可用;

缺点:在执行操作期间会造成锁表,如果表分区较大,持续时间将会很长,在生产中难以接受。

目前来看,对于7*24小时的系统,没有办法完美解决分区数据分离的问题,只有随时关注数据增长,尽量不要让数据进入part_max分区。接下来再找一找资料,争取对业务影响最小。

Oracle split分区表引起ORA-01502错误的更多相关文章

  1. Oracle分区表删除分区引发错误ORA-01502: 索引或这类索引的分区处于不可用状态

    (一)问题: 最近在做Oracle数据清理,在对分区表进行数据清理时,采用的方法是drop partition,删除的过程中,没有遇到任何问题,大概过了10分钟,开发人员反馈部分分区表上的业务失败.具 ...

  2. Oracle ORA-01033: ORACLE initialization or shutdown in progress 错误解决办法

    Oracle ORA-01033: ORACLE initialization or shutdown in progress 错误解决办法 登陆数据库时提示 “ORA-01033”错误在命令窗口以s ...

  3. Oracle问题之ORA-12560TNS:协议适配器错误

    Oracle问题之ORA-12560TNS:协议适配器错误 一.造成ORA-12560: TNS: 协议适配器错误的问题的原因有三个: 1.监听服务没有起起来.windows平台个一如下操作:开始-- ...

  4. Oracle问题之ORA-12560TNS:协议适配器错误-转载

    作者:@haimishasha本文为作者原创,转载请注明出处:https://www.cnblogs.com/haimishasha/p/5394963.html 目录 Oracle问题之ORA-12 ...

  5. ORACLE initialization or shutdown in progress 错误解决办法

    第一步,运行cmd 第一步.sqlplus /NOLOG第二步.SQL>connect sys/change_on_install as sysdba'/提示:已成功第三步.SQL>shu ...

  6. Oracle归档日志所在目录时间不对&&Oracle集群日志时间显示错误

    Oracle归档日志所在目录时间不对&&Oracle集群日志时间显示错误 前言 这个问题在18年的时候遇到了,基本不注意并且集群或者数据库运行正常是很难注意到的. 忘记当时怎么发现的了 ...

  7. 登陆Oracle,报oracle initializationg or shutdown in progress 错误提示

    前两天,登陆Oracle,发现登陆不上去了,报”oracle initializationg or shutdown in progress 错误提示” 错误. 然后就想着怎么去解决,首先自己到win ...

  8. 谈谈怎么实现Oracle数据库分区表

    谈谈怎么实现Oracle数据库分区表 数据库的读写分离 SQLSERVER性能监控级别步骤 Oracle索引问题诊断与优化(1)

  9. oracle: sql语句报ora-01461/ora-00911错误

    oracle: sql语句报ora-01461/ora-00911错误 ora-00911:sql语句中可能含有特殊字符,或者sql语句中不能用";"分号结尾. sql语句报ora ...

随机推荐

  1. Hibernate 函数 ,子查询 和原生SQL查询

    一. 函数 聚合函数:count(),avg(),sum(),min(),max() 例:(1)查询Dept表中的所有的记录条数. String hql=" select count(*) ...

  2. Bzoj3510:首都

    Sol \(LCT\)动态维护树重心 方法一 因为只有加边,所以可以暴力启发式合并,维护重心 维护子树信息,子树大小不超过一半 复杂度两只\(log\) 方法二 扣出两个重心的链,链上二分找 每次\( ...

  3. 机智的Popup,带着简单的图表感觉酷酷的

    之前有提过用 InfoTemplate 类对 FeatureLayer 的基本信息进行展示,今天为大家介绍 esri/dijit/Popup 的使用,这东西还有 简单的图表展示功能呢! <!DO ...

  4. 向Github提交更改的代码

    更改了本地的某一文件的代码,那么如何覆盖Github上的同一文件代码呢?请看以下步骤: 1.先用 git status 看你更改了哪些文件: 2.然后 git add 你想要提交的更改的文件 或者 g ...

  5. Tomcat的运行模式

    tomcat的三种运行模式 tomcat Tomcat Connector的三种不同的运行模式性能相差很大,有人测试过的结果如下:  这三种模式的不同之处如下: ●BIO:  一个线程处理一个请求.缺 ...

  6. Mysql 系统学习梳理_【All】

    0.Linux学习---CentOS 7编译安装MySQL 8.0 1.Mysql学习---SQL语言的四大分类 2.Mysql学习---基础操作学习 3.Mysql学习---基础操作学习2 4.My ...

  7. js创建对象的几种方式 标签: javascript 2016-08-21 15:23 123人阅读 评论(0)

    1.传统方法,创建一个对象,然后给这个对象创建属性和方法. var person = new Object(); person.name = "张三"; person.age = ...

  8. python 图形界面

    Python自带的库是支持Tk的Tkinter,使用Tkinter,无需安装任何包,就可以直接使用. Tk是一个图形库,支持多个操作系统 导入Tkinter包的所有内容: from tkinter i ...

  9. AutoHotkey使用Excel的Com对象可能导致进程残留问题的原因及解决方案

    在AutoHotkey脚本中,对Excel的应用体验很不错,xl := ComObjActive("Excel.Application")就和当前Excel表连接了, 通过xl变量 ...

  10. Docker技术三大要点:cgroup, namespace和unionFS的理解

    www.docker.com的网页有这样一张有意思的动画: 从这张gif图片,我们不难看出Docker网站想传达这样一条信息, 使用Docker加速了build,ship和run的过程. Docker ...