PostgreSQL Performance & Query Optimization · درس

عوامل JSONB واستعلامات الاحتواء

استخدم عوامل الاحتواء والمسار التي تستطيع فهارس GIN تسريعها فعليًا.

الدرس 1 من 413 خطوة

عوامل JSONB واستعلامات الاحتواء درس مجاني في PostgreSQL Performance & Query Optimization على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PostgreSQL Performance & Query Optimization، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PostgreSQL Performance & Query Optimization 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

Why Operator Choice Decides Index Use

In PostgreSQL, a column of type jsonb can be searched many different ways, but not every operator can use an index. Performance here is almost entirely about choosing operators that a GIN index can accelerate.

  • A GIN index (Generalized Inverted Index) stores the keys and values inside your JSON documents so lookups skip the full table.
  • The two operators that matter most are containment (@>) and key existence (?, ?|, ?&).

This lesson teaches exactly which operators those are, and how to write queries that stay index-friendly.

The Containment Operator @>

The containment operator @> asks: does the left JSONB contain the right JSONB? The right side is a fragment, and Postgres checks that every key/value in it appears in the left document.

  • '{"a":1,"b":2}' @> '{"a":1}' is true.
  • '{"a":1}' @> '{"a":1,"b":2}' is false (the right side has more).

This is the workhorse for filtering rows: WHERE data @> '{"status":"active"}' finds every row whose JSON includes that pair.

SELECT '{"a":1,"b":2}'::jsonb @> '{"a":1}'::jsonb AS contains_a,
       '{"a":1}'::jsonb @> '{"a":1,"b":2}'::jsonb AS contains_both;

Building a GIN Index for Containment

A plain GIN index on a jsonb column supports both containment and key-existence operators. This is the index you reach for first.

  • The default jsonb_ops operator class indexes every key and value.
  • It accelerates @>, ?, ?|, and ?&.

Create it once, and containment filters that previously scanned the whole table become bitmap index scans.

CREATE INDEX idx_events_data
  ON events
  USING GIN (data);

Containment Filters in WHERE

Once the GIN index exists, write the filter as a containment check so the planner can use it. Matching a nested fragment works too, because containment is recursive.

  • Top-level match: data @> '{"status":"active"}'.
  • Nested match: data @> '{"user":{"plan":"pro"}}'.

Notice we pass a JSON object literal on the right, not a column reference or function call. That literal shape is what makes the query index-eligible.

SELECT id, created_at
FROM events
WHERE data @> '{"user":{"plan":"pro"}}'
ORDER BY created_at DESC
LIMIT 50;

Key Existence Operators ? ?| ?&

Sometimes you only care whether a key is present, regardless of its value. The existence operators handle this and are also GIN-accelerated.

  • data ? 'email' — true if the top-level key email exists.
  • data ?| array['phone','email'] — true if any of these keys exist.
  • data ?& array['phone','email'] — true if all of these keys exist.

Important: ? checks top-level keys only, and for arrays it checks whether the string is an element.

SELECT '{"email":"x@y.z","phone":"123"}'::jsonb ? 'email'        AS has_email,
       '{"email":"x@y.z"}'::jsonb ?| array['phone','email']      AS has_any,
       '{"email":"x@y.z"}'::jsonb ?& array['phone','email']      AS has_all;

The Trap: Path Extraction Operators -> and ->>

The extraction operators look convenient but are not accelerated by a standard GIN index:

  • data -> 'status' returns the value as jsonb.
  • data ->> 'status' returns the value as text.

A query like WHERE data ->> 'status' = 'active' forces a sequential scan on a plain GIN index, because the index does not index extracted scalar comparisons. Prefer the containment form data @> '{"status":"active"}' instead.

-- Slow on a plain GIN index (seq scan):
SELECT * FROM events WHERE data ->> 'status' = 'active';

-- Fast equivalent (uses GIN):
SELECT * FROM events WHERE data @> '{"status":"active"}';

Rescuing ->> with an Expression Index

If you genuinely need range or pattern comparisons on one field, a B-tree expression index on the extracted text is the right tool — not GIN.

  • Index the exact expression you query.
  • Then comparisons like =, <, >, and BETWEEN can use it.

The query's expression must match the indexed expression character for character, or the planner ignores the index.

CREATE INDEX idx_events_status
  ON events ((data ->> 'status'));

