بهینه‌سازی پایگاه داده؛ عیب‌یابی Query، Index و Lock

ساعت ۹ شب است، کمپین فروش شروع شده و صفحه پرداخت فقط برای بعضی کاربران کند می‌شود. CPU سرور پایگاه داده بالا رفته، تیم زیرساخت پیشنهاد RAM بیشتر می‌دهد، توسعه‌دهنده می‌گوید «یک Index اضافه کنیم» و مدیر محصول می‌پرسد چرا همین سایت دیروز سریع بود. اگر بدون شواهد یکی از این نسخه‌ها را اجرا کنید، شاید علامت را موقتاً پنهان کنید؛ اما ممکن است نوشتن سفارش را کندتر، Lock را طولانی‌تر یا هزینه زیرساخت را بیشتر کنید.

بهینه‌سازی پایگاه داده یعنی کوتاه‌کردن زمان و هزینه مسیرهای واقعی کاربر با یک چرخه قابل‌اندازه‌گیری: Query مسئله‌دار را پیدا کنید، Execution Plan و الگوی بار را بخوانید، کم‌ریسک‌ترین فرضیه را آزمایش کنید و نتیجه را در Production بسنجید. این راهنما همین چرخه را برای MySQL، PostgreSQL، اپلیکیشن‌های وب و WordPress/WooCommerce توضیح می‌دهد؛ از Slow Query و EXPLAIN تا Index، N+۱، Lock، Connection Pool، Cache، نگهداری و Rollback.

بهینه‌سازی پایگاه داده چیست و چه چیزی نیست؟

Database Optimization مجموعه‌ای از تصمیم‌ها در سطح Query، Schema، Index، تراکنش، Connection، حافظه، ذخیره‌سازی و معماری اپلیکیشن است که یک نتیجه کسب‌وکاری مشخص را بهتر می‌کند. «بهتر» فقط کم‌شدن میانگین زمان Query نیست؛ ممکن است هدف، کاهش p95 پرداخت، جلوگیری از Timeout موجودی، کم‌کردن هزینه CPU یا پایدارماندن سامانه در ترافیک کمپین باشد.

برداشت ناقصتعریف عملیشاهد موفقیت
هرچه Index بیشتر، بهترکمترین مجموعه Index که Read حیاتی را پوشش دهد و Write را خفه نکندPlan بهتر، latency کمتر و هزینه Write قابل‌قبول
CPU بالا یعنی سرور ضعیف استCPU فقط یک علامت است؛ Query پرتکرار، Plan بد یا ازدحام می‌تواند علت باشدتفکیک DB time، calls، rows و wait
Cache مشکل دیتابیس را حل می‌کندCache برای داده مناسب، با TTL و Invalidation روشنHit ratio مفید بدون Stale یا خطای صحت
پاک‌سازی دیتابیس یعنی Optimizationنگهداری یکی از لایه‌هاست، نه جایگزین Query و Schema درستکاهش Bloat/حجم همراه با اثر واقعی بر Journey
یک عدد برای همه سایت‌ها کافی استSLO متناسب با مسیر، بار، موتور و زیرساختp50/p95/p99 و Error تحت بار نماینده

پایگاه داده بخشی از زنجیره درخواست است. DNS، شبکه، PHP/Node، سرویس بیرونی، Cache و مرورگر نیز زمان می‌گیرند. اگر سهم دیتابیس از درخواست کم باشد، تیون‌کردن آن اثر محسوسی بر کاربر ندارد. برای پیوند نتیجه فنی با تجربه و تبدیل، راهنمای سرعت سایت، UX، سئو و نرخ تبدیل را کنار این مقاله ببینید.

نیت جست‌وجو: کاربر این راهنما چه تصمیمی دارد؟

عبارت‌هایی مثل «بهینه‌سازی پایگاه داده»، «افزایش سرعت دیتابیس»، «Query کند»، «بهینه‌سازی MySQL» و «بهینه‌سازی دیتابیس وردپرس» چند نیاز متفاوت را پنهان می‌کنند. پاسخ خوب باید مرز آن‌ها را روشن کند:

نیازپرسش واقعیخروجی لازم
تشخیصکندی واقعاً از دیتابیس است؟Trace، DB time، Top Query و Wait
توسعهQuery یا ORM را چگونه اصلاح کنم؟Plan قبل/بعد، Index و تست صحت
عملیاتچرا در پیک کند یا ناپایدار می‌شود؟Connection، Lock، I/O، Replica lag و ظرفیت
WordPressآیا Transient و Revision را پاک کنم؟Backup، شناسایی جدول/Plugin و Runbook کم‌ریسک
تصمیم معماریCache، Replica یا NoSQL لازم است؟شواهد بار، Consistency و TCO؛ نه مد فناوری

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

TTFB بالا یا صفحه کند به‌تنهایی اثبات نمی‌کند دیتابیس مقصر است. یک Trace می‌تواند نشان دهد زمان در Query، صف Connection، API پرداخت یا رندر اپلیکیشن مصرف شده است. در مقابل، متوسط مناسب هم ممکن است p99 بسیار بد را پنهان کند.

