[Oracle DB] Vấn đề về lưu trữ: Thủ tục tự động thêm datafile mới khi datafile cũ sắp đầy

Hôm nay chúng ta sẽ thử làm một vài điều thú vị.

I. Bài toán

Như các bạn đã biết, các datafile của Oracle DB đều có giới hạn. Đối với các hệ thống bình thường (Qui mô dữ liệu chưa đến mức hàng trăm TB) thì với cấu hình blocksize 8KB => datafile sẽ có dung lượng tối đa là 32GB.

Vậy điều gì xảy ra khi datafile đạt tới hạn: Oracle DB sẽ không thể ghi dữ liệu vào datafile nữa, treo nghiệp vụ (app) => Trên thực tế, chúng ta sẽ cần làm các việc sau:

- Tạo job cảnh báo khi datafile chuẩn bị tới hạn.

- Tạo thủ tục tự động bổ sung datafile mới.

II. Thực hiện

--Tạo tablespace EMPLOYEE và bảng EMPLOYEES với các datafile giới hạn ở mức tối đa 100MB để test tình huống cần bổ sung datafile mới khi datafile cũ gần đầy:

CREATE TABLESPACE EMPLOYEE DATAFILE '/u01/app/oracle/oradata/CDB1/pdb1/EMPLOYEE.DBF' SIZE 250M AUTOEXTEND ON NEXT 250M MAXSIZE 1000M;
CREATE TABLE OAZ21.EMPLOYEES
	(
	  ID          NUMBER,
	  NAME        VARCHAR2(255 BYTE),
	  DATETIME    TIMESTAMP(6),
	  SALARY      NUMBER(10,2),
	  CREDITCARD  CHAR(12 BYTE)
	)
	TABLESPACE EMPLOYEE
	NOLOGGING 
	NOCOMPRESS 
	NOCACHE
;
-- Grant các quyền cho user nếu cần
GRANT SELECT ANY DICTIONARY TO OAZ21;
GRANT SELECT_CATALOG_ROLE TO OAZ21;
GRANT ALTER TABLESPACE TO OAZ21;
GRANT EXECUTE ON DBMS_LOCK TO OAZ21;

-- Thủ tục tự động thêm datafile mới khi dung lượng datafile cũ gần đầy:

-- Th? t?c thêm datafile n?u dung lu?ng tr?ng nh? hon 15%
CREATE OR REPLACE PROCEDURE AddDatafileIfFull(p_tablespace_name IN VARCHAR2 := 'EMPLOYEE') IS
  v_free_space NUMBER;
  v_datafile_path VARCHAR2(255) := '/u01/app/oracle/oradata/CDB1/pdb1/'; -- The path where your datafiles are stored.
  v_threshold NUMBER := 15; -- The free space threshold in percent below which to add a new datafile.
BEGIN
  -- Check the free space percentage in the specified tablespace.
  SELECT 100 - ROUND((df.bytes - SUM(fs.bytes)) * 100 / df.bytes) INTO v_free_space
  FROM dba_free_space fs
  JOIN (SELECT tablespace_name, SUM(bytes) AS bytes
        FROM dba_data_files
        GROUP BY tablespace_name) df ON fs.tablespace_name = df.tablespace_name
  WHERE df.tablespace_name = p_tablespace_name
  GROUP BY df.tablespace_name, df.bytes;

  -- If the free space is below the threshold, add a new datafile.
  IF v_free_space < v_threshold THEN
    EXECUTE IMMEDIATE 'ALTER TABLESPACE ' || p_tablespace_name || ' ADD DATAFILE ''' 
                      || v_datafile_path || p_tablespace_name || TO_CHAR(SYSDATE, 'YYYYMMDDHH24MISS') || '.DBF'' SIZE 250M AUTOEXTEND ON NEXT 250M MAXSIZE 1000M';
  END IF;
  COMMIT;
  EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
/

-- Tạo job gọi thủ tục trên chạy kiểm tra dung lượng datafile hiện hành, với lab thì tôi để tần suất 10 giây / lần:

-- Ð?t job ch?y t? d?ng ki?m tra và thêm datafile
BEGIN
  DBMS_SCHEDULER.CREATE_JOB (
    job_name        => 'add_datafile_job',
    job_type        => 'PLSQL_BLOCK',
    job_action      => 'BEGIN AddDatafileIfFull(''EMPLOYEE''); END;',
    start_date      => SYSTIMESTAMP,
--    repeat_interval => 'FREQ=HOURLY; BYMINUTE=0', -- Runs at the top of every hour
--	repeat_interval => 'FREQ=MINUTELY', -- Runs every minute
	repeat_interval => 'FREQ=SECONDLY; INTERVAL=15', -- Runs every 15 seconds
    end_date        => NULL,
    enabled         => TRUE,
    comments        => 'Job to add datafile if EMPLOYEE tablespace is 85% full'
  );
