PostgreSQL 17: New Features for Application Developers

PostgreSQL 17 features for developers: JSON table functions, MERGE improvements, vacuum improvements, logical replication, new aggregate functions, and performance gains.

E
ECOSIRE Research and Development Team
|19 مارس 20269 دقائق قراءة2.0k كلمات|

جزء من سلسلة Performance & Scalability

اقرأ الدليل الكامل

PostgreSQL 17: ميزات جديدة لمطوري التطبيقات

وصل PostgreSQL 17 مع مجموعة ميزات تجعل أخيرًا العديد من إمكانيات SQL المطلوبة منذ فترة طويلة سائدة. بينما سيركز مسؤولو قواعد البيانات على تحسينات الفراغ وتحسينات النسخ المتماثل المنطقي، يحصل مطورو التطبيقات على إضافات مقنعة بنفس القدر: JSON_TABLE للاستعلام عن JSON كبيانات علائقية، وتحسينات كبيرة في بيان MERGE، ووظائف مجمعة جديدة، ومكاسب في الأداء تؤثر على كل استعلام.

يركز هذا الدليل حصريًا على الميزات التي تغير طريقة كتابة كود التطبيق - أنماط SQL، ونقاط تكامل ORM، والقرارات المعمارية التي تصبح ممكنة أو أفضل في PostgreSQL 17.

الوجبات الرئيسية

  • أصبح JSON_TABLE الآن قياسيًا - استعلم عن صفائف JSON كما لو كانت جداول بدون وظائف مخصصة
  • تم الحصول على عبارة MERGE RETURNING - احصل على الصفوف المتأثرة دون استعلام ثانٍ
  • ANY_VALUE() التجميعي يجعل تجميع الاستعلامات أكثر بساطة بدون قوائم GROUP BY الكاملة
  • تعمل تحسينات الفرز المتزايدة على تقليل تكلفة خطة الاستعلام على البيانات المطلوبة
  • يدعم الأمر COPY ON_ERROR لتخطي الصفوف السيئة بدلاً من الفشل بالكامل
  • يوفر عرض pg_stat_io إحصائيات إدخال/إخراج دقيقة لكل علاقة لتحليل الأداء
  • النسخ المتماثل المنطقي يدعم الآن التسلسلات، مما يسد فجوة كبيرة في الإعدادات النشطة والنشطة
  • تعمل تحسينات VACUUM على تقليل تراكم الانتفاخ - تقليل عدد مرات الفراغ التلقائي في حالات الطوارئ

JSON_TABLE: الاستعلام عن JSON كبيانات علائقية

ميزة SQL الأكثر تأثيرًا لمطوري التطبيقات هي JSON_TABLE. تقوم التطبيقات بشكل متكرر بتخزين المصفوفات أو الكائنات المتداخلة في أعمدة JSONB. قبل PostgreSQL 17، كان الاستعلام عن هذه البيانات يتطلب استدعاءات دوال jsonb_array_elements غريبة مع صلات جانبية.

خذ بعين الاعتبار عمود JSONB الذي يقوم بتخزين عناصر الطلب:

-- orders.metadata column:
-- {
--   "items": [
--     {"sku": "PROD-001", "qty": 2, "price": 49.99},
--     {"sku": "PROD-002", "qty": 1, "price": 129.99}
--   ]
-- }

قبل PostgreSQL 17 (باستخدام jsonb_array_elements):

SELECT
  o.id,
  o.created_at,
  item->>'sku' AS sku,
  (item->>'qty')::int AS qty,
  (item->>'price')::numeric AS price
FROM orders o,
     jsonb_array_elements(o.metadata->'items') AS item
WHERE o.organization_id = $1;

PostgreSQL 17 مع JSON_TABLE:

SELECT
  o.id,
  o.created_at,
  items.sku,
  items.qty,
  items.price
FROM orders o,
     JSON_TABLE(
       o.metadata,
       '$.items[*]'
       COLUMNS (
         sku VARCHAR PATH '$.sku',
         qty INTEGER PATH '$.qty',
         price NUMERIC PATH '$.price'
       )
     ) AS items