علامتعلت‌های محتملشاهد بعدی
DB CPU بالاQuery پرتکرار، Sort/Hash بزرگ، Plan بد، Compaction یا VacuumTop SQL بر اساس total time و CPU
Latency بالا و CPU عادیLock، I/O، شبکه، Connection wait یا سرویس ذخیره‌سازیWait events، lock graph، disk latency
فقط در پیک کند استPool اشباع، Hot row، Cache miss storm، Queue buildupهم‌بستگی concurrency با p95 و wait
پس از Deploy کند شدQuery جدید، N+۱، تغییر Cardinality، Migration یا Cache purgeمقایسه release، query fingerprint و Plan
فقط گزارش‌ها کندندScan حجیم، Sort روی دیسک، رقابت OLTP و Analyticstemp I/O، rows scanned و schedule گزارش
Replica عقب می‌ماندWrite burst، Query بلند روی Replica، شبکه یا I/Olag زمانی/بایتی و نرخ WAL/binlog

تریاژ ۱۰دقیقه‌ای در Incident

  1. دامنه اثر را مشخص کنید: کدام Journey، Region، نسخه و بازه زمانی؟ پرداخت کند است یا همه صفحات؟
  2. خط زمانی را قفل کنید: Deploy، Migration، کمپین، Import، Cron یا تغییر شبکه هم‌زمان بوده است؟
  3. چهار Signal را ببینید: latency، traffic، errors و saturation؛ سپس سهم Database span را جدا کنید.
  4. Query fingerprintهای پرهزینه را رتبه‌بندی کنید: بر اساس زمان تجمعی، p95، تعداد فراخوانی و Lock time.
  5. اول مهار، بعد درمان: Feature flag، محدودکردن گزارش سنگین، کاهش concurrency کنترل‌شده یا Rollback؛ DDL عجولانه در اوج Incident اجرا نکنید.

برای طراحی Trace، Metric، Log، SLO و Alert به راهنمای Observability و مانیتورینگ لحظه‌ای مراجعه کنید. پایش بیرونیِ مسیرهای حیاتی نیز در راهنمای مانیتورینگ آپتایم سایت توضیح داده شده است.

Baseline بسازید؛ قبل از تغییر دقیقاً چه چیزی را ثبت کنیم؟

Baseline باید یک بازه نماینده از روز عادی و یک بازه پیک را پوشش دهد. Queryها را بر اساس Fingerprint یا Query ID گروه‌بندی کنید تا تغییر مقادیر پارامتر، یک Query واحد را به هزار ردیف تبدیل نکند. داده حساس را در لاگ Mask کنید.

لایهحداقل معیارچرا مهم است؟
Journeyp50/p95/p99، نرخ خطا، Throughputاثر قابل‌دیدن برای کاربر و کسب‌وکار
Querycalls، total/mean/max time، rowsتفکیک Query نادرِ کند از Query پرتکرار
Planestimated vs actual rows، loops، scan، sortکشف خطای تخمین و کار تکرارشونده
Waitlock، I/O، CPU، connection، networkفهمیدن اینکه زمان دقیقاً کجا منتظر می‌ماند
منابعCPU، memory، disk latency/IOPS، temp spaceتشخیص Saturation و Spill
صحتتعداد/مجموع/نمونه نتیجه و invariantجلوگیری از سریع‌ترشدنِ پاسخ اشتباه

یک فرمول ساده برای اولویت اولیه مفید است:

بار تجمعی Query ≈ تعداد فراخوانی × میانگین زمان اجرا

Query صد میلی‌ثانیه‌ای که در دقیقه ۲۰هزار بار اجرا می‌شود، ممکن است مهم‌تر از گزارش ۱۲ثانیه‌ای باشد که روزی یک بار اجرا می‌شود. اما این فرمول به‌تنهایی کافی نیست؛ مسیر پرداخت، Lock گسترده یا Query دارای ریسک از‌دست‌رفتن داده وزن بیشتری می‌گیرد.

ابزار مشاهده در MySQL و PostgreSQL

در MySQL، Slow Query Log شروع قابل‌فهمی است و Performance Schema امکان تجمیع Statementها را می‌دهد. مستندات رسمی MySQL توضیح می‌دهد که Slow Log بر پایه آستانه long_query_time کار می‌کند و جدول‌های خلاصه Performance Schema معیارهای تجمیعی ارائه می‌کنند: Slow Query Log در MySQL و Statement Summary Tables.

در PostgreSQL، افزونه pg_stat_statements آمار Planning و Execution را بر اساس Query نرمال‌شده جمع می‌کند. فعال‌سازی آن به تنظیمات و معمولاً Restart نیاز دارد؛ سربار و دسترسی به متن Query را نیز باید مدیریت کنید. جزئیات را در مستند رسمی pg_stat_statements ببینید.

-- نمونه رتبه‌بندی در PostgreSQL؛ نام ستون‌ها را با نسخه خود تطبیق دهید
SELECT queryid, calls, total_exec_time, mean_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

نکته امنیتی: متن Query و Bind value ممکن است ایمیل، موبایل یا شناسه سفارش را آشکار کند. Retention، Redaction، RBAC و دسترسی تیم پشتیبانی به Telemetry باید بخشی از طراحی باشد.

