مدیریت دادههای بزرگ، مخصوصاً لاگها و تراکنشهای روزانه، یکی از چالشهای مهم در دیتابیسهای امروزی است.
این مقاله آموزش کامل پارتیشنبندی نیست، بلکه متمرکز بر مدیریت عملی پارتیشنها شامل:
-
TRUNCATE PARTITION
-
DROP PARTITION
با تمرکز بر سناریوهای واقعی مثل آرشیو دادههای قدیمی برای بهینهسازی هزینهی نگهداری دیتابیس.
بسیاری از جداول لاگ تنها برای چند روز اخیر اهمیت دارند.
با آرشیو و حذف دادههای قدیمی:
-
فضای مصرفی دیتابیس کاهش مییابد
-
زمان Backup و Restore بهبود پیدا میکند
-
هزینهی Storage کمتر میشود
این دقیقاً همان هدف Information Lifecycle Management (ILM) است.
مرحله ۱: ساخت جدول پارتیشنبندی شده روزانه (بر اساس عدد)
توضیح فنی دربارهی INTERVAL
در پارتیشنبندی RANGE
معمولی، باید برای هر بازهی داده یک پارتیشن از قبل تعریف کنیم.
اما وقتی از INTERVAL
استفاده میکنیم، Oracle به صورت خودکار و دینامیک، برای مقادیر جدید، پارتیشن جدید میسازد.
در اینجا، INTERVAL (1)
یعنی اگر رکوردی وارد شود که تاریخش بزرگتر از آخرین پارتیشن است، Oracle خودش یک پارتیشن جدید برای آن روز میسازد.
ایجاد جدول:
CREATE TABLE vahiddb_daily_data (
id NUMBER,
logical_date NUMBER,
data_value VARCHAR2(100)
)
PARTITION BY RANGE (logical_date)
INTERVAL (1)
(
PARTITION p_start VALUES LESS THAN (20250101)
);
مرحله ۲: ساخت جدول مشابه بر پایهی DATE
توضیح فنی دربارهی INTERVAL
روی DATE
برای ستونهایی از نوع DATE
، باید INTERVAL
را با تابع NUMTODSINTERVAL
تعیین کنیم.
در این مثال، NUMTODSINTERVAL(1, 'DAY')
مشخص میکند که برای هر روز تقویمی یک پارتیشن جدید ساخته شود.
ایجاد جدول:
CREATE TABLE vahiddb_daily_data_date (
id NUMBER,
logical_date DATE,
data_value VARCHAR2(100)
)
PARTITION BY RANGE (logical_date)
INTERVAL (NUMTODSINTERVAL(1, 'DAY'))
(
PARTITION p_start VALUES LESS THAN (TO_DATE('2025-01-01', 'YYYY-MM-DD'))
);
مرحله ۳: درج دادههای تستی
برای جدول عددی:
BEGIN
FOR d IN 20250101 .. 20250110 LOOP
FOR i IN 1 .. 10000 LOOP
INSERT INTO vahiddb_daily_data (id, logical_date, data_value)
VALUES (
d * 100000 + i,
d,
RPAD('DATA_', 100, 'X')
);
END LOOP;
COMMIT;
END LOOP;
END;
برای جدول تاریخ:
BEGIN
FOR d IN 0 .. 9 LOOP
FOR i IN 1 .. 10000 LOOP
INSERT INTO vahiddb_daily_data_date (id, logical_date, data_value)
VALUES (
(20250101 + d) * 100000 + i, -- ساخت id یکتا بر اساس تاریخ
TO_DATE('2025-01-01', 'YYYY-MM-DD') + d, -- تاریخ روزانه
RPAD('DATA_', 100, 'X') -- داده حجیم
);
END LOOP;
COMMIT;
END LOOP;
END;
مرحله ۴: مشاهدهی پارتیشنها و حجم اشغالی
SELECT
segment_name,
partition_name,
bytes / 1024 / 1024 AS size_mb
FROM dba_segments
WHERE segment_name = 'VAHIDDB_DAILY_DATA'
ORDER BY partition_name;
مرحله ۵: مدیریت پارتیشنها
ابتدا: پیدا کردن اسم پارتیشن از روی مقدار High Value
با این روش، بدون دانستن اسم پارتیشن میتوانیم اسم دقیقش را استخراج کنیم:
SELECT
p.table_name,
p.partition_name,
REGEXP_SUBSTR(
DBMS_XMLGEN.getxmltype(
'SELECT high_value FROM dba_tab_partitions
WHERE table_name = ''' || p.table_name || '''
AND partition_name = ''' || p.partition_name || '''
AND table_owner = ''' || p.table_owner || ''''
).extract('//text()').getStringVal(),
'\d+'
) AS high_value_extracted
FROM dba_tab_partitions p
WHERE p.table_owner = 'SYS'
AND p.table_name = 'VAHIDDB_DAILY_DATA'
ORDER BY high_value_extracted;
توضیح کامل دربارهی استخراج HIGH_VALUE
از DBA_TAB_PARTITIONS
با XMLGEN
در جداول پارتیشنبندی شده، اطلاعات مرزبندی (Boundary) هر پارتیشن در ستون HIGH_VALUE
در ویوی DBA_TAB_PARTITIONS
ذخیره میشود.
اما مشکل اینجاست که مقدار HIGH_VALUE
در این ویو به صورت کد SQL (String Expression) ذخیره شده، نه به صورت مقدار ساده.
مثلاً برای یک جدول که بر اساس تاریخ پارتیشن شده، مقدار HIGH_VALUE
ممکن است اینطور باشد:
TO_DATE('2025-01-08 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
یا اگر بر اساس عدد باشد:
TO_NUMBER('20250108')
بنابراین، اگر بخواهیم مقدار واقعی پارتیشن (مثلاً 20250108 یا تاریخ معادل) را به صورت مستقیم استخراج کنیم، نیاز به پردازش بیشتری داریم.
از DBMS_XMLGEN
برای اجرای یک سابکوئری دینامیک استفاده میکند
DBMS_XMLGEN.getxmltype(
'SELECT high_value FROM dba_tab_partitions WHERE ...'
)
این بخش خروجی کوئری را به فرمت XML تبدیل میکند.
از EXTRACT('//text()')
برای گرفتن مقدار خام HIGH_VALUE
از XML استفاده میکند
.extract('//text()').getStringVal()
این کار باعث میشود که مقدار واقعی (نه ساختار XML) را استخراج کنیم.
از REGEXP_SUBSTR
برای پیدا کردن عدد از متن استخراجشده استفاده میشود
REGEXP_SUBSTR(..., '\d+')
این عبارت منظم (\d+
) فقط اعداد پشت سر هم را از متن جدا میکند.
مثلاً اگر HIGH_VALUE
این باشد:
TO_NUMBER('20250108')
عبارت REGEXP_SUBSTR
فقط 20250108
را جدا میکند.
TABLE_NAME PARTITION_NAME HIGH_VALUE_EXTRACTED
------------------------------ -------------------- ------------------------------
VAHIDDB_DAILY_DATA P_START 20250101
VAHIDDB_DAILY_DATA SYS_P791 20250102
VAHIDDB_DAILY_DATA SYS_P792 20250103
VAHIDDB_DAILY_DATA SYS_P793 20250104
VAHIDDB_DAILY_DATA SYS_P794 20250105
VAHIDDB_DAILY_DATA SYS_P795 20250106
VAHIDDB_DAILY_DATA SYS_P796 20250107
VAHIDDB_DAILY_DATA SYS_P797 20250108
VAHIDDB_DAILY_DATA SYS_P798 20250109
VAHIDDB_DAILY_DATA SYS_P799 20250110
VAHIDDB_DAILY_DATA SYS_P800 20250111
حالا داده ها را باید به جایی که مدنظر دارید با روش خودتان انتقال دهید. روشها می تواند شامل گرفتن یک DUMP یا INSERT به جدول دیگری یا EXCHANGE باشد.
بعد از پیدا کردن اسم پارتیشن، با دستور زیر TRUNCATE
یا DROP انجام میدهیم:
ALTER TABLE VAHIDDB_DAILY_DATA DROP PARTITION SYS_P799;
این روش سنتی است و همچنان معتبر.
حالا استفاده از روش مدرن PARTITION FOR (...)
بدون نیاز به دانستن اسم پارتیشن. فرض کنیم که میخواهیم با پارتیشنی کار کنیم که اسم پارتیشن را نمی دانیم. کافیست از روش زیر استفاده کنیم.
برای جدول عددی:
ALTER TABLE VAHIDDB_DAILY_DATA TRUNCATE PARTITION FOR (20250108);
برای جدول تاریخ:
ALTER TABLE VAHIDDB_DAILY_DATA_DATE TRUNCATE PARTITION FOR (TO_DATE('2025-01-08', 'YYYY-MM-DD'));
Oracle خودش پارتیشن مناسب را پیدا و عمل میکند.
نکته مهم:
دستور TRUNCATE PARTITION FOR (...)
در داکیومنت رسمی Oracle (تا تاریخ تهیه این مقاله) پیدا نکردم.
اگر کسی اطلاعات دقیقی از نسخه رسمی یا مستند آن دارد، خوشحال میشویم اعلام کند تا مقاله کاملتر شود.
نکات فنی مهم:
ایندکس های global در نسخه 11g ممکن است بعد از truncate/drop نیاز به rebuild داشته باشد