it-swarm.asia

عمل الفهارس في PostgreSQL

لدي سؤالان بخصوص عمل الفهارس في PostgreSQL. لدي جدول Friends مع الفهرس التالي:

   Friends ( user_id1 ,user_id2) 

user_id1 و user_id2 هي مفاتيح خارجية لجدول user

  1. هل هذه معادلة؟ إذا لم يكن كذلك فلماذا؟

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
    
  2. إذا قمت بإنشاء مفتاح أساسي (user_id1، user_id2) ، فهل يقوم تلقائيًا بإنشاء فهارس له و

    إذا كانت الفهارس في السؤال الأول غير متكافئة ، فما المؤشر الذي يتم إنشاؤه على أمر المفتاح الأساسي أعلاه؟

80
codecool

فيما يلي نتائج الاستعلام عن جدول على العمود الثاني من فهرس متعدد الأعمدة.
من السهل استنساخ الآثار على أي شخص. فقط جربه في المنزل.

اختبرت مع PostgreSQL 9.0.5 على دبيان باستخدام جدول متوسط ​​الحجم لقاعدة بيانات واقعية تحتوي على 23322 صفًا. يطبق العلاقة n: m بين الجدولين adr (العنوان) و att (السمة) ، لكن هذا غير ذي صلة هنا. مخطط مبسط:

CREATE TABLE adratt (
  adratt_id serial PRIMARY KEY
, adr_id    integer NOT NULL
, att_id    integer NOT NULL
, log_up    timestamp(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);

القيد UNIQUE يطبق بشكل فعال فهرس فريد. كررت الاختبار بمؤشر عادي للتأكد من الحصول على نتائج مماثلة كما هو متوقع.

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

يتم تجميع الجدول على adratt_uni الفهرس وقبل الاختبار جريت:

CLUSTER adratt;
ANALYZE adratt;

المسح المتسلسل للاستعلامات في (adr_id, att_id) بأسرع ما يمكن. سيستمر استخدام فهرس متعدد الأعمدة لشرط الاستعلام في عمود الفهرس الثاني وحده.

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

1. الاستعلام باستخدام كلا العمودين

SELECT *
FROM   adratt
WHERE  att_id = 90
AND    adr_id = 10;

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
(1 row)

مخرج ل EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
  Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms

2. الاستعلام باستخدام العمود الأول

SELECT * FROM adratt WHERE adr_id = 10

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       126 |     10 |     10 | 2008-07-29 09:35:54
       125 |     10 |     13 | 2008-07-29 09:35:54
      4711 |     10 |     21 | 2008-07-29 09:35:54
     29322 |     10 |     22 | 2011-06-06 15:50:38
     29321 |     10 |     30 | 2011-06-06 15:47:17
       124 |     10 |     62 | 2008-07-29 09:35:54
     21913 |     10 |     78 | 2008-07-29 09:35:54
       123 |     10 |     90 | 2008-07-29 09:35:54
     28352 |     10 |    106 | 2010-11-22 12:37:50
(9 rows)

مخرج ل EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
  Index Cond: (adr_id = 10)
Total runtime: 0.058 ms

3. الاستعلام باستخدام العمود الثاني

SELECT * FROM adratt WHERE att_id = 90

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
       180 |     39 |     90 | 2008-08-29 15:46:07
...
(83 rows)

مخرج ل EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1)
  Index Cond: (att_id = 90)
Total runtime: 0.849 ms

4. تعطيل indexscan و bitmapscan

SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90

إخراج تحليل محلل:

Bitmap Heap Scan on adratt  (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1)
  Recheck Cond: (att_id = 90)
  ->  Bitmap Index Scan on adratt_uni  (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1)
        Index Cond: (att_id = 90)
Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90

مخرج ل EXPLAIN ANALYZE:

Seq Scan on adratt  (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1)
  Filter: (att_id = 90)
Total runtime: 2.680 ms

استنتاج