چگونه EXPLAIN و Execution Plan را بخوانیم؟

EXPLAIN نقشه‌ای است که Optimizer برای اجرای Query انتخاب کرده؛ EXPLAIN ANALYZE معمولاً Query را واقعاً اجرا می‌کند و زمان و تعداد ردیف واقعی می‌دهد. در نتیجه روی Queryهای نوشتنی یا Query سنگین Production بدون شناخت رفتار نسخه و موتور اجرا نکنید. ابتدا Replica/Staging با داده نزدیک به واقعیت، سپس اجرای کنترل‌شده و Read-only.

مستندات رسمی EXPLAIN در MySQL ۸.۴ و Using EXPLAIN در PostgreSQL تفاوت Estimate و Actual را شرح می‌دهند.

نشانه در Planپرسش تشخیصیفرضیه اصلاح
Actual rows بسیار بیشتر از EstimateStatistics کهنه یا توزیع داده نامتوازن است؟ANALYZE، آمار گسترده‌تر یا بازنویسی Predicate
Full/Seq Scanجدول کوچک است یا Selectivity پایین؟Scan ممکن است درست باشد؛ Index را کورکورانه تحمیل نکنید
Loops زیادNested Loop یا N+۱ کار را تکرار می‌کند؟Join/Batch، Index سمت inner یا کاهش ردیف ورودی
Sort/Hash روی دیسکحجم میانی و حافظه کافی است؟کاهش داده، Index مرتب، تنظیم حافظه پس از اندازه‌گیری
Rows removed by filter زیادفیلتر دیر اعمال می‌شود؟Predicate قابل Index، Index مناسب یا بازنویسی
زمان کم هر Loop اما Loops بسیارهزینه تجمعی کجا ضرب شده؟Batching و حذف Round-trip

دیدن Table Scan به‌خودی‌خود خطا نیست. برای جدول کوچک یا Queryای که بخش بزرگی از جدول را می‌خواهد، Scan می‌تواند از رفت‌وبرگشت تصادفی Index ارزان‌تر باشد. به Actual time، rows، loops و Buffer/I/O با هم نگاه کنید.

نمونه فروشگاه ایرانی: لیست سفارش‌ها

SELECT id, total_amount, status, created_at
FROM orders
WHERE merchant_id = ?
  AND status = ?
  AND created_at >= ?
ORDER BY created_at DESC, id DESC
LIMIT 50;

اگر این Query مسیر اصلی پنل فروشنده است، یک Index ترکیبی مانند (merchant_id, status, created_at, id) می‌تواند فرضیه خوبی باشد؛ اما فقط پس از دیدن Plan، Cardinality و الگوی واقعی فیلتر. اگر اغلب Status حذف می‌شود یا چند Status انتخاب می‌شود، ترتیب مطلوب ممکن است فرق کند. Plan قبل و بعد، هزینه Write و اندازه Index را ثبت کنید.

بهینه‌سازی Query؛ ابتدا کار غیرضروری را حذف کنید

سریع‌ترین عملیات، عملیاتی است که اصلاً انجام نمی‌شود. پیش از افزایش منابع، ببینید آیا Query باید اجرا شود، آیا همه ردیف‌ها و ستون‌ها لازم‌اند و آیا نتیجه را می‌توان در یک درخواست Batch کرد.

الگوی مسئله‌دارچرا هزینه‌ساز است؟اصلاح محتمل
SELECT *انتقال و Decode ستون‌های غیرلازم؛ دشوارشدن Coveringفقط ستون‌های قرارداد پاسخ
LIKE '%عبارت%'B-tree معمولاً Prefix نامعلوم را خوب استفاده نمی‌کندFull-text/Search engine یا نیاز دقیق‌تر
تابع روی ستون فیلترممکن است Predicate قابل Index نباشدRange صریح یا Functional Index متناسب با موتور
OFFSET 500000ردشدن تعداد زیادی ردیف پیش از پاسخKeyset/Cursor pagination
Query داخل LoopRound-trip و اجرای تکراریBatch، eager loading یا Join کنترل‌شده
گزارش Aggregate روی OLTPرقابت CPU/I/O با تراکنش کاربرRead model، replica یا pipeline تحلیلی

Pagination با Cursor به‌جای Offset عمیق

SELECT id, total_amount, status, created_at
FROM orders
WHERE merchant_id = ?
  AND (created_at, id) < (?, ?)
ORDER BY created_at DESC, id DESC
LIMIT 50;

Cursor باید ترتیب قطعی داشته باشد؛ افزودن id از ابهام بین رکوردهای هم‌زمان جلوگیری می‌کند. رفتار درج جدید، حذف و حرکت بین صفحات را با تست Contract بررسی کنید.

طراحی Index؛ ترتیب ستون‌ها از نام ستون مهم‌تر است

Index یک کپی مرتب و هزینه‌دار از بخشی از داده است. سرعت Read را می‌خرد، اما فضا، Cache و Write amplification مصرف می‌کند. مستندات Optimization and Indexes در MySQL نیز هشدار می‌دهد Indexهای غیرضروری فضای ذخیره‌سازی و هزینه Insert/Update/Delete را افزایش می‌دهند.

