وقتی با PostgreSQL کار میکنید، تمام دادهها در نهایت به صورت فایل روی دیسک ذخیره میشوند. درک ساختار این فایلها و ارتباط آنها با اشیای پایگاه داده (مانند جدولها، ایندکسها و …) به شما کمک میکند تا عملکرد پایگاه داده را بهتر تحلیل کنید، مشکلات دیسک را ردیابی کنید و حتی در صورت نیاز به صورت دستی از دادهها نسخه پشتیبان تهیه کنید.
در این مطلب با ساختار دایرکتوری دادههای PostgreSQL، کاتالوگهای سیستمی، نحوه نگاشت شناسههای یکتای اشیا (OID) به فایلها و چند کوئری کاربردی آشنا میشوید. همه کوئریها قابل اجرا هستند و میتوانید آنها را روی پایگاه داده خودتان آزمایش کنید.
پس از نصب PostgreSQL، یک دایرکتوری اصلی به نام PGDATA (معمولاً /var/lib/postgresql/نسخه/main مانند /var/lib/postgresql/16/main یا مسیری مشابه) وجود دارد که تمام فایلهای مربوط به پستگرس در آن قرار میگیرند. در این بخش ساختار کلی این دایرکتوری و نقش هر زیرشاخه را بررسی میکنیم.
PostgreSQL Data Directory ($PGDATA)
├── base/ # محل ذخیرهسازی پایگاههای داده
│ └── {db_oid}/ # دایرکتوری مربوط به هر پایگاه داده (مثلاً ۱۶۳۸۴)
│ ├── {table_oid} # فایل داده اصلی جدول
│ ├── {table_oid}_fsm # نقشه فضای خالی (Free Space Map)
│ ├── {table_oid}_vm # نقشه نمایش (Visibility Map)
│ └── {index_oid} # فایلهای ایندکس
├── global/ # جدولهای سیستمی سراسری (مشترک بین همه پایگاهها)
├── pg_wal/ # لاگهای Write-Ahead (WAL)
└── postgresql.conf # فایل تنظیمات اصلی
در جدول زیر میبینید که هر نوع شیء در پایگاه داده با چه الگوی نامگذاری فایلی ذخیره میشود. معمولاً نام فایل با OID (شناسه یکتای شیء) برابر است. فایلهای _fsm و _vm نیز برای مدیریت فضای خالی و visibility هر جدول به کار میروند.
| نوع شیء | الگوی نام فایل | مثال |
|---|---|---|
| جدول | {oid} | ۱۶۳۸۵ |
| ایندکس | {oid} | ۱۶۳۹۰ |
| دنباله (Sequence) | {oid} | ۱۶۳۸۸ |
PostgreSQL برای مدیریت اطلاعات مربوط به ساختار پایگاه داده، از مجموعهای از جدولهای سیستمی استفاده میکند. این جدولها در واقع منبع اصلی اطلاعات درباره اشیا، ستونها، محدودیتها و آمار هستند. در اینجا مهمترین آنها را معرفی میکنیم.
در پایگاههای داده، مخصوصاً در PostgreSQL، واژهٔ کاتالوگ (Catalog) به مجموعهای از جداول سیستمی گفته میشود که اطلاعات ساختاری پایگاه داده را نگه میدارند.
👉 کاتالوگ = دفتر راهنمای داخلی پایگاه داده درباره خودش
| کاتالوگ | کاربرد | ستونهای کلیدی |
|---|---|---|
pg_database | لیست همه پایگاههای داده کلاستر | oid, datname, datdba |
pg_class | اطلاعات مربوط به همه اشیا (جدولها، ایندکسها، دنبالهها و …) | oid, relname, relkind |
pg_namespace | نامفضاها (schema) | oid, nspname |
pg_attribute | ستونهای هر جدول | attrelid, attname, atttypid |
pg_stat_user_tables | آمار فعالیت روی جدولهای کاربر | schemaname, tablename, n_tup_ins |
نکته مهم این است که همه کاتالوگهای سیستمی در یک جا ذخیره نمیشوند. بعضی از آنها مختص کل کلاستر هستند و در دایرکتوری global/ قرار میگیرند، در حالی که بقیه مخصوص هر پایگاه داده بوده و در زیرشاخه base/{db_oid}/ ذخیره میشوند. در ادامه این تفاوت را با مثال روشن میکنیم.
global/)این کاتالوگها اطلاعات مشترک بین همه پایگاههای داده را نگه میدارند:
pg_database (OID ~1262) – اطلاعات همه پایگاههای دادهpg_auth_members (OID ~1261) – عضویت نقشهاpg_authid – اطلاعات احراز هویت کاربران/نقشهاpg_tablespace – اطلاعات tablespaceهاچرا فایل آنها را در base/{db_oid}/ پیدا نمیکنید؟ چون این کاتالوگها سراسری هستند و برای کل کلاستر یک نسخه دارند.
base/{db_oid}/)این کاتالوگها فقط مربوط به یک پایگاه داده خاص هستند:
pg_class – اشیای آن پایگاه دادهpg_attribute – ستونهای جدولهای آن پایگاه دادهpg_namespace – نامفضاهای آن پایگاه دادهpg_constraint – محدودیتهاpg_stat_user_tables – آمار جدولهای کاربربا دو کوئری زیر تفاوت محل ذخیرهسازی را به وضوح میبینید. pg_database یک کاتالوگ سراسری است و فایل آن در آدرسی شبیه global/1262 قرار دارد، در حالی که pg_class مختص پایگاه داده جاری است و فایل آن در زیرشاخه base/ همان پایگاه داده قرار میگیرد.
-- وقتی از pg_class کوئری میگیرید که pg_database کجاست:
SELECT relname, oid, pg_relation_filepath(relname::regclass) as location
FROM pg_class WHERE relname = 'pg_database';
-- نتیجه: pg_database | 1262 | global/1262
-- یعنی فایل آن در global/1262 است، نه در base!
-- اما وقتی جدول خودتان را بررسی میکنید:
SELECT relname, oid, pg_relation_filepath(relname::regclass) as location
FROM pg_class WHERE relname = 'customers';
-- نتیجه: customers | 16400 | base/16384/16400
$PGDATA/
├── base/ # اشیای مختص هر پایگاه داده
│ ├── {db_oid}/ # پایگاه داده شما
│ │ ├── ۱۲۵۹ # pg_class (مختص این پایگاه داده)
│ │ ├── your_table # ✅ جدول شما
│ │ └── your_index # ✅ ایندکس شما
│ └── {other_db_oid}/ # پایگاه داده دیگر
│ └── their_objects # ✅ اشیای آن پایگاه داده
│
└── global/ # اشیای سراسری کلاستر
├── ۱۲۶۲ # pg_database (سراسری)
├── ۱۲۶۱ # pg_auth_members (سراسری)
└── ۱۴۱۷ # pg_foreign_server (سراسری)
در این مثال میبینید که pg_database در مسیر global/1262 و pg_class در مسیر base/16384/1259 قرار دارد (عدد ۱۶۳۸۴ بسته به پایگاه داده شما متفاوت خواهد بود).
-- pg_database سراسری است
SELECT relname, oid, pg_relation_filepath(relname::regclass) as location
FROM pg_class WHERE relname = 'pg_database';
-- نتیجه: pg_database | 1262 | global/1262
-- pg_class مختص پایگاه داده است
SELECT relname, oid, pg_relation_filepath(relname::regclass) as location
FROM pg_class WHERE relname = 'pg_class';
-- نتیجه: pg_class | 1259 | base/16384/1259
این یعنی چرا فایل ۱۲۶۲ را در دایرکتوری پایگاه داده خود نمیبینید! 🎯
پیش از ورود به کوئریهای پیچیده، بیایید با جدول مهم pg_class آشنا شویم. این جدول مرجع اصلی برای یافتن اشیا (جدولها، ایندکسها و …) در پایگاه داده است. ستون relkind نوع شیء را مشخص میکند.
-- دیدن ۱۰ شیء اول پایگاه داده فعلی
SELECT relname, relkind, oid
FROM pg_class
ORDER BY oid
LIMIT 10;
-- شمارش اشیا بر اساس نوع
SELECT relkind, COUNT(*) as count
FROM pg_class
GROUP BY relkind
ORDER BY relkind;
در جدول زیر معنی هر کد یک کاراکتری در ستون relkind که از اصلی ترین ستونهای جدول مهم pg_class است، توضیح داده شده است. این کدها به شما میگویند که هر ردیف در pg_class نماینده چه نوع شیئی است.
| relkind | نوع شیء | توضیح |
|---|---|---|
'r' | جدول معمولی | جدولهای دادههای کاربر |
'i' | ایندکس | ایندکسها (B-tree و غیره) |
'S' | دنباله | تولیدکننده مقادیر متوالی (مثلاً برای auto-increment) |
'v' | نما | نمای مجازی |
'm' | نمای مادیشده | نمای فیزیکی ذخیرهشده |
'c' | نوع ترکیبی | نوع داده سفارشی |
't' | جدول TOAST | ذخیرهسازی دادههای حجیم |
'f' | جدول خارجی | دادههای خارج از پایگاه داده |
با این کوئریها میتوانید جدولها، ایندکسها و دنبالههای موجود در نامفضای عمومی (public) پایگاه داده خود را فهرست کنید.
-- ۱. همه جدولهای نامفضای عمومی (public)
SELECT relname as table_name
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'r' AND n.nspname = 'public'
ORDER BY relname;
-- ۲. همه ایندکسها
SELECT relname as index_name
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'i' AND n.nspname = 'public'
ORDER BY relname;
-- ۳. همه دنبالهها
SELECT relname as sequence_name
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'S' AND n.nspname = 'public'
ORDER BY relname;
برای دیدن ستونهای یک جدول خاص، میتوانید از pg_attribute استفاده کنید. در مثال زیر ستونهای جدول products (فرض کنید چنین جدولی دارید) نمایش داده میشود.
-- مشاهده ستونهای یک جدول خاص (مثلاً 'products')
SELECT attname as column_name,
attnotnull as is_required,
attlen as storage_bytes
FROM pg_attribute
WHERE attrelid = 'products'::regclass -- نام جدول را بگذارید
AND attnum > 0 -- ستونهای سیستمی را حذف میکند
ORDER BY attnum;
-- تعداد ستونهای هر جدول
SELECT c.relname as table_name,
COUNT(a.attname) as column_count
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
LEFT JOIN pg_attribute a ON c.oid = a.attrelid AND a.attnum > 0
WHERE c.relkind = 'r' AND n.nspname = 'public'
GROUP BY c.relname
ORDER BY c.relname;
این کوئری یک نمای کلی از پایگاه داده فعلی به شما میدهد: تعداد جدولها، ایندکسها و دنبالهها.
-- نمای کلی پایگاه داده فعلی
SELECT 'Database: ' || current_database() as info
UNION ALL
SELECT 'Tables: ' || COUNT(*)::text
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'r' AND n.nspname = 'public'
UNION ALL
SELECT 'Indexes: ' || COUNT(*)::text
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'i' AND n.nspname = 'public'
UNION ALL
SELECT 'Sequences: ' || COUNT(*)::text
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'S' AND n.nspname = 'public';
این کوئری همه اشیا (جدول، ایندکس، دنباله، نما) را در نامفضای عمومی همراه با نوع و مسیر فایل مربوطه نمایش میدهد.
SELECT n.nspname as schema, c.relname as name,
CASE c.relkind WHEN 'r' THEN 'TABLE' WHEN 'i' THEN 'INDEX'
WHEN 'S' THEN 'SEQUENCE' WHEN 'v' THEN 'VIEW'
ELSE 'OTHER' END as type,
pg_relation_filepath(n.nspname||'.'||c.relname) as file_path
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind IN ('r','i','S','v') AND n.nspname = 'public'
ORDER BY c.relkind, c.relname;
همان کوئری بالا، اما به همراه اندازه هر شیء (با فرمت مناسب) و مرتبسازی نزولی بر اساس اندازه.
SELECT n.nspname||'.'||c.relname as object,
CASE c.relkind WHEN 'r' THEN 'TABLE' WHEN 'i' THEN 'INDEX'
WHEN 'S' THEN 'SEQUENCE' END as type,
pg_size_pretty(pg_relation_size(n.nspname||'.'||c.relname)) as size,
pg_relation_filepath(n.nspname||'.'||c.relname) as path
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind IN ('r','i','S') AND n.nspname = 'public'
ORDER BY pg_relation_size(n.nspname||'.'||c.relname) DESC;
سناریو:
فرض کنید در دایرکتوری دادهها فایل زیر را میبینید:
/var/lib/postgresql/18/main/base/16384/16427
میخواهید بفهمید این فایل متعلق به کدام پایگاه داده و کدام شیء است.
با استفاده از OID دایرکتوری (اینجا ۱۶۳۸۴) میتوانید نام پایگاه داده مربوطه را از pg_database بیابید.
-- پیدا کردن نام پایگاه داده بر اساس OID دایرکتوری
SELECT oid as database_oid, datname as database_name
FROM pg_database
WHERE oid = 16384;
نتیجه احتمالی:
database_oid | database_name
--------------+---------------
۱۶۳۸۴ | dblab
حالا با OID شیء (۱۶۴۲۷) در pg_class جستجو میکنیم تا نوع و نام آن را مشخص کنیم.
-- مشخصات شیء را از pg_class مییابیم
SELECT n.nspname as schema,
c.relname as object_name,
CASE c.relkind
WHEN 'r' THEN 'TABLE'
WHEN 'i' THEN 'INDEX'
WHEN 'S' THEN 'SEQUENCE'
WHEN 'v' THEN 'VIEW'
ELSE 'OTHER'
END as object_type,
c.oid as object_oid,
pg_relation_filepath(n.nspname||'.'||c.relname) as file_path,
pg_size_pretty(pg_relation_size(c.oid)) as size
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid = 16427;
نتیجه احتمالی:
schema | object_name | object_type | object_oid | file_path | size
--------+---------------+-------------+------------+------------------+--------
public | order_details | TABLE | 16427 | base/16384/16427 | 0 bytes
با pg_attribute میتوانیم ستونهای این جدول را ببینیم.
-- مشاهده ستونهای جدول
SELECT a.attname as column_name,
t.typname as data_type,
CASE WHEN a.attnotnull THEN 'NOT NULL' ELSE '' END as constraints,
pg_get_expr(ad.adbin, ad.adrelid) as default_value
FROM pg_attribute a
JOIN pg_type t ON a.atttypid = t.oid
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
WHERE a.attrelid = 16427 AND a.attnum > 0
ORDER BY a.attnum;
نتیجه احتمالی:
column_name | data_type | constraints | default_value
-----------------+-----------+-------------+--------------------------------------------------------
order_detail_id | int4 | NOT NULL | nextval('order_details_order_detail_id_seq'::regclass)
order_id | int4 | NOT NULL |
product_id | int4 | NOT NULL |
quantity | int4 | NOT NULL |
total_price | numeric | NOT NULL |
با کوئری زیر میتوانید ایندکسهای تعریفشده روی این جدول را بیابید.
-- چه ایندکسهایی روی این جدول وجود دارد؟
SELECT i.relname as index_name,
CASE WHEN idx.indisunique THEN 'UNIQUE' ELSE 'REGULAR' END as index_type,
pg_get_indexdef(i.oid) as index_definition
FROM pg_index idx
JOIN pg_class i ON idx.indexrelid = i.oid
WHERE idx.indrelid = 16427;
نتیجه احتمالی:
index_name | index_type | index_definition
--------------------+------------+----------------------------------------------------------------------------------------------
order_details_pkey | UNIQUE | CREATE UNIQUE INDEX order_details_pkey ON public.order_details USING btree (order_detail_id)
فایل مرموز: base/16384/16427
پایگاه داده: dblab (OID: 16384)
شیء: public.order_details (OID: 16427)
نوع: جدول با ۵ ستون
کلید اصلی: order_detail_id (auto-increment)
اندازه فعلی: ۰ بایت (جدول خالی)
در این بخش چند کوئری مفید برای نظارت بر پایگاه داده و یافتن اطلاعات مهم مانند حجم پایگاه داده، بزرگترین جدولها، ایندکسهای استفادهنشده و فعالیتهای اخیر آورده شده است.
SELECT current_database() as db, current_user as user,
pg_size_pretty(pg_database_size(current_database())) as size,
(SELECT count(*) FROM pg_stat_activity WHERE datname=current_database()) as connections;
SELECT schemaname||'.'||relname as table,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||relname)) as total_size,
n_live_tup as rows
FROM pg_stat_user_tables
WHERE schemaname='public'
ORDER BY pg_total_relation_size(schemaname||'.'||relname) DESC LIMIT 5;
SELECT relname as table, indexrelname as index, idx_scan as usage,
pg_size_pretty(pg_relation_size(indexrelid)) as size,
CASE WHEN idx_scan=0 THEN '⚠️ استفاده نشده' ELSE '✅ فعال' END as status
FROM pg_stat_user_indexes
WHERE schemaname='public'
ORDER BY idx_scan DESC, pg_relation_size(indexrelid) DESC;
SELECT pid, usename as user, datname as db, state,
CASE WHEN state='active' THEN left(query,40) ELSE 'idle' END as query
FROM pg_stat_activity
WHERE datname=current_database()
ORDER BY state_change DESC LIMIT 5;
SELECT relname as table, seq_scan as seq_scans, idx_scan as idx_scans,
n_tup_ins as inserts, n_tup_upd as updates, n_tup_del as deletes
FROM pg_stat_user_tables
WHERE schemaname='public'
ORDER BY (seq_scan + idx_scan) DESC LIMIT 10;
ابزار خط فرمان psql دستورات میانبری دارد که کار با پایگاه داده را سریعتر میکند. در اینجا پرکاربردترین آنها را فهرست کردهایم.
| دستور | کاربرد | مثال |
|---|---|---|
\l | لیست پایگاههای داده | \l |
\c dbname | اتصال به پایگاه داده | \c mydb |
\dt | لیست جدولها | \dt |
\di | لیست ایندکسها | \di |
\ds | لیست دنبالهها | \ds |
\dv | لیست نماها | \dv |
\d+ table | جزئیات جدول (شامل حجم فیزیکی) | \d+ users |
\conninfo | اطلاعات اتصال فعلی | \conninfo |
جدول زیر برخی نشانههای مشکل را همراه با کوئری چکآپ سریع نشان میدهد. البته این مطالب را در آینده، به تفصیل بررسی خواهیم کرد.
| مشکل | نشانه | کوئری چک سریع |
|---|---|---|
| ایندکسهای بیاستفاده | نوشتنهای کند، فضای هدررفته | idx_scan = 0 در pg_stat_user_indexes |
| ایندکسهای جاافتاده | کوئریهای کند، seq_scan بالا | seq_scan بالا در pg_stat_user_tables |
| بادکردگی جدول (bloat) | جدول بزرگ اما تعداد ردیف کم | مقایسه اندازه واقعی با تخمینی |
| مشکلات اتصال | عدم توانایی در اتصال | بررسی pg_stat_activity و max_connections |
اگر مسئول مدیریت یک پایگاه داده PostgreSQL هستید، میتوانید هر روز این کوئریها را اجرا کنید تا از سلامت کلی سیستم مطمئن شوید.
-- ۱. سلامت پایگاه داده
SELECT current_database(), pg_size_pretty(pg_database_size(current_database()));
-- ۲. اتصالات فعال
SELECT count(*) as active_connections FROM pg_stat_activity WHERE datname=current_database();
-- ۳. بزرگترین جدولها
SELECT schemaname||'.'||relname as table, pg_size_pretty(pg_total_relation_size(schemaname||'.'||relname)) as size
FROM pg_stat_user_tables WHERE schemaname='public' ORDER BY pg_total_relation_size(schemaname||'.'||relname) DESC LIMIT 3;
-- ۴. ایندکسهای استفادهنشده
SELECT count(*) as unused_indexes FROM pg_stat_user_indexes WHERE schemaname='public' AND idx_scan=0;
-- ۵. فعالیت یک ساعت اخیر
SELECT count(*) as recent_queries FROM pg_stat_activity WHERE datname=current_database() AND query_start > now() - interval '1 hour';
pg_class، pg_namespace و نماهای pg_stat_* تمام اطلاعات لازم را در اختیارتان میگذارند.pg_relation_filepath() میتوانید مسیر دقیق هر شیء را بیابید.seq_scan ممکن است نیاز به ایندکس جدید را نشان دهد.شروع سریع: کافیست دستور \dt و سپس \d+ name را برای هر جدول مهم اجرا کنید! 🔍
این راهنما بر اساس PostgreSQL 18 تهیه شده است! 🛠️
در فیلم آموزشی زیر، تمامی مطالب فوق به صورت عملی و کاربردی با توضیحات کامل بیان شده است و میتوانید با مشاهده آن به دید عمیقتری نسبت به پستگرس دست یابید. البته هنوز در گامهای اولیه هستیم و به تدریج، این دید، عمیقتر و کامل تر خواهد شد.
نکته : اگر فیلم را در بخش زیر مشاهده نمیکنید، اطمینان حاصل کنید که با آی پی ایران به مشاهده آن نشسته اید و یا اینترنت پروایدر دیگری را امتحان کنید.