يُنشئ PostgreSQL صفوفًا إضافية في فهرس القيود لأن الإشارة إلى جدول مقسّم يجب أن تعمل أيضًا عبر أقسامه المادية. لتحديد ما إذا كانت هذه الصفوف تمثل علاقة واحدة أم تعريفات منفصلة، افحص روابطها بالقيود الأصلية والجداول المشار إليها.
هذا الشرح مخصص لمطوّر يستخدم جهاز Mac للتحقيق في قائمة قيود غير متوقعة في متصفح قاعدة بيانات أو تطبيق Node.js. تتمثل المهمة في إعادة إنتاج السلوك باستخدام صفوف مقالات اصطناعية، وتتبع عائلة القيود، وتحديد ما ينبغي أن يتضمنه تقرير المخطط المنطقي.
لماذا ينتج عن مفتاح خارجي واحد عدة صفوف للقيود؟
يوفر الجدول المقسّم جدولًا منطقيًا تُخزّن صفوفه في أقسامه. ولا يملك الجدول الأب مساحة تخزين خاصة به. ويكتسب هذا الفرق أهمية عندما يشير جدول آخر إليه: إذ يجب على PostgreSQL الحفاظ على العلاقة عند تغيير الصفوف في الأقسام المادية. راجع وثائق تقسيم الجداول الرسمية.
توضح آلية PostgreSQL 18 هذه الطريقة صراحةً. إذ ينشئ الإجراء addFkRecurseReferenced مشغّلات إجراءات المفاتيح الخارجية وينشئ تكراريًا صفًا إضافيًا من نوع pg_constraint لكل قسم مشار إليه. ويوضح رمز التحقق أن هذه الصفوف على الجانب المشار إليه تدعم مشغّلات الإجراءات بدلًا من أن تمثل عمليات تحقق منفصلة. راجع تنفيذ إنشاء المفاتيح الخارجية والتحقق منها في PostgreSQL.
لذلك لا تعني هذه الصفوف أن كل قيمة مرجعية يجب أن تكون موجودة في كل قسم. تظل العلاقة المعرّفة تستهدف الجدول الأب المقسّم. ويحتاج PostgreSQL أيضًا إلى معالجة التغييرات على الجانب المشار إليه، بما فيها السلوك المهيأ عند حذف صف مشار إليه أو تغيير مفتاحه. يشرح قسم المفاتيح الخارجية هذه السلوكيات.
ما إصدارات PostgreSQL التي تدعم ذلك؟
أُضيف دعم المفاتيح الخارجية التي تشير إلى جدول مقسّم في PostgreSQL 12. ولا يمكن للإصدارات الأقدم استخدام التعريف نفسه الذي يستهدف الجدول الأب. لم يعد PostgreSQL 12 مدعومًا؛ فهو يمثل الحد التاريخي لظهور الميزة، وليس إصدارًا موصى به للتثبيت. تستند هذه المقالة إلى وثائق PostgreSQL 18 الحالية وفرع الشيفرة المصدرية المدعوم الخاص به. راجع ملاحظات إصدار PostgreSQL 12.
أعد إنتاج العلاقة باستخدام صفوف مقالات اصطناعية
استخدم قاعدة بيانات PostgreSQL محلية موجودة للتطوير وتثبيتًا صالحًا لـ psql. على جهاز Mac، افتح Terminal، واتصل باستخدام psql -d postgres، ثم تحقّق من الخادم بالأمر SHOW server_version;. غيّر اسم قاعدة بيانات التطوير عند الحاجة. تشرح وثائق psql الرسمية كيفية اختيار قاعدة البيانات وتنفيذ أوامر SQL التفاعلية.
شغّل إعداد التهيئة الكامل هذا مرة واحدة. فهو ينشئ مخططًا مخصصًا من دون إزالة الكائنات الموجودة. إذا كان هذا المخطط موجودًا مسبقًا، فتوقف واستخدم قاعدة بيانات تطوير جديدة أو غيّر اسم المخطط باستمرار في جميع الأمثلة.
BEGIN;
CREATE SCHEMA fk_partition_demo;
CREATE TABLE fk_partition_demo.articles (
article_id integer NOT NULL,
publication_year integer NOT NULL,
title text NOT NULL,
PRIMARY KEY (article_id, publication_year)
) PARTITION BY RANGE (publication_year);
CREATE TABLE fk_partition_demo.articles_2025
PARTITION OF fk_partition_demo.articles
FOR VALUES FROM (2025) TO (2026);
CREATE TABLE fk_partition_demo.articles_2026
PARTITION OF fk_partition_demo.articles
FOR VALUES FROM (2026) TO (2027);
CREATE TABLE fk_partition_demo.article_notes (
note_id integer PRIMARY KEY,
article_id integer NOT NULL,
publication_year integer NOT NULL,
note_text text NOT NULL,
CONSTRAINT article_notes_article_fk
FOREIGN KEY (article_id, publication_year)
REFERENCES fk_partition_demo.articles
(article_id, publication_year)
);
INSERT INTO fk_partition_demo.articles
(article_id, publication_year, title)
VALUES
(101, 2025, 'A guide to paper models'),
(102, 2026, 'Choosing a notebook layout');
INSERT INTO fk_partition_demo.article_notes
(note_id, article_id, publication_year, note_text)
VALUES
(1, 101, 2025, 'Add a materials list'),
(2, 102, 2026, 'Include a grid example');
COMMIT;
المفتاح الأساسي المركّب مقصود. يشترط PostgreSQL أن يتضمن القيد الأساسي أو الفريد للجدول المقسّم كل أعمدة مفتاح التقسيم، مع قيود إضافية على التعبيرات في مفاتيح التقسيم. وبما أن هذا الجدول مقسّم حسب publication_year، يتضمن المفتاح الأساسي ذلك العمود، ويشير المفتاح الخارجي إلى الزوج كاملًا. راجع التقسيم التصريحي: القيود.
وبالتالي، تحدد العينة المقالة باستخدام (article_id, publication_year). وهي لا تثبت أن article_id وحده فريد على مستوى الجدول كله. إذا كان تطبيقك يحتاج إلى مراجع تستخدم article_id وحده، فأعد النظر في تصميم التقسيم والمفاتيح قبل اعتماد هذا المثال.
تتبّع عائلة القيود بدلًا من مقارنة الأسماء
حقول الفهرس المفيدة هي conrelid، وهو الجدول الذي يحتوي المفتاح المرجعي؛ وconfrelid، وهو الجدول المشار إليه؛ وconparentid، وهو معرّف كائن قيد الأب. قد لا تكون أسماء القيود فريدة، لذلك لا تكفي الأسماء وحدها لإثبات أن صفين مستقلان. توثّق pg_constraint هذه الحقول.
بعد تشغيل إعداد التهيئة، نفّذ هذا الاستعلام. يبدأ بمفاتيح الجذر الخارجية المعرّفة على article_notes ويتتبع قيودها الفرعية تكراريًا.
WITH RECURSIVE fk_family AS (
SELECT c.oid, c.conparentid, c.oid AS root_oid, 0 AS depth
FROM pg_catalog.pg_constraint AS c
WHERE c.contype = 'f'
AND c.conrelid =
'fk_partition_demo.article_notes'::regclass
AND c.conparentid = 0
UNION ALL
SELECT child.oid, child.conparentid,
parent.root_oid, parent.depth + 1
FROM pg_catalog.pg_constraint AS child
JOIN fk_family AS parent
ON child.conparentid = parent.oid
WHERE child.contype = 'f'
)
SELECT
family.root_oid,
family.depth,
c.oid AS constraint_oid,
c.conname,
c.conparentid,
c.conrelid::regclass AS referencing_table,
c.confrelid::regclass AS referenced_table,
pg_get_constraintdef(c.oid) AS definition
FROM fk_family AS family
JOIN pg_catalog.pg_constraint AS c ON c.oid = family.oid
ORDER BY family.root_oid, family.depth, c.oid;
بالنسبة إلى هذا المثال ذي المستوى الواحد، تشير آلية التنفيذ إلى وجود ثلاثة صفوف للمفاتيح الخارجية: صف يستهدف articles وصف لكل واحد من قسميه. هذه نتيجة متوقعة مستنبطة من الشيفرة المصدرية، وليست مخرجات تنفيذ مسجلة. تجنّب تثبيت الأسماء المُنشأة وقيم معرّفات الكائنات في الشيفرة. تنشئ الشيفرة المصدرية نفسها أيضًا قيودًا على الجانب المشار إليه عند إنشاء قسم أو إرفاقه لاحقًا. راجع tablecmds.c.
| الصف في هذا المثال | الجدول الذي يحتوي المفتاح المرجعي | الجدول المشار إليه | رابط الأب |
|---|---|---|---|
| علاقة الجذر | article_notes |
articles |
conparentid = 0 |
| قيد فرعي لقسم مشار إليه | article_notes |
articles_2025 |
معرّف كائن قيد الجذر |
| قيد فرعي لقسم مشار إليه | article_notes |
articles_2026 |
معرّف كائن قيد الجذر |
التفصيل الذي يسهل إغفاله هو أن الجدول الذي يحتوي المفتاح المرجعي يظل article_notes في الصفوف الثلاثة. افحص عمودي الجدول ورابط الأب معًا؛ فقد يؤدي النظر إلى الجدول المرتبط بالقيد وحده إلى إخفاء بنية الأقسام المشار إليها.
يعرض الاستعلام أيضًا التعريفات المعاد تركيبها عبر pg_get_constraintdef(). يوثّق PostgreSQL هذه الدالة بوصفها إعادة تركيب للتعريف، لا نص SQL الأصلي المقدّم. استخدمها لفهم العلاقة، لا لاستعادة نص الترحيل الدقيق. راجع دوال ومعاملات معلومات النظام.
افحص الصفوف نفسها باستخدام node-postgres على جهاز Mac
يعيد node-postgres نتائج الاستعلام من الخادم؛ أما الصفوف المتعلقة بالتقسيم فمصدرها آلية القيود في PostgreSQL. تقبل واجهة الاستعلام الموثّقة فيه نص SQL ومصفوفة منفصلة من قيم المعاملات. هنا، يُمرر اسم العلاقة كقيمة نصية يحوّلها PostgreSQL إلى regclass. راجع الاستعلامات.
بعد تثبيت إصدار مدعوم وحديث من Node.js، أنشئ مجلد عمل وثبّت pg. يظهر أمر التثبيت وإرشادات التوافق مع بيئة التشغيل في مقدمة node-postgres.
mkdir pg-fk-inspection
cd pg-fk-inspection
npm init -y
npm install pg
احفظ البرنامج الكامل التالي باسم inspect-fks.mjs. ويتطلب إعداد SQL أعلاه بعد اعتماده. وعلى خلاف تقرير SQL التكراري، يسرد هذا البرنامج الصفوف المرتبطة مباشرةً بالجدول المحدد الذي يحتوي المفتاح المرجعي؛ وهذا كافٍ لجدول article_notes غير المقسّم في هذا المثال.
import pg from 'pg';
const { Client } = pg;
const client = new Client({ connectionTimeoutMillis: 5000 });
const relation = process.argv[2] ??
'fk_partition_demo.article_notes';
try {
await client.connect();
const version = await client.query('SHOW server_version');
console.log('PostgreSQL:', version.rows[0].server_version);
const result = await client.query(`
SELECT oid, conname,
conrelid::regclass::text AS referencing_table,
confrelid::regclass::text AS referenced_table,
conparentid,
conparentid = 0 AS is_root,
pg_get_constraintdef(oid) AS definition
FROM pg_catalog.pg_constraint
WHERE contype = 'f' AND conrelid = $1::regclass
ORDER BY conparentid, oid
`, [relation]);
console.table(result.rows);
} catch (error) {
console.error(error.message);
process.exitCode = 1;
} finally {
await client.end();
}
شغّله على قاعدة البيانات نفسها المستخدمة لإعداد SQL. يستخدم node-postgres متغيرات بيئة اتصال PostgreSQL، ومنها PGDATABASE، ويوثّق القيم الافتراضية لها في الاتصال.
PGDATABASE=postgres node inspect-fks.mjs
إذا تعذّر العثور على العلاقة، فتحقّق من أن الجلستين تستخدمان قاعدة البيانات نفسها ومن نجاح اعتماد إعداد التهيئة. تعني النتيجة الفارغة أن هذا الاستعلام لم يعثر على مفاتيح خارجية مرتبطة مباشرةً بهذه العلاقة؛ ولا يعرض المراجع الواردة من جداول أخرى. اجتاز JavaScript فحصًا نحويًا أثناء إعداد هذه المقالة. لم يكن تنفيذ قاعدة البيانات متاحًا، لذا فُحصت أمثلة SQL وبنية الفهرس المتوقعة بالرجوع إلى الوثائق والشيفرة المصدرية المسترجعة.
حدّد ما ينبغي عرضه أو التحقيق فيه
اتبع الخطوات التالية عندما تبدو قائمة القيود أطول من المتوقع. فهي تفصل تقرير العلاقات المنطقية عن جرد آلية التنفيذ، مع الإبقاء على الأدلة المفيدة.
- تحقّق من العلاقة وإصدار الخادم. استخدم اسم العلاقة المؤهل بالمخطط وسجّل
SHOW server_version;قبل مقارنة السلوك بالوثائق. - تتبّع روابط الأب. في هذا المثال، تنتمي الصفوف الفرعية التي تشترك في معرّف كائن الجذر إلى عائلة قيود العلاقة المعرّفة.
- قارن الطرفين. يفسر تغيّر
confrelidمع بقاء الجدول الذي يحتوي المفتاح المرجعي نفسه صفوف الأقسام المشار إليها الموضحة أعلاه. - حدّد غرض التقرير. لعلاقات الجذر المعرّفة على الجدول المحدد، استخدم التصفية بـ
contype = 'f' AND conparentid = 0. ولعرض العائلة كاملة، احتفظ بالتقرير التكراري. - افحص الجذور المنفصلة كلًا على حدة. قارن أزواج الأعمدة والجداول المشار إليها والإجراءات وحالة التحقق وقابلية التأجيل قبل الحكم بأن تعريفين زائدان عن الحاجة.
يتبع أسلوب إعداد التقارير هذا المعرّفات وحقول العلاقات الموثّقة في pg_constraint. نطاق مرشح الجذر فقط مقصود: فقد يشارك قسم عبر قيود فرعية موروثة، لذا فإن تصفية الجداول الطرفية إلى الجذور لا تقدم تقريرًا كاملًا بكل علاقة تؤثر فيها.
إذا كنت تحتاج إلى أدلة على كائنات الإنفاذ، فإن pg_trigger.tgconstraint يربط المشغّل بقيده، بينما يحدد tgrelid الجدول الذي يحمل المشغّل. ويحدد الفهرس أيضًا المشغّلات المُنشأة داخليًا. توفر هذه الحقول مسارًا إضافيًا للفحص في pg_trigger.
أبقِ الصفوف المُنشأة ما دامت العلاقة المعرّفة مطلوبة. في الحالة المُعاد إنتاجها، الحل العملي هو عرض علاقة الجذر في طريقة عرض المخطط المنطقي والإبقاء على الصفوف الفرعية في طريقة العرض التشخيصية. وبمجرد ظهور روابط الأب وطرفي الجدول، توضح الصفوف الإضافية كيفية تنفيذ PostgreSQL للإشارة عبر الأقسام.
المصادر
تقسيم الجداول — مجموعة تطوير PostgreSQL العالمية.
pg_constraint — مجموعة تطوير PostgreSQL العالمية.
الإصدار 12 — مجموعة تطوير PostgreSQL العالمية.
tablecmds.c، REL_18_STABLE — مجموعة تطوير PostgreSQL العالمية.
القيود — مجموعة تطوير PostgreSQL العالمية.
psql — مجموعة تطوير PostgreSQL العالمية.
دوال ومعاملات معلومات النظام — مجموعة تطوير PostgreSQL العالمية.
pg_trigger — مجموعة تطوير PostgreSQL العالمية.
مقدمة node-postgres — node-postgres.
الاستعلامات — node-postgres.
الاتصال — node-postgres.