پرسشآنچه باید ثبت شود
کدام Query مالک این Index است؟Fingerprint، Journey و فراوانی
ترتیب ستون‌ها چرا این است؟Equality، Range، Sort و Selectivity واقعی
آیا Index مشابه داریم؟Prefixهای هم‌پوشان و Usage
هزینه نوشتن چیست؟Latency و Throughput قبل/بعد روی INSERT/UPDATE/DELETE
ساخت Index چه Lockی می‌گیرد؟نسخه موتور، الگوریتم DDL، زمان و فضای موقت
Rollback چیست؟حذف امن، بازگشت Query و معیار توقف

قاعده «ستون با بیشترین Selectivity همیشه اول» عمومی و کافی نیست. ترتیب Index باید با Prefix قابل‌استفاده، شروط Equality/Range، Sort، نوع موتور و Queryهای واقعی سنجیده شود. Index پوشاننده نیز ممکن است Read را بهتر کند، ولی با ستون‌های اضافی بزرگ‌تر و گران‌تر می‌شود.

چگونه Index اضافی را حذف کنیم؟

  1. Usage را در بازه‌ای شامل چرخه‌های ماهانه، گزارش‌ها و کمپین ثبت کنید.
  2. Indexهای Duplicate یا Prefix هم‌پوشان را کاندید کنید، نه اینکه فوراً حذف کنید.
  3. وابستگی Constraint، Foreign Key و Queryهای نادر اما حیاتی را بررسی کنید.
  4. در صورت پشتیبانی، با سازوکار Invisible/Hypothetical یا محیط آزمایش اثر را بسنجید.
  5. در Maintenance window با Backup، مانیتورینگ و Rollback تغییر دهید.

مشکل N+۱ در ORM؛ یک صفحه، صدها Query

N+۱ وقتی رخ می‌دهد که یک Query فهرست N شیء را می‌گیرد و سپس برای هر شیء Query دیگری اجرا می‌شود. هر Query شاید سریع باشد، اما مجموع Round-trip و Connection time صفحه را کند می‌کند. این مشکل در ORMها، Resolverهای GraphQL، قالب‌های WordPress و Serializers رایج است.

روشمزیتریسک
Eager loadingکاهش تعداد QueryJoin انفجاری یا داده اضافه
Batch loaderتجمیع شناسه‌ها در یک Queryپیچیدگی Cache و ترتیب پاسخ
Joinیک Round-tripتکرار ردیف و Cardinality بالا
Precomputed read modelRead سریع و سادهتأخیر همگام‌سازی و Consistency

عدد Query به‌ازای هر Request را در تست Integration ثبت کنید. یک Assertion ساده می‌تواند بازگشت N+۱ را پیش از Production متوقف کند. برای APIهای چندلایه، راهنمای طراحی، امنیت و قابلیت اطمینان API به قرارداد Timeout، Idempotency و Observability کمک می‌کند.

Schema و نوع داده؛ صحت قبل از سرعت

نرمال‌سازی و Denormalization دو مذهب رقیب نیستند. مدل تراکنشی معمولاً از مرزهای روشن و یک منبع حقیقت سود می‌برد؛ Read model یا ستون مشتق‌شده وقتی ارزش دارد که هزینه Join/Aggregation اثبات شده و سازوکار همگام‌سازی و Reconciliation مشخص باشد.

  • نوع داده متناسب: پول را با نوع دقیق و واحد روشن ذخیره کنید؛ Float برای مبلغ تراکنش مناسب نیست.
  • زمان: Instant را با Time zone قراردادشده ذخیره و تقویم شمسی را در لایه نمایش مدیریت کنید؛ مرز روز تهران را صریح تست کنید.
  • شناسه: نوع کلید بر اندازه Index، Locality و توزیع اثر دارد؛ انتخاب UUID یا عدد ترتیبی باید آگاهانه باشد.
  • قیدها: Unique، Foreign Key و Check فقط هزینه نیستند؛ بخشی از دفاع صحت داده‌اند.
  • JSON: برای انعطاف مفید است، اما فیلتر پرتکرار و رابطه حیاتی را بدون برنامه Index و Validation در Blob پنهان نکنید.

در سامانه فروش ایرانی، Constraint جلوگیری از ثبت دوباره مرجع پرداخت یا Reservation بیش‌ازموجودی ممکن است ارزشمندتر از چند میلی‌ثانیه کاهش Write باشد. Performance هیچ‌وقت مجوز شکستن Invariant نیست.

تراکنش و Lock؛ کندی‌ای که با Index تنها حل نمی‌شود

تراکنش بلند، ترتیب متفاوت قفل‌گیری و تماس با سرویس بیرونی داخل Transaction می‌تواند زنجیره انتظار بسازد. Query قربانی شاید ساده باشد؛ مشکل، Query یا Job دیگری است که Lock را نگه داشته.

