איך לשחרר שטח מ-tablespace לגודל המינימלי שלו
כאשר נוצר מצב שבו ב-tablespace מתפנה מקום רב, אורקל מאפשרים להקטין את ה-tablespace על ידי מעבר על ה-datafile-ים שלו וביצוע פקודות resize. הבעיה מתעוררת כאשר ישנו Tablespace עם מספר datafiles (ולפעמים זה יכול להגיע גם לעשרות) ואז העבודה של מציאת הסגמנט ש"חוסם" את שחרור השטח הפנוי הופך להיות משימה מהגהינום: גם הזיהוי בצורה ידנית של הבלוק התפוס העליון (HWM) וגם ה-resize שלפעמים לוקח זמן.
דרך אגב, אם יש tablespace במצב read-only ובו אינדקסים של אובייקט שהוא לא ב-read-only, אז כאשר זורקים את הטבלה או ה-partition אז השטח ב-tablespace הזה לא משתחרר – הסגמנטים של האינדקס הופכים ל-temp segments ועד שלא נפתח את ה-tablespace ל-read write השטח לא יזוהה כפנוי.
כדי להתגבר על כל הבעיות האלה ניתן להשתמש בפרוצדורה הסופר שימושית הבאה.
הפרוצדורה יודעת לקבל שם של tablespace ואת כמות השטח (באחוזים) שאנחנו רוצים לשחרר (לדוגמה, אם יש 100 מגה פנוי שניתן לשחרר ואנחנו רוצים שישאר 20, ניתן לפרוצדורה 0.8) והיא יודעת לעבור בכל datafile ותשחרר את השטח הפנוי עד ל-High water mark. הפרוצדורה יודעת גם לייצר סקריפט מבלי להריץ את הפקודות בפועל ואף לייצר תחזית של כמות השטח שיתפנה מה-tablespace.
ונעבור לפרוצדורה:
procedure shrink_files_to_limit(p_tbs in varchar2, p_in_mode in char default 'N', p_full_pct in number default 0.2, p_do_cmd varchar2 default 'N') is cmd varchar2(200); p_mode varchar2(20); sum_mbytes_freed number := 0; tbs_status varchar2(100); -- Protected do_command: -- 1. Run command only if the p_do_cmd = Y -- 2. Igonres ORA-03297 procedure do_command_protect(p_cmd in varchar2, p_do_cmd in varchar2) is sql_err_msg varchar2(1000); begin dbms_output.put_line(p_cmd || ';'); if p_do_cmd = 'Y' then begin execute immediate (p_cmd); exception when others then sql_err_msg := sqlerrm; null; if sqlcode = -3297 then -- if unable to resize, ignore null; else dbms_output.put_line('error was ' || replace(sql_err_msg, 'ORA', 'ora')); null; -- raise; end if; end; end if; end do_command_protect; begin p_mode := upper(p_in_mode); if p_mode not in ('N', 'SHRINK') then raise_application_error(-20001, 'Please enter valid shrink mode: SHRINK/N'); end if; if p_do_cmd not in ('Y', 'N') then raise_application_error(-20002, 'Please enter valid do mode: Y/N'); end if; -- Open the R/O tablespace (this will drop automaticly temp segments) begin select distinct t.status into tbs_status from dba_data_files f join dba_tablespaces t on f.tablespace_name = t.tablespace_name where f.tablespace_name = p_tbs and t.status in ('READ ONLY', 'ONLINE') and t.contents = 'PERMANENT'; if p_mode = 'SHRINK' and tbs_status = 'READ ONLY' then cmd := 'alter tablespace ' || p_tbs || ' READ WRITE'; do_command_protect(cmd, p_do_cmd); end if; exception when no_data_found then raise_application_error(-20003, 'Tablespace ' || p_tbs || ' does not exist'); end; -- Check for the last extent on each datafile in the tablespace. This is the min resize size of the datafile. for cur in (select substr(f.file_name, 1, 70) filename, f.tablespace_name, ceil(max(nvl(e.block_id * t.block_size + e.bytes, nvl(t.next_extent, 104857600) + 4 * t.block_size)) / 1024) min_k_size, (max(f.bytes) / 1024 - ceil(max(nvl(e.block_id * (e.bytes / e.blocks) + e.bytes, nvl(t.next_extent, 104857600) + 4 * t.block_size)) / 1024)) free_k_size, max(f.bytes) / 1024 curr_k_size, f.autoextensible from dba_extents e right outer join dba_data_files f on e.file_id = f.file_id and e.tablespace_name = f.tablespace_name join dba_tablespaces t on f.tablespace_name = t.tablespace_name where f.tablespace_name = p_tbs and t.status in ('READ ONLY', 'ONLINE') and t.contents = 'PERMANENT' group by f.file_name, f.tablespace_name, f.autoextensible) loop -- Check if there is free space to be removed if cur.free_k_size > 0 and cur.min_k_size + ceil((1 - p_full_pct) * cur.free_k_size) < cur.curr_k_size then if p_mode = 'SHRINK' then -- If the datafile is not autoextandable, make the datafile autoextendable and set the maximum size to -- the size we just resized from. This is done to protect the datafile from over-shrinking if cur.autoextensible = 'NO' then cmd := 'alter database datafile ''' || cur.filename || ''' autoextend on next 16000k maxsize ' || trim(to_char(cur.curr_k_size)) || 'k'; do_command_protect(cmd, p_do_cmd); end if; -- resize the datafile to the new size cmd := 'alter database datafile ''' || cur.filename || ''' resize ' || to_char(cur.min_k_size + ceil((1 - p_full_pct) * cur.free_k_size)) || 'k'; do_command_protect(cmd, p_do_cmd); end if; sum_mbytes_freed := sum_mbytes_freed + floor((p_full_pct) * cur.free_k_size); end if; end loop; if p_mode = 'N' then dbms_output.put_line('Total space that can be freed for tablespace ' || p_tbs || ' (' || tbs_status || '): ' || round(sum_mbytes_freed / 1024, 1) || 'M'); end if; end shrink_files_to_limit;
אהבתי.
כל הכבוד.