Содержание
- Введение
- Как найти фрагментацию таблицы?
- Как сбросить HWM/убрать фрагментацию?
- Вариант 1. "alter table ... move + rebuild indexes"
- Вариант 2. "Create table as select"
- Вариант 3. "export/truncate/import"
- Вариант 4. "dbms_redefinition"
Введение
Когда строки не сохраняются рядом, или если строки разбиты больше чем в один блок, снижается производительность, потому что эти строки требуют дополнительного доступа к блокам. Следует понимать, что фрагментация таблиц отлична от файловой фрагментации. Когда выполняется серия операций DML над таблицей, таблица фрагментируется, потому что DML не освобождает свободное пространство до HWM.
HWM - это индикатор использования блоков (USED BLOCKS) в базе данных. Блоки идущие до линии HWM - используемые блоки и содержат данные. Эти данные могут быть удалены. Oracle знает какие блоки до HWM не содержат данных, он читает блоки выше HWM, когда выполняет полное сканирование таблицы.
DDL предложение всегда сбрасывает HWM.
Как найти фрагментацию таблицы?
Connected to Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Connected as hr select count(1) from all_objects; COUNT(1) ---------- 53805 create table big1 as select * from all_objects; Table created insert into big1 select * from big1; 53805 rows inserted insert into big1 select * from big1; 107610 rows inserted insert into big1 select * from big1; 215220 rows inserted insert into big1 select * from big1; 430440 rows inserted insert into big1 select * from big1; 860880 rows inserted select count(1) from big1; COUNT(1) ---------- 1721760 delete from big1 where rownum <= 700000; 700000 rows deleted select count(1) from big1; COUNT(1) ---------- 1021760 commit; Commit complete UPDATE BIG1 SET OBJECT_ID = 0 WHERE ROWNUM <=350000; 350000 rows updated commit; Commit complete exec dbms_stats.gather_table_stats('HR', 'BIG1'); PL/SQL procedure successfully completed
Размер таблицы (с фрагментацией):
SELECT TABLE_NAME, ROUND((BLOCKS * 8)/1024, 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 207
Реальные данные:
SELECT TABLE_NAME, ROUND((NUM_ROWS * AVG_ROW_LEN / 1024 / 1024), 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 98
Итого 207 - 98 = 109 Mb используется без толку.
Как сбросить HWM/убрать фрагментацию?
Для этого потребуется реорганизовать фрагментированную таблицу. Есть четыре опции для реорганизации фрагментированных таблиц:
- alter table ... move + rebuild indexes
- export / truncate / import
- create table as select (CTAS)
- dbms_redefinition
Вариант 1. "alter table ... move + rebuild indexes"
Перемещаем таблицу:
ALTER TABLE big1 MOVE; Table altered
Если у таблицы были индексы - нужно их перестроить:
SELECT STATUS, INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME = 'BIG1'; STATUS INDEX_NAME -------- ------------------------------ UNUSABLE BIGIDX ALTER INDEX BIGIDX REBUILD; Index altered. SELECT STATUS, INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME = 'BIG1'; STATUS INDEX_NAME -------- ------------------------------ VALID BIGIDXСобираем статистику по таблице:
exec dbms_stats.gather_table_stats('HR', 'BIG1'); PL/SQL procedure successfully completed
И смотрим результат.
Размер таблицы (с фрагментацией):
SELECT TABLE_NAME, ROUND((BLOCKS * 8)/1024, 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 120
Реальные данные:
SELECT TABLE_NAME, ROUND((NUM_ROWS * AVG_ROW_LEN / 1024 / 1024), 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 98
Итого, 207 - 120 = 87 Mb удается освободить.
Возвращаем таблицу к исходной фрагментации (например, удалив и повторив действия по созданию и наполнению) и смотрим второй способ.
Вариант 2. "Create table as select"
Если в таблице присутствуют индексы, то после дефрагментации этим вариантом нужно будет их создать заново. Получить DDL скрипт по созданию нужных индексов можно способом, описанным здесь.
CREATE TABLE BIG2 AS SELECT * FROM BIG1; Table created -- удаляем таблицу не помещаю в "корзину" DROP TABLE BIG1 PURGE; Table dropped RENAME BIG2 TO BIG1 / Table renamed exec DBMS_STATS.GATHER_TABLE_STATS('HR','BIG1') / PL/SQL procedure successfully completed
Размер таблицы (с фрагментацией):
SELECT TABLE_NAME, ROUND((BLOCKS * 8)/1024, 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 120
Реальные данные:
SELECT TABLE_NAME, ROUND((NUM_ROWS * AVG_ROW_LEN / 1024 / 1024), 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 98
Вариант 3. "export/truncate/import"
-- Размер таблицы (с фрагментацией) SELECT TABLE_NAME, ROUND((BLOCKS * 8)/1024, 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 86 -- Реальные данные SELECT TABLE_NAME, ROUND((NUM_ROWS * AVG_ROW_LEN / 1024 / 1024), 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1' TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 43 SELECT STATUS FROM USER_INDEXES WHERE TABLE_NAME = 'BIG1'; STATUS -------- VALID EXIT Disconnected from Oracle Database 10g Enterprise Edition Release 10.1.0.5.0 - Production With the Partitioning, OLAP and Data Mining options C:\>exp scott/tiger@Orcl file=c:\big1.dmp tables=big1 Export: Release 10.1.0.5.0 - Production on Sat Jul 28 16:30:44 2007 Copyright (c) 1982, 2005, Oracle. All rights reserved. Connected to: Oracle Database 10g Enterprise Edition Release 10.1.0.5.0 - Production With the Partitioning, OLAP and Data Mining options Export done in WE8MSWIN1252 character set and AL16UTF16 NCHAR character set About to export specified tables via Conventional Path ... . . exporting table BIG1 468904 rows exported Export terminated successfully without warnings. C:\>sqlplus scott/tiger@orcl SQL*Plus: Release 10.1.0.5.0 - Production on Sat Jul 28 16:31:12 2007 Copyright (c) 1982, 2005, Oracle. All rights reserved. Connected to: Oracle Database 10g Enterprise Edition Release 10.1.0.5.0 - Production With the Partitioning, OLAP and Data Mining options TRUNCATE TABLE BIG1; TABLE truncated. EXIT Disconnected from Oracle Database 10g Enterprise Edition Release 10.1.0.5.0 - Production With the Partitioning, OLAP and Data Mining options C:\>imp scott/tiger@Orcl file=c:\big1.dmp ignore=y Import: Release 10.1.0.5.0 - Production on Sat Jul 28 16:31:54 2007 Copyright (c) 1982, 2005, Oracle. All rights reserved. Connected to: Oracle Database 10g Enterprise Edition Release 10.1.0.5.0 - Production With the Partitioning, OLAP and Data Mining options Export file created by EXPORT:V10.01.00 via conventional path import done in WE8MSWIN1252 character set and AL16UTF16 NCHAR character set . importing SCOTT objects into SCOTT . . importing table "BIG1" 468904 rows imported Import terminated successfully without warnings. C:\>sqlplus scott/tiger@orcl SQL*Plus: Release 10.1.0.5.0 - Production on Sat Jul 28 16:32:21 2007 Copyright (c) 1982, 2005, Oracle. All rights reserved. Connected to: Oracle Database 10g Enterprise Edition Release 10.1.0.5.0 - Production With the Partitioning, OLAP and Data Mining options -- Размер таблицы (с фрагментацией) SELECT TABLE_NAME, ROUND((BLOCKS * 8)/1024, 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 86 -- Реальные данные SELECT TABLE_NAME, ROUND((NUM_ROWS * AVG_ROW_LEN / 1024 / 1024), 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1' TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 43 SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS('SCOTT','BIG1'); PL/SQL procedure successfully completed. -- Размер таблицы (с фрагментацией) SELECT TABLE_NAME, ROUND((BLOCKS * 8)/1024, 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1'; TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 52 -- Реальные данные SELECT TABLE_NAME, ROUND((NUM_ROWS * AVG_ROW_LEN / 1024 / 1024), 0) "SIZE, Mb" FROM USER_TABLES WHERE TABLE_NAME = 'BIG1' TABLE_NAME SIZE, Mb ------------------------------ ---------- BIG1 43 SELECT STATUS FROM USER_INDEXES WHERE TABLE_NAME = 'BIG1'; STATUS -------- VALID SQL> EXEC DBMS_REDEFINITION.CAN_REDEF_TABLE('SCOTT','BIG1', DBMS_REDEFINITION.CONS_USE_PK); PL/SQL procedure successfully completed.
Вариант 4. "dbms_redefinition"
Создадим тестовую таблицу с наполнением.
CREATE TABLE TABLE1( NO NUMBER, NAME VARCHAR2(20) DEFAULT 'NONE', DDATE DATE DEFAULT SYSDATE); Table created ALTER TABLE TABLE1 ADD CONSTRAINT PK_NO PRIMARY KEY(NO); Table altered BEGIN FOR X IN 1 .. 100000 LOOP INSERT INTO TABLE1 (NO, NAME, DDATE) VALUES (X, DEFAULT, DEFAULT); END LOOP; END / PL/SQL procedure successfully completed CREATE OR REPLACE TRIGGER TRI_TABLE1 AFTER INSERT ON TABLE1 BEGIN NULL; END; Trigger created SELECT COUNT(*) FROM TABLE1; COUNT(*) ---------- 100000 DELETE TABLE1 WHERE ROWNUM <= 50000; 50000 rows deleted commit; Commit complete EXEC DBMS_STATS.GATHER_TABLE_STATS('HR','TABLE1'); PL/SQL procedure successfully completed -- Размер таблицы с фрагментацией SELECT TABLE_NAME, ROUND((BLOCKS * 8), 0) "SIZE, Kb" FROM USER_TABLES WHERE TABLE_NAME = 'TABLE1'; TABLE_NAME SIZE, Kb ------------------------------ ---------- TABLE1 2960 -- Реальные данные SELECT TABLE_NAME, ROUND((NUM_ROWS * AVG_ROW_LEN / 1024 ), 0) "SIZE, Kb" FROM USER_TABLES WHERE TABLE_NAME = 'TABLE1' TABLE_NAME SIZE, Kb ------------------------------ ---------- TABLE1 830
Требуется роль "DBA" или право "EXECUTE" на dbms_redefinition pkg
Connected to Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Connected as SYS SQL> grant execute on dbms_redefinition to hr; Grant succeeded
Проверим что таблица кандидат на переопределение.
begin SYS.DBMS_REDEFINITION.CAN_REDEF_TABLE('HR', 'TABLE1', SYS.DBMS_REDEFINITION.CONS_USE_PK); end; PL/SQL procedure successfully completed
После проверки таблицы на возможность переопределения, вручную создается пустая временная таблица (в той же схеме и точно такая же как переопределяемая)
CREATE TABLE TABLE2 AS SELECT * FROM TABLE1 WHERE 0 = 1; Table created
Для выполнения этого и следующего скрипта мне почему-то не хватило привелегии EXECUTE на dbms_redefinition и пришлось подключиться как DBA.
EXEC SYS.DBMS_REDEFINITION.START_REDEF_TABLE('HR', 'TABLE1', 'TABLE2'); PL/SQL procedure successfully completed
Cинхронизируем две таблицы, исходную и временную.
EXEC SYS.DBMS_REDEFINITION.SYNC_INTERIM_TABLE('HR', 'TABLE1', 'TABLE2'); PL/SQL procedure successfully completed
Создаем PRIMARY KEY на временную таблицу(TABLE2).
ALTER TABLE TABLE2 ADD CONSTRAINT PK_NO1 PRIMARY KEY (NO); Table altered CREATE TRIGGER TRI_TABLE2 AFTER INSERT ON TABLE2 BEGIN NULL; END;
Отключаем внешний ключ на оригинальной таблице, если существует, прежде чем завершить процесс.
EXEC SYS.DBMS_REDEFINITION.FINISH_REDEF_TABLE('HR', 'TABLE1', 'TABLE2'); PL/SQL procedure successfully completed EXEC DBMS_STATS.GATHER_TABLE_STATS('HR','TABLE1'); PL/SQL procedure successfully completed -- Размер таблицы с фрагментацией SELECT TABLE_NAME, ROUND((BLOCKS * 8), 0) "SIZE, Kb" FROM USER_TABLES WHERE TABLE_NAME = 'TABLE1'; TABLE_NAME SIZE, Kb ------------------------------ ---------- TABLE1 1376 -- Реальные данные SELECT TABLE_NAME, ROUND((NUM_ROWS * AVG_ROW_LEN / 1024 ), 0) "SIZE, Kb" FROM USER_TABLES WHERE TABLE_NAME = 'TABLE1' TABLE_NAME SIZE, Kb ------------------------------ ---------- TABLE1 830 SELECT STATUS, CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME = 'TABLE1'; STATUS CONSTRAINT_NAME -------- ------------------------------ ENABLED PK_NO SELECT STATUS, TRIGGER_NAME FROM USER_TRIGGERS WHERE TABLE_NAME = 'TABLE1'; STATUS TRIGGER_NAME -------- ------------------------------ ENABLED TRI_TABLE1 DROP TABLE TABLE2 PURGE ORA-12083: следует использовать DROP MATERIALIZED VIEW для удаления "HR"."TABLE2" DROP materialized view TABLE2; Materialized view dropped
Источники (русский, английский).
Комментариев нет:
Отправить комментарий