كما هو متوقع ، يتم استخدام الفهرس متعدد الأعمدة للاستعلام في العمود الثاني وحده.
كما هو متوقع ، فهو أقل فعالية ، لكن الاستعلام لا يزال أسرع 3 مرات من دون الفهرس.
بعد تعطيل عمليات مسح الفهرس ، يختار مخطط الاستعلام فحص كومة نقطية ، والذي يؤدي بسرعة تقريبًا. فقط بعد تعطيل ذلك ، فإنه يعود مرة أخرى إلى الفحص المتسلسل.

83
Erwin Brandstetter

إعادة 1) نعم ولا.

للاستعلام الذي يستخدم كلا العمودين ، مثل where (user_id1, user_id2) = (1,2) لا يهم الفهرس الذي تم إنشاؤه.

للاستعلام الذي يحتوي على شرط في عمود واحد فقط ، على سبيل المثال where user_id1 = 1 لا يهم لأنه عادةً ما يمكن استخدام الأعمدة "البادئة" فقط للمقارنة بواسطة المُحسّن. لذا where user_id1 = 1 سيتمكن من استخدام الفهرس (user_id1، user_id2) لكنه لن يتمكن من الفهرس (user_id2، user_id1) لجميع الحالات.

بعد التلاعب بهذا (بعد أن أظهر لنا إروين بلطف إعدادًا يعمل فيه) ، يبدو أن هذا يعتمد بشكل كبير على توزيع البيانات في العمود الثاني على الرغم من أنني لم أكتشف بعد الوضع الذي يتيح للمحسن استخدام الأعمدة اللاحقة لشرط أين.

Oracle 11 الذي يمكنه أيضًا (أحيانًا) استخدام أعمدة ليست في بداية تعريف الفهرس.

إعادة 2) نعم سيتم إنشاء فهرس

اقتباس من الدليل

ستؤدي إضافة مفتاح أساسي تلقائيًا إلى إنشاء فهرس btree فريد على العمود أو مجموعة الأعمدة المستخدمة في المفتاح الأساسي.

re 2a) Primary Key (user_id1,user_id2) ستنشئ فهرسًا على (user_id1، user_id2) (والذي يمكنك اكتشافه بنفسك جدًا بسهولة إنشاء مثل هذا المفتاح الأساسي)

أوصي بشدة بقراءة الفصل حول الفهارس في الدليل ، فهو يجيب بشكل أساسي على جميع الأسئلة أعلاه.

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

30
a_horse_with_no_name

إعلان 1)
هناك قيود في PostgreSQL مثل وصفa_horse_with_no_name . حتى الإصدار 8. لا يمكن استخدام فهارس متعددة الأعمدة إلا للاستعلامات في العمود (الأعمدة) الأساسي. تم تحسين هذا في الإصدار 8.1. يوضح الدليل الحالي لـ Postgres 1 (محدث):

يمكن استخدام فهرس B-tree متعدد الأعمدة مع شروط الاستعلام التي تتضمن أي مجموعة فرعية من أعمدة الفهرس ، ولكن يكون الفهرس أكثر فعالية عندما تكون هناك قيود على الأعمدة البادئة (أقصى اليسار). القاعدة الدقيقة هي أن قيود المساواة على الأعمدة البادئة ، بالإضافة إلى أي قيود عدم المساواة في العمود الأول التي لا تحتوي على قيود المساواة ، سيتم استخدامها لتحديد جزء المؤشر الذي تم مسحه ضوئيًا. يتم فحص القيود على الأعمدة الموجودة على يمين هذه الأعمدة في الفهرس ، لذلك فهي تحفظ الزيارات إلى الجدول المناسب ، لكنها لا تقلل من جزء الفهرس الذي يجب مسحه ضوئيًا. على سبيل المثال ، بالنظر إلى مؤشر على (a, b, c) وشرط الاستعلام WHERE a = 5 AND b >= 42 AND c < 77 ، يجب مسح الفهرس من الإدخال الأول باستخدام a = 5 و b = 42 لأعلى من خلال الإدخال الأخير مع a = 5. إدخالات الفهرس باستخدام c> = 77 سيتم تخطيها ، ولكن لا يزال يتعين فحصها. يمكن مبدئيًا استخدام هذا الفهرس للاستعلامات التي لها قيود على b و/أو c بدون قيود على a - ولكن سيتعين مسح الفهرس بأكمله ، لذلك في معظم الحالات يفضل المخطط إجراء مسح تسلسلي على الجدول باستخدام الفهرس.