الگوپیامدراه اصلاح
درخواست درگاه داخل TransactionLock تا پایان شبکه خارجی باز می‌ماندمرز تراکنش کوتاه، state machine و Idempotency
به‌روزرسانی منابع با ترتیب متفاوتDeadlockترتیب قفل‌گیری یکسان و Retry محدود
Batch عظیمLock/WAL/binlog و Lag زیادChunk کوچک با checkpoint
تراکنش idleنگهداری Snapshot/Lock و اختلال نگهداریTimeout و مدیریت lifecycle Connection
Hot row شمارندهصف سریالی روی یک ردیفSharding منطقی، تجمیع یا طراحی رویداد

Deadlock را فقط با Retry بی‌نهایت پنهان نکنید. Retry باید فقط برای خطای موقت، با سقف تلاش، Backoff/Jitter و عملیات Idempotent باشد. Root cause را از Lock graph و ترتیب عملیات استخراج کنید.

Connection Pool؛ بیشتر همیشه بهتر نیست

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

حداکثر اتصال بالقوه = تعداد Instance × Pool هر Instance + Jobها + ابزارهای عملیاتی + حاشیه

معیارتفسیراقدام محتمل
Pool wait بالا، DB آرامPool کوچک یا Connection leakTrace checkout، timeout و leak detection
Connection زیاد، Throughput ثابتازدحام و Context switchingکاهش concurrency و صف کنترل‌شده
Idle زیادPool بیش‌ازنیاز یا autoscaling ناسازگارmin/max و idle timeout
Timeout در DeployConnection stormWarm-up پلکانی و startup jitter

Pool را با تست بار واقعی تیون کنید. صف کوتاه و قابل‌مشاهده اغلب از ورود concurrency نامحدود به دیتابیس سالم‌تر است.

حافظه، Buffer و Storage؛ تنظیمات را از اینترنت کپی نکنید

Buffer pool و Shared buffers می‌توانند Read فیزیکی را کم کنند، اما حافظه کل باید با Connectionها، Sort/Hash، Cache سیستم‌عامل، Replica و سایر Processها جمع شود. درصد ثابت مثل «همیشه ۸۰٪ RAM» بدون شناخت محیط، Container و Managed Database نسخه قابل‌اعتماد نیست.

Signalفرضیهآزمایش
Read I/O و latency بالاWorking set در حافظه جا نمی‌شود یا Query Scan داردPlan، cache miss، storage latency
Temp file زیادSort/Hash از حافظه عبور می‌کندQuery-specific temp و حجم میانی
Swap/OOMجمع تخصیص‌ها بیشتر از RAMPeak memory زیر concurrency واقعی
fsync latencyمحدودیت storage یا burst creditMetric سرویس ذخیره‌سازی و Write latency

SSD بدیهی به نظر می‌رسد، اما نوع Volume، IOPS تضمین‌شده، Throughput، latency دم‌بلند، Queue depth و Backup هم مهم‌اند. ارتقای Storage بدون اصلاح Scan ممکن است فقط هزینه خطا را بیشتر کند.

Cache؛ کاهش بار با قرارداد Freshness و Invalidation

Cache برای داده‌ای مفید است که زیاد خوانده، کمتر تغییر و در برابر کمی کهنگی تحمل‌پذیر باشد. قیمت لحظه‌ای، موجودی قابل‌فروش و وضعیت پرداخت قرارداد متفاوتی از فهرست دسته‌بندی دارند. قبل از Redis، این پنج پرسش را پاسخ دهید: مالک داده کیست؟ Cache key چیست؟ TTL چقدر است؟ چه رویدادی Invalidate می‌کند؟ در Cache miss یا خرابی چه می‌شود؟

مستندات رسمی Redis توضیح می‌دهد که maxmemory و Eviction policy باید متناسب با نقش Cache تنظیم شوند؛ بدون محدودیت و سیاست روشن، حافظه یا Write می‌تواند مسئله‌ساز شود: Redis key eviction.

الگومناسب برایریسک اصلی
Cache-asideRead پرتکرار با کنترل اپلیکیشنStale و Stampede هنگام Miss
Write-throughنیاز به Cache گرم پس از WriteLatency و پیچیدگی خطا
Event invalidationتغییراتی با رویداد دامنه روشنگم‌شدن Event و نیاز به Reconciliation
Short TTLداده کم‌ریسک و تغییرپذیرMiss زیاد یا کهنگی کوتاه

برای معماری کامل Browser/CDN/Application/Redis، مقاله کش چیست و چگونه طراحی می‌شود را بخوانید. اگر سایت WordPress است، Runbook تخصصی کش WordPress و رفع نسخه قدیمی مرز Page Cache و Object Cache را توضیح می‌دهد.

نگهداری MySQL و PostgreSQL؛ دستور یکسان ندارند

عبارت کلی «هر هفته دیتابیس را Optimize کن» می‌تواند گمراه‌کننده باشد. موتور، Storage engine، الگوی Update/Delete و نسخه تعیین می‌کنند چه نگهداری لازم است. Job نگهداری نیز باید SLO، مدت، Lock، فضای آزاد و اثر Replica داشته باشد.

