ORA-01450 maximum key length (3215) exceeded

一、 問題背景

給一個業務表online建索引時遇到了ORA-01450 maximum key length (3215) exceeded報錯,看字面意思是字段太長了,檢查表字段類型發現基本都是nvarchar2(2000),有些字段(例如unit)明顯是不需要這麼長的,表的設計有問題,聯繫開發按實際需求改短後能正常創建。

奇怪的是表的id字段類型也是nvarchar2(2000),但上面是有索引的,好奇爲啥這個字段就能建上,以及爲啥maximum key length是3215。

 

二、 報錯分析

根據網上文章,9i之後每個index key最大隻能爲block size的80%。理論上8k的塊可以創建最大長度爲8096*80%約爲6400左右長度的index。但是,online創建(包括rebuild)的過程中會生成一箇中間的IOT表,用來記錄創建過程中的變化。IOT表的限制比較嚴格,導致8k的block size最大長度只能有3215。當然普通創建的索引也是有限制的:ORA-01450: maximum key length (6398) exceeded。

按上面的建測試表和索引,發現的確online創建報錯,而普通創建可以成功。因爲nvarchar2(2000)字段最大可能長度是4000,創建索引時並不會看實際字段長度,直接按的最大長度。

建聯合索引會報錯,因爲nvarchar2(2000)+nvarchar2(2000)字段最大可能長度是8000

再測試varchar2(2000)類型,發現普通創建和online都能成功,因爲varchar2(2000)字段最大可能長度是2000

 

關於索引key最大長度,在文檔 ID 136158.1中給出了不同BLOCK SIZE的限制,你會發現8K BLOCK SIZE的maximum key length 文檔中寫的是3218而不是我們遇到的3215。這可能是由於文檔是針對8i的版本,在新版本中這個值變成了3215。

ORA-01450 maximum key length (758) exceeded  ->(2K Block)
ORA-01450 maximum key length (1578) exceeded ->(4K block)
ORA-01450 maximum key length (3218) exceeded ->(8K Block)
ORA-01450 maximum key length (6498) exceeded ->(16K Block)

報錯中限制的KEY SIZE包含:索引的長度+存儲索引長度的空間(2字節) + ROWID (6字節)+存儲ROWID長度佔用空間 (1字節),所以真正能夠存放的列數據的長度只有3218-2-6-1=3209,新版本應該是3215-2-6-1=3206。

  • 這裏的3209並不是實際的數據的長度,而是定義的列的長度。就像前面的例子,定義NVARCHAR2(2000),即使裏面只存放了一個字符,創建索引時也會報這個錯。ORACLE擔心以後裏面存放的數據萬一超過了,索引那邊沒辦法交代,所以乾脆從源頭上掐死。
  • 那能不能先定義一個小列,創建完索引後再把這個列的值改大?不行,你會遇到報錯:ORA-01404: ALTER COLUMN will make an index too large,告訴你加大列長度的命令可能會導致索引太大
  • 這裏面還有一個陷阱,就是你索引創建好了,一直使用也沒問題,但是當你ONLINE REBUILD的時候卻發現他的KEY SIZE超過限制了,導致索引只能不ONLINE的REBUILD,這對於24*7的系統而且必須REBUILD的情況比較痛苦。

 

常用的數據類型的KEY長度計算如下:

日期類型的長度是7;字符類型就是字段定義時候的長度;數字類型是22(數字類型的長度=精度/2+1),如果是負數,那麼長度要再加1;如果是函數索引,那就要按照函數索引的返回值來進行計算。

 

爲什麼ONLINE創建只能使用不到BLOCK SIZE一半的空間?

ORACLE的管理手冊中指明瞭索引的大小不能大於BLOCK_SIZE的一半,然後這一半的空間去掉ORACLE自己的PCTFREE、INITRANS以及BLOCK HEADER等等預留空間,實際可以使用的空間比一半要小很多。

當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老版本的小問題,結果爲了兼容一直沒改。

 

四、解決方法

查詢文檔,這個問題其實有挺多繞過的方法,整理學習一下。

1. 改短字段後創建

對於表設計明顯不合理的情況,這是比較合理的方法。

查看字段實際最大長度

select max ( length ( text_column ) ) mx_char_length,
       max ( lengthb ( text_column ) ) mx_byte_length
from   some_table;

改短字段長度

alter table some_table modify text_column nvarchar2(100);

如果確實不能改短,下面有一些workaround,但它們都有各自的限制,需要根據實際選擇。

 