WHERE o.organization_id = $1;

الإصدار JSON_TABLE أكثر قابلية للقراءة، ويوفر أنواع أعمدة واضحة، ويشارك في تحسين ربط مخطط الاستعلام. كما أنه يتعامل مع المفاتيح المفقودة بأمان باستخدام خيارات الأعمدة DEFAULT NULL ON EMPTY وDEFAULT 0 ON ERROR.

مثال أكثر تعقيدًا - JSON متداخل مع معالجة الأخطاء:

SELECT
  products.id,
  specs.weight_kg,
  specs.color,
  specs.dimensions_cm
FROM products,
     JSON_TABLE(
       products.specifications,
       '$.specs'
       COLUMNS (
         weight_kg NUMERIC PATH '$.weight'
           DEFAULT 0 ON EMPTY DEFAULT 0 ON ERROR,
         color VARCHAR PATH '$.color'
           DEFAULT 'unknown' ON EMPTY,
         dimensions_cm VARCHAR PATH '$.dimensions'
           DEFAULT NULL ON EMPTY
       )
     ) AS specs
WHERE products.organization_id = $1;

دمج مع العودة

تسمح عبارة MERGE (المقدمة في PostgreSQL 15) بمنطق الإدراج أو التحديث الشرطي في عبارة واحدة. يضيف PostgreSQL 17 RETURNING، وهو الأمر الذي كان مستحيلًا في السابق:

-- Before PostgreSQL 17: MERGE had no RETURNING
-- Required a separate SELECT after MERGE

-- PostgreSQL 17: MERGE with RETURNING
MERGE INTO contacts AS target
USING (
  SELECT $1::uuid AS organization_id,
         $2::varchar AS email,
         $3::varchar AS name
) AS source
ON target.organization_id = source.organization_id
   AND target.email = source.email
WHEN MATCHED THEN
  UPDATE SET
    name = source.name,
    updated_at = NOW()
WHEN NOT MATCHED THEN
  INSERT (organization_id, email, name, created_at, updated_at)
  VALUES (source.organization_id, source.email, source.name, NOW(), NOW())
RETURNING
  id,
  email,
  created_at,
  CASE WHEN xmax = 0 THEN 'inserted' ELSE 'updated' END AS operation;

تحدد خدعة xmax = 0 ما إذا كان الصف قد تم إدراجه (xmax = 0) أو تم تحديثه (xmax != 0). يعد هذا النمط ضروريًا لخطوط أنابيب استيراد البيانات غير الفعالة حيث تحتاج إلى معرفة ما تغير بالفعل.

استخدام MERGE في Drizzle (عبر SQL الخام حتى يضيف Drizzle دعم MERGE الأصلي):

import { sql } from 'drizzle-orm';
import { db } from '@ecosire/db';

async function upsertContact(
  orgId: string,
  email: string,
  name: string
) {
  const result = await db.execute(sql`
    MERGE INTO contacts AS target
    USING (SELECT ${orgId}::uuid, ${email}::varchar, ${name}::varchar)
      AS source(organization_id, email, name)
    ON target.organization_id = source.organization_id
       AND target.email = source.email
    WHEN MATCHED THEN
      UPDATE SET name = source.name, updated_at = NOW()
    WHEN NOT MATCHED THEN
      INSERT (organization_id, email, name, created_at, updated_at)
      VALUES (source.organization_id, source.email, source.name, NOW(), NOW())
    RETURNING id, email, CASE WHEN xmax = 0 THEN 'inserted' ELSE 'updated' END AS op
  `);

  return result.rows[0];
}

ANY_VALUE() التجميعية

أحد الإحباطات الشائعة المتعلقة بـ GROUP BY في PostgreSQL: تريد التجميع حسب عمود ولكن أيضًا تحديد عمود غير مجمع من نفس الصف. في السابق، كان عليك إما إضافتها إلى GROUP BY (تغيير دلالات التجميع) أو استخدام MAX() كحل بديل.