التأكيد على المنجم. يمكنني أن أؤكد ذلك من الخبرة.
انظر أيضًا حالة الاختبار المضافة إجابتي المتأخرة هنا .

12
Erwin Brandstetter

هذا رداً على إجابة جاك ، تعليق لن يفعل.

لم يكن هناك أي فهرس تغطية في PostgreSQL قبل الإصدار 9.2. نظرًا لنموذج MVCC ، يجب زيارة كل Tuple في مجموعة النتائج للتحقق من الرؤية. قد تكون تفكر في Oracle.

يتحدث مطورو PostgreSQL عن "فحوصات الفهرس فقط" . في الواقع ، تم إصدار الميزة مع Postgres 9.2. اقرأ رسالة الالتزام .
كتب Depesz مشاركة مدونة مفيدة للغاية .

يتم تقديم فهارس التغطية الحقيقية (التحديث) باستخدام عبارة INCLUDE مع Postgres 11. ذات الصلة:

هذا بعيد بعض الشيء أيضًا:

يعتمد ذلك على حقيقة أن "الفحص الكامل" للفهرس غالبًا ما يكون أسرع من "الفحص الكامل" للجدول المفهرس بسبب الأعمدة الإضافية في الجدول التي لا تظهر في الفهرس.

كما هو مذكور في التعليقات على إجابتي الأخرى ، فقد أجريت أيضًا اختبارات مع جدول من عددين صحيحين ولا شيء آخر. يحتوي الفهرس على نفس الأعمدة الموجودة في الجدول. يبلغ حجم فهرس btree حوالي 2/3 حجم الجدول. لا يكفي لشرح تسريع العامل 3. ​​لقد أجريت اختبارًا أكثر ، بناءً على إعدادك ، مبسطًا إلى عمودين ومع 100000 صف. في تثبيت PostgreSQL 9.0 ، كانت النتائج متناسقة.

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

لتلخيص النقاط الرئيسية:

  • يمكن استخدام الفهارس متعددة الأعمدة مع الاستعلامات عن الأعمدة غير البادئة ، ولكن التسارع لا يتجاوز سوى العامل 3 للمعايير الانتقائية (نسبة صغيرة من الصفوف في النتيجة). أعلى للصفوف الكبيرة ، وأقل للأجزاء الأكبر من الجدول في مجموعة النتائج.

  • قم بإنشاء فهرس إضافي على تلك الأعمدة إذا كان الأداء مهمًا.

  • إذا تم تضمين جميع الأعمدة المعنية في فهرس (فهرس يغطي) وكانت جميع الصفوف المعنية (لكل كتلة) مرئية لجميع المعاملات ، يمكنك الحصول على "فحص الفهرس فقط" في الصفحة 9.2 أو أحدث.

12
Erwin Brandstetter
  1. هل هذه معادلة؟ إذا لم يكن كذلك فلماذا؟

    الفهرس (user_id1 و user_id2) والفهرس (user_id2 و user_id1)

هذه ليست مكافئة ولن يكون المؤشر (شريط ، باز) بشكل عام فعالاً للاستعلامات من النموذج select * from foo where baz=?