MySQL/InnoDB

  • Slow Log و Performance Schema را برای رتبه‌بندی Queryها به کار ببرید، نه ثبت بی‌هدف همه چیز با Retention نامحدود.
  • Statistics و Plan را پس از تغییر بزرگ داده یا Index بررسی کنید؛ ANALYZE TABLE را با شناخت اثر نسخه اجرا کنید.
  • OPTIMIZE TABLE نسخه عمومی برای هر کندی نیست؛ ممکن است Table rebuild، فضا و زمان قابل‌توجه بخواهد.
  • رشد Undo/Redo، Buffer pool، History و Replication lag را همراه با بار Write ببینید.

PostgreSQL

در PostgreSQL، MVCC باعث می‌شود نسخه‌های مرده ردیف‌ها تا Vacuum باقی بمانند. Autovacuum بخشی حیاتی از سلامت است؛ خاموش‌کردن آن برای رفع بار کوتاه‌مدت معمولاً بدهی خطرناک می‌سازد. مستندات رسمی تفاوت VACUUM عادی و VACUUM FULL را روشن می‌کند: نوع FULL جدول را بازنویسی می‌کند، کندتر است و Lock انحصاری می‌خواهد. منبع: VACUUM در PostgreSQL.

  • Dead tuples، زمان آخر Vacuum/Analyze و جدول‌های دارای Bloat را پایش کنید.
  • Long transaction و Replication slot رهاشده می‌تواند پاک‌سازی را عقب بیندازد.
  • تنظیم Autovacuum را برای جدول پرWrite به‌صورت Table-specific و مبتنی بر شواهد انجام دهید.
  • VACUUM FULL را کار روزمره تلقی نکنید؛ قفل و فضای لازم را در Runbook بیاورید.
کار نگهداریشرط اجراGuardrail
Update statisticsتغییر معنادار توزیع یا Plan نامناسبPlan قبل/بعد و بار محدود
پاک‌سازی/ArchiveRetention مصوب و داده قابل‌حذفLegal hold، Backup و حذف Chunked
Reindex/RebuildBloat/Corruption/شاهد موتورمحورLock، فضای موقت، Replica و Rollback
Upgrade موتورنسخه پشتیبانی‌شده و نیاز امنیت/کاراییCompatibility، restore rehearsal و canary

بهینه‌سازی دیتابیس WordPress و WooCommerce

در WordPress، بزرگ‌شدن جدول لزوماً علت کندی نیست. ابتدا Query کند و Plugin/Route مالک آن را پیدا کنید. جدول‌های wp_options، wp_postmeta، Scheduler/Queue، Session، Log و Revision می‌توانند رشد کنند؛ اما حذف مستقیم ردیف‌ها بدون شناخت مالکیت Plugin ممکن است داده یا قابلیت بازیابی را از بین ببرد.

ناحیهریسک رایجاقدام امن
wp_optionsautoload حجیم یا گزینه یتیماندازه و مالک Plugin را بسنجید؛ حذف فقط با Backup
Post metaQuery روی meta_value و Join زیادQuery Monitor/APM، بازطراحی داده در نیاز پرتکرار
Revisionsرشد ذخیره‌سازیRetention آینده را تنظیم؛ حذف گذشته با تأیید محتوا
TransientsExpired یا Invalidation ناقصمالکیت و Object Cache را بفهمید؛ پاک‌سازی کنترل‌شده
Action SchedulerJob عقب‌افتاده یا Log انباشتهعلت Failure، Retention و Queue health
WooCommerce sessionsرشد و پاک‌سازی ناقصنسخه/تنظیمات رسمی و تست سبد/پرداخت

دستور wp db optimize در WP-CLI از ابزارهای خود دیتابیس استفاده می‌کند و باید با Backup و شناخت موتور اجرا شود؛ مستندات رسمی: WP-CLI db optimize. محدودکردن Revisionهای آینده نیز در راهنمای wp-config وردپرس مستند شده است.

Runbook امن برای سایت WordPress

  1. Backup بگیرید و Restore را روی محیط جدا واقعاً تمرین کنید.
  2. Slow Query/APM را با Route، Plugin، Theme و Cron مرتبط کنید.
  3. اندازه جدول، رشد روزانه، autoload و Job backlog را ثبت کنید.
  4. یک علت و یک تغییر کوچک انتخاب کنید؛ پاک‌سازی انبوه چند جدول نکنید.
  5. روی Staging با کپی Sanitized داده، صحت Checkout، Login، Search و Admin را تست کنید.
  6. در بازه کم‌ترافیک اجرا و Error، p95، Lock، Disk و Queue را مانیتور کنید.
  7. نتیجه، مالک و تاریخ بازبینی بعدی را ثبت کنید.

ملاحظات سایت ایرانی؛ شبکه، تقویم، پرداخت و زیرساخت

