Oracle 23c부터 테이블과 뷰의 최대 칼럼 수가 1000개에서 4096개로 늘어났습니다.
The maximum number of columns allowed in a database table or view has been increased to 4096. This feature allows you to build applications that can store attributes in a single table with more than the previous 1000-column limit. Some applications, such as Machine Learning and streaming IoT application workloads, may require the use of de-normalized tables with more than 1000 columns. You now have the ability to store a larger number of attributes in a single row which for some applications may simplify application design and implementation.
이 기능은 MAX_COLUMNS 파라미터로 설정할 수 있습니다.
-- 1-1
SELECT value, default_value, issys_modifiable, description
FROM v$parameter
WHERE name = 'max_columns';
NAME VALUE DEFAULT_VALUE ISSYS_MODIFIABLE DESCRIPTION
----------- -------- ------------- ---------------- --------------------------------------------------
max_columns STANDARD STANDARD FALSE maximum number of columns allowed in table or view
1개의 행이 선택되었습니다.
-- 1-2
SELECT name, ordinal, value, isdefault
FROM v$parameter_valid_values
WHERE name = 'max_columns';
NAME ORDINAL VALUE ISDEFAULT
----------- ------- -------- ---------
max_columns 1 STANDARD TRUE
max_columns 2 EXTENDED FALSE
2 행이 선택되었습니다.
아래와 같이 칼럼 수가 1000개인 t1 테이블을 생성하겠습니다. 참고로 칼럼 수가 1000개보다 많은 테이블을 생성하면 "ORA-01792: 테이블, 뷰에 지정 가능한 열의 최대수는 1000입니다" 에러가 발생합니다.
-- 2
DROP TABLE t1 PURGE;
DECLARE
v_sql_text CLOB;
BEGIN
v_sql_text := 'CREATE TABLE t1 (c1';
FOR i IN 2 .. 1000 LOOP v_sql_text := v_sql_text || ', c' || i; END LOOP;
v_sql_text := v_sql_text || ') AS ';
v_sql_text := v_sql_text || 'SELECT 1';
FOR i IN 2 .. 1000 LOOP v_sql_text := v_sql_text || ', 1'; END LOOP;
v_sql_text := v_sql_text || ' FROM DUAL';
EXECUTE IMMEDIATE v_sql_text;
END;
/
PL/SQL 처리가 정상적으로 완료되었습니다.
테스트를 위해 max_columns 파라미터를 EXTENDED로 설정하고 DB를 재기동하겠습니다.
-- 3: SYS
ALTER SYSTEM SET max_columns = EXTENDED SCOPE = SPFILE;
SHUTDOWN IMMEDIATE;
STARTUP;
아래와 같이 칼럼이 4096개인 t2 테이블을 생성하겠습니다.
-- 4
DROP TABLE t2 PURGE;
DECLARE
v_sql_text CLOB;
BEGIN
v_sql_text := 'CREATE TABLE t2 (c1';
FOR i IN 2 .. 4096 LOOP v_sql_text := v_sql_text || ', c' || i; END LOOP;
v_sql_text := v_sql_text || ') AS ';
v_sql_text := v_sql_text || 'SELECT 1';
FOR i IN 2 .. 4096 LOOP v_sql_text := v_sql_text || ', 1'; END LOOP;
v_sql_text := v_sql_text || ' FROM DUAL';
EXECUTE IMMEDIATE v_sql_text;
END;
/
PL/SQL 처리가 정상적으로 완료되었습니다.
아래는 DBMS_SPACE.SPACE_USAGE 프로시저 수행 결과입니다. t1 테이블은 1 블록, t2 테이블은 3 블록에 데이터를 저장합니다.
-- 5
+--------------------------+-------+-------+
|COLUMN_NAME | T1| T2|
+--------------------------+-------+-------+
|total_blocks | 8.00 | 8.00 |
|total_bytes | 64.00k| 64.00k|
|unused_blocks | 4.00 | 2.00 |
|unused_bytes | 32.00k| 16.00k|
|last_used_extent_file_id | 15| 15|
|last_used_extent_block_id | 152| 168|
|last_used_block | 4.00 | 6.00 |
|full_blocks | 1.00 | 3.00 | -- !
|full_bytes | 8.00k| 24.00k|
+--------------------------+-------+-------+
상세 분석을 위해 아래와 같이 블록 덤프를 생성하겠습니다.
-- 6-1
SELECT DISTINCT
b.file#, block#
FROM user_objects a
, v$bh b
WHERE a.object_name = 'T1'
AND b.objd = a.data_object_id
AND b.class# = 1;
FILE# BLOCK#
----- ------
15 155
1개의 행이 선택되었습니다.
-- 6-2
ALTER SYSTEM DUMP DATAFILE 15 BLOCK 155;
-- 6-3
SELECT DISTINCT
b.file#, block#
FROM user_objects a
, v$bh b
WHERE a.object_name = 'T2'
AND b.objd = a.data_object_id
AND b.class# = 1;
FILE# BLOCK#
----- ------
15 171
15 172
15 173
3 행이 선택되었습니다.
-- 6-4
ALTER SYSTEM DUMP DATAFILE 15 BLOCK 171;
ALTER SYSTEM DUMP DATAFILE 15 BLOCK 172;
ALTER SYSTEM DUMP DATAFILE 15 BLOCK 173;
아래는 블록 덤프 결과입니다. t1 테이블은 한 블록에서 4개의 로우가 intra-block row chaining됩니다. t2 테이블은 두 블록에서 각각 8개의 로우가 intra-block row chaining되고, 세 블록이 inter-block row chaining됩니다.
-- 8-1: T1, 15, 155 -> 1000
0xe:pti[0] nrow=4 offs=0
0x12:pri[0] offs=0x1c7a -- 255
0x14:pri[1] offs=0x1974 -- 255
0x16:pri[2] offs=0x166e -- 255
0x18:pri[3] offs=0x13aa -- 235
-- 8-2: T2, 15, 171 -> 2040 = 255 * 8
0xe:pti[0] nrow=8 offs=0
0x12:pri[0] offs=0x22
0x14:pri[1] offs=0x328
0x16:pri[2] offs=0x62e
0x18:pri[3] offs=0x934
0x1a:pri[4] offs=0xc3a
0x1c:pri[5] offs=0xf40
0x1e:pri[6] offs=0x1246
0x20:pri[7] offs=0x154c
-- 8-3: T2, 15, 172 -> 2040 = 255 * 8
0xe:pti[0] nrow=8 offs=0
0x12:pri[0] offs=0x22
0x14:pri[1] offs=0x328
0x16:pri[2] offs=0x62e
0x18:pri[3] offs=0x934
0x1a:pri[4] offs=0xc3a
0x1c:pri[5] offs=0xf40
0x1e:pri[6] offs=0x1246
0x20:pri[7] offs=0x154c
-- 8-4: T2, 15, 173 -> 16
0xe:pti[0] nrow=1 offs=0
0x12:pri[0] offs=0x1f4d
아래는 t1, t2 테이블을 조회하는 쿼리의 실행 계획입니다. row chaining에 의해 추가 블록 I/O가 발생하는 것을 확인할 수 있습니다.
-- 9-1
SELECT * FROM t1;
--------------------------------------------------------------
| Id | Operation | Name | A-Rows | Buffers | Reads |
--------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 2 |
| 1 | TABLE ACCESS FULL| T1 | 1 | 6 | 2 | -- 6 - 2 = 4, CEIL (1000 / 255) = 4
--------------------------------------------------------------
Rows (1st) Row Source Operation
---------- ---------------------------------------------------
1 TABLE ACCESS FULL T1 (cr=6 pr=2 pw=0 time=317 us starts=1 cost=2 size=3000 card=1)
WAIT #140239421487704: nam='db file sequential read' ela=11 file#=15 block#=154 blocks=1 obj#=130873 tim=395499883429
WAIT #140239421487704: nam='db file sequential read' ela=6 file#=15 block#=155 blocks=1 obj#=130873 tim=395499883480
-- 9-2
SELECT * FROM t2;
--------------------------------------------------------------
| Id | Operation | Name | A-Rows | Buffers | Reads |
--------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 21 | 4 |
| 1 | TABLE ACCESS FULL| T2 | 1 | 21 | 4 | -- 21 - 4 = 17, CEIL (4096 / 255) = 17
--------------------------------------------------------------
Rows (1st) Row Source Operation
---------- ---------------------------------------------------
1 TABLE ACCESS FULL T2 (cr=21 pr=4 pw=0 time=660 us starts=1 cost=3 size=12288 card=1)
WAIT #140673112127992: nam='db file sequential read' ela=9 file#=15 block#=170 blocks=1 obj#=130877 tim=395504377853
WAIT #140673112127992: nam='db file scattered read' ela=13 file#=15 block#=171 blocks=3 obj#=130877 tim=395504377956
참고로 inter-block row chaining에 의한 블록 I/O는 table fetch continued row 통계 값을 증가시킵니다.
-- 10
+--------------------------+----------+----------+------+
|NAME | T1| T2|CLASS |
+--------------------------+----------+----------+------+
|session logical reads | 6.00 | 21.00 |User |
|table fetch continued row | 0.00 | 2.00 |SQL |
+--------------------------+----------+----------+------+
마지막으로 max_columns 파라미터를 STANDARD로 설정하려면 칼럼이 1000개보다 많은 테이블이 존재하지 않아야 합니다.
-- 11
ALTER SYSTEM SET max_columns = STANDARD SCOPE = SPFILE;
ORA-60471: max_columns can not be set to STANDARD as there are one or more objects with more than 1000 columns
DROP TABLE tuna.t2 PURGE;
ALTER SYSTEM SET max_columns = STANDARD SCOPE = SPFILE;
SHUTDOWN IMMEDIATE;
STARTUP;
아래 링크에서 블록 덤파 파일을 다운로드할 수 있습니다.