继上次删除分区表的分区遇到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. java JDBC链接sqlserver/mysql/oracle

    今天初学数据库的一些简单创建数据库和表,并进行简单的查询,插入. 接下学习的就是java工程中怎么链接数据库呢.主要的方法和用到的类如下. 切记,mysql需要的jar包 mysql-connecto ...

  2. BZOJ2882: 工艺(后缀数组)

    题意 题目链接 Sol 直接把序列复制一遍 后缀数组即可 在前\(N\)个位置中取\(rak\)最小的输出 #include<bits/stdc++.h> using namespace ...

  3. 小白学习css记录

    一.复习 什么是CSS? 层叠样式表 -层叠样式只会被覆盖而不会被替代 CSS的使用方式 style属性---> <h1 style="css属性"></h ...

  4. gulpfile配置

    /** * 只包含合并压缩混淆,监听服务 */// 引入gulp模块var gulp = require('gulp'); // 引入其他模块var less = require('gulp-less ...

  5. ArcGIS DataStore手册——管理篇

    第二章:ArcGIS DataStore管理维护 1.备份管理 备份的目的在于发生原始数据损坏或其他突发情况时,可避免数据丢失,并可快速的使用备份数据来恢复,以保证服务仍可使用. 单机模式下,可使用D ...

  6. Jmeter参数化设置,多用户登录

    一.模拟多用户登录场景 如登录模式如下图所示,登录界面中需要输入:用户名.密码.验证码 用户名以及密码均是固定值,不需要做处理.验证码需要处理一下,可以后台配置成固定值,具体可以找开发咨询. 在此场景 ...

  7. 搭建git远程仓库

    基于本地协议搭建git远程仓库 1.任意目录下执行git init -bare创建裸仓库,建议目录名称以.git结尾 2.共享此目录,windows下右键裸仓库目录,切换到共享面板设置完成即可获取共享 ...

  8. Java—IO流 对象的序列化和反序列化

    序列化的基本操作 1.对象序列化,就是将Object转换成byte序列,反之叫对象的反序列化. 2.序列化流(ObjectOutputStream),writeObject 方法用于将对象写入输出流中 ...

  9. 【解决方法】jdb2/sdb1-8 io使用过高

    机器上面跑的mysql,使用的ssd告诉硬盘,但是 使用iotop发现这个进程 jdb2/sdb1-8 使用率高达80%多, 后来发现是因为参数 sync_binlog=1 导致,事务写入太频繁,改为 ...

  10. C++ 源代码到可执行代码的详细过程

    编译,编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序. 源代码-- ...