- 뷰에는 Before Trigger 를 생성할 수 없습니다.
- :NEW 값을 업데이트 할 수 있다고 하네요.
- :OLD 값은 업데이트 할 수 없습니다.
2010년 10월 19일 화요일
Oracle_030. Triggers #1 - BEFORE_AFTER INSERT
2010년 10월 15일 금요일
ORACLE_029. UTILITY - DATA PUMP #1 - EXPDP
- FULL - 전체 DATABASE 를 EXPORT 합니다. (EXP_FULL_DATABASE 권한 필요)
- SCHEMAS - 스키마 단위로 EXPORT 를 수행합니다.
- TABLES - 해당 테이블만 EXPORT 합니다.
- TABLESPACES - 테이블 스페이스를 EXPORT 합니다.
- DUMPFILE - 떨궈질 파일명을 지정합니다. 파일명에 %U 옵션을 붙이면 01-99 번까지 자동으로 번호가 붙습니다. %U는 병렬로 받을시, 파일명을 일일이 열거할 필요가 없어 편리합니다. :)
- STATUS - 진행 상황을 보여줍니다. 단위는 초 입니다. 20으로 설정하면 20초마다 진행되는 상황을 보여줍니다.
- LOGFILE - 로그를 기록할 파일명을 지정합니다.
- FILESIZE - 떨어질 덤프파일의 최대 용량을 지정합니다. 파일명을 여러개 지정했다면 첫번째 지정된 파일이 꽉 찼을경우 두번째 파일에 기록합니다. %U로 파일명을 지정하면 오라클이 알아서 숫자를 붙여줍니다.
- QUERY - 해당 조건에 해당하는 ROW들만 EXPORT 합니다. (QUERY=\"WHERE EMP_ID=3727\") - Unix 계열일 경우 이처럼 Escape 문자가 필요합니다.
- JOB_NAME - 해당 EXPORT DP작업명을 지정합니다. 지정하지 않을경우 오라클이 임의로 작업명을 붙여줍니다.
- PARALLEL - 병렬처리 갯수를 지정합니다. 병렬처리를 4개로 했다면 DUMPFILE 옵션에도 파일을 4개 지정해야 합니다. %U를 지정했다면 오라클이 알아서 번호를 붙여 생성합니다.
- EXCLUDE / INCLUDE - 특정 오브젝트들을 제외하거나 포함시킬 수 있습니다.
VIEW, PACKAGE, PROCEDURE, FUNCTION, GRANT, TRIGGER,
CONSTRAINT, REF_CONSTRAINT
(EXCLUDE = Object_type:Obj_name,...)
(EXCLUDE = INDEX:"LIKE 'DEPT%'") - DIRECTORY - 덤프를 받을 디렉토리명을 지정합니다. 디렉토리를 지정하는 방법은 밑에 기술하도록 하겠습니다.
- NETWORK_LINK - 원격지에 있는 DB의 데이터를 EXPORT 받을때 사용합니다. TNS 명이 아닌 DBMS상에 있는 DATABASE LINK 명을 사용합니다. 단 원격 DATABASE 로 접속하는 해당 계정이 EXPORT 권한을 가지고 있어야 합니다. (계정은 DATABASE LINK 생성시 지정하는거 아시죠?)
- TRANSPORT_TABLESPACES - Y 지정시 TTS 모드가 됩니다.
SQL> GRANT READ, WRITE ON DIRECTORY PUMPDIR TO SCOTT;
- 전체 DATABASE EXPORT
expdp dba/dba full=y dumpfile=fullexp_%U.dmp directory = pumpdir logfile=fullexp.log parallel=10 status = 20 - 특정 테이블 파티션 EXPORT
expdp dba/dba tables=scott.employees:partition03 dumpfile=expdp_%U.dmp directory = pumpdir logfile=partition.log parallel=5 status = 20 - 인덱스를 제외한 테이블 EXPORT
expdp dba/dba tables=scott.department dumpfile=department_%U.dmp directory=pumpdir logfile=department.log parallel = 5 exclude=index
2009년 12월 23일 수요일
ORACLE_TIP : How to add and drop online redo log members and groups?
|
원본 링크 : 클릭! 요새 계속 퍼오기만 하는군요!
How to add and drop online redo log members and groups?
| |||||
|
How to add and drop online redo log members and groups? Redo log Files: The Oracle server maintains online redo log files to minimize the loss of data in the database. The redo log files record all changes made to data in the database buffer cache with some exceptions; for example, in the case of direct writes. Redo log files are used in a situation such as an instance failure to recover committed data that has not been written to the data files. The redo log files are only used for recovery. What are online Redo Log Groups? � A set of identical copies of online redo log files is called an online redo log group. � The background process LGWR concurrently writes the same information to all online redo log files in a group. � The Oracle server needs a minimum of two online redo log file groups for the normal operation of a database. Oracle suggests keeping three groups. What are online Redo Log Members? � Each online redo log file in a group is called a member. � Each member in a group has identical log sequence number and the same size. The log sequence number is assigned each time the server starts writing to a log group to identify each redo log file uniquely. The current log sequence number is stored in the control file and in the header of all data files. How to obtain Information about Groups and Members? The following query returns information about the online redo log file from the control file: SQL> select group#, sequence#, bytes, members, status from v$log;
3 rows selected. The following query returns information about all members of a group: SVRMGR> select * from v$logfile;
GROUP# STATUS TYPE MEMBER ---------- ------- ------- ---------------------------------------- 3 ONLINE /u02/ORADATA/VTEST/REDO03.LOG 2 ONLINE /u03/ORADATA/VTEST/REDO02.LOG 1 ONLINE /u03/ORADATA/VTEST/REDO01.LOG 3 rows selected. Adding Online Redo Log Groups : In some cases you might need to create additional log file groups. For example, adding groups can solve availability problems. To create a new group of online redo log files use the following command: ALTER DATABASE ADD LOGFILE ('/DISK1/log3a.rdo','/DISK2/log3b.rdo') size 1M; Adding Online Redo Log Members:You can add new member to an existing redo log file group using the following command: ALTER DATABASE ADD LOGFILE MEMBER Dropping Online Redo Log Groups :To drop a group of online redo log files use the following command: ALTER DATABASE DROP LOGFILE GROUP 3; Dropping Online Redo Log Members: To drop a member of an online redo log group use the following command: ALTER DATABASE DROP LOGFILE MEMBER Please note when dropping redo log groups and redo log files, there must be at least two redo log groups and each redo log group must have at least one log member. |
| About author:
Vigyan Kaushik is an Oracle certified professional serving IT industry for more than 11 years as an Oracle DBA and System Administrator. He has expertise in Database Designing, Administration, Networking, Tuning, Implementation, Maintenance with web deployment activities on different Unix flavors as well as on Windows Operating Systems.
|
2009년 12월 16일 수요일
ORACLE_TIP : The Secrets of Oracle Row Chaining and Migration(영문)
원본링크
Overview
If you notice poor performance in your Oracle database Row Chaining and Migration may be one of several reasons, but we can prevent some of them by properly designing and/or diagnosing the database.
Row Migration & Row Chaining are two potential problems that can be prevented. By suitably diagnosing, we can improve database performance. The main considerations are:
- What is Row Migration & Row Chaining ?
- How to identify Row Migration & Row Chaining ?
- How to avoid Row Migration & Row Chaining ?
Migrated rows affect OLTP systems which use indexed reads to read singleton rows. In the worst case, you can add an extra I/O to all reads which would be really bad. Truly chained rows affect index reads and full table scans.
Oracle Block
The Operating System Block size is the minimum unit of operation (read /write) by the OS and is a property of the OS file system. While creating an Oracle database we have to choose the «Data Base Block Size» as a multiple of the Operating System Block size. The minimum unit of operation (read /write) by the Oracle database would be this «Oracle block», and not the OS block. Once set, the «Data Base Block Size» cannot be changed during the life of the database (except in case of Oracle 9i). To decide on a suitable block size for the database, we take into consideration factors like the size of the database and the concurrent number of transactions expected.
The database block has the following structure (within the whole database structure)
Header
Header contains the general information about the data i.e. block address, and type of segments (table, index etc). It Also contains the information about table and the actual row (address) which that holds the data.
Free Space
Space allocated for future update/insert operations. Generally affected by the values of PCTFREE and PCTUSED parameters.
Data
Actual row data.
FREELIST, PCTFREE and PCTUSED
While creating / altering any table/index, Oracle used two storage parameters for space control.
- PCTFREE - The percentage of space reserved for future update of existing data.
- PCTUSED - The percentage of minimum space used for insertion of new row data.
This value determines when the block gets back into the FREELISTS structure.
- FREELIST - Structure where Oracle maintains a list of all free available blocks.
Oracle will first search for a free block in the FREELIST and then the data is inserted into that block. The availability of the block in the FREELIST is decided by the PCTFREE value. Initially an empty block will be listed in the FREELIST structure, and it will continue to remain there until the free space reaches the PCTFREE value.
When the free space reach the PCTFREE value the block is removed from the FREELIST, and it is re-listed in the FREELIST table when the volume of data in the block comes below the PCTUSED value.
Oracle use FREELIST to increase the performance. So for every insert operation, oracle needs to search for the free blocks only from the FREELIST structure instead of searching all blocks.
Row Migration
We will migrate a row when an update to that row would cause it to not fit on the block anymore (with all of the other data that exists there currently). A migration means that the entire row will move and we just leave behind the «forwarding address». So, the original block just has the rowid of the new block and the entire row is moved.
Full Table Scans are not affected by migrated rows
The forwarding addresses are ignored. We know that as we continue the full scan, we'll eventually get to that row so we can ignore the forwarding address and just process the row when we get there. Hence, in a full scan migrated rows don't cause us to really do any extra work -- they are meaningless.
Index Read will cause additional IO's on migrated rows
When we Index Read into a table, then a migrated row will cause additional IO's. That is because the index will tell us «goto file X, block Y, slot Z to find this row». But when we get there we find a message that says «well, really goto file A, block B, slot C to find this row». We have to do another IO (logical or physical) to find the row.
Row Chaining
A row is too large to fit into a single database block. For example, if you use a 4KB blocksize for your database, and you need to insert a row of 8KB into it, Oracle will use 3 blocks and store the row in pieces. Some conditions that will cause row chaining are: Tables whose rowsize exceeds the blocksize. Tables with LONG and LONG RAW columns are prone to having chained rows. Tables with more then 255 columns will have chained rows as Oracle break wide tables up into pieces. So, instead of just having a forwarding address on one block and the data on another we have data on two or more blocks.
Chained rows affect us differently. Here, it depends on the data we need. If we had a row with two columns that was spread over two blocks, the query:
SELECT column1 FROM table
where column1 is in Block 1, would not cause any «table fetch continued row». It would not actually have to get column2, it would not follow the chained row all of the way out. On the other hand, if we ask for:
SELECT column2 FROM table
and column2 is in Block 2 due to row chaining, then you would in fact see a «table fetch continued row»
Example
The following example was published by Tom Kyte, it will show row migration and chaining. We are using an 4k block size:
SELECT name,value
FROM v$parameter
WHERE name = 'db_block_size';
NAME VALUE
-------------- ------
db_block_size 4096Create the following table with CHAR fixed columns:
CREATE TABLE row_mig_chain_demo (
x int PRIMARY KEY,
a CHAR(1000),
b CHAR(1000),
c CHAR(1000),
d CHAR(1000),
e CHAR(1000)
);That is our table. The CHAR(1000)'s will let us easily cause rows to migrate or chain. We used 5 columns a,b,c,d,e so that the total rowsize can grow to about 5K, bigger than one block, ensuring we can truly chain a row.
INSERT INTO row_mig_chain_demo (x) VALUES (1);
INSERT INTO row_mig_chain_demo (x) VALUES (2);
INSERT INTO row_mig_chain_demo (x) VALUES (3);
COMMIT;We are not interested about seeing a,b,c,d,e - just fetching them. They are really wide so we'll surpress their display.
column a noprint
column b noprint
column c noprint
column d noprint
column e noprintSELECT * FROM row_mig_chain_demo;
X
----------
1
2
3Check for chained rows:
SELECT a.name, b.value FROM v$statname a, v$mystat b WHERE a.statistic# = b.statistic# AND lower(a.name) = 'table fetch continued row';NAME VALUE ---------------------------------------------------------------- ---------- table fetch continued row 0Now that is to be expected, the rows came out in the order we put them in (Oracle full scanned this query, it processed the data as it found it). Also expected is the table fetch continued row is zero. This data is so small right now, we know that all three rows fit on a single block. No chaining.
Demonstration of the Row Migration
Now, lets do some updates in a specific way. We want to demonstrate the row migration issue and how it affects the full scan:
UPDATE row_mig_chain_demo SET a = 'z1', b = 'z2', c = 'z3' WHERE x = 3;
COMMIT;
UPDATE row_mig_chain_demo SET a = 'y1', b = 'y2', c = 'y3' WHERE x = 2;
COMMIT;
UPDATE row_mig_chain_demo SET a = 'w1', b = 'w2', c = 'w3' WHERE x = 1;
COMMIT;Note the order of updates, we did last row first, first row last.
SELECT * FROM row_mig_chain_demo;
X
----------
3
2
1SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 0Interesting, the rows came out «backwards» now. That is because we updated row 3 first. It did not have to migrate, but it filled up block 1. We then updated row 2. It migrated to block 2 with row 3 hogging all of the space, it had to. We then updated row 1, it migrated to block 3. We migrated rows 2 and 1, leaving 3 where it started.
So, when Oracle full scanned the table, it found row 3 on block 1 first, row 2 on block 2 second and row 1 on block 3 third. It ignored the head rowid piece on block 1 for rows 1 and 2 and just found the rows as it scanned the table. That is why the table fetch continued row is still zero. No chaining.
So, lets see a migrated row affecting the «table fetch continued row»:
SELECT * FROM row_mig_chain_demo WHERE x = 3;
X
----------
3SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 0This was an index range scan / table access by rowid using the primary key. We didn't increment the «table fetch continued row» yet since row 3 isn't migrated.
SELECT * FROM row_mig_chain_demo WHERE x = 1;
X
----------
1SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 1Row 1 is migrated, using the primary key index, we forced a «table fetch continued row».
Demonstration of the Row Chaining
UPDATE row_mig_chain_demo SET d = 'z4', e = 'z5' WHERE x = 3;
COMMIT;Row 3 no longer fits on block 1. With d and e set, the rowsize is about 5k, it is truly chained.
SELECT x,a FROM row_mig_chain_demo WHERE x = 3;
X
----------
3SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 1We fetched column «x» and «a» from row 3 which are located on the «head» of the row, it will not cause a «table fetch continued row». No extra I/O to get it.
SELECT x,d,e FROM row_mig_chain_demo WHERE x = 3;
SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 2Now we fetch from the «tail» of the row via the primary key index. This increments the «table fetch continued row» by one to put the row back together from its head to its tail to get that data.
Now let's see a full table scan - it is affected as well:
SELECT * FROM row_mig_chain_demo;
X
----------
3
2
1SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 3The «table fetch continued row» was incremented here because of Row 3, we had to assemble it to get the trailing columns. Rows 1 and 2, even though they are migrated don't increment the «table fetch continued row» since we full scanned.
SELECT x,a FROM row_mig_chain_demo;
X
----------
3
2
1SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 3No «table fetch continued row» since we didn't have to assemble Row 3, we just needed the first two columns.
SELECT x,e FROM row_mig_chain_demo;
X
----------
3
2
1SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 4But by fetching for d and e, we incemented the «table fetch continued row». We most likely have only migrated rows but even if they are truly chained, the columns you are selecting are at the front of the table.
So, how can you decide if you have migrated or truly chained?
Count the last column in that table. That'll force to construct the entire row.
SELECT count(e) FROM row_mig_chain_demo;
COUNT(E)
----------
1SELECT a.name, b.value
FROM v$statname a, v$mystat b
WHERE a.statistic# = b.statistic#
AND lower(a.name) = 'table fetch continued row';NAME VALUE
---------------------------------------------------------------- ----------
table fetch continued row 5Analyse the table to verify the chain count of the table:
ANALYZE TABLE row_mig_chain_demo COMPUTE STATISTICS;
SELECT chain_cnt
FROM user_tables
WHERE table_name = 'ROW_MIG_CHAIN_DEMO';CHAIN_CNT
----------
3Three rows that are chained. Apparently, 2 of them are migrated (Rows 1 and 2) and one is truly chained (Row 3).
Total Number of «table fetch continued row» since instance startup?
The V$SYSSTAT view tells you how many times, since the system (database) was started you did a «table fetch continued row» over all tables.
sqlplus system/<password>
SELECT 'Chained or Migrated Rows = '||value
FROM v$sysstat
WHERE name = 'table fetch continued row';Chained or Migrated Rows = 31637
You could have 1 table with 1 chained row that was fetched 31'637 times. You could have 31'637 tables, each with a chained row, each of which was fetched once. You could have any combination of the above -- any combo.
Also, 31'637 - maybe that's good, maybe that's bad. it is a function of
- how long has the database has been up
- how many rows is this as a percentage of total fetched rows.
For example if 0.001% of your fetched are table fetch continued row, who cares!Therefore, always compare the total fetched rows against the continued rows.
SELECT name,value FROM v$sysstat WHERE name like '%table%';
NAME VALUE
---------------------------------------------------------------- ----------
table scans (short tables) 124338
table scans (long tables) 1485
table scans (rowid ranges) 0
table scans (cache partitions) 10
table scans (direct read) 0
table scan rows gotten 20164484
table scan blocks gotten 1658293
table fetch by rowid 1883112
table fetch continued row 31637table lookup prefetch client count 0
How many Rows in a Table are chained?
The USER_TABLES tells you immediately after an ANALYZE (will be null otherwise) how many rows in the table are chained.
ANALYZE TABLE row_mig_chain_demo COMPUTE STATISTICS;
SELECT chain_cnt,
round(chain_cnt/num_rows*100,2) pct_chained,
avg_row_len, pct_free , pct_used
FROM user_tables
WHERE table_name = 'ROW_MIG_CHAIN_DEMO';
CHAIN_CNT PCT_CHAINED AVG_ROW_LEN PCT_FREE PCT_USED
---------- ----------- ----------- ---------- ----------
3 100 3691 10 40PCT_CHAINED shows 100% which means all rows are chained or migrated.
List Chained Rows
You can look at the chained and migrated rows of a table using the ANALYZE statement with the LIST CHAINED ROWS clause. The results of this statement are stored in a specified table created explicitly to accept the information returned by the LIST CHAINED ROWS clause. These results are useful in determining whether you have enough room for updates to rows.
Creating a CHAINED_ROWS Table
To create the table to accept data returned by an ANALYZE ... LIST CHAINED ROWS statement, execute the UTLCHAIN.SQL or UTLCHN1.SQL script in $ORACLE_HOME/rdbms/admin. These scripts are provided by the database. They create a table named CHAINED_ROWS in the schema of the user submitting the script.
create table CHAINED_ROWS (
owner_name varchar2(30),
table_name varchar2(30),
cluster_name varchar2(30),
partition_name varchar2(30),
subpartition_name varchar2(30),
head_rowid rowid,
analyze_timestamp date
);After a CHAINED_ROWS table is created, you specify it in the INTO clause of the ANALYZE statement.
ANALYZE TABLE row_mig_chain_demo LIST CHAINED ROWS;
SELECT owner_name, table_name, head_rowid FROM chained_rows OWNER_NAME TABLE_NAME HEAD_ROWID ------------------------------ ------------------------------ ------------------ SCOTT ROW_MIG_CHAIN_DEMO AAAPVIAAFAAAAkiAAA SCOTT ROW_MIG_CHAIN_DEMO AAAPVIAAFAAAAkiAAB
How to avoid Chained and Migrated Rows?
Increasing PCTFREE can help to avoid migrated rows. If you leave more free space available in the block, then the row has room to grow. You can also reorganize or re-create tables and indexes that have high deletion rates. If tables frequently have rows deleted, then data blocks can have partially free space in them. If rows are inserted and later expanded, then the inserted rows might land in blocks with deleted rows but still not have enough room to expand. Reorganizing the table ensures that the main free space is totally empty blocks.
The ALTER TABLE ... MOVE statement enables you to relocate data of a nonpartitioned table or of a partition of a partitioned table into a new segment, and optionally into a different tablespace for which you have quota. This statement also lets you modify any of the storage attributes of the table or partition, including those which cannot be modified using ALTER TABLE. You can also use the ALTER TABLE ... MOVE statement with the COMPRESS keyword to store the new segment using table compression.
- ALTER TABLE MOVE
First count the number of Rows per Block before the ALTER TABLE MOVE
SELECT dbms_rowid.rowid_block_number(rowid) "Block-Nr", count(*) "Rows"
FROM row_mig_chain_demo
GROUP BY dbms_rowid.rowid_block_number(rowid) order by 1;
Block-Nr Rows
---------- ----------
2066 3
Now, de-chain the table, the ALTER TABLE MOVE rebuilds the row_mig_chain_demo table in a new segment, specifying new storage parameters:
ALTER TABLE row_mig_chain_demo MOVE
PCTFREE 20
PCTUSED 40
STORAGE (INITIAL 20K
NEXT 40K
MINEXTENTS 2
MAXEXTENTS 20
PCTINCREASE 0);
Table altered.
Again count the number of Rows per Block after the ALTER TABLE MOVE
SELECT dbms_rowid.rowid_block_number(rowid) "Block-Nr", count(*) "Rows"
FROM row_mig_chain_demo
GROUP BY dbms_rowid.rowid_block_number(rowid) order by 1;
Block-Nr Rows
---------- ----------
2322 1
2324 1
2325 1
- Rebuild the Indexes for the Table
Moving a table changes the rowids of the rows in the table. This causes indexes on the table to be marked UNUSABLE, and DML accessing the table using these indexes will receive an ORA-01502 error. The indexes on the table must be dropped or rebuilt. Likewise, any statistics for the table become invalid and new statistics should be collected after moving the table.
ANALYZE TABLE row_mig_chain_demo COMPUTE STATISTICS;
ERROR at line 1:
ORA-01502: index 'SCOTT.SYS_C003228' or partition of such index is in unusable
state
This is the primary key of the table which must be rebuilt.
ALTER INDEX SYS_C003228 REBUILD;Index altered.
ANALYZE TABLE row_mig_chain_demo COMPUTE STATISTICS;Table analyzed.
SELECT chain_cnt,
round(chain_cnt/num_rows*100,2) pct_chained,
avg_row_len, pct_free , pct_used
FROM user_tables
WHERE table_name = 'ROW_MIG_CHAIN_DEMO';
CHAIN_CNT PCT_CHAINED AVG_ROW_LEN PCT_FREE PCT_USED
---------- ----------- ----------- ---------- ----------
1 33.33 3687 20 40If the table includes LOB column(s), this statement can be used to move the table along with LOB data and LOB index segments (associated with this table) which the user explicitly specifies. If not specified, the default is to not move the LOB data and LOB index segments.
Detect all Tables with Chained and Migrated Rows
Using the CHAINED_ROWS table, you can find out the tables with chained or migrated rows.
- Create the CHAINED_ROWS table
cd $ORACLE_HOME/rdbms/admin
sqlplus scott/tiger
@utlchain.sql
- Analyse all or only your Tables
SELECT 'ANALYZE TABLE '||table_name||' LIST CHAINED ROWS INTO CHAINED_ROWS;'
FROM user_tables
/
ANALYZE TABLE ROW_MIG_CHAIN_DEMO LIST CHAINED ROWS INTO CHAINED_ROWS;
ANALYZE TABLE DEPT LIST CHAINED ROWS INTO CHAINED_ROWS;
ANALYZE TABLE EMP LIST CHAINED ROWS INTO CHAINED_ROWS;
ANALYZE TABLE BONUS LIST CHAINED ROWS INTO CHAINED_ROWS;
ANALYZE TABLE SALGRADE LIST CHAINED ROWS INTO CHAINED_ROWS;
ANALYZE TABLE DUMMY LIST CHAINED ROWS INTO CHAINED_ROWS;
Table analyzed.
Show the RowIDs for all chained rows
This will allow you to quickly see how much of a problem chaining is in each table. If chaining is prevalent in a table, then that table should be rebuild with a higher value for PCTFREE
SELECT owner_name,
table_name,
count(head_rowid) row_count
FROM chained_rows
GROUP BY owner_name,table_name
/
OWNER_NAME TABLE_NAME ROW_COUNT
------------------------------ ------------------------------ ----------
SCOTT ROW_MIG_CHAIN_DEMO 1
Conclusion
Migrated rows affect OLTP systems which use indexed reads to read singleton rows. In the worst case, you can add an extra I/O to all reads which would be really bad. Truly chained rows affect index reads and full table scans.
Row migration is typically caused by UPDATE operation
Row chaining is typically caused by INSERT operation.
SQL statements which are creating/querying these chained/migrated rows will degrade the performance due to more I/O work.
To diagnose chained/migrated rows use ANALYZE command , query V$SYSSTAT view
To remove chained/migrated rows use higher PCTFREE using ALTER TABLE MOVE.
2009년 12월 9일 수요일
ORACLE_TIP : DYNAMIC SGA
OTN Discussion Forums 에서 SGA에 관한 문서를 긁어옵니다. 원본은 여기를 클릭하세요~
----------------------------------------------------------------------------------------
PURPOSE
Oracle 9i의 새 기능인 동적으로 SGA 파라미터들을 변경하는 방법에
대하여 알아보기로 한다.
Explanation
Oracle 8i까지는 Buffer Cache, Shared Pool, Large Pool 등과 같은 SGA
파라미터들에 대해 그 크기를 동적으로, db가 운영 중인 상태에서는 변경할
수가 없었다.
즉, 이러한 파라미터들을 변경하려면 db를 shutdown하고 initSID.ora 화일에
서 그 크기를 다시 설정하고, 이 파라미터를 이용해서 db 인스턴스를 restart
해야만 했었다.
Oracle 9i에서는 DBA가 ALTER SYSTEM 명령을 이용해서 SGA 파라미터의 크기
를 동적으로 변경할 수 있게 되었다. 이 특정을 'Dynamic SGA'라고 부른다.
SGA 전체의 최대 크기(SGA_MAX_SIZE)를 정의하고 그 한도 내에서 파라미터의
크기를 변경할 수 있는 것이다. 데이타베이스를 shutdown/startup 없이 작업
이 가능하기 때문에 'Planned Downtime'을 줄이는 한 방법으로도 이해할 수
있다.
이 글에서는 SGA에 할당할 수 있는 최소 단위인 'Granule'의 개념을 살펴보
고, 이 granule이 어떠한 방법에 의해 동적으로 할당되는지에 대해 알아보고
자 한다.
또한 Buffer Cache 파라미터 중 새로운 것과 이전 버전에 비해 달라진 내용
을 소개하기로 한다.
1. Granule
Granule은 가상 메모리 상의 연속된 공간으로, dynamic SGA 모델에서 할당할
수 있는 최소 단위이다. 이 granule의 크기는 SGA 전체의 추정값
(SGA_MAX_SIZE)에 따라 다음과 같이 구분된다.
4MB if estimated SGA size is < 128M
16MB otherwise
SGA의 Buffer Cache, Shared Pool, Large Pool 등의 파라미터는 이 granule
단위로 늘어나거나 줄어들 수 있다. (현재 dynamic SGA를 사용할 수 있는
SGA 관련 파라미터는 Buffer Cache, Shared Pool, Large Pool 세 가지이다.)
2. Dynamic SGA(DB_CACHE_SIZE, SHARED_POOL_SIZE)
DBA는 ALTER SYSTEM 명령을 통해 initSID.ora 화일에 정의된 SGA 관련 파라미
터 값을 동적으로 변경할 수 있다. SGA 파라미터의 크기를 늘려주기 위해서
는 필요한 만큼의 free granule이 존재해야만 하며, 현재 사용하고 있는 SGA
의 크기가 SGA_MAX_SIZE보다 작아야 한다. Free granule이 없다고 해서 다른
파라미터로부터 granule을 free시켜서 그 granule을 이용할 수 있는 것은 아
니다.
반드시 DBA가 명시적으로 free/allocate해야 한다.
다음의 예를 살펴보자. 설명을 단순화하기 위해 이 경우는 SGA가 Buffer
Cache와 Shared Pool로만 구성되었다고만 하자.
예) initSID.ora
SGA_MAX_SIZE = 128M
DB_CACHE_SIZE = 96M
SHARED_POOL_SIZE = 32M
Note : DB_CACHE_SIZE는 Oracle 9i에 새롭게 도입된 파라미터이다.
위와 같은 상태일 때 동적으로 SHARED_POOL_SIZE를 64M로 늘리면 에러가 발생
한다.
SQL> ALTER SYSTEM SET SHARED_POOL_SIZE=64M;
(insufficient memory error message)
이 에러는 SHARED_POOL_SIZE를 늘림으로써 전체 SGA의 크기가 SGA_MAX_SIZE
보다 커지기 때문에 발생한다. (96M + 64M > 128M)
이를 해결하기 위해서는 DB_CACHE_SIZE를 줄인 후, SHARED_POOL_SIZE를 늘린다.
SQL> ALTER SYSTEM SET DB_CACHE_SIZE=64M;
SQL> ALTER SYSTEM SET SHARED_POOL_SIZE=64M;
Note : DB_CACHE_SIZE가 shrink되는 동안에
ALTER SYSTEM SET SHARED_POOL_SIZE=64M;
를 하면 insufficient error가 발생할 수도 있다.
이 경우는 DB_CACHE_SIZE가 shrink된 후 다시 수행하면 정상적으로
수행이 된다.
Note : 위 예제의 경우 estimated SGA 크기가 128M 이상이므로, granule의
단위는 16M이다. 따라서 SGA 파라미터의 크기를 16M의 정수배로 했다.
16M의 정수배가 아닌 경우는 지정한 값보다 큰 값에 대해 16M의
정수배 중 가장 가까운 값을 택하게 된다.
즉, 아래 두 문장의 결과는 똑같다.
SQL> ALTER SYSTEM SET SHARED_POOL_SIZE=64M;
SQL> ALTER SYSTEM SET SHARED_POOL_SIZE=49M;
Note : LARGE_POOL_SIZE 와 JAVA_POOL_SIZE 파라미터는 동적으로 변경하는
것이 불가능하다.
1) Dynamic Shared Pool
인스턴스 start 후, Shared Pool의 크기는 다음과 같은 명령에 의해 동적으
로 변경(grow or shrink)될 수 있다.
ALTER SYSTEM SET SHARED_POOL_SIZE=64M;
다음과 같은 제약 사항이 있다.
- 실제 할당되는 크기는 16M의 정수배가 된다.
- 전체 SGA의 크기는 SGA_MAX_SIZE를 초과할 수는 없다.
2) Dynamic Buffer Cache
인스턴스 start 후, Buffer Cache의 크기는 다음과 같은 명령에 의해 동적으
로 변경(grow or shrink)될 수 있다.
ALTER SYSTEM SET DB_CACHE_SIZE=96M;
다음과 같은 제약 사항이 있다.
- 실제 할당되는 크기는 16M의 정수배가 된다.
- 전체 SGA의 크기는 SGA_MAX_SIZE를 초과할 수는 없다.
- DB_CACHE_SIZE는 0이 될 수 없다.
3. Buffer Cache 파라미터의 변경된 내용
여기서는 Buffer Cache 파라미터와 관련하여 Oracle 9i에 의미가 없어진 파라
미터와 새롭게 추가된 파라미터, 그리고 dynamic SGA 중 Buffer Cache와 관련
이 있는 부분에 대해 기술하고자 한다.
1) Deprecated Buffer Cache Parameters
다음의 세 가지 파라미터는 backward compatibility를 위해 존재하는 것으
로, 차후 의미가 없어진다.
- DB_BLOCK_BUFFERS
- BUFFER_POOL_KEEP
- BUFFER_POOL_RECYCLE
위의 파라미터들이 정의되어 있으면 이 값들을 사용하게 될 것이다. 하지만,
다음에 나올 새로운 파라미터들을 사용하는 것이 좋으며, 만일 위 파라미터
(DB_BLOCK_BUFFERS, BUFFER_POOL_KEEP, BUFFER_POOL_RECYCLE) 값들을 사용
한다면 이 글에서 설명한 dynamic SGA 특징을 사용할 수는 없다. 또한
initSID.ora 화일에 위 파라미터들과 새로운 파라미터를 동시에 기술한다면
에러가 발생한다.
2) New Buffer Cache Sizing Parameters
다음의 세 파라미터가 추가되었다. 이 파라미터들은 primary block size에
대한 buffer cache 정보를 다루고 있다.
- DB_CACHE_SIZE
- DB_KEEP_CACHE_SIZE
- DB_RECYCLE_CACHE_SIZE
DB_CACHE_SIZE 파라미터에 지정된 값은 primary block size에 대한 default
Buffer Pool의 크기를 의미한다. 또한 이전 버전과 마찬가지로 KEEP과
RECYCLE buffer pool을 둘 수 있는데, 이는 DB_KEEP_CACHE_SIZE,
DB_RECYCLE_CACHE_SIZE 라는 파라미터를 이용한다.
이전 버전과 다른 점은 이전 버전의 경우 각각의 파라미터
(DB_BLOCK_BUFFERS, BUFFER_POOL_KEEP,BUFFER_POOL_RECYCLE)에 정의된 값들
이 buffer 갯수(즉, 실제 메모리 크기를 구하려면 db_block_size를 곱했어야
했다. )였는데 반해 이제는 구체적인 메모리 크기이다.
또한 이전에는 DB_BLOCK_BUFFERS가 BUFFER_POOL_KEEP, BUFFER_POOL_RECYCLE
의 값을 포함하고 있었지만, 이제는 DB_CACHE_SIZE가 DB_KEEP_CACHE_SIZE,
DB_RECYCLE_CACHE_SIZE를 포함하고 있지 않다.
즉, 각각의 파라미터들은 독립적이다.
Note : Oracle 9i부터는 multiple block size(2K, 4K, 8K, 16K, 32K)를 지원한다.
위에서 언급한 primary block size는 DB_BLOCK_SIZE에 의해 정해진 block
size를 의미한다. (SYSTEM tablespace는 이 block size를 이용한다.)
3) Dynamic Buffer Cache Size Parameters
바로 위에서 언급한 세 파라미터는 아래와 같이 ALTER SYSTEM 명령에 의해
동적으로 변경 가능하다.
SQL> ALTER SYSTEM SET DB_CACHE_SIZE=96M;
SQL> ALTER SYSTEM SET DB_KEEP_CACHE_SIZE=16M;
SQL> ALTER SYSTEM SET DB_RECYCLE_CACHE_SIZE=16M;
Example
none
Reference Documents
<Note:148495.1>
2009년 11월 27일 금요일
ORACLE_TIP : ROWNUM
오라클 사이트에서 ROWNUM 에 관한 재미있는 문서가 있어서 긁어 왔습니다. 원본을 보고 싶으신 분은 http://www.oracle.com/technology/global/kr/oramag/oracle/06-sep/o56asktom.html 를 클릭하세요.
-------------------------------------------------------------
TECHNOLOGY: Ask Tom
ROWNUM의 동작 원리와 활용 방법
저자 - Tom Kyte
오라클 전문가 Tom Kyte가 ROWNUM의 동작 원리와 활용 방법에 대해 설명합니다.
이번 호의 Ask Tom 컬럼은 지금까지와는 조금 다른 내용을 담고 있습니다. 필자는 오라클 데이터베이스에서 Top-N 쿼리와 페이지네이션(pagination) 쿼리를 구현하는 방법에 대해 자주 질문을 받곤 합니다. 하나의 컬럼을 통해 이러한 질문에 한꺼번에 대답하기 위한 방편으로, < Effective Oracle by Design (Oracle Press, 2003)> 의 내용을 인용하기로 했습니다. 컬럼의 포맷에 맞게 책의 내용이 다소 수정되었음을 참고하시기 바랍니다.
결과 셋의 제한
ROWNUM은 오라클 데이터베이스가 제공하는 마술과도 같은 컬럼입니다. 이 때문에 많은 사용자들이 문제를 겪기도 합니다. 하지만 그 원리와 활용 방법을 이해한다면 매우 유용하게 사용할 수 있습니다. 필자는 주로 두 가지 목적으로 ROWNUM을 사용합니다.
- Top-N 프로세싱: 이 기능은 다른 일부 데이터베이스가 제공하는 LIMIT 구문과 유사합니다.
- 쿼리 내에서의 페이지네이션(pagination) – 특히 웹과 같은 "stateless" 환경에서 자주 활용됩니다. 필자는 asktom.oracle.com 웹 사이트에서도 이 테크닉을 사용하고 있습니다.
두 가지 활용 방안을 설명하기 전에, 먼저 ROWNUM의 동작 원리에 대해 살펴 보기로 하겠습니다
ROWNUM의 동작 원리
ROWNUM은 쿼리 내에서 사용 가능한 (실제 컬럼이 아닌) 가상 컬럼(pseudocolumn)입니다. ROWNUM에는 숫자 1, 2, 3, 4, ... N의 값이 할당됩니다. 여기서 N 은 ROWNUM과 함께 사용하는 로우의 수를 의미합니다. ROWNUM의 값은 로우에 영구적으로 할당되지 않습니다(이는 사람들이 많이 오해하는 부분이기도 합니다). 테이블의 로우는 숫자와 연계되어 참조될 수 없습니다. 따라서 테이블에서 "row 5"를 요청할 수 있는 방법은 없습니다. "row 5"라는 것은 존재하지 않기 때문입니다.
또 ROWNUM 값이 실제로 할당되는 방법에 대해서도 많은 사람들이 오해를 하고 있습니다. ROWNUM 값은 쿼리의 조건절이 처리되고 난 이후, 그리고 sort, aggregation이 수행되기 이전에 할당됩니다. 또 ROWNUM 값은 할당된 이후에만 증가(increment) 됩니다. 따라서 아래 쿼리는 로우를 반환하지 않습니다.
select * from t where ROWNUM > 1;
첫 번째 로우에 대해 ROWNUM > 1의 조건이 True가 아니기 때문에, ROWNUM은 2로 증가하지 않습니다. 아래와 같은 쿼리를 생각해 봅시다.
select ..., ROWNUM from t where <where clause> group by <columns> having <having clause> order by <columns>;
이 쿼리는 다음과 같은 순서로 처리됩니다.
1. FROM/WHERE 절이 먼저 처리됩니다.
2. ROWNUM이 할당되고 FROM/WHERE 절에서 전달되는 각각의 출력 로우에 대해 증가(increment) 됩니다.
3. SELECT가 적용됩니다.
4. GROUP BY 조건이 적용됩니다.
5. HAVING이 적용됩니다.
6. ORDER BY 조건이 적용됩니다.
따라서 아래와 같은 쿼리는 에러가 발생할 수 밖에 없습니다.
select * from emp where ROWNUM <= 5 order by sal desc;
이 쿼리는 가장 높은 연봉을 받는 다섯 명의 직원을 조회하기 위한 Top-N 쿼리로 작성되었습니다. 하지만 실제로 쿼리는 5 개의 레코드를 랜덤하게(조회되는 순서대로) 반환하고 salary를 기준으로 정렬합니다. 이 쿼리를 위해서 사용되는 가상코드(pseudocode)가 아래와 같습니다.
ROWNUM = 1
for x in
( select * from emp )
loop
exit when NOT(ROWNUM <= 5)
OUTPUT record to temp
ROWNUM = ROWNUM+1
end loop
SORT TEMP
위에서 볼 수 있듯 처음의 5 개 레코드를 가져 온후 바로 sorting이 수행됩니다. 쿼리에서 "WHERE ROWNUM = 5" 또는 "WHERE ROWNUM > 5"와 같은 조건은 의미가 없습니다. 이는 ROWNUM 값이 조건자(predicate) 실행 과정에서 로우에 할당되며, 로우가 WHERE 조건에 의해 처리된 이후에만 increment 되기 때문입니다.
올바르게 작성된 쿼리가 아래와 같습니다.
select *
from
( select *
from emp
order by sal desc )
where ROWNUM <= 5;
위 쿼리는 salary를 기준으로 EMP를 내림차순으로 정렬한 후, 상위의 5 개 레코드(Top-5 레코드)를 반환합니다. 아래에서 다시 설명되겠지만, 오라클 데이터베이스가 실제로 전체 결과 셋을 정렬하지 않습니다. (오라클 데이터베이스는 좀 더 지능적인 방식으로 동작합니다.) 하지만 사용자가 얻는 결과는 동일합니다.
ROWNUM을 이용한 Top-N 쿼리 프로세싱
일반적으로 Top-N 쿼리를 실행하는 사용자는 다소 복잡한 쿼리를 실행하고, 그 결과를 정렬한 뒤 상위의 N 개 로우만을 반환하는 방식을 사용합니다. ROWNUM은 Top- N쿼리를 위해 최적화된 기능을 제공합니다. ROWNUM을 사용하면 대량의 결과 셋을 정렬하는 번거로운 과정을 피할 수 있습니다. 먼저 그 개념을 살펴보고 예제를 통해 설명하기로 하겠습니다.
아래와 같은 쿼리가 있다고 가정해 봅시다.
select ... from ... where ... order by columns;
또 이 쿼리가 반환하는 데이터가 수천 개, 수십만 개, 또는 그 이상에 달한다고 가정해 봅시다. 하지만 사용자가 실제로 관심 있는 것은 상위 N개(Top 10, Top 100)의 값입니다. 이 결과를 얻기 위한 방법에는 두 가지가 있습니다.
- 클라이언트 애플리케이션에서 쿼리를 실행하고 상위 N 개의 로우만을 가져오도록 명령
- • 쿼리를 인라인 뷰(inline view)로 활용하고, ROWNUM을 이용하여 결과 셋을 제한 (예: SELECT * FROM (your_query_here) WHERE ROWNUM <= N)
두 번째 접근법은 첫 번째에 비해 월등한 장점을 제공합니다. 그 이유는 두 가지입니다. 첫 번째로, ROWNUM을 사용하면 클라이언트의 부담이 줄어듭니다. 데이터베이스에서 제한된 결과 값만을 전송하기 때문입니다. 두 번째로, 데이터베이스에서 최적화된 프로세싱 방법을 이용하여 Top N 로우를 산출할 수 있습니다. Top-N 쿼리를 실행함으로써, 사용자는 데이터베이스에 추가적인 정보를 전달하게 됩니다. 그 정보란 바로 "나는N 개의 로우에만 관심이 있고, 나머지에 대해서는 관심이 없다"는 메시지입니다. 이제, 정렬(sorting) 작업이 데이터베이스 서버에서 어떤 원리로 실행되는지 설명을 듣고 나면 그 의미를 이해하실 수 있을 것입니다. 샘플 쿼리에 위에서 설명한 두 가지 접근법을 적용해 보기로 합시다.
select * from t order by unindexed_column;
여기서 T가 1백만 개 이상의 레코드를 저장한 큰 테이블이라고, 그리고 각각의 레코드가 100 바이트 이상으로 구성되어 있다고 가정해 봅시다. 그리고 UNINDEXED_COLUMN은 인덱스가 적용되지 않은 컬럼이라고, 또 사용자는 상위 10 개의 로우에만 관심이 있다고 가정하겠습니다. 오라클 데이터베이스는 아래와 같은 순서로 쿼리를 처리합니다.
1. T에 대해 풀 테이블 스캔을 실행합니다.
2. UNINDEXED_COLUMN을 기준으로 T를 정렬합니다. 이 작업은 "full sort"로 진행됩니다.
3. Sort 영역의 메모리가 부족한 경우 임시 익스텐트를 디스크에 스왑하는 작업이 수행됩니다.
4. 임시 익스텐트를 병합하여 상위 10 개의 레코드를 확인합니다.
5.쿼리가 종료되면 임시 익스텐트에 대한 클린업 작업을 수행합니다. .
결과적으로 매우 많은 I/O 작업이 발생합니다. 오라클 데이터베이스가 상위 10 개의 로우를 얻기 위해 전체 테이블을 TEMP 영역으로 복사했을 가능성이 높습니다.
그럼 다음으로, Top-N 쿼리를 오라클 데이터베이스가 개념적으로 어떻게 처리할 수 있는지 살펴 보기로 합시다.
select * from (select * from t order by unindexed_column) where ROWNUM < :N;
오라클 데이터베이스가 위 쿼리를 처리하는 방법이 아래와 같습니다.
1. 앞에서와 마찬가지로 T에 대해 풀-테이블 스캔을 수행합니다(이 과정은 피할 수 없습니다).
2. :N 엘리먼트의 어레이(이 어레이는 메모리에 저장되어 있을 가능성이 높습니다)에서 :N 로우만을 정렬합니다.
상위N 개의 로우는 이 어레이에 정렬된 순서로 입력됩니다. N +1 로우를 가져온 경우, 이 로우를 어레이의 마지막 로우와 비교합니다. 이 로우가 어레이의 N +1 슬롯에 들어가야 하는 것으로 판명되는 경우, 로우는 버려집니다. 그렇지 않은 경우, 로우를 어레이에 추가하여 정렬한 후 기존 로우 중 하나를 삭제합니다. Sort 영역에는 최대 N 개의 로우만이 저장되며, 따라서 1 백만 개의 로우를 정렬하는 대신N 개의 로우만을 정렬하면 됩니다.
이처럼 간단한 개념(어레이의 활용, N개 로우의 정렬)을 이용하여 성능 및 리소스 활용도 면에서 큰 이익을 볼 수 있습니다. (TEMP 공간을 사용하지 않아도 된다는 것을 차치하더라도) 1 백만 개의 로우를 정렬하는 것보다 10 개의 로우를 정렬하는 것이 메모리를 덜 먹는다는 것은 당연합니다.
아래의 테이블 T를 이용하면, 두 가지 접근법이 모두 동일한 결과를 제공하지만 사용되는 리소스는 극적인 차이를 보임을 확인할 수 있습니다.
create table t
as
select dbms_random.value(1,1000000)
id,
rpad('*',40,'*' ) data
from dual
connect by level <= 100000;
begin
dbms_stats.gather_table_stats
( user, 'T');
end;
/
Now enable tracing, via
exec
dbms_monitor.session_trace_enable
(waits=>true);
And then run your top-N query with ROWNUM:
select * from (select * from t order by id) where rownum <= 10;
마지막으로 상위 10 개의 레코드만을 반환하는 쿼리를 실행합니다.
declare
cursor c is
select *
from t
order by id;
l_rec c%rowtype;
begin
open c;
for i in 1 .. 10
loop
fetch c into l_rec;
exit when c%notfound;
end loop;
close c;
end;
/
이 쿼리를 실행한 후, TKPROF를 사용해서 트레이스 결과를 확인할 수 있습니다. 먼저 Top-N 쿼리 수행 후 확인한 트레이스 결과가 Listing 1과 같습니다.
Code Listing 1: ROWNUM을 이용한 Top-N 쿼리
select * from (select * from t order by id) where rownum <= 10 call count cpu elapsed disk query current rows -------- -------- ------- ------- ------- -------- -------- ------ Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 2 0.04 0.04 0 949 0 10 -------- -------- ------- ------- ------- -------- -------- ------ total 4 0.04 0.04 0 949 0 10 Rows Row Source Operation ----------------- --------------------------------------------------- 10 COUNT STOPKEY (cr=949 pr=0 pw=0 time=46997 us) 10 VIEW (cr=949 pr=0 pw=0 time=46979 us) 10 SORT ORDER BY STOPKEY (cr=949 pr=0 pw=0 time=46961 us) 100000 TABLE ACCESS FULL T (cr=949 pr=0 pw=0 time=400066 us)
이 쿼리는 전체 테이블을 읽어 들인 후, SORT ORDER BY STOPKEY 단계를 이용해서 임시 공간에서 사용되는 로우를 10 개로 제한하고 있습니다. 마지막 Row Source Operation 라인을 주목하시기 바랍니다. 쿼리가 949 번의 논리적 I/O를 수행했으며(cr=949), 물리적 읽기/쓰기는 전혀 발생하지 않았고(pr=0, pw=0), 불과 400066 백만 분의 일초 (0.04 초) 밖에 걸리지 않았습니다. 이 결과를 Listing 2의 실행 결과와 비교해 보시기 바랍니다.
Code Listing 2: ROWNUM을 사용하지 않은 쿼리
SELECT * FROM T ORDER BY ID call count cpu elapsed disk query current rows -------- -------- ------- ------- ------- -------- -------- ------ Parse 1 0.00 0.00 0 0 0 0 Execute 2 0.00 0.00 0 0 0 0 Fetch 10 0.35 0.40 155 949 6 10 -------- -------- ------- ------- ------- -------- -------- ------ total 13 0.36 0.40 155 949 6 10 Rows Row Source Operation ----------------- --------------------------------------------------- 10 SORT ORDER BY (cr=949 pr=155 pw=891 time=401610 us) 100000 TABLE ACCESS FULL T (cr=949 pr=0 pw=0 time=400060 us) Elapsed times include waiting for the following events: Event waited on Times ------------------------------ ------------ direct path write temp 33 direct path read temp 5
결과가 완전히 다른 것을 확인하실 수 있습니다. "elapsed/CPU time"이 크게 증가했으며, 마지막 Row Source Operation 라인을 보면 그 이유를 이해할 수 있습니다. 정렬 작업은 디스크 상에서 수행되었으며, 물리적 쓰기(physical write) 작업이 "pw=891"회 발생했습니다. 또 다이렉트 경로를 통한 읽기/쓰기 작업이 발생했습니다. (10 개가 아닌) 100,000 개의 레코드가 디스크 상에서 정렬되었으며, 이로 인해 쿼리의 실행 시간과 런타임 리소스가 급증하였습니다.
ROWNUM을 이용한 페이지네이션
필자가 ROWNUM을 가장 즐겨 사용하는 대상이 바로 페이지네이션(pagination)입니다. 필자는 결과 셋의 로우 N 에서 로우 M까지를 가져오기 위해 ROWNUM을 사용합니다. 쿼리의 일반적인 형식이 아래와 같습니다.
select *
from ( select /*+ FIRST_ROWS(n) */
a.*, ROWNUM rnum
from ( your_query_goes_here,
with order by ) a
where ROWNUM <=
:MAX_ROW_TO_FETCH )
where rnum >= :MIN_ROW_TO_FETCH;
where
여기서,
- FIRST_ROWS(N)는 옵티마이저에게 "나는 앞부분의 로우에만 관심이 있고, 그 중 N 개를 최대한 빨리 가져오기를 원한다"는 메시지를 전달하는 의미를 갖습니다.
- :MAX_ROW_TO_FETCH는 결과 셋에서 가져올 마지막 로우로 설정됩니다. 결과 셋에서 50 번째 – 60 번째 로우만을 가져오려 한다면 이 값은 60이 됩니다.
- :MIN_ROW_TO_FETCH는 결과 셋에서 가져올 첫 번째 로우로 설정됩니다. 결과 셋에서 50 번째 – 60 번째 로우만을 가져오려 한다면 이 값은 50이 됩니다.
이 시나리오는 웹 브라우저를 통해 접속한 사용자가 검색을 마치고 그 결과를 기다리고 있는 상황을 가정하고 있습니다. 따라서 첫 번째 결과 페이지(그리고 이어서 두 번째, 세 번째 결과 페이지)를 최대한 빨리 반환해야 할 것입니다. 쿼리를 자세히 살펴 보면, (처음의 :MAX_ROW_TO_FETCH 로우를 반환하는) Top-N 쿼리가 사용되고 있으며, 따라서 위에서 설명한 최적화된 기능을 이용할 수 있음을 알 수 있습니다. 또 네트워크를 통해 클라이언트가 관심을 갖는 로우만을 반환하며, 조회 대상이 아닌 로우는 네트워크로 전송되지 않습니다.
페이지네이션 쿼리를 사용할 때 주의할 점이 하나 있습니다. ORDER BY 구문은 유니크한 컬럼을 대상으로 적용되어야 합니다. 유니크하지 않은 컬럼 값을 대상으로 정렬을 수행해야 한다면 ORDER BY 조건에 별도의 조건을 추가해 주어야 합니다. 예를 들어 SALARY를 기준으로 100 개의 레코드를 정렬하는 상황에서 100 개의 레코드가 모두 동일한 SALARY 값을 갖는다면, 로우의 수를 20-25 개로 제한하는 것은 의미가 없을 것입니다. 여러 개의 중복된 ID 값을 갖는 작은 테이블을 예로 들어 설명해 보겠습니다.
SQL> create table t
2 as
3 select mod(level,5) id,
trunc(dbms_random.value(1,100)) data
4 from dual
5 connect by level <= 10000;
Table created.
ID 컬럼을 정렬한 후 148-150 번째 로우, 그리고 148–151 번째 로우를 쿼리해 보겠습니다.
SQL> select * 2 from 3 (select a.*, rownum rnum 4 from 5 (select id, data 6 from t 7 order by id) a 8 where rownum <= 150 9 ) 10 where rnum >= 148; ID DATA RNUM ------- ---------- ----------- 0 38 148 0 64 149 0 53 150 SQL> SQL> select * 2 from 3 (select a.*, rownum rnum 4 from 5 (select id, data 6 from t 7 order by id) a 8 where rownum <= 151 9 ) 10 where rnum >= 148; ID DATA RNUM ------- ---------- ----------- 0 59 148 0 38 149 0 64 150 0 53 151
로우 148의 경우 DATA=38의 결과가 반환되었습니다. 두 번째 쿼리에서는 DATA=59의 결과가 반환되었습니다. 두 가지 쿼리 모두 올바른 결과를 반환하고 있습니다. 쿼리는 데이터를 ID 기준으로 정렬한 후 앞부분의 147 개 로우를 버린 후 그 다음의 3 개 또는 4 개의 로우를 반환합니다. 하지만 ID에 중복값이 너무 많기 때문에, 쿼리는 항상 동일한 결과를 반환함을 보장할 수 없습니다. 이 문제를 해결하려면 ORDER BY 조건에 유니크한 값을 추가해 주어야 합니다. 위의 경우에는 ROWID를 사용하면 됩니다.
SQL> select * 2 from 3 (select a.*, rownum rnum 4 from 5 (select id, data 6 from t 7 order by id, rowid) a 8 where rownum <= 150 9 ) 10 where rnum >= 148; ID DATA RNUM ------- ---------- ----------- 0 45 148 0 99 149 0 41 150 SQL> SQL> select * 2 from 3 (select a.*, rownum rnum 4 from 5 (select id, data 6 from t 7 order by id, rowid) a 8 where rownum <= 151 9 ) 10 where rnum >= 148; ID DATA RNUM ------- ---------- ----------- 0 45 148 0 99 149 0 41 150 0 45 151
이제 쿼리를 반복 실행해도 동일한 결과를 보장할 수 있게 되었습니다. ROWID는 테이블 내에서 유니크한 값을 가집니다. 따라서 ORDER BY ID 조건과 ORDER BY ROWID 기준을 함께 사용함으로써 사용자가 기대한 순서대로 페이지네이션 쿼리의 결과를 확인할 수 있습니다.
|
ASK Tom 추가 자료: |
ROWNUM 개념 정리
지금까지 ROWNUM에 관련하여 아래와 같은 개념을 설명하였습니다.
- ROWNUM의 할당 원리와 잘못된 쿼리 작성을 피하는 방법
- ROWNUM이 쿼리 프로세싱에 미치는 영향과 웹 환경의 페이지네이션을 위한 활용 방안
- ROWNUM을 이용하여 Top N쿼리로 인한 TEMP 공간의 사용을 피하고 쿼리 응답 속도를 개선하는 방법
이번 오라클 매거진은 오픈월드 특별호로 기획되었습니다. 필자는 이번 행사를 통해 많은 사람들과 직접 대면할 수 있는 기회를 얻게 된 것을 무척 기쁘게 생각하고 있습니다. 행사에 참석하신다면 필자가 진행하는 세션에도 참석해 주실 것을 기대합니다. (필자는 데이터베이스 및 개발에 관련한 세션을 진행할 예정입니다.) 또 OTN이 주최하는 "Meet the Expert" 행사에도 참가할 것입니다. 이미 오래 전부터 오라클 오픈월드 행사에 참여해 왔지만, 일대일 또는 그룹으로 사용자와 만날 수 있다는 것은 언제나 즐거운 경험입니다. 필자가 진행하는 세션과 OTN 이벤트의 스케줄을 한 번 확인해 보시기 바랍니다.
또 필자는 블로그(tkyte.blogspot.com)를 통해 행사 현장의 뉴스와 사진을 제공해 드리고 있습니다. 그 밖에 OTN 에서도 포드캐스트, 동영상, 프리젠테이션 자료와 같은 행사 컨텐트를 다운로드하실 수 있습니다.
Tom Kyte는 1993년부터 오라클에서 일해 왔습니다. 그는 현재 오라클 퍼블릭 섹터 그룹 담당 부사장으로 근무 중이며, Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions (Apress, 2005) , Effective Oracle by Design (Oracle Press, 2003)와 같은 전문서를 집필하였습니다.
2009년 11월 18일 수요일
ORACLE_027. Managing Tables Part - I
Creating Tables
Creating a Table
다음 구문을 입력하면, admin_emp 라는 이름의 테이블을 admin_tbs 테이블 스페이스의 hr 스키마로 테이블을 생성할 수 있습니다.
CREATE TABLE hr.admin_emp (
empno NUMBER(5) PRIMARY KEY,
ename VARCHAR2(15) NOT NULL,
job VARCHAR2(10),
mgr NUMBER(5),
hiredate DATE DEFAULT (sysdate),
sal NUMBER(7,2),
comm NUMBER(7,2),
deptno NUMBER(3) NOT NULL CONSTRAINT admin_dept_fkey
REFERENCE hr.department (department_id) )
TABLESPACE dmin_tbs
STORAGE ( INITAIL 50K
NEXT 50K
MAXEXTENTS 10
PCTINCREASE 25 );
Creating a Temporary Table
임시 테이블을 만드는 것도 가능합니다. 임시 테이블로 정의를 하면 모든 세션에 사용가능하지만 임시 테이블의 데이터는 해당 데이터를 테이블에 넣은 세션의 사용자만 볼 수 있습니다. CREATE GLOBAL TEMPORARY TABLE 구문을 통해서 임시 테이블을 생성할 수 있습니다. ON COMMIT 키워드의 옵션에 따라 트렌젝션 의존적인지 세션 의존적인지를 결정할 수 있습니다.
- ON COMMIT DELETE ROWS
트렌젝션이 COMMIT 된 순간 해당 임시 테이블의 모든 행은 삭제됩니다. - ON COMMIT PRESERVE ROWS
세션이 종료될 경우 임시 테이블에 있는 모든 행은 삭제됩니다.
다음 예제는 트랜젝션 의존적인 임시 테이블을 만드는 방법입니다.
CREATE GLOBAL TEMPORARY TABLE admin_work_area
(startdate DATE,
enddate DATE,
class CHAR(20))
ON COMMIT DELETE ROWS;
임시 테이블에 인덱스가 만들어지기도 합니다. 해당 인덱스도 임시이고 테이블에 있는 데이터에 기반하여 세션이나 트랜젝션이 유효할 동안만 존재합니다.
Parallelizing Table Creation
테이블을 만들때 AS SELECT 단서를 이용할 경우 PARALLEL 구문을 주어 수행할 수 있습니다. CREATE TABLE ... AS SELECT 구문은 두 파트로 이루어져 있습니다. 첫번째는 CREATE 파트(DDL), 두번째는 SELECT 파트(query) 입니다. 오라클은 이 두 파트를 병렬로 수행할 수 있습니다. 다음의 내용중 하나라도 해당사항에 포함된다면 CREATE 파트는 병렬로 수행할 수 있습니다.
- PARALLEL 단서가 CREATE TABLE ... AS SELECT 구문에 포함 되었을 경우
- ALTER SESSION FORCE PARALLEL DDL 구문이 정의되었을 경우
쿼리 파트가 다음 사항을 모두 만족할 경우 병렬로 수행됩니다.
- 쿼리가 병렬수행의 힌트절(PARALLEL 또는 PARALLEL_INDEX)을 가지고 있을경우나 CREATE 파트가 PARALLEL 단서를 가지고 있을 경우, 또는 스키마 오브젝트가 참조하는 쿼리가 PARALLEL 정의가 되어 있을 경우.
- 최소 하나의 테이블이 여러개의 파티션을 풀 테이블 스켄하거나 인덱스 범위 스켄을 하도록 지정되어 있을 경우
테이블을 병렬로 만들 경우 병렬 정의(PARALLEL)가 테이블에 정의됩니다. 어떤 다음 DML 이나 쿼리가 테이블에 사용된다고 하여도 - 병렬수행이 가능하다면 - 병렬로 수행하려 할 것 입니다.
다음은 병렬수행을 하는 테이블을 만드는 방법입니다.
CREATE TABLE hr.admin_emp_dept
PARALLEL
AS SELECT * FROM hr.employees
WHERE department_id = 10;
Automatically Collecting Statistics on Tables
PL/SQL 패키지인 DBMS_STATS 는 코스트 기반의 최적화를 위한 통계정보를 생성하고 관리할 수 있게 해 줍니다. 이 패키지를 이용하여 통계정보를 수집, 변경, 확인, 익스포트, 임포트 그리고 삭제를 할 수 있습니다. 또한 수집된 통계정보에 이름을 부여하여 통계정보를 구분할 수 있습니다.
CREATE (혹은 ALTER) TABLE 구문에 MONITORING 단서를 달아서 DBMS_STATS 가 자동으로 테이블의 통계정보를 수집할 수 있게 할 수 있습니다.
모니터링을 해제하기 위해서는 NOMONITORING 단서를 달면 됩니다.
Altering Tables
ALTER TABLE 구문을 이용하여 테이블을 변경할 수 있습니다. 테이블을 변경하기 위해 해당 테이블은 해당 스키마에 포함되어 있어야 하거나 오브젝트에 대한 ALTER 권한을 가지거나 ALTER ANY TABLE 시스템 권한이 있어야 합니다.
테이블을 변경하는 이유는 다양합니다.
- 물리적인 특성을 변경하기 위해 (PCTFREE, PCTUSED, INITRANS, MAXTRANS, 혹은 스토리지 파라메터)
- 테이블을 새로운 세그먼트나 새로운 테이블스페이스로의 이동
- 익스텐트를 할당하거나 사용하지 않는 공간을 해제하기 위해
- 컬럼의 추가, 이름 변경, 컬럼의 정의를 변경할때 (데이터 타입, 길이, 기본값, NOT NULL 제약조건)
- 테이블의 로깅 옵션을 변경할때
- CACHE/NOCACHE 특성을 병경할때
- 테이블과 연관된 제약조건들을 변경하거나 추가
- 병렬처리의 DEGREE 값을 변경하기 위해
- 통계정보의 수집/비수집(MONITORING/NOMONITORING)
- 테이블명 변경
- IOT(Index-Organized Table)의 특성 변경하거나 추가
- 대외 테이블의 특성 변경
- LOB 컬럼의 변경 혹은 추가
- 오브젝트 타임의 변경 및 추가
Altering Physical Attributes of a Table
테이블의 데이터블록 공간을 파라메터(PCTFREE 와 PCTUSED)로 변경시키면 데이터가 들어있는 블록을 보함한 테이블이 소유하고 있는 모든 공간에 대한 설정값이 변경됩니다. 하지만 이미 데이터가 들어있는 데이터 블록의 재정렬 작업은 즉시 일어나진 않고 재정렬이 필요할 시에 재정렬이 수행됩니다.
테이블의 트랜젝션 엔트리 설정(INITRANS, MAXTRANS)을 변경할때 새로운 INITRANS 값은 새로운 MAXTRANS 값이 테이블의 모든 블록에 적용될동안 순차적으로 적용됩니다.
INITIAL 과 MINEXTENTS 파라메터는 변경할 수 없습니다. 다른 스토리지 파라메터(NEXT, PCTINCREASE)는 다음번에 추가로 생성되는 테이블의 익스텐트에만 적용이 됩니다. 다음 익스텐트의 크기 할당량은 현재의 NEXT 와 PCTINCREASE 값에 의해 정의되고 첫 생성시의 기본값에 영향을 받지 않습니다.
Moving a Table to a New Segment or Tablespace
ALTER TABLE ... MOVE 구문은 파티션되지 않은 테이블의 데이터를 새로운 세그먼트에 위치시키거나 선택적으로 다른 테이블스페이스에 위치시킬 수 있습니다. 이 문은 또한 ALTER TABLE 구문으로는 변경할 수 없는 테이블 저장소에 관련된 특성을 변경할 수 있게 해 줍니다.
다음 예제는 hr.admin_emp 테이블을 새로운 세그먼트로 옮기며 새로운 저장공간에 관한 파라메터를 설정합니다.
ALTER TABLE hr.admin_emp MOVE
STORAGE ( INITIAL 20K
NEXT 40K
MINEXTENTS 2
MAXEXTENTS 20
PCTINCREASE 0 );
테이블이 LOB 컬럼을 가지고 있을시 사용자가 정의해 줄 경우에 LOB 데이터나 LOB 인덱스를 새로운 세그먼트로 옮길 수 있습니다. 정의하지 않으면 LOB 데이터와 LOB 인덱스는 옮겨지지 않습니다.
Manually Allocating Storage for a Table
오라클은 필요할시에 테이블에 새로운 익스텐트를 동적으로 추가합니다. 하지만 사용자가 지금 당장 추가적으로 익스텐트를 할당할 수 있습니다. 새로운 익스텐트는 ALTER TABLE ... ALLOCATE EXTENT 구문을 통해 할당받습니다. 사용하지 않는 공간에 대한 익스텐트를 제거하기 위해서는 ATLER TABLE .. DEALLOCATE UNSED 구문을 사용하여 공간을 해제합니다.
Modifying an Existing Column's Definition
ALTER TABLE ... MODIFY 구문을 통해 기 존재하는 컬럼의 데이터 타입, 기본값 혹은 제약조건등의 정의를 변경할 수 있습니다.
또한 컬럼의 새로운 크기가 기존에 존재하는 컬럼의 데이터 크기를 만족한다면 컬럼의 크기 역시 바꿀 수 있습니다. 비어있지 않은 CHAR 컬럼의 크기를 조절하기 위해서는 BLANK_TRIMMING=TRUE 초기화 파라미터가 설정되어 있어야 합니다.
CHAR 컬럼의 크기를 증가시키는 것은 시간을 요구하는 명령이며 추가적인 공간이 요구될 수도 있는 작업이며 특히 많은 행을 가진 테이블에서 그렇습니다. 이는 각 행의 CHAR 의 값이 새로운 컬럼의 길이를 맞추기 위한 빈 공간이 삽입되기 때문입니다.
Adding Table Columns
기 존재하는 테이블에 컬럼을 추가하기 위해 ALTER TABLE ... ADD 구문을 사용합니다. 다음 구문은 hr.admin_emp 테이블에 bonus 컬럼을 추가하는 명령입니다.
ALTER TABLE hr.admin_emp ADD (bonus NUMBER(7,2));
테이블에 새로운 컬럼이 추가될시 DEFAULT 단서를 달지 않았다면 초기 값은 NULL 입니다. 기본값을 정의하면서 새로운 컬럼을 만들면 각 행에 기본값을 넣으며 컬럼을 추가합니다.
테이블이 아무 행도 가지고 있지 않거나 기본값을 정의했다면 NOT NULL 제약조건을 사용할 수 있습니다.
Renaming Table Columns
테이블이 이미 존재하는 컬럼의 이름을 바꿀 수 있습니다. ALTER TABLE .. RENAME COLUMN 구문을 이용하면 됩니다. 새로운 컬럼명은 이미 존재하는 다른 컬럼들과 이름이 겹쳐서는 안됩니다. RENAME COLUMN 에 추가적으로 달리는 명령은 없습니다.
다음 예제는 hr.admin_emp 테이블의 comm 컬럼명을 바꾸는 방법입니다.
ALTER TABLE hr.admin_emp RENAME COLUMN comm TO commission;
테이블의 컬럼명을 바꾸는 것은 해당 컬럼을 참조하는 의존적인 오브젝트를 무효로 만들 수 있습니다만 컬럼명을 바꿀시 오라클은 function-based indexes 와 check constraints 는 유효상태로 두기위해 딕셔너리 테이블은 업데이트를 합니다.
Dropping Table Columns
테이블이나 IOT (Index-organized Table)에서 더이상 필요하지 않은 컬럼이 있다면 삭제할 수 있습니다. 이는 데이터베이스의 공간 관리를 편하게 해 주며, 데이터를 익스포트/임포트하고 인덱스와 제약조건을 다시 만드는 행위를 막아줍니다.
SYS 가 소유하고 있는 테이블의 컬럼이나 한 테이블의 모든 컬럼을 제거할 수는 없습니다.
Removing Columns from table
ALTER TABLE ... DROP COLUMN 구문을 수행시 테이블의 각 행에서 컬럼에 대한 기술과 연관된 데이터는 삭제됩니다. 또한 한번에 여러개의 컬럼을 지울 수 있습니다. 다음 예제는 hr.admin_emp 테이블에서 컬럼을 제거하는 방법입니다.
ALTER TABLE hr.admin_emp DROP COLUMN sal;
다음 예제는 bonus 와 comm 컬럼을 한번에 삭제하는 방법입니다.
ALTER TABLE hr.admin_emp DROP COLUMN (bonus, comm);
Marking Columns Unused
큰 테이블에서 컬럼을 삭제하느라 시간이 오래 걸릴것이 걱정된다면 ALTER TABLE .. SET UNSED 구문을 사용하여 해결할 수 있습니다. 이 구문은 해당 컬럼을 사용하지 못하게 표시를 해 놓지만 데이터는 삭제되지 않고 이들이 사용하고 있는 공간을 해제하지도 않습니다. 하지만 표시된 컬럼은 더이상 쿼리나 데이터 딕셔너리 뷰를 통해 확인할 수 없으며 새롭게 만들게 되는 컬럼이 해당 이름을 사용할 수 있게 됩니다. 해당 컬럼에 속한 모든 제약조건, 인덱스, 그리고 통계정보 역시 삭제됩니다.
hiredate 와 mgr 컬럼을 사용하지 않는 컬럼으로 설정하기 위해 다음 방법을 사용하면 됩니다.
ALTER TABLE hr.admin_emp SET UNSED (hiredate, mgr);
후에 표시된 컬럼을 삭제하기 위해 ALTER TABLE .. DROP UNSED COLUMNS 구문을 통해 수행할 수 있습니다.
USER_UNUSED_COL_TABS, ALL_UNSED_COL_TABS, DBA_UNSED_COL_TABS 뷰를 통해 테이블에 사용하지 않게 표시된 컬럼이 몇개 있는지 확인할 수 있습니다. COUNT 필드는 해당 테이블의 사용하지 않는 컬럼 갯수 입니다.
Removing Unsed Columns
ALTER TABLE .. DROP UNUSED COLUMNS 은 오직 UNUSED 표시가 되어 있는 컬럼에만 적용됩니다. 물리적으로 테이블의 컬럼을 삭제하며 차지하고 있던 공간도 반환됩니다.
다음 예제는 CHECKPOINT 단서가 달려 있습니다. 이 옵션은 정의된 숫자만큼의 행에서 컬럼 데이터가 삭제되었을시 체크포인트가 발생하게 되며 이 예제에서는 250개의 행마다 수행후 체크포인트가 발생됩니다. 체크포인트를 통해 언도 스페이스의 공간이 전부 소진되는 것을 막으며 언두 로그의 크기를 줄이는 역할을 합니다.
ALTER TABLE hr.admin_emp DROP UNUSED COLUMNS CHECKPOINT 250;
Redefining Tables Online
때때론 상당히 크고 자주 사용하는 테이블들의 쿼리 성능을 향상시키거나 이들에게 DML 작업을 수행해야 할 때가 있습니다. 오라클은 이러한 테이블들의 정의를 온라인 상에서 변경할 수 있는 메커니즘을 제공하고 있습니다.
The DBMS_REDEFINITION Package
온라인상에서 테이블을 재정의 하는 방법은 DBMS_REDEFINITION PL/SQL 패키지를 사용하는 방법 입니다. 이 패키지를 수행하기 위해서는 EXECUTE_CATALOG_ROLE 권한이 있어야 하며 더불어 다음의 권한 역시 요구됩니다.
- CREATE ANY TABLE
- ALTER ANY TABLE
- DROP ANY TABLE
- LOCK ANY TABLE
- SELECT ANY TABLE
Steps for Online Redifinition of Tables
온라인 상에서 테이블 정의를 변경하기 위해서는 다음 순서를 따릅니다.
- 재정의 하는 두가지 방법중 다음 한가지를 선택합니다.
- 첫번째 방법은 재정의를 하는데 프라이머리 키를 사용하는 방법입니다. 이 방법은 테이블의 재정의 전후와 비교하여 같은 프라이머리 키 컬럼을 가지고 있게 됩니다. 이 방법은 재정의의 기본 방법입니다.
- 재정의의 두번째 방법은 Rowid를 이용하는 방법입니다. 이 방법은 IOT(Index Organized Table)이 아닐 경우에 사용할 수 있습니다. 또한 이 방법은 숨겨진 컬럼(M_ROW$$) 이 추가되며 테이블 재정의가 끝난후 이 컬럼을 UNSED 로 설정하거나 아니면 삭제하기를 권장합니다.
- 첫번째 방법은 재정의를 하는데 프라이머리 키를 사용하는 방법입니다. 이 방법은 테이블의 재정의 전후와 비교하여 같은 프라이머리 키 컬럼을 가지고 있게 됩니다. 이 방법은 재정의의 기본 방법입니다.
- DBMS_REDEFINITION.CAN_REDEF_TABLE() 프로시저를 이용하여 온라인 상에서 테이블을 변경하거나 재정의 하는 방법을 확인하도록 하십시요. 만약 해당 테이블이 온라인상에서 변경할 수 없는 테이블이라면 왜 온라인상에서 테이블을 변경할 수 없는지를 설명하는 에러 메세지가 출력될 것 입니다.
- 변경하고자 했던 테이블의 속성을 가진 (재정의 될 테이블과 같은 스키마로)임시 테이블을 만듭니다. 만약 재정의 될 테이블중 드롭될 컬럼이 있다면, 해당 컬럼은 임시 테이블에 만들지 않습니다. 컬럼이 추가된다면 임시 테이블에도 추가될 컬럼을 포함하여 생성하도록 합니다.
- DBMS_REDEFINITION.START_REDEF_TABLE() 를 다음의 값을 주어 호출하여 테이블 재정의를 시작합니다.
- 재정의될 테이블
- 임시 테이블
- 컬럼 맵핑
- 재정의 수단
- 트리거, 인덱스, 권한, 제약조건을 임시 테이블에 작성합니다. 임시 테이블에 포함된 어떤 제약조건이라도 반드시 Disable 상태로 만들어야 합니다. 재정의 과정이 성공하든 실패하든 임시 테이블에 존재하는 어떤 트리거들도 수행되지 않습니다.
재정의가 완료되면 임시테이블의 트리거, 제약조건, 인덱스, 그리고 권한을 재정의 된 테이블에 대체시킵니다. 임시테이블의 참조 제약조건은 재정의된 테이블에 전송하고 해당 제약조건들을 Enable 시킵니다. - DBMS_REDEFINITION.FINISH_REDEF_TABLE() 프로시저를 호출하여 테이블의 재정의를 완료합니다. 이 과정동안 원래의 테이블은 Exclusive Lock 을 걸게 됩니다. 이 과정에서 다음과 같은 일이 발생하게 됩니다.
- 원래의 테이블은 임시 테이블의 특성, 인덱스, 제약조건, 권한 그리고 트리거와 같이 재정의 됩니다.
- 임시 테이블의 제약조건들은 이제 재정의된 테이블도 해당 제약조건을 가지고 있으며 활성된 상태로 있습니다.
- 원래의 테이블은 임시 테이블의 특성, 인덱스, 제약조건, 권한 그리고 트리거와 같이 재정의 됩니다.
- 선택적으로 변경된 임시테이블의 인덱스, 트리거, 제약조건들은 재정의된 테이블에 함께 적용됩니다. ROWID를 이용하여 재정의를 했으면 재정의된 테이블은 숨겨진 컬럼(M_ROW$$)를 가지게 되고 이 숨겨진 컬럼은 다음과 같은 방법으로 사용하지 않게 마킹에 해두는 것을 권장합니다.
ALTER TABLE table_name SET UNUSED (M_ROW$$); - 다음은 재정의 과정의 마지막 결과 입니다.
- 원본 테이블의 형식과 모양은 임시 테이블과 같이 재 정의 됩니다.
- 트리거, 권한, 인덱스, 그리고 제약조건은 START_REDEF_TABLE() 이후에 정의되고 FINISH_REDEF_TABLE() 이전에 재정의될 테이블에 정의됩니다. 재정의 과정 전에 있던 임시 테이블의 참조 제약조건은 재정의 된 테이블에 적용되며 활성화 됩니다.
- 기존 재정의 되기 전의 원본 테이블이 가지고 있는 인덱스, 권한, 트리거, 제약조건등은 임시 테이블에 그대로 정의되며 사용자가 임시 테이블을 삭제할때 같이 제거됩니다. 원래의 제약조건등은 임시 테이블에 존재하게 되며, 비활성화 된 상태로 있게 됩니다.
- 재정의 전의 원본 테이블에 정의되 있던 PL/SQL 프로시저나 커서는 더이상 사용할 수 없습니다. 이들은 다음 언젠가 수행하기 전 자동으로 재확인 하게 됩니다. (재정의 과정의 결과로 테이블의 모양이 바뀌게 되면 재확인 절차는 실패하게 됩니다.)
Intermediate Syncronization
START_REDEF_TABLE() 을 호출하여 재정의 과정을 수행 후 FINISH_REDEF_TABLE() 이 호출되기 전까지 많은 수의 DML 명령이 원본 테이블에 수행될 수 있습니다. 이러한 경우 임시 테이블을 원본 테이블과 주기적으로 동기화를 시켜주는 것을 권장합니다. 이 방법은 DBMS_REDEFINITION.SYNC_INTERM_TABLE() 프로시저를 통해 수행할 수 있습니다. 이 프로시저를 호출함으로써 FINISH_REDEF_TABLE()을 호출하여 재정의를 마치는 사이의 시간을 절약할 수 있습니다.
Abort and Cleanup After Errors
재정의 과정에서 에러가 발생하게 되거나 재정의 작업을 취소하고 싶다면 DBMS_REDEFINITION.ABORT_REDEF_TABLE() 프로시저를 호출할 수 있습니다. 이 프로시저는 임시 로그와 재정의 작업과 관련된 테이블들을 제거합니다. 이 프로시저가 호출된 후 임시 테이블, 임시 테이블과 연관된 오브젝트들을 삭제할 수 있습니다.
Example of Online Table Redefinition
다음 예제는 empno, ename, job, deptno 컬럼을 가진 hr.admin_emp 테이블을 온라인중에 테이블을 재정의 하는 과정입니다. 이 테이블은 다음과 같이 재정의 됩니다.
- mgr, hiredate, sal, bonus 컬럼의 추가
- 새로운 컬럼은 0으로 초기화
- deptno 컬럼은 10 단위로 증가
- 재정의된 테이블은 empno 기준으로 파티션됨
이 과정은 다음과 같습니다.
- 해당 테이블이 온라인상에서 재정의가 가능한지 검증합니다.
BEGIN
DBMS_REDEFINITION.CAN_REDEF_TABLE('hr','admin_emp',dbms_redefinition.cons_use_pk);
END;
/ - 임시 테이블 hr.int_amdin_emp 를 만듭니다.
CREATE TABLE hr.int_admin_emp
(empno NUMBER(5) PRIMARY KEY,
ename VARCHAR2(15) NOT NULL,
job VARCHAR2(10),
mgr NUMBER(5),
hiredate DATE DEFAULT (sysdate),
sal NUMBER(7,2),
deptno NUMBER(3) NOT NULL,
bonus NUMBER(7,2) DEFAULT (1000))
PARTITION BY RANGE(empno)
(PARTITION emp1000 VALUES LESS THAN (1000) TABLESPACE admin_tbs,
PARTITION emp2000 VALUES LESS THAN (2000) TABLESPACE admin_tbs2) - 재정의 과정을 시작합니다.
BEGIN
DBMS_REDEFINITION.START_REDEF_TABLE
('hr','admin_emp','int_admin_emp',
'empno empno, ename ename, job job, deptno+10 deptno,0 bonus',dbms_redefinition.cons_use_pk);
END;
/ - hr.int_admin_emp 에 트리거, 인덱스, 제약조건을 만듭니다. 재정의의 마지막 과정에서 이것들은 원본 테이블로 전송됩니다. hr.int_admin_emp 테이블의 제약조건들은 비활성 상태여야 합니다. 임시테이블과 연관된 어던 권한도 줄 수 있습니다. 재정의가 끝나면 원본 테이블에 권한을 덮어 씁니다.
ALTER TABLE hr.int_admin_emp ADD CONSTRAINT admin_dept_fkey2
FOREIGN KEY (deptno) REFERRENCE hr.departments (department_id);
ALTER TABLE hr.int_admin_emp MODIFY CONSTRAINT admin_dept_fkey2
DISABLE KEEP INDEX;
비활성된 admin_dept_fkey2 제약조건은 재정의 과정의 마지막에 자동으로 활성화 되며 admin_emp 테이블에 적용됩니다. - 선택적으로 임시 테이블 hr.int_admin_emp 에 동기화 작업을 수행할 수 있습니다.
BEGIN
DBMS_REDEFINITION.SYNC_INTERM_TABLE('hr','admin_emp','int_admin_emp');
END;
/ - 재정의 완료
BEGIN
DBMS_REDEFINITION.FINISH_REDEF_TABLE('hr','admin_emp','int_admin_emp');
END;
/ - 임시 테이블 삭제
Open Restrictions
Restrictions
The following restrictions apply to the online redefinition of tables:
- If the table is to be redefined using primary keys, then the table to be redefined and the post-redefinition table must have the same primary key columns. If the table is to be redefined using
ROWIDs, then the table must not be an index-organized table. - Tables that have materialized views and materialized view logs defined on them cannot be online redefined.
- Tables that are materialized view container tables and Advanced Queuing tables cannot be online redefined.
- The overflow table of an index-organized table cannot be online redefined.
- Tables with user-defined types (objects,
REFs, collections, typed tables) cannot be online redefined. - Tables with
BFILEcolumns cannot be online redefined. - Tables with
LONGcolumns cannot be online redefined. Tables withLOBcolumns are acceptable. - The table to be redefined cannot be part of a cluster.
- Tables in the
SYSandSYSTEMschema cannot be online redefined. - Temporary tables cannot be redefined.
- There is no horizontal subsetting support.
- Only simple deterministic expressions can be used when mapping the columns in the interim table to those of the original table. For example, subqueries are not allowed.
- If new columns (which are not instantiated with existing data for the original table) are being added as part of the redefinition, then they must not be declared
NOT NULLuntil the redefinition is complete. - There cannot be any referential constraints between the table being redefined and the interim table.
- Table redefinition cannot be done NOLOGGING.
Continue_