لقد بدأنا بمشاكل متعلقة
بالعزلة ، وقمنا باستكشاف
بنية البيانات منخفضة المستوى ، ثم ناقشنا
إصدارات الصف ولاحظنا كيفية الحصول على
لقطات البيانات من إصدارات الصفوف.
آخر مرة تحدثنا فيها عن تحديثات HOT
ومكنسة كهربائية في الصفحة ، واليوم سننتقل إلى
فراغ مألوف معروف. حقًا ، لقد كُتب الكثير بالفعل حول هذا الموضوع لدرجة أنه لا يمكنني إضافة أي شيء جديد ، لكن جمال الصورة الكاملة يتطلب التضحية. لذلك الحفاظ على الصبر.
فراغ
ماذا فراغ تفعل؟
يعمل الفراغ الموجود في الصفحة بسرعة ، ولكنه يحرر جزءًا فقط من المساحة. تعمل ضمن صفحة جدول واحد ولا تلمس الفهارس.
يتم الفراغ "العادي" الأساسي باستخدام أمر VACUUM ، وسنسميها "فراغ" فقط (مع ترك "فراغ تلقائي" لمناقشة منفصلة).
لذلك ، فراغ يعالج الجدول بأكمله. إنه لا يختفي فقط tuples الميتة ، ولكن أيضًا يشير إليهم من جميع الفهارس.
الفراغ متزامن مع الأنشطة الأخرى في النظام. يمكن استخدام الجدول والفهارس بطريقة منتظمة لكل من القراءات والتحديثات (ومع ذلك ، فإن التنفيذ المتزامن لأوامر مثل CREATE INDEX و ALTER TABLE وبعضها الآخر مستحيل).
يتم فحص صفحات الجدول تلك فقط من خلال المكان الذي تمت فيه بعض الأنشطة. لاكتشافهم ، يتم استخدام
خريطة الرؤية (لتذكيرك ، تقوم الخريطة بتتبع الصفحات التي تحتوي على tuples القديمة ، والتي تكون مرئية في جميع لقطات البيانات بالتأكيد). تتم معالجة تلك الصفحات فقط التي لا يتم تعقبها بواسطة خريطة الرؤية ، ويتم تحديث الخريطة نفسها.
يتم تحديث
خريطة المساحة الحرة أيضًا في هذه العملية لتعكس المساحة الحرة الإضافية في الصفحات.
كالعادة ، لنقم بإنشاء جدول:
=> CREATE TABLE vac( id serial, s char(100) ) WITH (autovacuum_enabled = off); => CREATE INDEX vac_s ON vac(s); => INSERT INTO vac(s) VALUES ('A'); => UPDATE vac SET s = 'B'; => UPDATE vac SET s = 'C';
نحن نستخدم المعلمة
autov Vacuum_enabled لإيقاف تشغيل عملية autov Vacuum. سنناقشها في المرة القادمة ، والآن من الأهمية بمكان بالنسبة لتجاربنا أن نتحكم يدويًا في التفريغ.
يحتوي الجدول الآن على ثلاثة صفوف ، يتم الإشارة إلى كل منها من الفهرس:
=> SELECT * FROM heap_page('vac',0);
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+----------+----------+-----+-----+-------- (0,1) | normal | 4000 (c) | 4001 (c) | | | (0,2) (0,2) | normal | 4001 (c) | 4002 | | | (0,3) (0,3) | normal | 4002 | 0 (a) | | | (0,3) (3 rows)
=> SELECT * FROM index_page('vac_s',1);
itemoffset | ctid ------------+------- 1 | (0,1) 2 | (0,2) 3 | (0,3) (3 rows)
بعد التنظيف بالمكنسة الكهربائية ، يتم إزالة الفراغات الميتة ، ويبقى واحد فقط يعيش. ويبقى مرجع واحد فقط في الفهرس:
=> VACUUM vac; => SELECT * FROM heap_page('vac',0);
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+----------+-------+-----+-----+-------- (0,1) | unused | | | | | (0,2) | unused | | | | | (0,3) | normal | 4002 (c) | 0 (a) | | | (0,3) (3 rows)
=> SELECT * FROM index_page('vac_s',1);
itemoffset | ctid ------------+------- 1 | (0,3) (1 row)
لاحظ أن أول مؤشرين حصلا على الحالة "غير المستخدمة" بدلاً من "الميت" ، والتي سوف يكتسبها مع الفراغ في الصفحة.
حول أفق الصفقة مرة أخرى
كيف يقوم برنامج PostgreSQL بتحديد أي التلاميذ يمكن اعتبارهم ميتين؟ لقد تطرقنا بالفعل إلى مفهوم أفق المعاملات عند مناقشة
لقطات البيانات ، ولكن لن يضر تكرار مثل هذه المسألة الهامة.
لنبدأ التجربة السابقة مرة أخرى.
=> TRUNCATE vac; => INSERT INTO vac(s) VALUES ('A'); => UPDATE vac SET s = 'B';
ولكن قبل تحديث الصف مرة أخرى ، دع عملية واحدة أخرى تبدأ (لكن لا تنتهي). في هذا المثال ، سوف يستخدم مستوى "قراءة ملتزم" ، لكن يجب أن يحصل على رقم معاملة حقيقي (غير افتراضي). على سبيل المثال ، يمكن أن تتغير المعاملة بل وتغلق صفوفًا معينة في أي جدول ، وليس إجباريًا:
| => BEGIN; | => SELECT s FROM t FOR UPDATE;
| s | ----- | FOO | BAR | (2 rows)
=> UPDATE vac SET s = 'C';
هناك ثلاثة صفوف في الجدول وثلاثة مراجع في الفهرس الآن. ماذا سيحدث بعد كنس؟
=> VACUUM vac; => SELECT * FROM heap_page('vac',0);
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+----------+----------+-----+-----+-------- (0,1) | unused | | | | | (0,2) | normal | 4005 (c) | 4007 (c) | | | (0,3) (0,3) | normal | 4007 (c) | 0 (a) | | | (0,3) (3 rows)
=> SELECT * FROM index_page('vac_s',1);
itemoffset | ctid ------------+------- 1 | (0,2) 2 | (0,3) (2 rows)
تبقى توابلتان في الجدول: قررت VACUUM أن الفراغ (0،2) لا يمكن تفريغه بعد. السبب هو بالتأكيد في أفق المعاملات لقاعدة البيانات ، والتي يتم تحديدها في هذا المثال بالمعاملة غير المكتملة:
| => SELECT backend_xmin FROM pg_stat_activity WHERE pid = pg_backend_pid();
| backend_xmin | -------------- | 4006 | (1 row)
يمكننا أن نسأل VACUUM للإبلاغ عما يحدث:
=> VACUUM VERBOSE vac;
INFO: vacuuming "public.vac" INFO: index "vac_s" now contains 2 row versions in 2 pages DETAIL: 0 index row versions were removed. 0 index pages have been deleted, 0 are currently reusable. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. INFO: "vac": found 0 removable, 2 nonremovable row versions in 1 out of 1 pages DETAIL: 1 dead row versions cannot be removed yet, oldest xmin: 4006 There were 1 unused item pointers. Skipped 0 pages due to buffer pins, 0 frozen pages. 0 pages are entirely empty. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. VACUUM
لاحظ أن:
2 nonremovable row versions
من الصفوف غير القابلة 2 nonremovable row versions
- وهما الجدولان لا يمكن حذفهما في الجدول.1 dead row versions cannot be removed yet
واحد من 1 dead row versions cannot be removed yet
- أحدها ميت.oldest xmin
يظهر الأفق الحالي.
دعنا نكرر الاستنتاج: إذا كانت قاعدة البيانات تحتوي على معاملات طويلة الأمد (لم تكتمل أو يجري تنفيذها طويلًا بالفعل) ، فقد يستلزم ذلك ازدهارًا في الجدول بغض النظر عن عدد مرات حدوث كنس. لذلك ، تتعايش أحمال العمل من نوع OLTP- و OLAP بشكل سيء في قاعدة بيانات PostgreSQL واحدة: لن تسمح التقارير التي يتم تشغيلها لساعات بامتصاص الجداول المحدثة على النحو الواجب. قد يكون إنشاء نسخة متماثلة منفصلة لأغراض إعداد التقارير حلاً ممكنًا لذلك.
بعد الانتهاء من الصفقة المفتوحة ، يتحرك الأفق ، ويتم إصلاح الموقف:
| => COMMIT;
=> VACUUM VERBOSE vac;
INFO: vacuuming "public.vac" INFO: scanned index "vac_s" to remove 1 row versions DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s INFO: "vac": removed 1 row versions in 1 pages DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s INFO: index "vac_s" now contains 1 row versions in 2 pages DETAIL: 1 index row versions were removed. 0 index pages have been deleted, 0 are currently reusable. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. INFO: "vac": found 1 removable, 1 nonremovable row versions in 1 out of 1 pages DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 4008 There were 1 unused item pointers. Skipped 0 pages due to buffer pins, 0 frozen pages. 0 pages are entirely empty. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. VACUUM
الآن فقط ، نسخة حية ، يتم ترك نسخة من الصف في الصفحة:
=> SELECT * FROM heap_page('vac',0);
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+----------+-------+-----+-----+-------- (0,1) | unused | | | | | (0,2) | unused | | | | | (0,3) | normal | 4007 (c) | 0 (a) | | | (0,3) (3 rows)
يحتوي الفهرس أيضًا على صف واحد فقط:
=> SELECT * FROM index_page('vac_s',1);
itemoffset | ctid ------------+------- 1 | (0,3) (1 row)
ماذا يحدث في الداخل؟
يجب أن يقوم الفراغ بمعالجة الجدول والفهارس في نفس الوقت والقيام بذلك حتى لا يتم قفل العمليات الأخرى. كيف يمكن أن تفعل ذلك؟
كل شيء يبدأ مع مرحلة
كومة المسح (خريطة الرؤية تؤخذ في الاعتبار ، كما ذكر بالفعل). في الصفحات المقروءة ، يتم الكشف عن tuples الميتة ، ويتم تدوين
tid
إلى مجموعة متخصصة. يتم تخزين الصفيف في الذاكرة المحلية لعملية فراغ ، حيث يتم تخصيص بايت
maintenance_work_mem من الذاكرة لذلك. القيمة الافتراضية لهذه المعلمة هي 64 ميغابايت. لاحظ أنه يتم تخصيص كامل حجم الذاكرة مرة واحدة ، بدلاً من الحاجة. ومع ذلك ، إذا لم يكن الجدول كبيرًا ، يتم تخصيص كمية أقل من الذاكرة.
ثم نصل إلى نهاية الجدول أو انتهت الذاكرة المخصصة للصفيف. في كلتا الحالتين ، تبدأ مرحلة
فهارس الكنس . تحقيقًا لهذه الغاية ،
يتم فحص كل فهرس تم إنشاؤه على الجدول
بشكل كامل بحثًا عن الصفوف التي تشير إلى التلاميذ الذين تم تذكرهم. يتم تنظيف الصفوف الموجودة بعيدًا عن صفحات الفهرس.
نحن هنا نواجه ما يلي: لا تحتوي الفهارس بالفعل على إشارات إلى tuples ، بينما لا يزال الجدول بها. وهذا لا يتعارض مع أي شيء: عند تنفيذ طلب البحث ، فإننا لا نضرب التلاميذ القتلى (مع إمكانية الوصول إلى الفهرس) أو نرفضهم عند فحص الرؤية (عند مسح الجدول).
بعد ذلك ، تبدأ المرحلة
كومة كنس . يتم فحص الجدول مرة أخرى لقراءة الصفحات المناسبة ، وإفراغها من التلاميذ الذين تم تذكرهم وإطلاق المؤشرات. يمكننا القيام بذلك نظرًا لعدم وجود مراجع من الفهارس بعد الآن.
إذا لم يتم قراءة الجدول بالكامل خلال الدورة الأولى ، فسيتم مسح الصفيف ويتكرر كل شيء من حيث وصلنا.
باختصار:
- يتم دائما مسح الجدول مرتين.
- إذا كان الفراغ يحذف عددًا كبيرًا من المجموعات التي لا تناسبها جميعًا في ذاكرة maintenance_work_mem بالحجم ، فسيتم مسح جميع الفهارس عدة مرات حسب الحاجة.
بالنسبة للجداول الكبيرة ، قد يتطلب ذلك الكثير من الوقت وإضافة عبء عمل كبير على النظام. بالطبع ، لن يتم تأمين الاستعلامات ، لكن الإدخال / الإخراج الإضافي أمر غير مرغوب فيه بالتأكيد.
لتسريع العملية ، من المنطقي إما استدعاء VACUUM في كثير من الأحيان (بحيث لا يتم تفريغ الكثير من المجموعات في كل مرة) أو تخصيص المزيد من الذاكرة.
لملاحظة الأقواس ، بدءًا من الإصدار 11 ،
يمكن لـ PostgreSQL
تخطي عمليات مسح الفهرسة ما لم تكن هناك حاجة ملحة. هذا يجب أن يجعل الحياة أسهل لأصحاب الجداول الكبيرة حيث يتم إضافة صفوف فقط (ولكن لا تتغير).
مراقبة
كيف يمكننا معرفة أن VACUUM لا يمكنها القيام بعملها في دورة واحدة؟
لقد رأينا بالفعل الطريقة الأولى: للاتصال بأمر VACUUM باستخدام خيار VERBOSE. في هذه الحالة ، سيتم إخراج المعلومات حول مراحل العملية إلى وحدة التحكم.
ثانيًا ، بدءًا من الإصدار 9.6 ،
pg_stat_progress_vacuum
عرض
pg_stat_progress_vacuum
، والذي يوفر أيضًا كافة المعلومات اللازمة.
(تتوفر الطريقة الثالثة أيضًا: لإخراج المعلومات إلى سجل الرسائل ، ولكن هذا يعمل فقط مع فراغ تلقائي ، والذي سيتم مناقشته في المرة القادمة.)
لنقم بإدراج بضعة صفوف في الجدول ، لكي تستمر عملية التفريغ لفترة طويلة ، ولنقم بتحديثها جميعًا ، حتى تحصل VACUUM على أشياء للقيام بها.
=> TRUNCATE vac; => INSERT INTO vac(s) SELECT 'A' FROM generate_series(1,500000); => UPDATE vac SET s = 'B';
لنقل حجم الذاكرة المخصصة لمجموعة المعرفات:
=> ALTER SYSTEM SET maintenance_work_mem = '1MB'; => SELECT pg_reload_conf();
لنبدأ تشغيل VACUUM وأثناء العمل ، دعنا
pg_stat_progress_vacuum
عرض
pg_stat_progress_vacuum
عدة مرات:
=> VACUUM VERBOSE vac;
| => SELECT * FROM pg_stat_progress_vacuum \gx
| -[ RECORD 1 ]------+------------------ | pid | 6715 | datid | 41493 | datname | test | relid | 57383 | phase | vacuuming indexes | heap_blks_total | 16667 | heap_blks_scanned | 2908 | heap_blks_vacuumed | 0 | index_vacuum_count | 0 | max_dead_tuples | 174762 | num_dead_tuples | 174480
| => SELECT * FROM pg_stat_progress_vacuum \gx
| -[ RECORD 1 ]------+------------------ | pid | 6715 | datid | 41493 | datname | test | relid | 57383 | phase | vacuuming indexes | heap_blks_total | 16667 | heap_blks_scanned | 5816 | heap_blks_vacuumed | 2907 | index_vacuum_count | 1 | max_dead_tuples | 174762 | num_dead_tuples | 174480
هنا يمكننا أن نرى ، على وجه الخصوص:
- اسم المرحلة الحالية - ناقشنا ثلاث مراحل رئيسية ، ولكن هناك الكثير منها بشكل عام.
- إجمالي عدد صفحات الجدول (
heap_blks_total
). - عدد الصفحات الممسوحة ضوئيًا (
heap_blks_scanned
). - عدد الصفحات
heap_blks_vacuumed
بالفعل ( heap_blks_vacuumed
). - عدد دورات فراغ الفهرس (
index_vacuum_count
).
يتم تحديد التقدم العام من خلال نسبة
heap_blks_vacuumed
إلى
heap_blks_total
، ولكن يجب أن نأخذ في الاعتبار أن هذه القيمة تتغير في الزيادات الكبيرة بدلاً من السلاسة بسبب مسح الفهارس. ومع ذلك ، ينبغي إيلاء الاهتمام الرئيسي لعدد دورات الفراغ: فالعدد الأكبر من 1 يعني أن الذاكرة المخصصة لم تكن كافية لإكمال الفراغ في دورة واحدة.
يُظهر إخراج أمر VACUUM VERBOSE ، الذي تم إكماله بالفعل بحلول ذلك الوقت ، الصورة العامة:
INFO: vacuuming "public.vac"
INFO: scanned index "vac_s" to remove 174480 row versions DETAIL: CPU: user: 0.50 s, system: 0.07 s, elapsed: 1.36 s INFO: "vac": removed 174480 row versions in 2908 pages DETAIL: CPU: user: 0.02 s, system: 0.02 s, elapsed: 0.13 s
INFO: scanned index "vac_s" to remove 174480 row versions DETAIL: CPU: user: 0.26 s, system: 0.07 s, elapsed: 0.81 s INFO: "vac": removed 174480 row versions in 2908 pages DETAIL: CPU: user: 0.01 s, system: 0.02 s, elapsed: 0.10 s
INFO: scanned index "vac_s" to remove 151040 row versions DETAIL: CPU: user: 0.13 s, system: 0.04 s, elapsed: 0.47 s INFO: "vac": removed 151040 row versions in 2518 pages DETAIL: CPU: user: 0.01 s, system: 0.02 s, elapsed: 0.08 s
INFO: index "vac_s" now contains 500000 row versions in 17821 pages DETAIL: 500000 index row versions were removed. 8778 index pages have been deleted, 0 are currently reusable. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. INFO: "vac": found 500000 removable, 500000 nonremovable row versions in 16667 out of 16667 pages DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 4011 There were 0 unused item pointers. 0 pages are entirely empty. CPU: user: 1.10 s, system: 0.37 s, elapsed: 3.71 s. VACUUM
يمكننا أن نرى هنا أنه قد تم إجراء ثلاث دورات على الفهارس ، وفي كل دورة ، تم تفريغ 174480 من المؤشرات إلى المجموعات الميتة. لماذا بالضبط هذا الرقم؟ يشغل واحد
tid
6 بايت ، و 1024 * 1024/6 = 174762 ، وهو الرقم الذي نراه في
pg_stat_progress_vacuum.max_dead_tuples
. في الواقع ، قد يتم استخدام أقل من ذلك بقليل: وهذا يضمن أنه عند قراءة الصفحة التالية ، فإن جميع المؤشرات إلى tuples tattles سوف توضع في الذاكرة بالتأكيد.
تحليل
التحليل ، أو بعبارة أخرى ، جمع الإحصائيات لمخطط الاستعلام ، لا علاقة له رسميًا بالمكنسة الكهربائية على الإطلاق. ومع ذلك ، يمكننا إجراء التحليل ليس فقط باستخدام أمر ANALYZE ، ولكن نجمع بين التنظيف والتحليل في فراغ تحليل. هنا يتم الفراغ أولاً ثم التحليل ، لذلك هذا لا يعطي أي مكاسب.
ولكن كما سنرى لاحقًا ، يتم إجراء الفحص التلقائي والتحليل التلقائي في عملية واحدة ويتم التحكم فيها بطريقة مماثلة.
فراغ كامل
كما هو مذكور أعلاه ، يحرر الفراغ مساحة أكبر من الفراغ الموجود في الصفحة ، لكنه لا يحل المشكلة تمامًا.
إذا زاد حجم الجدول أو الفهرس كثيرًا لأسباب ، فسيوفر VACUUM مساحة داخل الصفحات الحالية: "الثقوب" ستحدث هناك ، والتي سيتم استخدامها بعد ذلك لإدراج مجموعات جديدة. لكن عدد الصفحات لن يتغير ، وبالتالي ، من وجهة نظر نظام التشغيل ، ستشغل الملفات نفس المساحة تمامًا كما كانت عليه قبل الفراغ. وهذا ليس جيدًا للأسباب التالية:
- الفحص الكامل للجدول (أو الفهرس) يبطئ.
- قد تكون هناك حاجة إلى ذاكرة تخزين مؤقت أكبر (نظرًا لأنها الصفحات المخزنة هناك وتناقص كثافة المعلومات المفيدة).
- في شجرة الفهرس ، يمكن أن يحدث مستوى إضافي ، مما سيؤدي إلى إبطاء الوصول إلى الفهرس.
- تشغل الملفات مساحة إضافية على القرص وفي نسخ احتياطية.
(الاستثناء الوحيد هو الصفحات ذات الفراغ الكامل ، الموجودة في نهاية الملف. يتم قطع هذه الصفحات من الملف وإعادتها إلى نظام التشغيل.)
إذا كانت مشاركة المعلومات المفيدة في الملفات أقل من حد معقول ، فيمكن للمسؤول تنفيذ VACUUM FULL من الجدول. في هذه الحالة ، يتم إعادة بناء الجدول وجميع فهارسه من نقطة الصفر ويتم تعبئة البيانات بطريقة مضغوطة في الغالب (بالطبع ، معلمة
fillfactor
تؤخذ في الاعتبار). أثناء إعادة الإنشاء ، يقوم PostgreSQL أولاً بإعادة إنشاء الجدول ثم يقوم كل من فهارسه الواحد تلو الآخر. لكل كائن ، يتم إنشاء ملفات جديدة ، وتتم إزالة الملفات القديمة في نهاية إعادة البناء. يجب أن نأخذ في الاعتبار أن هناك حاجة إلى مساحة إضافية على القرص في هذه العملية.
لتوضيح ذلك ، دعنا ندرج مرة أخرى عددًا معينًا من الصفوف في الجدول:
=> TRUNCATE vac; => INSERT INTO vac(s) SELECT 'A' FROM generate_series(1,500000);
كيف يمكننا تقدير كثافة المعلومات؟ للقيام بذلك ، من المريح استخدام ملحق متخصص:
=> CREATE EXTENSION pgstattuple; => SELECT * FROM pgstattuple('vac') \gx
-[ RECORD 1 ]------+--------- table_len | 68272128 tuple_count | 500000 tuple_len | 64500000 tuple_percent | 94.47 dead_tuple_count | 0 dead_tuple_len | 0 dead_tuple_percent | 0 free_space | 38776 free_percent | 0.06
تقوم الوظيفة بقراءة الجدول بأكمله وتظهر الإحصائيات: أي البيانات تشغل مساحة في الملفات. المعلومات الرئيسية التي
tuple_percent
الآن هي حقل
tuple_percent
: النسبة المئوية للبيانات المفيدة. إنه أقل من 100 بسبب المعلومات العامة التي لا مفر منها داخل الصفحة ، لكنها لا تزال مرتفعة.
بالنسبة إلى الفهرس ، يتم إخراج المعلومات المختلفة ، لكن حقل
avg_leaf_density
له نفس المعنى: النسبة المئوية للمعلومات المفيدة (في الصفحات الورقية).
=> SELECT * FROM pgstatindex('vac_s') \gx
-[ RECORD 1 ]------+--------- version | 3 tree_level | 3 index_size | 72802304 root_block_no | 2722 internal_pages | 241 leaf_pages | 8645 empty_pages | 0 deleted_pages | 0 avg_leaf_density | 83.77 leaf_fragmentation | 64.25
وهذه هي أحجام الجدول والفهارس:
=> SELECT pg_size_pretty(pg_table_size('vac')) table_size, pg_size_pretty(pg_indexes_size('vac')) index_size;
table_size | index_size ------------+------------ 65 MB | 69 MB (1 row)
الآن دعونا نحذف 90 ٪ من جميع الصفوف. نقوم باختيار عشوائي من الصفوف المراد حذفها ، لذلك من المحتمل جدًا بقاء صف واحد على الأقل في كل صفحة:
=> DELETE FROM vac WHERE random() < 0.9;
DELETE 450189
ما حجم الكائنات بعد الفراغ؟
=> VACUUM vac; => SELECT pg_size_pretty(pg_table_size('vac')) table_size, pg_size_pretty(pg_indexes_size('vac')) index_size;
table_size | index_size ------------+------------ 65 MB | 69 MB (1 row)
يمكننا أن نرى أن الحجم لم يتغير: فراغ لا يمكن بأي حال من الأحوال تقليل حجم الملفات. وهذا على الرغم من أن كثافة المعلومات انخفضت بنحو 10 مرات:
=> SELECT vac.tuple_percent, vac_s.avg_leaf_density FROM pgstattuple('vac') vac, pgstatindex('vac_s') vac_s;
tuple_percent | avg_leaf_density ---------------+------------------ 9.41 | 9.73 (1 row)
الآن دعونا نتحقق مما حصلنا عليه بعد الفراغ الكامل. الآن يستخدم الجدول والفهارس الملفات التالية:
=> SELECT pg_relation_filepath('vac'), pg_relation_filepath('vac_s');
pg_relation_filepath | pg_relation_filepath ----------------------+---------------------- base/41493/57392 | base/41493/57393 (1 row)
=> VACUUM FULL vac; => SELECT pg_relation_filepath('vac'), pg_relation_filepath('vac_s');
pg_relation_filepath | pg_relation_filepath ----------------------+---------------------- base/41493/57404 | base/41493/57407 (1 row)
يتم استبدال الملفات بأخرى جديدة الآن. انخفضت أحجام الجدول والفهارس بشكل كبير ، بينما زادت كثافة المعلومات وفقًا لذلك:
=> SELECT pg_size_pretty(pg_table_size('vac')) table_size, pg_size_pretty(pg_indexes_size('vac')) index_size;
table_size | index_size ------------+------------ 6648 kB | 6480 kB (1 row)
=> SELECT vac.tuple_percent, vac_s.avg_leaf_density FROM pgstattuple('vac') vac, pgstatindex('vac_s') vac_s;
tuple_percent | avg_leaf_density ---------------+------------------ 94.39 | 91.08 (1 row)
لاحظ أن كثافة المعلومات في الفهرس أكبر من الكثافة الأصلية. من الأفضل إعادة إنشاء فهرس (شجرة B) من البيانات المتاحة بدلاً من إدراج البيانات في صف فهرس موجود حاليًا.
وظائف ملحق
pgstattuple التي استخدمناها قراءة الجدول بأكمله. لكن هذا غير مريح إذا كان الجدول كبيرًا ، لذا فإن الملحق يحتوي على وظيفة
pgstattuple_approx
، التي تتخطى الصفحات المحددة في خريطة الرؤية وتظهر الأرقام التقريبية.
هناك طريقة أخرى ، ولكنها أقل دقة ، وهي استخدام كتالوج النظام لتقدير نسبة حجم البيانات إلى حجم الملف تقريبًا. يمكنك العثور على أمثلة من هذه الاستعلامات
في الويكي .
VACUUM FULL غير مخصص للاستخدام المنتظم لأنه يحظر أي عمل مع الجدول (يتم تضمين الاستعلام) طوال مدة العملية. من الواضح أنه بالنسبة للنظام المستخدم بكثرة ، فقد يبدو هذا غير مقبول. ستتم مناقشة الأقفال بشكل منفصل ، ولن نذكر الآن سوى ملحق
pg_repack ، الذي
يغلق الطاولة لفترة قصيرة من الوقت فقط في نهاية العمل.
أوامر مماثلة
هناك بعض الأوامر التي تقوم أيضًا بإعادة إنشاء الجداول والفهارس بالكامل وبالتالي تشبه VACUUM FULL. جميعهم يحظرون تمامًا أي عمل مع الجدول ، ويقومون جميعًا بإزالة ملفات البيانات القديمة وإنشاء ملفات جديدة.
يتشابه الأمر CLUSTER مع VACUUM FULL ، ولكنه أيضًا يطلب أوامرًا فعلية وفقًا لأحد الفهارس المتاحة. يتيح ذلك للمخطط استخدام الوصول إلى الفهرس بشكل أكثر كفاءة في بعض الحالات. ولكن يجب أن نضع في اعتبارنا أنه لا يتم الاحتفاظ بالتجميع: سيتم كسر الترتيب المادي لل tuples بالتغييرات اللاحقة للجدول.
يعيد الأمر REINDEX إنشاء فهرس منفصل على الجدول. فراغ كامل و CLUSTER فعلياً استخدام هذا الأمر لإعادة إنشاء الفهارس.
يشبه منطق أمر TRUNCATE منطق DELETE - يحذف جميع صفوف الجدول. لكن DELETE ، كما ذكرنا سابقًا ، تقوم فقط بتحديد العلامات على أنها محذوفة ، وهذا يتطلب مزيدًا من الفراغ. ويؤدي TRUNCATE فقط إلى إنشاء ملف جديد ونظيف بدلاً من ذلك. كقاعدة عامة ، يعمل هذا بشكل أسرع ، ولكن يجب أن نضع في الاعتبار أن TRUNCATE سيمنع أي عمل مع الجدول حتى نهاية المعاملة.
اقرأ على .