Erwin أثبتت أن هذه الفهارس يمكنها بالفعل تسريع الاستعلام ولكن هذا التأثير محدود وليس من نفس الترتيب الذي تتوقعه بشكل عام لتحسين الفهرس - يعتمد على حقيقة أن غالبًا ما يكون فحص "فهرس" أسرع من "مسح كامل" للجدول المفهرس بسبب الأعمدة الإضافية في الجدول التي لا تظهر في الفهرس.

ملخص: يمكن أن تساعد الفهارس في الاستعلامات حتى في الأعمدة غير الرائدة ، ولكن بإحدى طريقتين ثانوية وثانوية نسبيًا وليس بالطريقة الدرامية تتوقع عادةً مساعدة الفهرس بسبب هيكله btree

ملحوظة: الطريقتان اللتان يمكن أن يساعدهما الفهرس هما إذا كان الفحص الكامل للفهرس أرخص بكثير من الفحص الكامل للجدول وإما: 1. عمليات البحث عن الجدول رخيصة (لأن هناك القليل منها أو أنها مجمعة) ، أو 2. الفهرس يغطي لذلك لا توجد عمليات بحث جدول على الإطلاق عفوًا ، راجع تعليقات Erwins هنا

اختبار:

create table foo(bar integer not null, baz integer not null, qux text not null);

insert into foo(bar, baz, qux)
select random()*100, random()*100, 'some random text '||g from generate_series(1,10000) g;

استعلام 1 (لا يوجد فهرس ، ضرب 74 مخزن مؤقت ):

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.41..181.42 rows=1 width=32) (actual time=3.301..3.302 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..181.30 rows=43 width=32) (actual time=0.043..3.228 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.335 ms

استعلام 2 (مع الفهرس - يتجاهل المحسن الفهرس - ضرب 74 مخزنًا مؤقتًا مرة أخرى):

create index bar_baz on foo(bar, baz);

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=199.12..199.13 rows=1 width=32) (actual time=3.277..3.277 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..199.00 rows=50 width=32) (actual time=0.043..3.210 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.311 ms

استعلام 2 (مع الفهرس - ونحن خداع للمحسن لاستخدامه):

explain (buffers, analyze, verbose) select max(qux) from foo where bar>-1000 and baz=0;
                                                       QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=115.56..115.57 rows=1 width=32) (actual time=1.495..1.495 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=36 read=30
   ->  Bitmap Heap Scan on stack.foo  (cost=73.59..115.52 rows=17 width=32) (actual time=1.370..1.428 rows=52 loops=1)
         Output: bar, baz, qux
         Recheck Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
         Buffers: shared hit=36 read=30
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..73.58 rows=17 width=0) (actual time=1.356..1.356 rows=52 loops=1)
               Index Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
               Buffers: shared read=30
 Total runtime: 1.535 ms

لذا فإن الوصول عبر الفهرس أسرع مرتين في هذه الحالة حيث يصل إلى 30 مخزنًا مؤقتًا - والتي من حيث الفهرسة هي 'أسرع قليلاً'! و YMMV اعتمادًا على الحجم النسبي للجدول والفهرس ، إلى جانب عدد الصفوف التي تمت تصفيتها وخصائص التجميع للبيانات في الجدول

على النقيض من ذلك ، تستفيد الاستعلامات في العمود البادء من بنية btree للفهرس - في هذه الحالة يتم ضرب 2 من المخازن المؤقتة :

explain (buffers, analyze, verbose) select max(qux) from foo where bar=0;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=75.70..75.71 rows=1 width=32) (actual time=0.172..0.173 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=38
   ->  Bitmap Heap Scan on stack.foo  (cost=4.64..75.57 rows=50 width=32) (actual time=0.036..0.097 rows=59 loops=1)
         Output: bar, baz, qux
         Recheck Cond: (foo.bar = 0)
         Buffers: shared hit=38
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..4.63 rows=50 width=0) (actual time=0.024..0.024 rows=59 loops=1)
               Index Cond: (foo.bar = 0)
               Buffers: shared hit=2
 Total runtime: 0.209 ms
8
Jack says try topanswers.xyz