در یکی از پروژه‌های سه‌ساله، سایت مشتری به‌طور ناگهانی کند شد. طراحی قالب عوض نشده بود، افزونه جدیدی نصب نشده بود و هاست هم ارتقا یافته بود. بررسی که کردیم، TTFB روی همه صفحات به بالای یک و نیم ثانیه رسیده بود. علت، در جایی بود که کمتر کسی نگاه می‌کند: جدول wp_options با ۲.۴ مگابایت داده autoload شده در هر درخواست PHP. با پاک‌سازی این لایه، TTFB به ۳۵۰ میلی‌ثانیه برگشت. آن تجربه، دلیل نوشتن این مقاله است.

چرا دیتابیس وردپرس می‌تواند گلوگاه جدی شود؟

دیتابیس وردپرس، پایگاه داده MySQL است که تمام محتوای سایت را نگه می‌دارد: نوشته‌ها، برگه‌ها، کاربران، تنظیمات، سفارش‌های ووکامرس و متادیتا. برخلاف هاست یا قالب، دیتابیس در نگاه اول دیده نمی‌شود اما گلوگاه‌های آن از هر جای دیگری سریع‌تر در TTFB ظاهر می‌شوند. اگر با مفاهیم کلی این لایه آشنا نیستید، مطلب تأثیر دیتابیس بر سرعت سایت نقطه شروع کاملی است.

دیتابیس وردپرس سه دلیل اصلی کندی می‌گیرد. اول، انباشت داده غیرضروری. هر افزونه‌ای که نصب می‌شود، در دیتابیس ردیف‌هایی باقی می‌گذارد و اگر افزونه حذف شود، بخشی از این داده‌ها همچنان باقی می‌مانند. دوم، کوئری‌های بد نوشته‌شده. بخشی از افزونه‌ها در هر بازدید، ده‌ها کوئری می‌زنند که نیمی از آن‌ها بلااستفاده است. سوم، نبود ایندکس مناسب در جداول پرترافیک. این لایه در سایت‌های فروشگاهی، مسئله جدی است.

در سطح معماری، دیتابیس وردپرس در سه لایه کار می‌کند. لایه اول، Cache Layer که در همان MySQL یا در سرویس جداگانه اجرا می‌شود. لایه دوم، SQL Parser و Optimizer که کوئری را پردازش می‌کند. لایه سوم، Storage Engine (معمولاً InnoDB) که داده را روی دیسک مدیریت می‌کند. تحلیل عمیق این لایه‌ها و تفاوت‌های InnoDB و MyISAM در تفاوت InnoDB و MyISAM آمده است.

دیتابیس مثل یک انبار است؛ اگر مرتب پاک‌سازی نشود، پیدا کردن یک قلم کالا ساعت‌ها طول می‌کشد.

معماری دیتابیس وردپرس در یک نگاه

دیتابیس وردپرس از حدود دوازده جدول پیش‌فرض تشکیل شده. مهم‌ترین این جدول‌ها از منظر بهینه‌سازی، پنج مورد هستند.

جدول wp_posts. ذخیره نوشته‌ها، برگه‌ها، revisionها، محصولات ووکامرس و هر post type دیگری. این جدول معمولاً سریع‌ترین رشد را دارد.

جدول wp_postmeta. ذخیره متادیتای هر post. هر افزونه می‌تواند کلید متای خودش را اضافه کند و در پروژه‌های چندساله این جدول می‌تواند بزرگ‌ترین جدول دیتابیس شود.

جدول wp_options. ذخیره تنظیمات سایت. این جدول از نظر ساختاری کوچک است اما به‌دلیل autoload، می‌تواند بار سنگینی روی هر درخواست PHP تحمیل کند.

جدول wp_users و wp_usermeta. ذخیره کاربران و متادیتای آن‌ها. در سایت‌های عضویت‌محور، این دو جدول می‌توانند به‌سرعت بزرگ شوند.

جداول ووکامرس. اگر فروشگاه دارید، جداول wp_wc_order_stats، wp_woocommerce_order_items و مشابه آن‌ها نقش جدی در کارایی دارند.