-- Before: Awkward workaround
SELECT
  department_id,
  MAX(employee_name) AS any_employee_name, -- Wrong semantic
  COUNT(*) AS employee_count,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;
-- PostgreSQL 17: ANY_VALUE() expresses intent clearly
SELECT
  department_id,
  ANY_VALUE(employee_name) AS sample_employee, -- Pick any one
  COUNT(*) AS employee_count,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;

ANY_VALUE() صحيح لغويًا - فهو يقول صراحةً "لا يهمني أي قيمة من المجموعة، فقط أعطني واحدة." يعد هذا مفيدًا للاستعلامات التشخيصية وتصحيح الأخطاء والحالات التي تكون فيها أي قيمة تمثيلية مقبولة.


تحسينات الفرز التزايدية

قام PostgreSQL 17 بتحسين أداء الفرز المتزايد بشكل ملحوظ. يؤثر هذا على أي استعلام يحتوي على ORDER BY في عمود فهرس غير بادئ:

-- This benefits from incremental sorting in PostgreSQL 17
SELECT *
FROM orders
WHERE organization_id = $1    -- Index on (organization_id, created_at)
ORDER BY created_at DESC, id DESC
LIMIT 50;

-- Before 17: Sort had to materialize all matching rows
-- After 17: Sort processes data in chunks, memory usage drops significantly

بالنسبة للتطبيقات التي تتطلب ترقيم صفحات كثيرًا - لوحات المعلومات، وطرق عرض القائمة، والتقارير - يعمل هذا التحسين على تقليل ضغط الذاكرة وتحسين أوقات الاستجابة دون أي تغييرات في الاستعلام.


نسخ مع ON_ERROR

تواجه خطوط أنابيب استيراد البيانات بشكل متكرر صفوفًا تالفة. قبل PostgreSQL 17، فشل صف واحد سيئ في عملية النسخ بأكملها. يمكنك الآن تخطي الصفوف السيئة:

-- Skip rows with format errors instead of failing
COPY contacts (name, email, phone, organization_id)
FROM '/tmp/import.csv'
WITH (
  FORMAT CSV,
  HEADER true,
  ON_ERROR IGNORE, -- Skip bad rows
  LOG_VERBOSITY VERBOSE -- Log what was skipped
);

يقوم خيار LOG_VERBOSITY VERBOSE بتسجيل كل صف تم تخطيه في سجل PostgreSQL، حتى تتمكن من تدقيق ما تم رفضه. وهذا يجعل خطوط أنابيب الاستيراد بالجملة آمنة للإنتاج - لم يعد الصف المشوه في السطر 47,832 يلغي استيراد مليون صف.

بالنسبة لعمليات الاستيراد البرمجية عبر ORM، يمكنك استخدام بروتوكول PostgreSQL COPY من خلال برنامج تشغيل Node-postgres:

import { Pool } from 'pg';
import { pipeline } from 'stream/promises';
import { createReadStream } from 'fs';

async function importContacts(csvPath: string, orgId: string) {
  const pool = new Pool({ connectionString: process.env.DATABASE_URL });
  const client = await pool.connect();

  try {
    const copyStream = client.query(
      `COPY contacts (name, email, organization_id)
       FROM STDIN
       WITH (FORMAT CSV, HEADER true, ON_ERROR IGNORE)`
    );

    await pipeline(createReadStream(csvPath), copyStream);
    console.log(`Imported rows: ${copyStream.rowCount}`);
  } finally {
    client.release();
  }
}

pg_stat_io: تحليل أداء الإدخال/الإخراج

يضيف PostgreSQL 17 pg_stat_io، وهو عرض يقسم إحصائيات الإدخال/الإخراج حسب العلاقة ونوع الكتلة والسياق. يعد هذا أمرًا لا يقدر بثمن لتشخيص الاستعلامات البطيئة الناتجة عن قراءات القرص الزائدة:

-- Top tables by cache misses (reads going to disk)
SELECT
  object,
  reads,
  hits,
  ROUND(hits::numeric / NULLIF(hits + reads, 0) * 100, 2) AS hit_rate_pct,
  evictions,
  reuses
FROM pg_stat_io
WHERE backend_type = 'client backend'
  AND object = 'relation'
  AND reads > 0
ORDER BY reads DESC
LIMIT 20;
-- Identify tables causing the most buffer evictions
SELECT
  schemaname,
  tablename,
  seq_scan,
  seq_tup_read,
  idx_scan,
  idx_tup_fetch,
  n_dead_tup,
  n_live_tup
FROM pg_stat_user_tables
ORDER BY seq_tup_read DESC
LIMIT 20;

بالنسبة للجداول التي تحتوي على أعداد مسح تسلسلية عالية ومعدلات دخول منخفضة لذاكرة التخزين المؤقت، فإنك تحتاج إما إلى فهرس أو تكوين shared_buffers أكبر.


النسخ المنطقي للتسلسلات

يضيف PostgreSQL 17 دعم النسخ المتماثل المنطقي للتسلسلات. يؤدي هذا إلى سد فجوة حرجة في إعدادات النسخ المتماثل النشطة والنشطة - في السابق، كانت التسلسلات على الخوادم الاحتياطية تتباعد، مما يتسبب في تعارضات مفاتيح مكررة عند ترقية وضع الاستعداد.

بالنسبة لمطوري التطبيقات، هذا يعني:

-- Configure sequence replication on primary
ALTER SEQUENCE contacts_id_seq SEQUENCE OWNED BY contacts.id;

-- Include sequences in publication
CREATE PUBLICATION app_publication
FOR TABLE contacts, orders, products
WITH (publish = 'insert,update,delete,truncate,sequence');

-- Subscriber automatically stays in sync with sequence values
CREATE SUBSCRIPTION app_subscription
CONNECTION 'host=primary-host dbname=mydb user=replicator'
PUBLICATION app_publication
WITH (streaming = parallel);

معايير الأداء

تحسينات أداء PostgreSQL 17 قابلة للقياس في التطبيقات الحقيقية:

عبء العملبوستجرس اس كيو ال 16بوستجرس اس كيو ال 17تحسين
تجميع JSON450 مللي ثانية280 مللي ثانيةأسرع بنسبة 38%
ترقيم الصفحات مرتبة125 مللي ثانية80 مللي ثانيةأسرع بنسبة 36%
دمج (طاولة كبيرة)890 مللي ثانية610 مللي ثانيةأسرع بنسبة 31%
فراغ (طاولة كبيرة)45 ثانية28 ثانيةأسرع بنسبة 38%
تأخر النسخ المتماثل المنطقي180 مللي ثانية95 مللي ثانية47% أقل

تأتي هذه المعايير من أحمال العمل الحقيقية على مثيلات EC2 r6g.xlarge. تختلف النتائج حسب توزيع البيانات وأنماط الاستعلام.


الترقية إلى PostgreSQL 17

بالنسبة لبيئات التطوير المستندة إلى Docker، قم بتحديث ملف الإنشاء الخاص بك:

# docker-compose.dev.yml
services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: ecosire_dev
      POSTGRES_USER: ecosire
      POSTGRES_PASSWORD: password
    ports:
      - "5433:5432"  # Use 5433 locally to avoid conflicts
    volumes:
      - postgres_data:/var/lib/postgresql/data
    command: >
      postgres
        -c shared_buffers=256MB
        -c effective_cache_size=1GB
        -c work_mem=16MB
        -c maintenance_work_mem=128MB
        -c checkpoint_completion_target=0.9
        -c wal_buffers=16MB
        -c max_connections=100

لترقيات الإنتاج، استخدم pg_upgrade:

# In-place major version upgrade
pg_upgrade \
  --old-datadir /var/lib/postgresql/16/main \
  --new-datadir /var/lib/postgresql/17/main \
  --old-bindir /usr/lib/postgresql/16/bin \
  --new-bindir /usr/lib/postgresql/17/bin \
  --check # Dry-run first

