PostgreSQL: المعاملات والتحكم في التزامن في PostgreSQL

آخر تحديث: 2026-08-26

1. ما ستتعلمه


2. القصة

Alice مسؤولة عن نظام الطلبات في منصة تجارة إلكترونية. خلال عرض ترويجي، كان لدى عنصر محدود الإصدار وحدة واحدة فقط متبقية في المخزون، وقام مستخدمان بتقديم طلب في نفس الوقت تقريباً:

بدون حماية المعاملات، يمكن لكلاهما تقديم الطلب بنجاح، مما يدفع المخزون إلى -1 ويسبب بيعاً زائداً. تحتاج Alice إلى عزل المعاملات في PostgreSQL، MVCC، وآليات قفل الصفوف لضمان أن مستخدماً واحداً فقط ينجح في خصم المخزون بينما يتم حظر الآخر أو يحصل على خطأ — ضماناً لاتساق البيانات.


3. المفهوم: ACID وأساسيات المعاملات

(1) خصائص ACID الأربعة

الخاصية الإنجليزية المعنى تنفيذ PostgreSQL
الذرية Atomicity المعاملة كل شيء أو لا شيء: تنجح بالكامل أو تتراجع بالكامل WAL (سجل الكتابة المسبقة)
الاتساق Consistency قاعدة البيانات تحقق قيودها قبل وبعد المعاملة القيود، المشغلات، فحوصات الأنواع
العزل Isolation المعاملات المتزامنة لا تتداخل مع بعضها MVCC + الأقفال
المتانة Durability البيانات المودعة لا تفقد WAL + fsync

(1) ▶ مثال: ذرية ACID — التحويل إما ينجح بالكامل أو يتراجع بالكامل

SQL
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 500 WHERE account_id = 2;
COMMIT;

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(2) عبارات التحكم في المعاملات

العبارة الغرض SQL المقابل
بدء المعاملة فتح معاملة صراحةً BEGIN أو START TRANSACTION
إيداع المعاملة استمرار جميع التغييرات COMMIT
التراجع عن المعاملة التراجع عن جميع التغييرات ROLLBACK

(2) ▶ مثال: التراجع عن معاملة لاستعادة البيانات

SQL
BEGIN;
DELETE FROM orders WHERE order_id = 999;
-- عذراً، حذف خاطئ!
ROLLBACK;
-- تم استعادة البيانات

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(3) وضع الالتزام التلقائي

PostgreSQL تفعل الالتزام التلقائي افتراضياً — كل عبارة SQL تودع تلقائياً كمعاملة خاصة بها.

الوضع السلوك إعداد psql
الالتزام التلقائي ON كل عبارة تودع تلقائياً افتراضي
الالتزام التلقائي OFF يتطلب COMMIT يدوياً \set AUTOCOMMIT off

(3) ▶ مثال: تعطيل الالتزام التلقائي والإيداع يدوياً

BASH
\set AUTOCOMMIT off
DELETE FROM orders WHERE order_status = 'cancelled';
-- تحقق قبل الإيداع
SELECT count(*) FROM orders WHERE order_status = 'cancelled';
COMMIT;

Output:

TEXT 📖 للعرض فقط
# command executed successfully

(4) SAVEPOINT التراجع الجزئي

تعيين نقطة حفظ داخل المعاملة بحيث يمكنك التراجع فقط إلى تلك النقطة بدلاً من المعاملة بالكا��ل.

(4) ▶ مثال: استخدام SAVEPOINT للتراجع الجزئي

SQL
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
SAVEPOINT sp1;
UPDATE accounts SET balance = balance - 200 WHERE account_id = 1;
-- التحديث الثاني كان خاطئاً، تراجع إلى نقطة الحفظ
ROLLBACK TO SAVEPOINT sp1;
-- التحديث الأول لا يزال نشطاً، يمكن الإيداع
COMMIT;

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully
العبارة الغرض
SAVEPOINT sp_name إنشاء نقطة حفظ
ROLLBACK TO SAVEPOINT sp_name التراجع إلى نقطة حفظ
RELEASE SAVEPOINT sp_name تحرير نقطة حفظ (التراجعات اللاحقة لا يمكنها الإشارة إليها)