2. 不使用online創建

對於前面的例子,nvarchar2(2000)的字段可以用這個方法繞過最大長度限制。但也就像之前寫的,這個長度沒辦法創建聯合索引,以後也不能使用online rebuild,對於7*24小時的系統,如果是大表可能難以接受。

 

3. 使用更大的block size存儲索引

將索引存放在單獨的表空間,並將表空間block size設爲16k或32k,當然也需要先設置好DB_nK_CACHE_SIZE 參數。這種方法打破了DB的標準化,可能會使運維管理更加複雜。

alter system set DB_32K_CACHE_SIZE=256M;
create tablespace tblsp_32k_blocks datafile 'tblsp_32k_blocks' size 1m blocksize 32768;
create index text_index on some_table(text_column) tablespace tblsp_32k_blocks;

 

4. 創建基於STANDARD_HASH函數的索引

standard_hash函數會返回一個固定長度的值,在等值查詢時能用到該索引。

create index text_index on some_table(standard_hash(text_column));

select * from some_table where  text_column = 'this';
----------------------------------------------------------                              
| Id  | Operation                           | Name       |                              
----------------------------------------------------------                              
|   0 | SELECT STATEMENT                    |            |                              
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| SOME_TABLE |                              
|*  2 |   INDEX RANGE SCAN                  | TEXT_INDEX |                              
----------------------------------------------------------

但範圍查詢和like查詢都用不到

select * from some_table where text_column >= 't' and text_column<'u';
----------------------------------------                                             
| Id  | Operation         | Name       |                                             
----------------------------------------                                             
|   0 | SELECT STATEMENT  |            |                                             
|*  1 |  TABLE ACCESS FULL| SOME_TABLE |                                             
---------------------------------------- 
 
select * from some_table where text_column like 'this%';
----------------------------------------                                                 
| Id  | Operation         | Name       |                                                 
----------------------------------------                                                 
|   0 | SELECT STATEMENT  |            |                                                 
|*  1 |  TABLE ACCESS FULL| SOME_TABLE |                                                 
----------------------------------------

 

5. 創建基於substr函數的索引

對於範圍查詢,可以使用基於substr函數的索引(like依然用不到),但是它可能會導致查詢效率較低。

create index text_substr_index on some_table(substr(text_column,1,10));

select * from some_table where text_column = 'this';
-----------------------------------------------------------------                      
| Id  | Operation                           | Name              |                      
-----------------------------------------------------------------                      
|   0 | SELECT STATEMENT                    |                   |                      
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| SOME_TABLE        |                      
|*  2 |   INDEX RANGE SCAN                  | TEXT_SUBSTR_INDEX |                      
-----------------------------------------------------------------                      
                                                                                        
select * from some_table where text_column >= 't' and text_column < 'u';
-----------------------------------------------------------------           
| Id  | Operation                           | Name              |           
-----------------------------------------------------------------           
|   0 | SELECT STATEMENT                    |                   |           
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| SOME_TABLE        |           
|   2 |   INDEX RANGE SCAN                  | TEXT_SUBSTR_INDEX |           
-----------------------------------------------------------------
 
select * from some_table where text_column like 'this%';
----------------------------------------                    
| Id  | Operation         | Name       |                    
----------------------------------------                    
|   0 | SELECT STATEMENT  |            |                    
|   1 |  TABLE ACCESS FULL| SOME_TABLE |                    
----------------------------------------

 

6. 定義virtual列

虛擬列其實就是對列進行運算或者在列上使用函數,oracle在運行時纔會計算該列的值。我們可以用standard_hash函數建虛擬列,然後對該列建普通索引。虛擬列相比函數索引有以下好處:

  • 優化器可獲得虛擬列的統計信息
  • 能夠看到索引值,更易於理解
alter table some_table add text_hash varchar2(40 char) as (standard_hash(text_column));
create index vc_text_hash_index on some_table (text_hash);

 

7. 使用全文索引(Oracle Text Index)

創建時需要指定indextype子句,查詢時使用contains操作符

create index oracle_text_index on some_table(text_column) indextype is ctxsys.context;

select * from some_table where contains (text_column,'value')>0;

 

參考

https://blog.csdn.net/tnndwdl/article/details/78452967

https://blogs.oracle.com/sql/how-to-fix-ora-01450-maximum-key-length-6398-exceeded-errors

ORA-01450 and Maximum Key Length - How it is Calculated (文檔 ID 136158.1)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章