از منظر فنی، جداول InnoDB از B-tree index برای جستجو استفاده می‌کنند. تفاوت یک کوئری با ایندکس مناسب و بدون ایندکس، می‌تواند از چند میلی‌ثانیه به چند ثانیه برسد. این لایه، شایع‌ترین دلیل کندی دیتابیس در پروژه‌های بزرگ است.

تحلیل wp_options و مسئله autoload

جدول wp_options، پرتکرارترین گلوگاه پنهان دیتابیس وردپرس است. این جدول دو نوع داده را ذخیره می‌کند. اول، گزینه‌های سیستمی مثل آدرس سایت، عنوان، افزونه‌های فعال و مشابه آن‌ها. دوم، گزینه‌هایی که افزونه‌ها و قالب‌ها اضافه می‌کنند.

مفهوم کلیدی در این جدول، ستون autoload است. وقتی این ستون روی yes باشد، وردپرس آن گزینه را در هر درخواست PHP به‌طور خودکار بارگذاری می‌کند. این طراحی، سرعت را در سایت‌های کوچک بالا می‌برد اما در سایت‌های چندساله، به فاجعه تبدیل می‌شود.

در یکی از پروژه‌ها، متوجه شدم یک افزونه قدیمی، ۲.۴ مگابایت داده autoload در جدول options ذخیره کرده بود. این یعنی هر بازدید سایت، ۲.۴ مگابایت داده اضافه در هر درخواست PHP خوانده می‌شد. پاک‌سازی این داده، TTFB را از ۱۲۰۰ میلی‌ثانیه به ۳۵۰ میلی‌ثانیه رساند. راهنمای عملی این لایه در مطلب تأثیر دیتابیس بر سرعت سایت آمده است.

روش بررسی این لایه با یک کوئری ساده در phpMyAdmin یا از طریق خط فرمان MySQL انجام می‌شود:

SELECT COUNT(*) AS total,
       SUM(LENGTH(option_value)) AS total_size
FROM wp_options
WHERE autoload = 'yes';

اگر خروجی این کوئری بیشتر از ۱ مگابایت بود، نیاز به بازبینی جدی دارد. سه راهکار عملی برای کاهش این لایه. اول، حذف ردیف‌های مربوط به افزونه‌های غیرفعال. دوم، تغییر autoload گزینه‌های بزرگ و کم‌استفاده به no. سوم، بررسی اینکه آیا یک افزونه بزرگ، داده‌هایش را به‌جای autoload در جدول جداگانه ذخیره نمی‌کند.

پاک‌سازی wp_postmeta و متادیتای یتیم

جدول wp_postmeta، ذخیره‌کننده متادیتای هر نوشته، برگه و محصول است. هر افزونه‌ای که نصب می‌شود، ممکن است کلید متای اختصاصی خودش را به این جدول اضافه کند. پس از حذف افزونه، این متادیتا معمولاً در جدول باقی می‌ماند و به آن متادیتای یتیم یا Orphan Meta گفته می‌شود.

متادیتای یتیم دو مشکل جدی ایجاد می‌کند. اول، حجم دیتابیس را بالا می‌برد. دوم، کوئری‌های عمومی دیتابیس را کند می‌کند چون MySQL باید روی جدول بزرگ‌تر جستجو کند.

روش بررسی این لایه با یک کوئری ساده است که متادیتا بدون post مرتبط را نمایش می‌دهد:

SELECT pm.meta_id, pm.post_id, pm.meta_key
FROM wp_postmeta pm
LEFT JOIN wp_posts p ON p.ID = pm.post_id
WHERE p.ID IS NULL
LIMIT 100;

اگر خروجی این کوئری بزرگ بود، پاک‌سازی متادیتای یتیم می‌تواند حجم دیتابیس را به‌طور محسوس کاهش دهد. یکی از ابزارهای مناسب این کار، افزونه WP-Sweep است که هم پاک‌سازی امن انجام می‌دهد و هم گزارش دقیق ارائه می‌دهد. رویکرد امن در این لایه، ابتدا گرفتن بکاپ کامل و سپس پاک‌سازی در فازهای کوچک است.

نکته تکمیلی درباره postmeta: در فروشگاه‌های ووکامرس، تعداد ردیف‌های این جدول می‌تواند به میلیون‌ها برسد. اگر روی هاست اشتراکی هستید و فروشگاه دارید، پاک‌سازی دوره‌ای این لایه ضروری است. تحلیل عمیق این لایه در مطلب بهینه‌سازی دیتابیس ووکامرس آمده است.

مدیریت post revision و transients

دو نوع داده که به‌سرعت در دیتابیس وردپرس انباشته می‌شوند و در نگاه اول دیده نمی‌شوند.

Post Revisions. وردپرس به‌طور پیش‌فرض هر بار ذخیره نوشته، یک نسخه کامل از آن را به‌عنوان revision ذخیره می‌کند. اگر یک نوشته را ۵۰ بار ویرایش کنید، ۵۰ نسخه ذخیره می‌شود. در سایت‌های پرمحتوا، تعداد revisionها می‌تواند به صدها هزار برسد.

دو راهکار برای مدیریت این لایه. اول، محدودسازی تعداد revisionها از طریق فایل wp-config.php با تنظیم ثابت WP_POST_REVISIONS روی یک عدد مناسب مثل ۵. دوم، پاک‌سازی دوره‌ای revisionها با افزونه‌هایی مثل WP-Sweep.

Transients. Transients داده‌های موقتی هستند که افزونه‌ها برای کش کردن اطلاعات استفاده می‌کنند. این داده‌ها در همان جدول wp_options با پیشوند _transient_ ذخیره می‌شوند. مشکل اصلی این است که بعضی افزونه‌ها Transients را با زمان انقضای طولانی ذخیره می‌کنند و پس از حذف افزونه، این داده‌ها برای همیشه باقی می‌مانند.

روش بررسی این لایه با کوئری زیر انجام می‌شود:

SELECT COUNT(*) AS total,
       SUM(LENGTH(option_value)) AS total_size
FROM wp_options
WHERE option_name LIKE '_transient_%';

اگر خروجی این کوئری بزرگ بود، پاک‌سازی Transients منقضی‌شده می‌تواند حجم دیتابیس را کاهش دهد. در مفاهیم کش وردپرس که در بهترین افزونه‌های کش وردپرس آمده، این لایه به‌عنوان بخشی از استراتژی کش دیده می‌شود.

ایندکس‌گذاری: کجا و چگونه؟

ایندکس‌گذاری، لایه‌ای است که در پروژه‌های حرفه‌ای تفاوت جدی ایجاد می‌کند اما در سایت‌های کوچک اهمیت کمتری دارد. وردپرس در نصب پیش‌فرض، ایندکس‌های پایه را روی ستون‌های کلیدی مثل ID، post_name و meta_key اضافه می‌کند.

در پروژه‌های خاص، ایندکس‌گذاری اضافه می‌تواند کارایی را چند برابر کند. سه سناریو که در تجربه‌ام ایندکس اضافه کمک کرده است.

سناریو اول، ووکامرس با فیلترهای پیچیده. اگر فروشگاه شما فیلترهای متعدد دارد و کوئری‌های ترکیبی روی wp_postmeta می‌زند، اضافه کردن ایندکس ترکیبی روی (meta_key, meta_value) می‌تواند تفاوت جدی ایجاد کند.

سناریو دوم، سئوی فروشگاهی. کوئری‌های مربوط به محصولات موجود یا قیمت‌های خاص، با ایندکس مناسب سریع‌تر اجرا می‌شوند.

سناریو سوم، سایت‌های عضویت‌محور. کوئری‌های مربوط به usermeta در سایت‌های با کاربران زیاد، با ایندکس ترکیبی مناسب کارایی بالاتری دارند.

نکته مهم: ایندکس‌گذاری اضافه، هزینه هم دارد. ایندکس‌های زیاد، سرعت Insert و Update را کاهش می‌دهند. توصیه من: فقط روی کوئری‌های شناسایی‌شده با EXPLAIN، ایندکس اضافه کنید. تحلیل کوئری‌های سنگین در مطلب بهینه‌سازی کوئری‌های MySQL آمده است.

کش آبجکت با Redis یا Memcached

کش آبجکت یا Object Cache، یکی از قوی‌ترین ابزارهای بهینه‌سازی دیتابیس وردپرس است. برخلاف کش صفحه که HTML نهایی را کش می‌کند، کش آبجکت نتایج کوئری‌های دیتابیس را در حافظه RAM نگه می‌دارد.

وردپرس به‌طور پیش‌فرض یک کش آبجکت سبک در حافظه PHP دارد که فقط در همان درخواست کار می‌کند. اما برای کش پایدار بین درخواست‌ها، نیاز به Redis یا Memcached دارید.

سه مزیت اصلی کش آبجکت. اول، کاهش تعداد کوئری‌های دیتابیس. در سایت‌های پربازدید، این کاهش می‌تواند به ۷۰ درصد برسد. دوم، سرعت پاسخ‌گویی سریع‌تر به‌دلیل خواندن از RAM. سوم، کاهش بار روی MySQL در زمان اوج.

پیاده‌سازی این لایه سه شرط دارد. اول، سرور Redis یا Memcached نصب شده. دوم، افزونه Object Cache مثل Redis Object Cache در وردپرس. سوم، فایل object-cache.php در پوشه wp-content که وردپرس از آن استفاده کند.

تجربه شخصی من: در یکی از پروژه‌های فروشگاهی، افزودن Redis Object Cache زمان لود صفحه اصلی را از ۱.۸ ثانیه به ۰.۹ ثانیه کاهش داد. این لایه، در سایت‌های با کوئری‌های سنگین، تفاوت جدی ایجاد می‌کند. مبحث مرتبط با زیرساخت سرور در تأثیر هاست بر سرعت سایت آمده است.

عیب‌یابی کوئری‌های سنگین

روش سیستماتیک عیب‌یابی کوئری‌های سنگین، تفاوت بین بهینه‌سازی حدسی و بهینه‌سازی علمی است. چهار گام اصلی در این لایه.

گام اول، شناسایی کوئری‌های کند. دو ابزار اصلی وجود دارد. اول، افزونه Query Monitor که کوئری‌های هر صفحه را نمایش می‌دهد. دوم، لاگ Slow Query در MySQL که کوئری‌های بیش از زمان مشخص را ذخیره می‌کند.

گام دوم، تحلیل کوئری با EXPLAIN. دستور EXPLAIN در MySQL نشان می‌دهد که چطور کوئری اجرا می‌شود. اگر در خروجی این دستور، نوع jOIN روی ALL یا Index روی NULL باشد، نشانه نبود ایندکس مناسب است.

EXPLAIN SELECT * FROM wp_postmeta
WHERE meta_key = 'price' AND meta_value > '1000';

گام سوم، شناسایی منبع کوئری. کوئری سنگین می‌تواند از قالب، افزونه یا هسته وردپرس بیاید. با Query Monitor می‌توانید ببینید هر کوئری از کدام فایل PHP آمده است.

گام چهارم، تصمیم‌گیری بین چند راه‌حل. اگر کوئری از یک افزونه خاص می‌آید، می‌توانید افزونه را با جایگزین بهتر عوض کنید یا کوئری را با فیلتر وردپرس اصلاح کنید. اگر از قالب می‌آید، باید قالب را اصلاح کنید. اگر از هسته وردپرس می‌آید، معمولاً مسئله در پیکربندی سایت است، نه در خود هسته. تحلیل این لایه در تست و دیباگ پروژه‌های وردپرس آمده است.

کارهایی که نباید بکنید

پنج کار که در بهینه‌سازی دیتابیس وردپرس به‌عنوان قاعده پایه از آن‌ها پرهیز می‌کنم.

اول، بهینه‌سازی بدون بکاپ. هر تغییری در دیتابیس، پتانسیل از بین بردن داده را دارد. همیشه بکاپ کامل و تست‌شده داشته باشید. این لایه در بکاپ دیتابیس وردپرس به‌طور عمیق آمده است.

دوم، حذف جداول بدون بررسی. بعضی افزونه‌ها جداول اختصاصی دارند. اگر جدولی را ناشناس حذف کنید، ممکن است داده‌های حیاتی از دست برود.

سوم، بهینه‌سازی جداول در ساعت اوج. عملیات OPTIMIZE TABLE روی جداول بزرگ، می‌تواند ساعت‌ها طول بکشد و در طول اجرا، سایت کندی جدی داشته باشد. این عملیات باید در ساعات کم‌ترافیک انجام شود.

