ليه COUNT بطيء في PostgreSQL؟ وإمتى تستخدم reltuples بداله
count(*) في PostgreSQL ممكن يعمل scan للجدول كله بسبب الـ MVCC. لو الرقم للعرض بس، خد رقم تقريبي من pg_class.reltuples، وسيب الرقم المظبوط للحاجات المهمة.
صفحة الـ admin بتاخد ثانيتين تفتح، وإنت متأكد إن الـ query الأساسية سريعة… والسبب في الآخر بيطلع سطر صغير فوق الجدول مكتوب فيه «إجمالي النتائج». ده الجزء ٣٨ من «Full Stack Tips»، وموضوعه: ليه count(*) في PostgreSQL ممكن يبقى بطيء، وإمتى تقدر تستغنى عنه.
ليه COUNT بطيء أصلًا؟
حسب صفحة Slow Counting على PostgreSQL wiki، السبب هو الـ MVCC (Multi-Version Concurrency Control):
- كل transaction ممكن تشوف نسخة مختلفة من الداتا، حسب إمتى بدأت وإيه اللي اتعمله commit.
- عشان كده مفيش رقم واحد جاهز لعدد صفوف الجدول ينفع لكل الناس.
- فـ PostgreSQL لازم يمشي على الصفوف ويتأكد إن كل صف «ظاهر» للـ transaction بتاعتك.
- النتيجة غالبًا sequential scan على الجدول كله، وده بيكبر مع حجم الجدول.
يعني السطر ده:
SELECT count(*) FROM orders;
شكله بريء، لكن على جدول كبير ممكن يبقى أبطأ من الصفحة كلها.
الحل لو الرقم للعرض بس
في أغلب الـ pagination والـ dashboards، المستخدم مش محتاج الرقم بالظبط. هنا تقدر تاخد رقم تقريبي من الـ catalog بتاع PostgreSQL من غير ما تلف على الجدول:
SELECT reltuples::bigint FROM pg_class WHERE relname = 'orders';
ولو عندك نفس اسم الجدول في أكتر من schema، استخدم الـ oid بدل الاسم عشان ما تجيبش رقم جدول تاني:
SELECT reltuples::bigint FROM pg_class WHERE oid = 'public.orders'::regclass;
الرقم ده بيتحدث إمتى؟
حسب توثيق pg_class، العمود reltuples هو عدد تقديري للصفوف، والـ planner نفسه بيستخدمه:
- بيتحدث مع VACUUM و ANALYZE وشوية أوامر DDL زي CREATE INDEX.
- لو الجدول عمره ما اتعمله vacuum أو analyze، القيمة بتبقى -1، يعني «مش معروف».
فاتعامل مع الحالة دي في الكود: لو الرقم سالب، اعرض «—» أو ارجع للـ count العادي مرة واحدة. ولو عايز الرقم يبقى أحدث، شغّل:
ANALYZE orders;
ولو محتاج رقم مظبوط؟
ساعات الرقم لازم يبقى دقيق، زي تقارير فيها فلوس. هنا حط WHERE على عمود عليه index:
SELECT count(*) FROM orders WHERE status = 'pending';
PostgreSQL هيستخدم الـ index يقلل عدد الصفوف اللي بيعدها، بس خد بالك إنه لسه لازم يتأكد من الـ visibility لكل صف، فده مش سحر، لكنه أخف بكتير من عد الجدول كله.
اعمل إيه دلوقتي
- دوّر على كل
count(*)من غير WHERE في صفحاتك الـ paginated. - اسأل: المستخدم محتاج الرقم بالظبط؟ ولا «حوالي» كفاية؟
- قيس: جرّب
EXPLAIN ANALYZEعلى الـ count وقارن الوقت قبل وبعد التغيير.
رأيي
أغلب واجهات الـ admin بتعرض «إجمالي» محدش بيبص عليه بالظبط. الفرق بين الرقم المظبوط و«حوالي كذا» مش فارق مع حد في صفحة بحث، لكن تمنه ممكن يبقى scan للجدول كله في كل فتحة صفحة. خلي الرقم المظبوط للحاجات اللي فيها فلوس أو تقارير رسمية، والباقي خليه تقريبي.
وإنت، بتعرض الإجمالي في الـ pagination بتاعك بالظبط؟ ولا تقريبي؟
المصادر
- PostgreSQL
- Database
- Backend
- SQL
- Performance
- Full Stack Tips