-- Now this can use the B-tree index:
SELECT * FROM events WHERE (data ->> 'status') = 'active';

jsonb_path_ops: Smaller, Faster, Containment-Only

The alternative operator class jsonb_path_ops indexes hashed root-to-leaf paths instead of every key.

  • It produces a smaller index and is typically faster for @> queries.
  • Trade-off: it supports only containment (@>), not the existence operators ?, ?|, ?&.

Choose jsonb_path_ops when your workload is dominated by containment filtering and you never need key-existence searches.

CREATE INDEX idx_events_data_path
  ON events
  USING GIN (data jsonb_path_ops);

Containment Against Arrays

Containment also matches inside JSON arrays, which makes it ideal for tag-style data. To ask "does this array contain a value," wrap the value in an array on the right side.

  • '["a","b","c"]' @> '["b"]' is true.
  • For a tagged document: data @> '{"tags":["urgent"]}' finds rows whose tags array includes urgent.

This stays fully index-eligible on a GIN index, so tag filtering scales well.

SELECT '["a","b","c"]'::jsonb @> '["b"]'::jsonb   AS has_b,
       '{"tags":["urgent","billing"]}'::jsonb
         @> '{"tags":["urgent"]}'::jsonb            AS is_urgent;

Verify With EXPLAIN

Never assume the index is used — confirm it. Run EXPLAIN and look for a Bitmap Index Scan on your GIN index. A Seq Scan means your operator or expression defeated the index.

  • Good sign: Bitmap Index Scan on idx_events_data.
  • Bad sign: Seq Scan on events with a JSON filter.

Use EXPLAIN (ANALYZE, BUFFERS) to also see real timing and how many pages were read.

EXPLAIN (ANALYZE, BUFFERS)
SELECT id FROM events
WHERE data @> '{"status":"active"}';

Putting It Together: A Decision Rule

Use this quick rule when writing a JSONB filter:

  • Matching key/value or nested fragment? Use @> with a GIN index.
  • Only checking a key is present? Use ?/?|/?& with default jsonb_ops GIN.
  • Containment-only workload, want the smallest index? Use jsonb_path_ops GIN.
  • Range or pattern on one scalar field? Use a B-tree expression index on ->>.

Avoid ->> equality filters without a matching expression index — they trigger sequential scans.

Quick Check

You have a default jsonb_ops GIN index on events.data. Which WHERE clause can use that index?

Recap

You learned which JSONB operators actually benefit from indexing:

  • @> (containment) is the primary GIN-accelerated filter, including nested objects and arrays.
  • ?, ?|, ?& (key existence) are GIN-accelerated, but only with the default jsonb_ops class, and check top-level keys.
  • jsonb_path_ops gives a smaller, faster containment-only index.
  • -> and ->> extraction filters do NOT use a plain GIN index; rewrite as @> or add a B-tree expression index.
  • Always confirm with EXPLAIN that you get a Bitmap Index Scan, not a Seq Scan.
البدء مجانًا

تعلم SQL مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
22
الدروس
88

الأسئلة الشائعة

هل درس «عوامل JSONB واستعلامات الاحتواء» مجاني؟

نعم — نص درس «عوامل JSONB واستعلامات الاحتواء» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PostgreSQL Performance & Query Optimization، انتقل إلى CoddyKit PRO. تتضمن دورة PostgreSQL Performance & Query Optimization 4 دروس في المجموع.

ماذا ستتعلم في «عوامل JSONB واستعلامات الاحتواء»؟

استخدم عوامل الاحتواء والمسار التي تستطيع فهارس GIN تسريعها فعليًا. تتمرن على PostgreSQL Performance & Query Optimization مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ PostgreSQL Performance & Query Optimization؟

لا تُشترط خبرة سابقة. PostgreSQL Performance & Query Optimization على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «عوامل JSONB واستعلامات الاحتواء»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس PostgreSQL Performance & Query Optimization هذا؟

نعم. كل درس في PostgreSQL Performance & Query Optimization يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. عوامل JSONB واستعلامات الاحتواء
  2. مقارنة GIN بفهرس التعبير على JSONB
  3. الاستعلام عن JSONB باستخدام JSONPath
  4. متى نطبّع البيانات خارج JSONB
← العودة إلى PostgreSQL Performance & Query Optimization