چهارم، استفاده از افزونه‌های بهینه‌سازی خودکار. بخشی از افزونه‌ها ادعای بهینه‌سازی جادویی دارند اما در عمل می‌توانند داده‌های مهم را حذف کنند. فقط ابزارهای شناخته‌شده و مورد اعتماد را استفاده کنید.

پنجم، نادیده گرفتن بکاپ قبل و بعد از بهینه‌سازی. حتی اگر بهینه‌سازی موفق باشد، بهتر است نسخه قبل و بعد از بهینه‌سازی را داشته باشید تا در صورت بروز مشکل، بازگشت ممکن باشد.

در بهینه‌سازی دیتابیس، محافظه‌کاری بیشتر از جسارت به شما پاداش می‌دهد.

پرسش‌های پرتکرار درباره بهینه‌سازی دیتابیس وردپرس

چطور بفهمم دیتابیس وردپرس کند است؟ با بررسی TTFB روی همه صفحات. اگر TTFB روی همه صفحات بالای ۸۰۰ میلی‌ثانیه است و قالب و هاست خوب هستند، دیتابیس گلوگاه است.

آیا بهینه‌سازی دیتابیس برای همه سایت‌ها لازم است؟ برای سایت‌های کوچک با ترافیک پایین، بهینه‌سازی پیشرفته ضروری نیست. برای سایت‌های متوسط و بزرگ، تفاوت جدی ایجاد می‌کند.

چطور بکاپ امن از دیتابیس بگیرم؟ با افزونه‌های بکاپ مثل UpdraftPlus یا از phpMyAdmin. راهنمای کامل در مطلب بکاپ دیتابیس آمده است.

آیا استفاده از Redis ضروری است؟ برای سایت‌های پربازدید، بله. برای سایت‌های کوچک، می‌تواند اضافه‌کاری باشد.

چطور متادیتای یتیم را حذف کنم؟ با ابزارهایی مثل WP-Sweep و بکاپ کامل قبل از اجرا.

آیا حذف revisionها روی سایت اثر می‌گذارد؟ فقط امکان بازگشت به نسخه‌های قدیمی نوشته‌ها را از بین می‌برد. اگر این قابلیت برایتان مهم نیست، پاک‌سازی بی‌خطر است.

آیا ایندکس‌گذاری اضافه می‌تواند مشکل ایجاد کند؟ بله، ایندکس‌های زیاد می‌توانند سرعت Insert و Update را کاهش دهند. فقط با تحلیل EXPLAIN اضافه کنید.

چند وقت یک بار دیتابیس را بهینه کنیم؟ برای سایت‌های متوسط، هر سه ماه. برای سایت‌های پربازدید، هر ماه.

جمع‌بندی و توصیه عملی

بهینه‌سازی دیتابیس وردپرس، ترکیبی از تحلیل عددی، پاک‌سازی محافظه‌کارانه و درک معماری MySQL است. هر تصمیم بدون عدد، حدس است. هر پاک‌سازی بدون بکاپ، قمار.

اگر سایت شما کوچک است، ابتدا با جدول wp_options و autoload شروع کنید. این لایه، شایع‌ترین گلوگاه پنهان است. اگر سایت شما متوسط است، پاک‌سازی postmeta یتیم و Transients را جدی بگیرید. اگر سایت شما فروشگاهی است، بهینه‌سازی جداول ووکامرس و افزودن Redis Object Cache را در برنامه نگهداری بگنجانید.

سه سؤال کلیدی برای شروع. اول، آخرین بکاپ دیتابیس شما چه زمانی بوده؟ اگر بیش از یک هفته، اول همین. دوم، آیا TTFB روی همه صفحات کند است یا فقط بعضی صفحات؟ اگر همه، دیتابیس مشکوک است. سوم، آیا کوئری‌های سایت را با Query Monitor بررسی کرده‌اید؟ اگر نه، اولین کار بررسی این لایه است.

در نهایت، بهینه‌سازی دیتابیس، لایه‌ای نیست که یک بار انجام و رها شود. یک فرآیند دوره‌ای است که در طول عمر سایت ادامه پیدا می‌کند. اگر تجربه‌ای از بهینه‌سازی دیتابیس یا چالشی در این لایه داشته‌اید، خوشحال می‌شوم در دیدگاه‌ها بشنوم. 🗄️