يمكن لمعاملة PostgreSQL مفتوحة للقراءة فقط أن تمنع DROP TABLE لأن SELECT الخاص بها يحتفظ بقفل جدول ACCESS SHARE إلى أن تنتهي المعاملة. يتطلب الإسقاط قفل ACCESS EXCLUSIVE متعارضًا، لذا ينتظر، حتى بعد عودة الاستعلام. PostgreSQL: القفل الصريح
يوضح هذا الدليل كيفية إعادة إنتاج هذا الانتظار من جهاز Mac، وتحديد الجلسة التي تحجب العملية، وإنهاء المعاملة على نحو صحيح باستخدام node-postgres. يستخدم الدليل صفوف مقالات اصطناعية فقط. تستهدف أوامر SQL الإصدار PostgreSQL 18، وهو الإصدار الذي تعرضه الوثائق الرسمية الحالية التي جرى الرجوع إليها لهذا المقال؛ راجع دليل الإصدار المطابق لخادمك قبل تطبيق إعدادات خاصة بإصدار معين.
ما المقصود بوضع القراءة فقط في ما يتعلق بأقفال PostgreSQL؟
يقيّد READ ONLY الأوامر التي يمكن للمعاملة تنفيذها. فهو، على سبيل المثال، يمنع إجراء تعديلات على الجداول العادية، كما يمنع أوامر مثل DROP TABLE. لكنه لا يعفي عمليات القراءة من قفل الجداول. في هذا السيناريو، القارئ والجلسة التي تحاول الإسقاط معاملتان منفصلتان. PostgreSQL: SET TRANSACTION
يحصل SELECT عادي تلقائيًا على قفل ACCESS SHARE في الجداول المشار إليها. ويتطلب DROP TABLE قفل ACCESS EXCLUSIVE، وهو متعارض مع ذلك القفل. تستمر الأقفال عادةً إلى أن تنتهي المعاملة؛ ومن الاستثناءات الموثقة التراجع إلى نقطة حفظ أُنشئت قبل الحصول على القفل. لا يستخدم المثال أدناه أي نقاط حفظ. PostgreSQL: القفل الصريح
الفرق الأساسي هو بين اكتمال الاستعلام واكتمال المعاملة. ظهور صفوف المقالات في الطرفية يعني أن الاستعلام قد أعاد النتيجة. تحقّق مما إذا كانت المعاملة المحيطة به قد انتهت أيضًا. في psql، يكون الالتزام التلقائي مفعّلًا عادةً، لكن BEGIN صريحًا يُبقي المعاملة مفتوحة إلى أن تنهيها. PostgreSQL: psql
كيف تعيد إنتاج تعطل الإسقاط على جهاز Mac؟
استخدم قاعدة بيانات اختبار محلية موجودة مسبقًا بإصدار PostgreSQL 18، مع توفر psql بالفعل في طرفية Mac. افتح ثلاث علامات تبويب للطرفية، واتصل بقاعدة البيانات نفسها من كل واحدة. سمّها A وB وC. يجب أن تكون هذه اتصالات منفصلة؛ فمجرد فتح لوحات تحرير منفصلة لا يحقق ذلك.
يفترض أمر الاتصال التالي أن اسم قاعدة بيانات الاختبار المحلية هو postgres. استبدله باسم قاعدة بيانات الاختبار الموجودة لديك عند الحاجة. شغّل الأمر في كل علامة تبويب. يتجنب الخيار -X تحميل ملف بدء التشغيل الشخصي psql. PostgreSQL: psql
psql -X -d postgres
في كل اتصال، فعّل الالتزام التلقائي صراحةً وتحقق من إصدار الخادم. الكتل أدناه شرح متسلسل؛ إذ تعتمد الكتل اللاحقة على الإعداد وحالة الجلسة اللذين أُنشئا سابقًا.
\set AUTOCOMMIT on
SHOW server_version;
إنشاء جدول المقالات الاصطناعي
في علامة التبويب C، شغّل الإعداد مرة واحدة. استخدم اسم جدول غير مستخدم: إذا كان public.article_lock_demo موجودًا بالفعل، فتوقف واختر اسمًا آخر واستخدمه باستمرار في جميع الأمثلة. لا يزيل الإعداد جدولًا موجودًا عمدًا.
CREATE TABLE public.article_lock_demo (
id integer PRIMARY KEY,
title text NOT NULL
);
INSERT INTO public.article_lock_demo (id, title)
VALUES
(1, 'Choosing a notebook'),
(2, 'Organizing a reading list');
اترك معاملة القارئ مفتوحة
في علامة التبويب A، شغّل هذه الكتلة واترك الاتصال عند موجه الأوامر. لا تنفّذ الالتزام بعد.
SET application_name = 'article_reader_demo';
BEGIN READ ONLY;
SHOW transaction_read_only;
SELECT id, title
FROM public.article_lock_demo
ORDER BY id;
توقع أن يعرض transaction_read_only القيمة on، ثم الصفين الاصطناعيين. اكتملت القراءة، لكن المعاملة الصريحة لا تزال مفتوحة.
جرّب الإسقاط من اتصال آخر
في علامة التبويب B، شغّل الكتلة التالية. أرسل DROP TABLE بوصفه العبارة الأخيرة، من دون إلحاق أمر آخر.
SET application_name = 'article_drop_demo';
BEGIN;
SET LOCAL lock_timeout = '60s';
DROP TABLE public.article_lock_demo;
ينبغي أن ينتظر الإسقاط ما دامت A مفتوحة. تمنحك مهلة الستين ثانية التوضيحية وقتًا لفحص الانتظار؛ وهي ليست إعدادًا عامًا موصى به. يُجهض lock_timeout العبارة التي تنتظر طويلًا للحصول على قفل، ويُطبّق على كل محاولة للحصول على قفل على حدة. PostgreSQL: الإعدادات الافتراضية لاتصال العميل
أبقِ المعاملة الصريحة في B مفتوحة بعد عودة الإسقاط. ستتراجع عنها لاحقًا، ما يحافظ على جدول العرض حتى لو نجح الإسقاط. يزيل DROP TABLE الجدول والفهارس والقيود المرتبطة به عند الالتزام بمعاملته. PostgreSQL: DROP TABLE
كيف تثبت أي جلسة تحجب الإسقاط؟
أثناء انتظار B، شغّل الاستعلام التالي في C. يحدد اتصالي العرض ويسأل PostgreSQL عن الجلسات التي تحجب طلب القفل لكل اتصال. يعيد pg_blocking_pids() مصفوفة بمعرّفات العمليات التي تحجب الجلسة، أو مصفوفة فارغة إذا لم تكن العملية محجوبة. PostgreSQL: دوال ومشغّلات معلومات النظام
SELECT
pid,
application_name,
state,
xact_start,
wait_event_type,
wait_event,
pg_blocking_pids(pid) AS blocking_pids,
query
FROM pg_stat_activity
WHERE datname = current_database()
AND application_name IN (
'article_reader_demo',
'article_drop_demo'
)
ORDER BY application_name;
توقع أن تظهر A بالحالة idle in transaction: لديها معاملة مفتوحة لكنها لا تنفذ استعلامًا. وقد تظهر B بالحالة active أثناء الانتظار، مع نوع حدث انتظار Lock. توثّق PostgreSQL أن حالة التنفيذ وأحداث الانتظار مستقلان؛ لذا فإن active وحدها لا تثبت أن الأمر يحرز تقدمًا. PostgreSQL: نظام الإحصاءات التراكمي
بعد ذلك، افحص طلبات أقفال الجدول الفعلية. شغّل هذا الاستعلام في C قبل إنهاء معاملة A.
SELECT
a.application_name,
l.pid,
l.mode,
l.granted
FROM pg_locks AS l
JOIN pg_stat_activity AS a ON a.pid = l.pid
WHERE l.locktype = 'relation'
AND l.database = (
SELECT oid
FROM pg_database
WHERE datname = current_database()
)
AND l.relation = 'public.article_lock_demo'::regclass
ORDER BY a.application_name, l.mode;
النمط المتوقع أدناه توضيحي، وليس ناتج تنفيذ مسجلًا. في pg_locks، تعني granted = true أن العملية تحتفظ بالقفل؛ وتعني false أنها تنتظره. PostgreSQL: pg_locks
| الجلسة | وضع القفل | granted |
التفسير |
|---|---|---|---|
article_reader_demo |
AccessShareLock |
true |
القارئ يحتفظ بقفل الجدول |
article_drop_demo |
AccessExclusiveLock |
false |
الإسقاط ينتظر قفل الجدول |
استخدم الاستعلامين معًا في تسلسل تشخيصي: أثبت أولًا علاقة الحجب، ثم افحص الأقفال المحتفظ بها والمطلوبة على الجدول المعني. خمول الجلسة وحده دليل غير كافٍ؛ فالعلاقة وصفوف الأقفال تربطانها بعملية الإسقاط هذه تحديدًا.
إذا كانت B قد انتهت مهلتها بالفعل، فلن يظهر طلب القفل الذي كانت تنتظره. شغّل ROLLBACK; في B، وأعد كتلة معاملة B، ثم افحص بسرعة بينما لا تزال A مفتوحة.
كيف تحرر القارئ وتنهي العرض التوضيحي؟
في A، أنهِ معاملة القراءة فقط:
COMMIT;
إذا كانت B لا تزال تنتظر وكانت A هي الحاجب الوحيد لها، فيمكن للإسقاط الآن أن يكتمل. في B، تراجع فورًا عن إسقاط العرض حتى يظل الجدول الاصطناعي متاحًا:
ROLLBACK;
إذا انتهت مهلة B قبل تنفيذ الالتزام في A، فشغّل ROLLBACK; في B أولًا. بعد انتهاء A، أعد تشغيل كتلة B ثم شغّل ROLLBACK; مرة أخرى بعد نجاح الإسقاط. يعالج هذا كلا الاحتمالين من دون ترك معاملة B غير منتهية.
تساعد أداة القرار التالية على التمييز بين مشكلة تتعلق بمدة المعاملة وتفسير آخر. تتبع تسميات النشاط حالات الجلسات الموثقة في PostgreSQL. PostgreSQL: نظام الإحصاءات التراكمي
| الملاحظة | الإجراء التالي |
|---|---|
A في حالة idle in transaction، ومصفوفة الحجب في B تحتوي على معرّف عملية A |
أنهِ معاملة A من اتصالها |
| انتهت A، لكن B لا تزال تعرض معرّفات عمليات حاجبة | افحص الحواجب المتبقية |
| تعرض B مهلة انتظار قفل | تراجع عن المعاملة الصريحة في B قبل إعادة المحاولة |
| تعرض B خطأ تبعية | افحص التبعيات بدلًا من التعامل معه على أنه انتظار قفل |
| تعود B فورًا | تحقّق مما إذا كانت معاملة القارئ المقصودة لا تزال مفتوحة |
يتعامل IF EXISTS مع غياب الجدول، بينما يتحكم CASCADE في إزالة العناصر التابعة. ولا يمثل أي منهما خيارًا للانتظار على قفل. أبقِ سلوك التبعيات الافتراضي لهذا التمرين؛ فلا توجد صيغة CONCURRENTLY ضمن الصياغة الرسمية لأمر DROP TABLE. PostgreSQL: DROP TABLE
كيف ينبغي لـ node-postgres إنهاء معاملة قراءة فقط؟
في node-postgres، استخدم عميلًا واحدًا مستعارًا عبر BEGIN، ولكل عبارة في المعاملة، ولعبارة COMMIT أو ROLLBACK الختامية. تحذّر وثائق المعاملات الرسمية صراحةً من استخدام pool.query() للمعاملة، لأن العبارات قد تُرسل إلى عملاء مختلفين. node-postgres: المعاملات
تعامل مع إنهاء المعاملة وتحرير العميل بوصفهما خطوتين منفصلتين في التطبيق. يعيد client.release() العميل المستعار إلى مجمّعه؛ لذا أنهِ المعاملة قبل استدعاء هذه الدالة. إذا فشل التراجع، تتيح واجهة المجمّع إتلاف العميل بتمرير قيمة صادقة إلى release(). node-postgres: واجهة المجمّع
في هذا البرنامج الكامل، أبقِ الجدول الاصطناعي الذي أُنشئ في الشرح. استخدم بيئة تشغيل Node.js مثبّتة تدعم وحدات ES وAggregateError، وثبّت pg في دليل مشروع العمل:
npm install pg
احفظ ما يلي باسم read-articles.mjs. اضبط اتصاله على قاعدة بيانات الاختبار المحلية نفسها المستخدمة في psql. يدعم node-postgres متغيرات بيئة اتصال PostgreSQL، بما فيها PGDATABASE. node-postgres: الاتصال
import pg from 'pg';
const { Pool } = pg;
const pool = new Pool({ connectionTimeoutMillis: 5000 });
async function main() {
try {
const client = await pool.connect();
let discard = false;
try {
await client.query('BEGIN READ ONLY');
const result = await client.query(
'SELECT id, title FROM public.article_lock_demo ORDER BY id'
);
await client.query('COMMIT');
console.table(result.rows);
} catch (error) {
try {
await client.query('ROLLBACK');
} catch (rollbackError) {
discard = true;
throw new AggregateError(
[error, rollbackError],
'Query and rollback failed'
);
}
throw error;
} finally {
client.release(discard);
}
} finally {
await pool.end();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
PGDATABASE=postgres node read-articles.mjs
ينفذ البرنامج الالتزام قبل عرض الصفوف، ويتراجع عند الفشل، ويحرر العميل في الحالتين. جرى التحقق من صياغة JavaScript باستخدام Node.js؛ أما خطوات قاعدة البيانات فتمت مراجعتها بالاستناد إلى الوثائق، لكن لم تُنفذ هنا لعدم توفر PostgreSQL في بيئة البحث.
لقراءة مستقلة واحدة، يُعد pool.query() المسار الميسّر الموثق، ويجنبك استعارة عميل يدويًا. اختر معاملة صريحة عندما تتطلبها مهمتك، وأبقِ معالجة الملفات غير المرتبطة أو التفاعل مع سطح المكتب خارج مدتها. node-postgres: واجهة المجمّع
أي مهلة تعالج بقاء القارئ دون إنهاء؟
استخدم المهلات وفقًا لما ينتظر. يحد lock_timeout من مدة انتظار الإسقاط للحصول على قفل. ويحد statement_timeout من مدة تنفيذ العبارة، لذا فهو لا يغلق معاملة لمجرد أن التطبيق توقف عن إرسال الأوامر بعد قراءة ناجحة. أما idle_in_transaction_session_timeout فينهي تحديدًا الجلسات التي تظل خاملة داخل معاملة مفتوحة. هذه إعدادات موثقة في PostgreSQL 18؛ اختر مددًا تناسب العمل المتوقع في تطبيقك. PostgreSQL: الإعدادات الافتراضية لاتصال العميل
في هذه المهمة، يتمثل الفحص الحاسم في التأكد مما إذا كانت معاملة القارئ قد انتهت. تحقق من معرّف العملية الحاجبة، وافحص أقفال الجدول، وأنهِ المعاملة على الاتصال الذي يملكها. في node-postgres، اجعل أمر إنهاء المعاملة الأخير جزءًا من مساري النجاح والخطأ المعتادين حتى لا تترك قراءة مكتملة عملية إسقاط الجدول التالية معلقة.
المصادر
- 13.3. القفل الصريح — مجموعة PostgreSQL العالمية للتطوير
- SET TRANSACTION — مجموعة PostgreSQL العالمية للتطوير
- psql — مجموعة PostgreSQL العالمية للتطوير
- 19.11. الإعدادات الافتراضية لاتصال العميل — مجموعة PostgreSQL العالمية للتطوير
- DROP TABLE — مجموعة PostgreSQL العالمية للتطوير
- 9.27. دوال ومشغّلات معلومات النظام — مجموعة PostgreSQL العالمية للتطوير
- 27.2. نظام الإحصاءات التراكمي — مجموعة PostgreSQL العالمية للتطوير
- 53.13. pg_locks — مجموعة PostgreSQL العالمية للتطوير
- المعاملات — node-postgres
- واجهة المجمّع — node-postgres
- الاتصال — node-postgres