END;
/

-- Để kiểm nghiệm, tôi sẽ viết thủ tục giả lập và insert 10 triệu bản ghi vào bảng EMPLOYEES, tổng dung lượng bảng sau khi insert khoảng 700 MB:

BEGIN
  DBMS_SCHEDULER.CREATE_JOB(
    job_name           => 'INSERT01',
    job_type           => 'PLSQL_BLOCK',
    job_action         => q'[
        DECLARE
		  v_start_date TIMESTAMP := TO_TIMESTAMP('2024-01-01 00:00:01', 'YYYY-MM-DD HH24:MI:SS');
		  v_end_date TIMESTAMP := TO_TIMESTAMP('2024-12-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS');
		  v_diff_seconds NUMBER;
		  v_name VARCHAR2(255);
		  v_creditcard CHAR(12);
		  v_datetime TIMESTAMP;
		BEGIN
		  -- Calculate the difference in seconds between the start and end dates
		  SELECT (EXTRACT(DAY FROM (v_end_date - v_start_date)) * 86400 +
				  EXTRACT(HOUR FROM (v_end_date - v_start_date)) * 3600 +
				  EXTRACT(MINUTE FROM (v_end_date - v_start_date)) * 60 +
				  EXTRACT(SECOND FROM (v_end_date - v_start_date)))
		  INTO v_diff_seconds
		  FROM dual;

		  FOR i IN 1..10000000 LOOP
			v_name := 'Employee ' || TO_CHAR(i);
			v_creditcard := LPAD(TRUNC(DBMS_RANDOM.VALUE(100000000000, 999999999999)), 12, '0');
			-- Add a random interval, up to the difference calculated, to the start date
			v_datetime := v_start_date + NUMTODSINTERVAL(TRUNC(DBMS_RANDOM.VALUE(0, v_diff_seconds)), 'SECOND');

			INSERT INTO EMPLOYEES (ID, Name, Datetime, Salary, CreditCard)
			VALUES (i, v_name, v_datetime, DBMS_RANDOM.VALUE(10000, 20000), v_creditcard);

			IF MOD(i, 500000) = 0 THEN
			  COMMIT;
			  DBMS_LOCK.SLEEP(5); -- Pause for 5 seconds after each commit to reduce load
			END IF;
		  END LOOP;
		  COMMIT; -- Ensure the final commit
		END;
    ]',
    start_date         => SYSTIMESTAMP,
    repeat_interval    => NULL,
    end_date           => NULL,
    enabled            => TRUE,
    auto_drop          => TRUE,
    comments           => ''
  );
END;
/

-- Và bắt đầu chạy thủ tục insert, khi này ta có thể dùng công cụ Toad để xem các datafile mà thủ tục tự động tạo thêm để ghi được dữ liệu:

-- Liệt kê các datafile thuộc tablespace này:
SELECT FILE_NAME,FILE_ID,TABLESPACE_NAME,round(BYTES /1024 /1024,2) BYTES_MB
FROM dba_data_files
where TABLESPACE_NAME = 'EMPLOYEE';
-- Các câu lệnh stop job nếu cần:
--GRANT EXECUTE ON DBMS_LOCK TO OAZ21;
BEGIN
    -- Stop the job first
    DBMS_SCHEDULER.STOP_JOB(
        job_name => 'OAZ21.INSERT_EMPLOYEE_S01',
        force    => TRUE -- Use force to immediately stop the job
    );
    -- Removed the sleep delay
    -- Now drop the job
    DBMS_SCHEDULER.DROP_JOB(
        job_name => 'OAZ21.INSERT_EMPLOYEE_S01'
    );
END;
/

select * from ALL_SCHEDULER_JOB_RUN_DETAILS
--where OWNER = 'TUANTN'
order by LOG_ID desc;

--watch -n 5 'echo "SELECT JOB_NAME,SLAVE_OS_PROCESS_ID,ELAPSED_TIME,CPU_USED FROM USER_SCHEDULER_RUNNING_JOBS;" | sqlplus tuantn/1985Mobi@cdb1'
SELECT *--JOB_NAME,SLAVE_OS_PROCESS_ID,ELAPSED_TIME,CPU_USED
FROM USER_SCHEDULER_RUNNING_JOBS;
===

Nhận xét

Bài đăng phổ biến