معماری برای کاربران ایران باید فرضیات خود را صریح کند. فاصله شبکه بین App و Database، کیفیت Route، محدودیت سرویس خارجی، تغییر IP، تقویم و ساعت تهران، پیامک و درگاه پرداخت می‌توانند در Trace شبیه «کندی دیتابیس» دیده شوند.

  • هم‌مکانی: App و Primary Database را بدون دلیل در دو شبکه دور قرار ندهید؛ RTT در N+۱ ضرب می‌شود.
  • درگاه پرداخت: تماس شبکه را داخل تراکنش دیتابیس نگه ندارید؛ State و Callback را Idempotent طراحی کنید.
  • موجودی: Cache را منبع حقیقت موجودی قابل‌فروش نکنید مگر قرارداد Consistency و Reservation روشن باشد.
  • تقویم و Time zone: بازه گزارش «امروز» را با مرز تهران و DST تاریخی تست کنید؛ نمایش شمسی را از Instant ذخیره‌شده جدا نگه دارید.
  • داده حساس: شماره موبایل، کد ملی و Reference پرداخت را در Query log و APM Mask کنید.
  • Backup خارج از Failure domain: نسخه بازیابی‌شدنی را جدا از همان حساب/سرور نگه دارید و RPO/RTO را تست کنید.

اگر رشد تیم و سیستم بحث جداسازی دیتابیس یا سرویس‌ها را ایجاد کرده، مقاله مونولیت یا میکروسرویس کمک می‌کند از ساخت Distributed Monolith جلوگیری کنید. Database per Service بدون مرز دامنه، مالکیت داده، Observability و عملیات بالغ معمولاً هزینه را چندبرابر می‌کند.

امنیت پایگاه داده بخشی از Performance Engineering است

Query سریع اما آسیب‌پذیر خروجی قابل‌قبول نیست. Parameterized Query، حداقل دسترسی، تفکیک کاربر Migration از Runtime، TLS، Secret rotation و Audit باید همراه Optimization باقی بمانند. OWASP ساخت Query با String concatenation را ناامن می‌داند و Prepared/Parameterized Query و Least Privilege را توصیه می‌کند: SQL Injection Prevention Cheat Sheet.

میان‌بر خطرناکپیامدجایگزین
حذف Validation برای سرعتداده ناسالم و InjectionValidation مرزی + Parameterization
دادن DBA به اپلیکیشنBlast radius بسیار بزرگRole حداقلی و جدا برای Migration
ثبت Bind value کاملنشت PII در LogFingerprint و Redaction
غیرفعال‌کردن TLSریسک شنود/دستکاریکاهش Round-trip با Pool، نه حذف امنیت

استقرار امن تغییرات دیتابیس در Production

تغییر Query کوچک ممکن است ساده Rollback شود؛ ساخت Index یا Migration جدول بزرگ چنین نیست. قبل از DDL، رفتار دقیق نسخه موتور، Lock level، Disk headroom، Replica lag و زمان اجرا را بررسی کنید.

مرحلهخروجی اجباریمعیار توقف
BaselineWindow، load، p95/p99، calls، planداده نماینده نیست
فرضیهعلت، تغییر، انتظار عددیعلت با شاهد ناسازگار است
آزمایشداده/بار نزدیک Production و تست صحتResult mismatch یا Regression نوشتن
Canaryدرصد محدود Route/User/InstanceError/lock/lag از Guardrail عبور کند
Rolloutگام‌های تدریجی و مالک حاضرSLO یا ظرفیت تهدید شود
بازبینیPlan و معیار ۲۴ ساعت/۷ روز بعدسود پایدار نیست

الگوی Migration سازگار با نسخه‌های هم‌زمان

  1. Expand: ستون/جدول جدید را به‌شکل سازگار اضافه کنید.
  2. Dual-read یا Dual-write کنترل‌شده: فقط اگر لازم است و با Telemetry/مقایسه صحت.
  3. Backfill Chunked: با Rate limit، checkpoint و توقف‌پذیری.
  4. Switch: خواندن را با Feature flag منتقل کنید.
  5. Verify: Count، checksum یا invariantهای کسب‌وکار را تطبیق دهید.
  6. Contract: وابستگی قدیمی را پس از عبور یک بازه امن حذف کنید.

این کار نوعی مدیریت بدهی و ریسک تغییر است؛ راهنمای شناسایی و مدیریت بدهی فنی برای ثبت مالک، بهره و زمان بازپرداخت مفید است.

ماتریس اولویت‌بندی؛ اول کدام مشکل را حل کنیم؟

فهرست Queryها را فقط با Latency مرتب نکنید. یک امتیاز قابل‌توضیح بسازید:

Priority = User impact × Frequency × Risk × Confidence ÷ Effort

کاندیداثراطمینانهزینه/ریسکاولویت
N+۱ در صفحه محصول پرترافیکزیادزیاد؛ Trace روشنکم تا متوسطفوری
Index برای گزارش ماهانهکممتوسطهزینه Write/فضابعدی
Migration کل دیتابیس به NoSQLنامعلومکمبسیار زیادرد تا اثبات
کوتاه‌کردن Transaction پرداختبسیار زیادزیاد؛ Lock graphمتوسطفوری با تست

نقشه ۳۰روزه بهینه‌سازی پایگاه داده

بازهکارتحویل‌دادنی
روز ۱ تا ۳Journey/SLO، Trace و Baselineداشبورد p95/p99 و سهم DB
روز ۴ تا ۷Top Query و Wait analysis۱۰ Fingerprint با مالک و اثر
هفته دومPlan، N+۱، Query/Index آزمایشیBenchmark و تست صحت قبل/بعد
هفته سومLock، Pool، Cache و JobهاGuardrail ظرفیت و Runbook Incident
هفته چهارمCanary، Rollout و مستندسازینتیجه عددی، Rollback و Backlog بعدی