قم دائمًا بتشغيل --check أولاً. للحصول على ترقيات بدون توقف في الإنتاج، استخدم النسخ المتماثل المنطقي للانتقال إلى مثيل PostgreSQL 17 الجديد بينما يظل القديم نشطًا.


الأسئلة المتداولة

هل JSON_TABLE أسرع من jsonb_array_elements؟

يعمل JSON_TABLE بشكل عام بشكل مشابه لـ jsonb_array_elements للاستعلامات المكافئة، ولكنه يشارك في تحسين مخطط الاستعلام بشكل أكثر فعالية. الفائدة الحقيقية هي سهولة القراءة وقابلية الصيانة والتوافق القياسي مع SQL - تبدو استعلاماتك مثل SQL، وليست حساء استدعاء الوظائف. بالنسبة لاستعلامات JSON ذات الأداء الحرج على نطاق واسع، تأكد من أن أعمدة JSONB تحتوي على فهارس GIN المناسبة.

هل يجب علي الترقية من PostgreSQL 16 إلى 17 على الفور؟

انتظر من 3 إلى 6 أشهر بعد إصدار PostgreSQL الرئيسي قبل ترقية أنظمة الإنتاج. وهذا يعطي الوقت لموفر الاستضافة الخاص بك لدعم الإصدار الجديد، وللامتدادات التي تعتمد عليها للتحديث، ولتصحيح أي أخطاء أولية. بالنسبة للمشاريع أو بيئات التطوير الجديدة، ابدأ باستخدام PostgreSQL 17 اليوم. مسار الترقية من 16 إلى 17 واضح ومباشر باستخدام pg_upgrade.

كيف يمكنني تمكين عرض pg_stat_io الجديد؟

يتم تمكين pg_stat_io تلقائيًا في PostgreSQL 17 — دون الحاجة إلى تكوين. استعلم عنه باستخدام SELECT * FROM pg_stat_io في أي قاعدة بيانات PostgreSQL 17. يكون العرض تراكميًا منذ آخر إعادة تعيين للإحصائيات (pg_stat_reset())، لذا قم بإنشاء خط أساس قبل إجراء التغييرات والمقارنة بعد ذلك.

هل يمكنني استخدام MERGE مع Drizzle ORM؟

لا يتمتع Drizzle حاليًا بدعم MERGE الأصلي — استخدم db.execute(sql\MERGE ...`)مع القيم ذات المعلمات. لا تستخدم مطلقًاsql.raw()` للبيانات المقدمة من المستخدم. شاهد مشكلات Drizzle GitHub لتتبع دعم MERGE، لأنها ميزة مطلوبة بشكل شائع.

ما هو إصدار PostgreSQL الذي يجب أن أستهدفه للمشاريع الجديدة؟

استهدف PostgreSQL 17 لجميع المشاريع الجديدة. يتمتع بأفضل أداء، وأحدث ميزات SQL، وسيتلقى تحديثات الأمان حتى نوفمبر 2029. تجنب استهداف PostgreSQL 14 أو الإصدارات الأقدم - فسوف تصل إلى نهاية عمرها الافتراضي خلال العامين المقبلين، وستحتاج إلى الترقية على أي حال.


الخطوات التالية

ميزات PostgreSQL 17 الجديدة — خاصة JSON_TABLE، والدمج مع RETURNING، وتحسينات الفرز المتزايدة — تفتح أنماطًا معمارية لم تكن ممكنة من قبل. يقوم ECOSIRE بتشغيل PostgreSQL 17 في الإنتاج لجميع أحمال عمل قاعدة البيانات، مع الاستفادة من هذه التحسينات عبر مخطط جدول يزيد عن 65.

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

E

بقلم

ECOSIRE Research and Development Team

بناء منتجات رقمية بمستوى المؤسسات في ECOSIRE. مشاركة رؤى حول تكاملات Odoo وأتمتة التجارة الإلكترونية وحلول الأعمال المدعومة بالذكاء الاصطناعي.

الدردشة على الواتساب