4. المفهوم: مستويات عزل المعاملات

(1) مستويات العزل الثلاثة في PostgreSQL

تنفذ PostgreSQL ثلاثة مستويات عزل فقط (READ UNCOMMITTED تعين إلى READ COMMITTED):

مستوى العزل قراءة متسخة قراءة غير قابلة للتكرار قراءة وهمية تنفيذ PG
READ COMMITTED مستحيلة ممكنة ممكنة المستوى الافتراضي؛ كل استعلام يرى آخر لقطة مودعة
REPEATABLE READ مستحيلة مستحيلة ممكنة (PG تمنعها فعلياً) المعاملة ترى اللقطة عند بدايتها
SERIALIZABLE مستحيلة مستحيلة مستحيلة الأكثر صرامة؛ تكتشف تعارضات التسلسل

(5) ▶ مثال: تعيين مستوى العزل

SQL
BEGIN ISOLATION LEVEL READ COMMITTED;
-- أو
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- أو
BEGIN ISOLATION LEVEL SERIALIZABLE;

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(2) READ COMMITTED مقابل REPEATABLE READ عملياً

(6) ▶ مثال: READ COMMITTED — استعلامان في نفس المعاملة يعيدان نتائج مختلفة

SQL
-- الجلسة 1
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM accounts WHERE account_id = 1; -- يعيد 1000

-- الجلسة 2 (اتصال آخر)
UPDATE accounts SET balance = 800 WHERE account_id = 1;
COMMIT;

-- العودة إلى الجلسة 1
SELECT balance FROM accounts WHERE account_id = 1; -- يعيد 800 (يرى التغيير المودع)
COMMIT;

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(7) ▶ مثال: REPEATABLE READ — استعلامان في نفس المعاملة يعيدان نتائج متسقة

SQL
-- الجلسة 1
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE account_id = 1; -- يعيد 1000

-- الجلسة 2 (اتصال آخر)
UPDATE accounts SET balance = 800 WHERE account_id = 1;
COMMIT;

-- العودة إلى الجلسة 1
SELECT balance FROM accounts WHERE account_id = 1; -- لا يزال يعيد 1000
COMMIT;

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(3) مستوى العزل SERIALIZABLE

SERIALIZABLE هو المستوى الأكثر صرامة؛ PostgreSQL تستخدم عزل اللقطة القابل للتسلسل (SSI) لاكتشاف تعارضات التسلسل.

(8) ▶ مثال: اكتشاف تعارض SERIALIZABLE

SQL
-- الجلسة 1
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT balance FROM accounts WHERE account_id = 1;

-- الجلسة 2
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
COMMIT;

-- العودة إلى الجلسة 1
UPDATE accounts SET balance = balance + 50 WHERE account_id = 1;
-- ERROR: could not serialize access due to concurrent update

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)
اختيار مستوى العزل السيناريو المستوى الموصى به
معظم تطبيقات الويب READ COMMITTED افتراضي، يوازن الأداء والاتساق
التقارير/التدقيق التي تحتاج لقطة متسقة REPEATABLE READ قراءات نفس المعاملة تبقى متسقة
المعاملات المصرفية/المالية الأساسية SERIALIZABLE الأكثر صرامة، يقايض الأداء بالأمان

5. المفهوم: MVCC التحكم في التزامن متعدد الإصدارات

(1) المبدأ الأساسي لـ MVCC

MVCC (التحكم في التزامن متعدد الإصدارات) هو جوهر التحكم في التزامن في PostgreSQL. كل معاملة ترى لقطة من البيانات عند نقطة زمنية؛ القراءات لا تحظر الكتابات، والكتابات لا تحظر القراءات.

كل إصدار صف (tuple) يحتوي أربعة حقول مخفية:

الحقل المعنى
xmin معرف المعاملة التي أدرجت الصف
xmax معرف المعاملة التي حذفت/حدثت الصف (0 يعني لا يزال صالحاً)
رؤية xmin معاملة معرفها < اللقطة الحالية يمكنها رؤية هذا الصف
رؤية xmax معاملة معرفها >= اللقطة الحالية لا يمكنها رؤية هذا الحذف

(9) ▶ مثال: عرض معلومات إصدار الصف

SQL
SELECT xmin, xmax, * FROM products WHERE product_id = 1;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) تدفق التزامن في القراءة/الكتابة MVCC

100%
sequenceDiagram
    participant R as قارئ(Tx1)
    participant W as كاتب(Tx2)
    participant T as جدول

    R->>T: SELECT (لقطة عند بدء Tx1)
    T-->>R: يعيد الإصدار V1 (xmin=100, xmax=0)
    W->>T: UPDATE (ينشئ إصداراً جديداً)
    T-->>T: V1 xmax=200, V2 xmin=200 xmax=0
    R->>T: SELECT مرة أخرى (نفس اللقطة)
    T-->>R: لا يزال يعيد V1 (Tx2 لم يودع)
    W->>T: COMMIT
    Note over T: V2 الآن مرئي للمعاملات الجديدة
    R->>T: SELECT مرة أخرى (نفس اللقطة)
    T-->>R: لا يزال يعيد V1 (REPEATABLE READ)

(10) ▶ مثال: قراءات MVCC لا تحظر الكتابات

SQL
-- الجلسة 1: قراءة طويلة الأمد
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM orders WHERE order_date = '2025-01-01';
-- هذا الاستعلام لا يحظر الكتّاب

-- الجلسة 2: كتابة متزامنة (لا حظر من الجلسة 1)
UPDATE orders SET order_status = 'shipped' WHERE order_id = 100;
COMMIT;

Output:

TEXT 📖 للعرض فقط
UPDATE 3

(3) تكلفة MVCC: الصفوف الميتة و VACUUM

تحت MVCC، UPDATE لا تعدل الصف الأصلي — تنشئ إصداراً جديداً. الصف القديم يصبح صفاً ميتاً ويحتاج تنظيف VACUUM.

العملية سلوك MVCC الصفوف الميتة
INSERT تنشئ صفاً جديداً لا شيء
DELETE توسم xmax للصف القديم تنتج صفاً ميتاً واحداً
UPDATE توسم xmax للصف القديم + تنشئ صفاً جديداً تنتج صفاً ميتاً واحداً

(11) ▶ مثال: عرض عدد الصفوف الميتة

SQL
SELECT relname, n_live_tup, n_dead_tup, last_vacuum, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(12) ▶ مثال: تشغيل VACUUM يدوياً

SQL
VACUUM orders;              -- استعادة المساحة، لا تحظر القراءة
VACUUM FULL orders;         -- إعادة كتابة كاملة للجدول، تقفل الجدول
VACUUM ANALYZE orders;      -- استعادة + تحديث الإحصائيات

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully
نوع VACUUM مستوى القفل استعادة المساحة السرعة
VACUUM SHARE توسم قابلة لإعاد�� الاستخدام، لا تعيد للقرص سريعة
VACUUM FULL ACCESS EXCLUSIVE تعيد بالكامل للقرص بطيئة، تقفل الجدول
VACUUM ANALYZE SHARE نفس VACUUM + تحديث الإحصائيات سريعة

6. المفهوم: الأقفال

(1) أقفال مستوى الصف

PostgreSQL تكتسب تلقائياً قفل صف عند تعديل صف؛ المعاملات الأخرى يجب أن تنتظر.

نوع قفل الصف كيفية الاكتساب التعارض
FOR UPDATE SELECT ... FOR UPDATE قفل حصري، يحظر FOR UPDATE / FOR NO KEY UPDATE أخرى
FOR NO KEY UPDATE SELECT ... FOR NO KEY UPDATE لا يحظر FOR KEY SHARE
FOR SHARE SELECT ... FOR SHARE يحظر FOR UPDATE / FOR NO KEY UPDATE
FOR KEY SHARE SELECT ... FOR KEY SHARE الأضعف، يحظر FOR UPDATE فقط

(13) ▶ مثال: SELECT FOR UPDATE يمنع البيع الزائد

SQL
BEGIN;
SELECT stock FROM products WHERE product_id = 1 FOR UPDATE;
-- stock = 1، الصف مقفل
UPDATE products SET stock = stock - 1 WHERE product_id = 1;
COMMIT;
-- إذا حاولت معاملة أخرى FOR UPDATE على نفس الصف، تنتظر

Output:

TEXT 📖 للعرض فقط
UPDATE 3

(2) أقفال مستوى الجدول

وضع القفل كيفية الاكتساب التعارض
ACCESS SHARE SELECT يتعارض مع ACCESS EXCLUSIVE
ROW SHARE SELECT FOR يتعارض مع EXCLUSIVE / ACCESS EXCLUSIVE
ROW EXCLUSIVE UPDATE/DELETE يتعارض مع SHARE / EXCLUSIVE، إلخ
SHARE LOCK TABLE ... SHARE يتعارض مع ROW EXCLUSIVE، إلخ
ACCESS EXCLUSIVE ALTER TABLE يتعارض مع جميع الأقفال

(14) ▶ مثال: قفل جدول صريح

SQL
LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;
-- تنفيذ عمليات حرجة
COMMIT;

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(3) Advisory Locks (خاصة بـ PostgreSQL)

Advisory Locks هي أقفال على مستوى التطبيق غير مرتبطة بأي صف جدول، مناسبة للتنسيق الموزع.

الدالة الميزة
pg_try_advisory_lock(id) غير حاظرة؛ تعيد false فوراً عند الفشل
pg_advisory_lock(id) تحظر منتظرة حتى الاكتساب
pg_advisory_unlock(id) تحرير القفل
pg_advisory_xact_lock(id) تحرر تلقائياً عند نهاية المعاملة

(15) ▶ مثال: استخدام Advisory Lock لمنع المعالجة المكررة

SQL
-- محاولة غير حاظرة
SELECT pg_try_advisory_lock(12345);
-- تعيد true: حصلت على القفل، تابع
-- تعيد false: جلسة أخرى تملكه، تجاوز

-- قفل استشاري على مستوى المعاملة (تحرير تلقائي عند COMMIT/ROLLBACK)
BEGIN;
SELECT pg_advisory_xact_lock(12345);
-- قم بالعمل...
COMMIT; -- القفل يحرر تلقائياً

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(16) ▶ مثال: Advisory Lock للمهام أحادية المثيل

SQL
CREATE FUNCTION run_daily_report() RETURNS void AS $$
BEGIN
  IF pg_try_advisory_lock(99999) THEN
    -- جلسة واحدة فقط يمكنها تشغيل هذا في وقت واحد
    INSERT INTO report_log (report_date, status)
    VALUES (CURRENT_DATE, 'running');
    PERFORM pg_sleep(5); -- محاكاة العمل
    UPDATE report_log SET status = 'done' WHERE report_date = CURRENT_DATE;
    PERFORM pg_advisory_unlock(99999);
  ELSE
    RAISE NOTICE 'Report already running in another session';
  END IF;
END;
$$ LANGUAGE plpgsql;

Output:

TEXT 📖 للعرض فقط
INSERT 0 1

7. المفهوم: اكتشاف ومعالجة الجمود (Deadlock)

(1) كيف ينشأ الجمود

معاملتان تنتظران أقفال بعضهما البعض، مشكلتين اعتماداً دائرياً.

100%
flowchart LR
    A[Tx1: قفل الصف A] -->|ينتظر الصف B| B[Tx2: قفل الصف B]
    B -->|ينتظر الصف A| A
    A -->|جمود!| C[PG تكتشف تلقائياً في 1s]

(17) ▶ مثال: سيناريو الجمود

SQL
-- الجلسة 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1; -- قفل الصف 1

-- الجلسة 2
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 2; -- قفل الصف 2

-- الجلسة 1 (تنتظر الآن الصف 2)
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;

-- الجلسة 2 (جمود!)
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- ERROR: deadlock detected

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(2) اكتشاف ومنع الجمود

الاستراتيجية الوصف
اكتشاف تلقائي PostgreSQL تتحقق مرة في الثانية افتراضياً (deadlock_timeout)
تراجع تلقائي عند اكتشاف جمود، تتراجع تلقائياً عن معاملة واحدة
ترتيب قفل ثابت اكتسب الأقفال دائماً بنفس الترتيب، متجنباً الانتظارات الدائرية
معاملات قصيرة قلل الوقت الذي تحتفظ فيه المعاملة بالأقفال

(18) ▶ مثال: منع الجمود بترتيب قفل ثابت

SQL
-- دائماً اقفل الصفوف بترتيب account_id
BEGIN;
SELECT * FROM accounts WHERE account_id IN (1, 2) ORDER BY account_id FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT;

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

8. المفهوم: القفل المتشائم مقابل القفل المتفائل

(1) مقارنة استراتيجيتي التزامن

البعد القفل المتشائم القفل المتفائل
الفكرة اقفل أولاً، ثم عدل؛ احظر عند التعارض عدل أولاً، ثم تحقق؛ أعد المحاولة عند التعارض
التنفيذ SELECT FOR UPDATE عمود version + WHERE version = old
تكرار التعارض مناسب لسيناريوهات التعارض العالي مناسب لسيناريوهات التعارض المنخف��
الأداء عبء انتظار القفل عبء إعادة المحاولة
خطر الجمود نعم لا

(19) ▶ مثال: قفل متشائم لخصم المخزون

SQL
BEGIN;
SELECT stock FROM products WHERE product_id = 1 FOR UPDATE;
-- stock = 1، الصف مقفل
IF stock > 0 THEN
  UPDATE products SET stock = stock - 1 WHERE product_id = 1;
END IF;
COMMIT;

Output:

TEXT 📖 للعرض فقط
UPDATE 3

(1) ▶ مال: قفل متفائل لخصم المخزون

SQL
-- إضافة عمود version
ALTER TABLE products ADD COLUMN version INT DEFAULT 1;

-- تحديث متفائل
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE product_id = 1 AND version = 5;
-- إذا كانت الصفوف المتأثرة = 0، شخص آخر عدلها، تحتاج إعادة المحاولة

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(20) ▶ مثال: منطق إعادة المحاولة في طبقة التطبيق للقفل المتفائل

SQL
CREATE FUNCTION deduct_stock(p_id INT, p_qty INT) RETURNS BOOLEAN AS $$
DECLARE
  v_version INT;
  v_stock INT;
  v_updated INT;
BEGIN
  LOOP
    SELECT stock, version INTO v_stock, v_version
    FROM products WHERE product_id = p_id;

    IF v_stock < p_qty THEN RETURN FALSE; END IF;

    UPDATE products
    SET stock = stock - p_qty, version = version + 1
    WHERE product_id = p_id AND version = v_version;

    GET DIAGNOSTICS v_updated = ROW_COUNT;
    IF v_updated > 0 THEN RETURN TRUE; END IF;
    -- تعارض، أعد المحاولة
  END LOOP;
END;
$$ LANGUAGE plpgsql;

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

9. المفهوم: الالتزام على مرحلتين (2PC)

(1) 2PC للمعاملات الموزعة

عندما تمتد المعاملة عبر قواعد بيانات متعددة أو موارد خارجية، يحتاج الالتزا�� على مرحلتين لضمان الذرية.

المرحلة العملية الوصف
PREPARE PREPARE TRANSACTION 'tx_id' تدخل المعاملة حالة محضرة، مكتوبة في WAL
COMMIT COMMIT PREPARED 'tx_id' المرحلة 2 تؤكد الالتزام
ROLLBACK ROLLBACK PREPARED 'tx_id' المرحلة 2 تؤكد التراجع

(21) ▶ مثال: تدفق الالتزام على مرحلتين

SQL
-- المرحلة 1: التحضير
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE account_id = 1;
PREPARE TRANSACTION 'transfer_out';

-- (المنسق يؤكد أن جميع المشاركين محضرون)

-- المرحلة 2: الالتزام
COMMIT PREPARED 'transfer_out';

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(22) ▶ مثال: عرض المعاملات المحضرة

SQL
SELECT * FROM pg_prepared_xacts;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
المعامل الافتراضي الوصف
max_prepared_transactions 0 يجب أن يكون > 0 لاستخدام 2PC
deadlock_timeout 1s فاصل اكتشاف الجمود
idle_in_transaction_session_timeout 0 مهلة خمول المعاملة (مللي ثانية)

10. تطبيق عملي: معاملة خصم مخزون كاملة للتجارة الإلكترونية

تحتاج Alice إلى معاملة خصم مخزون كاملة تعالج الطلبات المتزامنة، تمنع البيع الزائد، وتسجل الطلب.

SQL
-- الخطوة 1: إنشاء الجداول
CREATE TABLE products (
  product_id INT PRIMARY KEY,
  product_name TEXT NOT NULL,
  stock INT NOT NULL DEFAULT 0,
  price NUMERIC(10,2) NOT NULL,
  version INT NOT NULL DEFAULT 1
);

CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT NOT NULL,
  product_id INT NOT NULL,
  quantity INT NOT NULL,
  total_price NUMERIC(10,2) NOT NULL,
  order_status TEXT DEFAULT 'pending',
  created_at TIMESTAMP DEFAULT now()
);

INSERT INTO products VALUES
  (1, 'Limited Edition Watch', 1, 299.99, 1),
  (2, 'Wireless Headphones', 50, 89.99, 1);

-- الخطوة 2: نهج القفل المتشائم للعناصر الساخنة
CREATE FUNCTION place_order(
  p_customer_id INT, p_product_id INT, p_qty INT
) RETURNS INT AS $$
DECLARE
  v_stock INT;
  v_order_id INT;
BEGIN
  BEGIN
    -- قفل صف المنتج
    SELECT stock INTO v_stock
    FROM products
    WHERE product_id = p_product_id
    FOR UPDATE;

    IF v_stock < p_qty THEN
      RAISE EXCEPTION 'Insufficient stock: % < %', v_stock, p_qty;
    END IF;

    -- خصم المخزون
    UPDATE products
    SET stock = stock - p_qty
    WHERE product_id = p_product_id;

    -- إنشاء طلب
    INSERT INTO orders (customer_id, product_id, quantity, total_price)
    VALUES (p_customer_id, p_product_id, p_qty,
            p_qty * (SELECT price FROM products WHERE product_id = p_product_id))
    RETURNING order_id INTO v_order_id;

    RETURN v_order_id;
  EXCEPTION
    WHEN OTHERS THEN
      RAISE NOTICE '%', SQLERRM;
      RETURN -1;
  END;
END;
$$ LANGUAGE plpgsql;

-- الخطوة 3: اختبار الطلبات المتزامنة
SELECT place_order(101, 1, 1); -- نجاح، تم إنشاء الطلب
SELECT place_order(102, 1, 1); -- فشل، المخزون غير كافٍ

-- الخطوة 4: التحقق من النتائج
SELECT product_id, stock FROM products WHERE product_id = 1;
SELECT order_id, customer_id, order_status FROM orders;

