[ORA-01450] maximum key length (3215) exceeded

背景:

在进行索引在线表空间迁移的时候报出的错误,具体命令

alter index index_name rebuild online tablespace tablespace_name;

问题分析:

9i之后每个index key最大可以为block size的80%。
所以理论上来说,是可以创建最大长度为block size=8096*80%约为6400左右长度的index。
但因为online创建的过程中会生成一个中间表,用来记录创建过程中的变化,而这个表是IOT表。经过测试,发现IOT表的限制比较严格,8k的block size,最大长度只能有3215,所以普通创建可以成功,而online创建则不行,关键还在背后的IOT表上。

解决方法:
去掉online可以成功:
SQL> alter index index_name rebuild tablespace tablespace_name;


Index altered.


限制的原因以及来由:

在ORACLE的ML:136158.1中详细描述了这个问题。首先这个限制的KEY SIZE并不是指表中的列存储的数据的SIZE,而是指将要存储到索引中的这个KEY的SIZE

这个SIZE包含:索引的长度+存储索引长度的空间(2字节) + ROWID (6字节)+存储ROWID长度占用空间 (1字节)。所以真正能够存放的列数据的长度只有3218-2-6-1=3209。

这里有几个注意点(下面的例子全部以最常见的8K BLOCK SIZE来说明):
1、这里的3209并不是实际的数据的长度,而是定义的列的长度。比如,定义一个VARCHAR2(4000),即使里面只存放了一个字节,那么创建索引的时候也会报这个错,因为ORACLE担心你以后里面万一存放的数据超过了3209的长度后,索引那边没办法交代,所以干脆从源头上掐死。
2、既然列太长不让我建,那我能不能先搞一个小列,创建完索引后再去忽悠ORACLE,把这个列的值改大。不行!因为ORACLE会报错:ORA-01404: ALTER COLUMN will make an index too large,告诉你加大列长度的命令可能会导致索引太大。
3、为什么我照着上面测试,发现是可以改大的,而且没有3000多长度的限制?那是因为你创建索引的时候不是ONLINE的,而是直接创建的,ONLINE的时候(包括REBUILD)以为内ORACLE要在一个BLOCK中存储KEY的前后值,所以ONLINE的时候允许的KEY SIZE只有比BLOCK SIZE的一半还小。直接创建的索引也是有限制的:ORA-01450: maximum key length (6398) exceeded

4、为什么文档中写的是限制是3128而我这里却报3215?这个我也存疑,因为ML中提到的是8I中的,或许是我测试的11G版本中,BLOCK脑袋更大了,导致身体能容纳的数据变小
5、这里面还有一个陷阱,就是你索引创建好了,一直使用也没问题,但是当你ONLINE REBUILD的时候却发现他的KEY SIZE超过限制了,导致索引只能不ONLINE的REBUILD,这对于24*7的系统而且必须REBUILD的情况比较痛苦。

常用的数据类型的KEY长度计算如下:
日期类型的长度是7;字符类型就是字段定义时候的长度;数字类型是22(数字类型的长度=精度/2+1),如果是负数,那么长度要再加1;如果是函数索引,那就要按照函数索引的返回值来进行计算。
ORACLE的管理手册中指明了索引的大小不能大于一个BLOCK_SIZE的一半,然后这一半的空间去掉ORACLE自己的PCTFREE、INITRANS以及BLOCK HEADER等等预留空间,实际可以使用的空间比一半要小很多。


为什么ONLINE的时候只能使用不到BLOCK SIZE一半的空间呢?

当ONLINE创建一个索引的时候,ORACLE为这个表的变化创建一个日志,一旦日志创建好了,ORACLE使用表数据的一致性拷贝去创建一个新的索引,然后再把变化的日志拷贝到新创建的索引中,并且在最后更新数据字典,删除临时段并删除这个日志表。
这个过程将会锁表两次(ROW SHARE MODE)。一次是开始创建日志表的时候,另一次是结束的时候删除日志表。日志表是一个名字类似SYS_JOURNAL_NNNNN这样的IOT表,其中的NNNNN是ONLINE REBUILD的索引的OBJECT_ID。因为IOT表的限制只能使用BLOCKSIZE的40%左右,而且这个IOT表的KEY就是索引中使用的KEY并加上ROWID的值,所以只有ONLINE创建或者REBUILD索引的时候会碰到这个问题。

下面来做一个演示,先创建一个表:
create table test(a varchar2(10),b varchar2(11),c varchar2(12),d number(10),e varchar2(13));
然后打开跟踪并ONLINE的创建索引:
create index idx_test on test(a,b,c,d,e) online;

关闭跟踪并查看TRACE文件,可以发现如下语句:
create table "SYS"."SYS_JOURNAL_74346" (C0 VARCHAR2(10), C1 VARCHAR2(11), C2 VARCHAR2(12), C3 NUMBER(10,0), C4
VARCHAR2(13), opcode char(1), partno number, rid rowid, primary key( C0, C1, C2, C3, C4 , rid )) organization
index TABLESPACE "SYSTEM"

其中前面的C0,C1等列就是索引的KEY值,并且经过测试,索引由几列组成,这个临时的IOT表前面也会有几列,后面三列是不变的,根据字面意思推测应该是操作的代码(增加、删除、更新) 、分区号(分区索引用到)、ROWID。而主键是由所有的KEY值和ROWID列组成,这也正好跟前面的长篇大论相吻合。
但至于IOT为啥只能用一半,有些说是为了B*TREE的分裂,有些说是ORACLE老版本的小问题,结果为了兼容一直没改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值