اشتباهات رایج در بهینه‌سازی دیتابیس

  • شروع با ابزار به‌جای سؤال: داشبورد زیاد بدون Journey و SLO تصمیم نمی‌سازد.
  • بهینه‌کردن میانگین: p95/p99 و کاربران پیک نادیده می‌مانند.
  • ساخت Index بر اساس حدس: Write، Disk و Planهای دیگر Regression می‌گیرند.
  • اجرای EXPLAIN ANALYZE بی‌محابا: Query واقعاً اجرا می‌شود و ممکن است سنگین یا نوشتنی باشد.
  • کش‌کردن داده حساس به صحت: موجودی و پرداخت Stale می‌شود.
  • پاک‌سازی مستقیم جدول WordPress: مالکیت Plugin و Recovery نادیده می‌ماند.
  • افزایش بی‌حد Connection: صف را از App به داخل دیتابیس منتقل می‌کند.
  • مهاجرت فناوری به‌عنوان درمان: Query بد در موتور جدید هم Query بد می‌ماند.
  • نداشتن Rollback: تغییر ظاهراً سریع به Incident طولانی تبدیل می‌شود.

چک‌لیست نهایی بهینه‌سازی پایگاه داده

  • Journey حیاتی و SLO آن مشخص است.
  • سهم دیتابیس از latency با Trace یا اندازه‌گیری معتبر جدا شده است.
  • Queryها با Fingerprint و بر اساس total time، p95، calls و risk رتبه‌بندی شده‌اند.
  • Plan قبل و بعد با داده و بار نماینده ثبت شده است.
  • تست صحت نتیجه در کنار Benchmark وجود دارد.
  • هزینه Read، Write، Disk، Lock و Replica برای Index سنجیده شده است.
  • N+۱، Pagination عمیق و Queryهای Loop بررسی شده‌اند.
  • Transaction کوتاه است و تماس خارجی داخل آن انجام نمی‌شود.
  • مجموع Connection همه Instanceها از ظرفیت برنامه‌ریزی‌شده عبور نمی‌کند.
  • Cache key، TTL، Invalidation، Stampede و Failure mode مستند است.
  • Backup قابل‌بازیابی و Restore rehearsal وجود دارد.
  • DDL/Migration دارای Canary، Guardrail، مالک و Rollback است.
  • Query log و APM داده حساس را Mask می‌کنند.
  • نتیجه ۲۴ ساعت و ۷ روز بعد دوباره سنجیده می‌شود.

پرسش‌های متداول

از کجا بفهمیم کندی سایت از پایگاه داده است؟

با Trace درخواست یا APM، زمان Database span را از App، شبکه و سرویس بیرونی جدا کنید. سپس همان بازه را با Top Query، Wait، CPU، I/O و Connection تطبیق دهید. TTFB بالا به‌تنهایی مدرک کافی نیست.

اول Query را اصلاح کنیم یا Index بسازیم؟

Plan و الگوی فراخوانی تعیین می‌کند. حذف N+۱، ستون و ردیف اضافی یا Pagination بد معمولاً پیش از Index ارزش بررسی دارد. اگر Access path مناسب وجود ندارد، Index با معیار قبل/بعد و سنجش هزینه Write بسازید.

آیا EXPLAIN ANALYZE روی Production امن است؟

همیشه نه. این دستور معمولاً Query را واقعاً اجرا می‌کند؛ روی Query سنگین یا نوشتنی می‌تواند داده یا بار را تغییر دهد. رفتار دقیق نسخه موتور را بخوانید، ابتدا Staging/Replica و سپس Read-only کنترل‌شده با Timeout و دسترسی محدود استفاده کنید.

آیا Redis جایگزین بهینه‌سازی دیتابیس است؟

خیر. Redis می‌تواند Read مناسب را از Primary دور کند، اما Query بد، Invalidation ناقص و Stampede را حل نمی‌کند. ابتدا قرارداد صحت و الگوی دسترسی را مشخص کنید؛ سپس Cache را با Hit ratio، stale rate و بار Database بسنجید.

بهینه‌سازی دیتابیس WordPress را هر چند وقت انجام دهیم؟

تقویم ثابت عمومی وجود ندارد. رشد جدول، Slow Query، autoload، Queue backlog و Retention را پایش کنید و فقط با علت روشن اقدام کنید. پیش از حذف Revision، Transient، Session یا جدول Plugin، Backup و تست Restore داشته باشید.

جمع‌بندی

بهینه‌سازی پایگاه داده پروژه خرید RAM یا افزودن Index نیست؛ یک روش تصمیم‌گیری است. مسیر حیاتی را انتخاب کنید، Baseline بسازید، Query و Wait پرهزینه را پیدا کنید، Plan را با Actual data بخوانید، یک فرضیه کم‌ریسک را آزمایش و با Canary منتشر کنید. موفقیت وقتی ثابت می‌شود که Journey کاربر سریع‌تر و پایدارتر شده، صحت داده حفظ شده و هزینه یا ریسک به لایه دیگری منتقل نشده باشد.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *