PostgreSQL: مشروع شامل
آخر تحديث: 2026-08-26
1. ما ستتعلمه
- إكمال تدفق قاعدة بيانات التجارة الإلكترونية بالكامل، من تحليل المتطلبات إلى الإطلاق
- تصميم مخططات ER وهياكل الجداول لـ 8 وحدات أعمال
- دمج جميع التقنيات: الفهارس، JSONB، البحث النصي الكامل، طرق العرض المادية، الإجراءات المخزنة، RLS، التجزئة، FDW، و pgvector
- كتابة سكريبت تهيئة كامل لقاعدة البيانات
- تعلم كيفية كتابة تقرير مقارنة بين PostgreSQL و MySQL
2. القصة
يطلق Bob و Alice منصة تجارة إلكترونية عبر الحدود لمنطقة الشرق الأوسط. المتطلبات معقدة: بحث نصي كامل بالعربية، سمات منتجات ديناميكية بـ JSONB، طلبات مجزأة شهرياً، أمان على مستوى الصفوف لعزل المستأجرين، وتوصيات منتجات بالذكاء الاصطناعي. يحلون كل ذلك بـ PostgreSQL في مكان واحد — tsvector للبحث العربي، jsonb للـ SKUs المرنة، التجزئة التعريفية لمئات الملايين من الطلبات، pgvector للتوصيات الدلالية، و postgres_fdw لنقل البيانات من PG القديم. قاعدة بيانات PG واحدة تستبدل MySQL + Elasticsearch + Redis + محرك توصيات.
3. المفهوم: تحليل المتطلبات وتصميم البنية
(1) تفصيل وحدات الأعمال
| الوحدة | الجداول الأساسية | الميزات الرئيسية |
|---|---|---|
| نظام المستخدمين | users, user_addresses | أمان RLS على مستوى الصفوف، كلمات مرور bcrypt |
| الفئات والمنتجات | categories, products | سمات ديناميكية JSONB، بحث نصي كامل |
| الطلبات وعناصر الطلبات | orders, order_items | تجزئة RANGE شهرية |
| سلة التسوق | cart_items | UPSERT عالي التزامن |
| المدفوعات | payments | أنواع Enum، سجل تدقيق |
| تتبع الشحن | shipments, shipment_events | بيانات سلاسل زمنية، أحداث JSONB |
| التقييمات والمراجعات | reviews | تجميع النجوم، فهرس GIN |
| تقارير البيانات | mv_daily_sales، إلخ | طرق عرض مادية، تحديث مجدول |
(2) مخطط ER الكامل
erDiagram
USERS ||--o{ USER_ADDRESSES : "لديه"
USERS ||--o{ ORDERS : "يقدم"
USERS ||--o{ CART_ITEMS : "لديه"
USERS ||--o{ REVIEWS : "يكتب"
CATEGORIES ||--o{ CATEGORIES : "أصل"
CATEGORIES ||--o{ PRODUCTS : "يحتوي"
PRODUCTS ||--o{ ORDER_ITEMS : "مضمن_في"
PRODUCTS ||--o{ CART_ITEMS : "مضاف_إلى"
PRODUCTS ||--o{ REVIEWS : "مُراجع_في"
ORDERS ||--o{ ORDER_ITEMS : "يحتوي"
ORDERS ||--o{ PAYMENTS : "مدفوع_بواسطة"
ORDERS ||--o{ SHIPMENTS : "مشحون_عبر"
SHIPMENTS ||--o{ SHIPMENT_EVENTS : "مُتَتَبَّع_بواسطة"
USERS {
bigint id PK
text email UK
text password_hash
text role
timestamptz created_at
}
USER_ADDRESSES {
bigint id PK
bigint user_id FK
text address_line
text city
text country
}
CATEGORIES {
int id PK
text name
int parent_id FK
int sort_order
}
PRODUCTS {
bigint id PK
text name
text name_ar
int category_id FK
numeric price
jsonb attributes
tsvector search_vector
vector embedding
}
ORDERS {
bigint id PK
bigint user_id FK
date order_date
numeric total_amount
text status
}
ORDER_ITEMS {
bigint id PK
bigint order_id FK
bigint product_id FK
int quantity
numeric unit_price
}
CART_ITEMS {
bigint id PK
bigint user_id FK
bigint product_id FK
int quantity
}
PAYMENTS {
bigint id PK
bigint order_id FK
text method
numeric amount
text status
timestamptz paid_at
}
SHIPMENTS {
bigint id PK
bigint order_id FK
text carrier
text tracking_code
text status
}
SHIPMENT_EVENTS {
bigint id PK
bigint shipment_id FK
text event_type
jsonb metadata
timestamptz event_time
}
REVIEWS {
bigint id PK
bigint user_id FK
bigint product_id FK
int rating
text comment
}
(3) تقرير مقارنة PostgreSQL مقابل MySQL
| البعد | PostgreSQL | MySQL |
|---|---|---|
| سمات JSONB الديناميكية | jsonb أصلي + فهرس GIN + معاملات | نوع JSON، لكن الفهرسة ضعيفة |
| البحث النصي الكامل | tsvector/tsquery مدمج، متعدد اللغات | لا دعم أصلي، يحتاج Elasticsearch |
| البحث المتجهي | امتداد pgvector أصلي | يحتاج خدمة خارجية |
| التجزئة | RANGE/LIST/HASH تعريفية | 8.0+ تدعم، لكن أضعف |
| أمان مستو�� الصفوف | سياسات RLS | غير مدعوم |
| طرق العرض المادية | دعم أصلي + تحديث مجدول | غير مدعوم |
| منظومة الامتدادات | غنية (PostGIS/pgcrypto/FDW) | ملحقات أقل |
| الاستعلامات المعقدة | دوال النوافذ/CTE/LATERAL | 8.0+ تدعم تدريجياً |
| النضج التشغيلي | عالي، autovacuum/PITR | عالي، نسخ متماثل رئيسي-تابع ناضج |
| نشاط المجتمع | الأسرع نمواً | أكبر قاعدة مستخدمين |
الخلاصة: هذا المشروع يحتاج سمات JSONB ديناميكية، بحث نصي كامل، بحث متجهي، RLS، تجزئة، وطرق عرض مادية — PostgreSQL تدعمها جميعاً بشكل أصلي، بينما MySQL ستحتاج 4+ مكونات وسيطة خارجية، لذا PostgreSQL هو الاختيار.
4. التنفيذ: الوحدة 1 — نظام المستخدمين
(1) جداول المستخدمين والعناوين
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'customer'
CHECK (role IN ('customer','vendor','admin')),
tenant_id BIGINT DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE user_addresses (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
address_line TEXT NOT NULL,
city TEXT NOT NULL,
country TEXT NOT NULL,
is_default BOOLEAN DEFAULT false
);
(1) ▶ مثال
-- تفعيل RLS على جدول users
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- عزل المستأجرين: المستخدمون يرون فقط نفس المستأجر
CREATE POLICY tenant_isolation ON users
USING (tenant_id = current_setting('app.tenant_id')::bigint);
-- المسؤول يمكنه رؤية الكل
CREATE POLICY admin_all_access ON users
USING (role = 'admin');
-- تعيين سياق المستأجر لكل جلسة
SET app.tenant_id = '1';
SELECT * FROM users;
Output:
CREATE TABLE
(2) ▶ مثال
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- تسجيل بتشفير bcrypt
INSERT INTO users (email, password_hash, role, tenant_id)
VALUES (
'alice@example.com',
crypt('SecurePass123', gen_salt('bf')),
'customer',
1
);
-- التحقق من تسجيل الدخول
SELECT id, role FROM users
WHERE email = 'alice@example.com'
AND password_hash = crypt('SecurePass123', password_hash);
Output:
INSERT 0 1
5. التنفيذ: الوحدة 2 — الفئات والمنتجات
(3) ▶ مثال
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
name_ar TEXT,
parent_id INT REFERENCES categories(id),
sort_order INT DEFAULT 0
);
INSERT INTO categories (name, name_ar, parent_id, sort_order) VALUES
('Electronics', 'إلكترونيات', NULL, 1),
('Phones', 'هواتف', 1, 1),
('Laptops', 'حاسبات', 1, 2),
('Clothing', 'ملابس', NULL, 2);
-- استعلام تكراري: شجرة الفئات
WITH RECURSIVE cat_tree AS (
SELECT id, name, name_ar, parent_id, 0 AS level
FROM categories WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.name_ar, c.parent_id, ct.level + 1
FROM categories c JOIN cat_tree ct ON c.parent_id = ct.id
)
SELECT repeat(' ', level) || name AS tree, name_ar
FROM cat_tree ORDER BY level, sort_order;
Output:
INSERT 0 1
(4) ▶ مثال
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE products (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
name_ar TEXT,
category_id INT NOT NULL REFERENCES categories(id),
price NUMERIC(12,2) NOT NULL,
attributes JSONB DEFAULT '{}',
search_vector TSVECTOR GENERATED ALWAYS AS (
setweight(to_tsvector('simple', coalesce(name, '')), 'A') ||
setweight(to_tsvector('simple', coalesce(name_ar, '')), 'B')
) STORED,
embedding vector(1536)
);
Output:
CREATE TABLE
(5) ▶ مثال
-- إدراج بسمات ديناميكية
INSERT INTO products (name, name_ar, category_id, price, attributes) VALUES
('iPhone 15 Pro', 'آيفون 15 برو', 2, 1199.00,
'{"color": "titanium", "storage": "256GB", "5g": true}'::jsonb),
('MacBook Air M3', 'ماك بوك إير', 3, 1299.00,
'{"color": "midnight", "ram": "16GB", "screen": "15 inch"}'::jsonb);
-- البحث عن هواتف 5G تحت 1200 دولار
SELECT name, price, attributes->>'storage' AS storage
FROM products
WHERE attributes @> '{"5g": true}'::jsonb
AND price < 1200;
-- فهرس GIN لاستعلامات احتواء JSONB
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
Output:
INSERT 0 1
(6) ▶ مثال
-- فهرس GIN للبحث النصي الكامل
CREATE INDEX idx_products_search ON products USING GIN (search_vector);
-- البحث بالإنجليزية أو العربية
SELECT name, name_ar, ts_rank(search_vector, q) AS rank
FROM products, plainto_tsquery('simple', 'iphone') q
WHERE search_vector @@ q
ORDER BY rank DESC;
-- البحث بالعربية
SELECT name, name_ar
FROM products, plainto_tsquery('simple', 'آيفون') q
WHERE search_vector @@ q;
Output:
CREATE TABLE
(7) ▶ مثال
-- فهرس HNSW للبحث المتجهي
CREATE INDEX idx_products_embedding ON products
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- البحث عن منتجات متشابهة
SELECT p2.name, p2.price,
p2.embedding <=> p1.embedding AS distance
FROM products p1
CROSS JOIN LATERAL (
SELECT * FROM products
WHERE id != p1.id
ORDER BY embedding <=> p1.embedding
LIMIT 3
) p2
WHERE p1.name = 'iPhone 15 Pro';
Output:
CREATE TABLE
6. التنفيذ: الوحدة 3 — الطلبات وعناصر الطلبات
(8) ▶ مثال
CREATE TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY,
user_id BIGINT NOT NULL REFERENCES users(id),
order_date DATE NOT NULL DEFAULT current_date,
total_amount NUMERIC(12,2) DEFAULT 0,
status TEXT DEFAULT 'pending'
CHECK (status IN ('pending','paid','shipped','completed','cancelled')),
created_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (id, order_date)
) PARTITION BY RANGE (order_date);
CREATE TABLE orders_2024_01 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE orders_2024_02 PARTITION OF orders
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
CREATE TABLE orders_2024_03 PARTITION OF orders
FOR VALUES FROM ('2024-03-01') TO ('2024-04-01');
CREATE TABLE orders_default PARTITION OF orders DEFAULT;
CREATE TABLE order_items (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL,
order_date DATE NOT NULL,
product_id BIGINT NOT NULL REFERENCES products(id),
quantity INT NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(12,2) NOT NULL,
FOREIGN KEY (order_id, order_date) REFERENCES orders(id, order_date)
);
CREATE INDEX idx_orders_user ON orders (user_id, order_date);
CREATE INDEX idx_order_items_order ON order_items (order_id, order_date);
Output:
CREATE TABLE
(9) ▶ مثال
-- إنشاء طلب
WITH new_order AS (
INSERT INTO orders (user_id, order_date, status)
VALUES (1, '2024-01-15', 'pending')
RETURNING id, order_date
)
INSERT INTO order_items (order_id, order_date, product_id, quantity, unit_price)
SELECT new_order.id, new_order.order_date, p.id, 2, p.price
FROM new_order, products p
WHERE p.name = 'iPhone 15 Pro';
-- تحديث إجمالي الطلب
UPDATE orders o SET total_amount = (
SELECT SUM(quantity * unit_price)
FROM order_items oi
WHERE oi.order_id = o.id AND oi.order_date = o.order_date
)
WHERE o.id = 1 AND o.order_date = '2024-01-15';
Output:
result
----------
42.50
(1 row)
7. التنفيذ: الوحدة 4 — سلة التسوق
(10) ▶ مثال
CREATE TABLE cart_items (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
product_id BIGINT NOT NULL REFERENCES products(id),
quantity INT NOT NULL DEFAULT 1 CHECK (quantity > 0),
added_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (user_id, product_id)
);
-- إضافة أو تحديث عنصر السلة (UPSERT)
INSERT INTO cart_items (user_id, product_id, quantity)
VALUES (1, 1, 1)
ON CONFLICT (user_id, product_id)
DO UPDATE SET quantity = cart_items.quantity + EXCLUDED.quantity;
-- عرض السلة مع تفاصيل المنتج
SELECT p.name, p.price, ci.quantity,
p.price * ci.quantity AS line_total
FROM cart_items ci
JOIN products p ON ci.product_id = p.id
WHERE ci.user_id = 1;
Output:
INSERT 0 1
8. التنفيذ: الوحدة 5 — المدفوعات
(11) ▶ مثال
CREATE TYPE payment_method AS ENUM ('credit_card', 'paypal', 'bank_transfer', 'cod');
CREATE TYPE payment_status AS ENUM ('pending', 'completed', 'failed', 'refunded');
CREATE TABLE payments (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL,
order_date DATE NOT NULL,
method payment_method NOT NULL,
amount NUMERIC(12,2) NOT NULL,
status payment_status DEFAULT 'pending',
paid_at TIMESTAMPTZ,
FOREIGN KEY (order_id, order_date) REFERENCES orders(id, order_date)
);
-- تسجيل دفعة ناجحة
INSERT INTO payments (order_id, order_date, method, amount, status, paid_at)
VALUES (1, '2024-01-15', 'credit_card', 2398.00, 'completed', now());
-- تحديث حالة الطلب بعد الدفع
UPDATE orders SET status = 'paid'
WHERE id = 1 AND order_date = '2024-01-15';
Output:
INSERT 0 1
9. التنفيذ: الوحدة 6 — تتبع الشحن
(12) ▶ مثال
CREATE TABLE shipments (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL,
order_date DATE NOT NULL,
carrier TEXT NOT NULL,
tracking_code TEXT NOT NULL UNIQUE,
status TEXT DEFAULT 'created'
CHECK (status IN ('created','in_transit','delivered','failed')),
FOREIGN KEY (order_id, order_date) REFERENCES orders(id, order_date)
);
CREATE TABLE shipment_events (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
shipment_id BIGINT NOT NULL REFERENCES shipments(id),
event_type TEXT NOT NULL,
metadata JSONB DEFAULT '{}',
event_time TIMESTAMPTZ DEFAULT now()
);
-- تتبع الشحنة بالأحداث
INSERT INTO shipments (order_id, order_date, carrier, tracking_code)
VALUES (1, '2024-01-15', 'DHL Express', 'DHL123456789');
INSERT INTO shipment_events (shipment_id, event_type, metadata) VALUES
(1, 'picked_up', '{"location": "Dubai Warehouse"}'::jsonb),
(1, 'in_transit', '{"location": "Bahrain Hub", "eta": "2024-01-18"}'::jsonb),
(1, 'out_for_delivery', '{"location": "Riyadh"}'::jsonb);
-- تسلسل زمني للشحنة
SELECT se.event_time, se.event_type, se.metadata->>'location' AS location
FROM shipment_events se
WHERE se.shipment_id = 1
ORDER BY se.event_time;
Output:
INSERT 0 1
10. التنفيذ: الوحدة 7 — التقييمات والمراجعات
(13) ▶ مثال
CREATE TABLE reviews (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
product_id BIGINT NOT NULL REFERENCES products(id),
rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5),
comment TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (user_id, product_id)
);
INSERT INTO reviews (user_id, product_id, rating, comment) VALUES
(1, 1, 5, 'Excellent phone, fast delivery'),
(2, 1, 4, 'Good but expensive'),
(3, 2, 5, 'Best laptop ever');
-- ملخص تقييم المنتج باستخدام دالة النوافذ
SELECT p.name,
COUNT(r.id) AS review_count,
AVG(r.rating)::numeric(3,2) AS avg_rating,
COUNT(r.id) FILTER (WHERE r.rating = 5) AS five_star,
COUNT(r.id) FILTER (WHERE r.rating = 4) AS four_star
FROM products p
LEFT JOIN reviews r ON r.product_id = p.id
GROUP BY p.id, p.name;
Output:
count
-------
5
(1 row)
11. التنفيذ: الوحدة 8 — تقارير البيانات
(14) ▶ مثال
CREATE MATERIALIZED VIEW mv_daily_sales AS
SELECT order_date,
COUNT(DISTINCT user_id) AS unique_buyers,
COUNT(*) AS order_count,
SUM(total_amount) AS daily_revenue,
AVG(total_amount)::numeric(12,2) AS avg_order_value
FROM orders
WHERE status = 'completed'
GROUP BY order_date
ORDER BY order_date;
CREATE UNIQUE INDEX idx_mv_daily_sales_date ON mv_daily_sales (order_date);
-- تحديث يومي (يمكن جدولته مع pg_cron)
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_sales;
-- أفضل أيام الإيرادات
SELECT order_date, daily_revenue, order_count
FROM mv_daily_sales
ORDER BY daily_revenue DESC
LIMIT 10;
Output:
count
-------
5
(1 row)
(15) ▶ مثال
CREATE MATERIALIZED VIEW mv_category_sales AS
SELECT c.name AS category,
p.name AS product_name,
SUM(oi.quantity) AS total_sold,
SUM(oi.quantity * oi.unit_price) AS revenue
FROM order_items oi
JOIN products p ON oi.product_id = p.id
JOIN categories c ON p.category_id = c.id
JOIN orders o ON oi.order_id = o.id AND oi.order_date = o.order_date
WHERE o.status = 'completed'
GROUP BY c.name, p.name
ORDER BY revenue DESC;
REFRESH MATERIALIZED VIEW mv_category_sales;
Output:
result
----------
42.50
(1 row)
12. التنفيذ: الإجراءات المخزنة والأتمتة
(16) ▶ مثال
CREATE OR REPLACE PROCEDURE create_order(
p_user_id BIGINT,
p_items JSONB
)
LANGUAGE plpgsql AS
DECLARE
v_order_id BIGINT;
v_order_date DATE := current_date;
v_item JSONB;
BEGIN
INSERT INTO orders (user_id, order_date, status)
VALUES (p_user_id, v_order_date, 'pending')
RETURNING id INTO v_order_id;
FOR v_item IN SELECT * FROM jsonb_array_elements(p_items)
LOOP
INSERT INTO order_items (order_id, order_date, product_id, quantity, unit_price)
VALUES (v_order_id, v_order_date,
(v_item->>'product_id')::bigint,
(v_item->>'quantity')::int,
(SELECT price FROM products WHERE id = (v_item->>'product_id')::bigint));
END LOOP;
UPDATE orders SET total_amount = (
SELECT SUM(quantity * unit_price) FROM order_items
WHERE order_id = v_order_id AND order_date = v_order_date
) WHERE id = v_order_id AND order_date = v_order_date;
COMMIT;
END;
;
-- استدعاء الإجراء
CALL create_order(1, '[
{"product_id": 1, "quantity": 1},
{"product_id": 2, "quantity": 2}
]'::jsonb);
Output:
result
----------
42.50
(1 row)
(17) ▶ مثال
CREATE OR REPLACE FUNCTION maintain_order_partitions()
RETURNS VOID AS
DECLARE
v_next_month DATE;
v_part_name TEXT;
BEGIN
v_next_month := date_trunc('month', current_date + interval '1 month')::date;
v_part_name := 'orders_' || to_char(v_next_month, 'YYYY_MM');
IF NOT EXISTS (
SELECT 1 FROM pg_class WHERE relname = v_part_name
) THEN
EXECUTE format(
'CREATE TABLE %I PARTITION OF orders
FOR VALUES FROM (%L) TO (%L)',
v_part_name,
v_next_month,
(v_next_month + interval '1 month')::date
);
END IF;
-- فصل الأجزاء الأقدم من سنتين
FOR v_part_name IN
SELECT relname FROM pg_class
WHERE relname LIKE 'orders_20__%'
AND relkind = 'r'
AND relname < 'orders_' || to_char(current_date - interval '2 years', 'YYYY_MM')
LOOP
EXECUTE format('ALTER TABLE orders DETACH PARTITION %I', v_part_name);
END LOOP;
END;
LANGUAGE plpgsql;
Output:
CREATE TABLE
13. التنفيذ: نقل بيانات FDW والنسخ الاحتياطي PITR
(18) ▶ مثال
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE SERVER legacy_pg FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host '10.0.1.50', port '5432', dbname 'legacy_shop');
CREATE USER MAPPING FOR current_user SERVER legacy_pg
OPTIONS (user 'migrate_user', password 'secure_pass');
IMPORT FOREIGN SCHEMA public LIMIT TO (old_users, old_products)
FROM SERVER legacy_pg INTO legacy;
-- نقل المستخدمين مع إعادة تشفير كلمة المرور
INSERT INTO users (email, password_hash, role, tenant_id, created_at)
SELECT email, crypt(raw_password, gen_salt('bf')), 'customer', 1, created_at
FROM legacy.old_users
ON CONFLICT (email) DO NOTHING;
Output:
INSERT 0 1
(19) ▶ مثال
# نسخ احتياطي أساسي
pg_basebackup -D /backup/base -Ft -z -P
# أرشفة WAL (postgresql.conf)
wal_level = replica
archive_mode = on
archive_command = 'cp %p /backup/wal/%f'
# استعادة إلى نقطة زمنية محددة
pg_restore --target-time='2024-03-15 14:30:00' -d shop_db /backup/base
Output:
# command executed successfully
| الاستراتيجية | التكرار | الاحتفاظ | وقت الاستعادة |
|---|---|---|---|
| pg_basebackup كامل | يومي | 7 أيام | 30-60 دقيقة |
| أرشفة WAL | مستمر | 7 أيام | أي نقطة زمنية |
| نسخ احتياطي منطقي pg_dump | أسبوعي | 4 أسابيع | 1-4 ساعات |
14. مثال شامل
-- سكريبت تهيئة كامل لقاعدة بيانات التجارة الإلكترونية
-- الوحدة 1: نظام المستخدمين
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'customer'
CHECK (role IN ('customer','vendor','admin')),
tenant_id BIGINT DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT now()
);
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON users
USING (tenant_id = current_setting('app.tenant_id')::bigint);
CREATE TABLE user_addresses (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
address_line TEXT NOT NULL, city TEXT NOT NULL, country TEXT NOT NULL,
is_default BOOLEAN DEFAULT false
);
-- الوحدة 2: الفئات والمنتجات
CREATE TABLE categories (
id SERIAL PRIMARY KEY, name TEXT NOT NULL,
name_ar TEXT, parent_id INT REFERENCES categories(id),
sort_order INT DEFAULT 0
);
CREATE TABLE products (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL, name_ar TEXT,
category_id INT NOT NULL REFERENCES categories(id),
price NUMERIC(12,2) NOT NULL,
attributes JSONB DEFAULT '{}',
search_vector TSVECTOR GENERATED ALWAYS AS (
setweight(to_tsvector('simple', coalesce(name,'')), 'A') ||
setweight(to_tsvector('simple', coalesce(name_ar,'')), 'B')
) STORED,
embedding vector(1536)
);
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
CREATE INDEX idx_products_search ON products USING GIN (search_vector);
CREATE INDEX idx_products_embedding ON products
USING hnsw (embedding vector_cosine_ops) WITH (m=16, ef_construction=64);
-- الوحدة 3: الطلبات (مجزأة شهرياً)
CREATE TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY,
user_id BIGINT NOT NULL REFERENCES users(id),
order_date DATE NOT NULL DEFAULT current_date,
total_amount NUMERIC(12,2) DEFAULT 0,
status TEXT DEFAULT 'pending'
CHECK (status IN ('pending','paid','shipped','completed','cancelled')),
created_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (id, order_date)
) PARTITION BY RANGE (order_date);
CREATE TABLE orders_2024_q1 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');
CREATE TABLE orders_default PARTITION OF orders DEFAULT;
CREATE TABLE order_items (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL, order_date DATE NOT NULL,
product_id BIGINT NOT NULL REFERENCES products(id),
quantity INT NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(12,2) NOT NULL,
FOREIGN KEY (order_id, order_date) REFERENCES orders(id, order_date)
);
-- الوحدة 4: سلة التسوق
CREATE TABLE cart_items (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
product_id BIGINT NOT NULL REFERENCES products(id),
quantity INT NOT NULL DEFAULT 1, added_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (user_id, product_id)
);
-- الوحدة 5: المدفوعات
CREATE TYPE payment_method AS ENUM ('credit_card','paypal','bank_transfer','cod');
CREATE TYPE payment_status AS ENUM ('pending','completed','failed','refunded');
CREATE TABLE payments (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL, order_date DATE NOT NULL,
method payment_method NOT NULL, amount NUMERIC(12,2) NOT NULL,
status payment_status DEFAULT 'pending', paid_at TIMESTAMPTZ,
FOREIGN KEY (order_id, order_date) REFERENCES orders(id, order_date)
);
-- الوحدة 6: الشحن
CREATE TABLE shipments (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL, order_date DATE NOT NULL,
carrier TEXT NOT NULL, tracking_code TEXT NOT NULL UNIQUE,
status TEXT DEFAULT 'created'
CHECK (status IN ('created','in_transit','delivered','failed')),
FOREIGN KEY (order_id, order_date) REFERENCES orders(id, order_date)
);
CREATE TABLE shipment_events (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
shipment_id BIGINT NOT NULL REFERENCES shipments(id),
event_type TEXT NOT NULL, metadata JSONB DEFAULT '{}',
event_time TIMESTAMPTZ DEFAULT now()
);
-- الوحدة 7: المراجعات
CREATE TABLE reviews (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
product_id BIGINT NOT NULL REFERENCES products(id),
rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5),
comment TEXT, created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (user_id, product_id)
);
-- الوحدة 8: طرق العرض المادية للتقارير
CREATE MATERIALIZED VIEW mv_daily_sales AS
SELECT order_date, COUNT(DISTINCT user_id) AS unique_buyers,
COUNT(*) AS order_count, SUM(total_amount) AS daily_revenue
FROM orders WHERE status = 'completed'
GROUP BY order_date;
CREATE UNIQUE INDEX idx_mv_daily ON mv_daily_sales (order_date);
❓ أسئلة شائعة
📖 ملخص
- تحليل المتطلبات أولاً: 8 وحدات تغطي سلسلة التجارة الإلكترونية بالكامل
- مخطط ER هو جوهر تصميم الجداول؛ علاقات المفاتيح الخارجية تحدد ترتيب إنشاء الجداول
- نظام المستخدمين: أمان RLS على مستوى الصفوف + تشفير كلمات المرور بـ pgcrypto
- وحدة المنتجات: سمات JSONB ديناميكية + بحث نصي كامل tsvector + توصيات دلالية pgvector
- وحدة الطلبات: تجزئة RANGE شهرية + مفتاح خارجي مركب + أتمتة بالإجراءات المخزنة
- وحدة الشحن: تدفق أحداث JSONB + تتبع سلاسل زمنية
- وحدة التقارير: طرق عرض مادية + تحديث CONCURRENTLY
- PG مقابل MySQL: JSONB / البحث النصي / المتجهات / RLS / طرق العرض المادية / منظومة الامتدادات هي مزايا PG الأساسية
- FDW يتيح نقل البيانات دون توقف؛ PITR يحمي سلامة البيانات
📝 تمارين
-
⭐ باتباع سكريبت المثال الشامل، أنشئ قاعدة بيانات التجارة الإلكترونية الكاملة على مثيل PG المحلي لديك، وأدرج 10 صفوف اختبارية، وتحقق من الاستعلام الأساسي لكل وحدة (تسجيل دخول المستخدم، بحث المنتجات، إنشاء الطلب، UPSERT السلة).
-
⭐⭐ وسّع المشروع الشامل: (1) أضف وحدة كوبونات (جدول coupons + قواعد JSONB + إجراء مخزن للتحقق منها)؛ (2) اكتب دالة
search_products(keyword TEXT, min_price NUMERIC, max_price NUMERIC, category_id INT)تجمع البحث النصي الكامل + تصفية JSONB + نطاق السعر؛ (3) أنشئ مهمة مجدولةpg_cronللتجزئة التلقائية الشهرية. -
⭐⭐⭐ أنتج تقرير تحسين بمستوى إنتاجي: (1) استخدم
pg_stat_statementsلجمع أعلى 10 استعلامات بطيئة واقترح خطط تحسين؛ (2) صمم استراتيجية autovacuum لجميع الجداول المجزأة؛ (3) اضبط النسخ الاحتياطي PITR واختبر الاستعادة لنقطة زمنية؛ (4) اكتب سكريبتpostgres_fdwكامل لنقل البيانات من مثيل PG إلى آخر؛ (5) استخدم EXPLAIN ANALYZE للتحقق من أن جميع الاستعلامات الرئيسية تأخذ الخطة المثلى.