❓ أسئلة شائعة

س لماذا لا يوجد مستوى عزل READ UNCOMMITTED في PostgreSQL؟
ج PostgreSQL تعين READ UNCOMMITTED إلى READ COMMITTED، لأنه تحت بنية MVCC من المستحيل قراءة البيانات غير المودعة (تقرأ دائماً لقطة)، لذا القراءات المتسخة لا يمكن أن تحدث في PG.
س هل يمكن لـ REPEATABLE READ منع القراءات الوهمية؟
ج REPEATABLE READ في PostgreSQL يمكنها فعلياً منع القراءات الوهمية، متجاوزة ما يتطلبه معيار SQL من ذلك المستوى. في المعيار فقط SERIALIZABLE يمنع القراءات الوهمية، لكن آلية لقطة MVCC في PG تحظر أيضاً القراءات الوهمية تحت REPEATABLE READ.
س هل يمكن تداخل SAVEPOINTs؟
ج نعم. SAVEPOINTs تدعم التداخل؛ ROLLBACK TO SAVEPOINT الداخلي يتراجع فقط إلى نقطة الحفظ تلك، دون التأثير على الخارجية. RELEASE SAVEPOINT يحرر نقطة الحفظ المسماة وجميع نقاط الحفظ المنشأة بعدها.
س ما الفرق بين SELECT FOR UPDATE و UPDATE المباشر؟
ج SELECT FOR UPDATE يقفل الصف أولاً ثم يقرر ما إذا كان سيعدل — مثالي لعمليات "اقرأ ثم اكتب" المركبة؛ UPDATE المباشر يفعل كل ذلك في خطوة واحدة. ميزة SELECT FOR UPDATE هي أنه يمكنك إصدار حكم تجاري قبل التعديل.
س متى يجب استخدام VACUUM FULL؟
ج فقط عندما يكون الجدول منتفخاً بشدة ولا يمكن لـ VACUUM الروتيني استعادة المساحة. VACUUM FULL يحتاج قفل ACCESS EXCLUSIVE، وخلاله الجدول غير متاح بالكامل. الصيانة الروتينية يجب أن تعتمد على autovacuum.
س ما الفرق بين Advisory Lock والقفل العادي؟
ج Advisory Lock معرف من قبل التطبيق، غير مرتبط بجدول أو صف، ولا يحرر تلقائياً عند نهاية المعاملة (إلا إذا استخدمت إصدار xact). الأقفال العادية تدار تلقائياً بواسطة PostgreSQL ومرتبطة بكائنات قاعدة بيانات محددة.
س متى يستخدم 2PC؟
ج 2PC يستخدم أساساً للمعاملات الموزعة عبر قواعد البيانات أو الأنظمة، ويتطلب منسقاً للالتزام بشكل موحد. معاملة قاعدة بيانات واحدة تستخدم BEGIN/COMMIT الع��دي؛ 2PC له عبء إضافي ويتطلب تكوين max_prepared_transactions.

📖 ملخص


📝 تمارين

  1. ⭐ اكتب معاملة تحول 200 دولار من الحساب 1 إلى الحساب 2 في جدول accounts، وتتراجع إذا كان رصيد الحساب 1 غير كافٍ.

  2. ⭐⭐ استخدم SAVEPOINT لكتابة معاملة: أولاً أدرج سجل طلب، ثم حاول تحديث المخزون؛ إذا كان المخزون غير كافٍ، تراجع إلى نقطة الحفظ لكن احتفظ بالطلب (علّم حالته كـ 'failed')، ثم أودع المعاملة.

  3. ⭐⭐⭐ نفذ إصدار قفل متفائل لخصم المخزون: استخدم عمود version، أعد المحاولة تلقائياً عند التعارض المتزامن حتى 3 مرات، وأعد FALSE إذا فشلت جميع المحاولات الـ 3. أيضاً سجل كل محاولة في جدول retry_log.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%