A slow database makes every page slow, and on shared hosting it also holds PHP requests open until your account runs into its limits. Most slow queries have the same few causes: a missing index, a query that reads far more rows than it returns, or a table that has grown for years without clean-up. This guide shows how to find the slow query, read EXPLAIN, fix it with the right index, and which server settings matter only when you run your own server.
Find the slow query first (Query Monitor on WordPress, your own timing logs, or the slow query log on your own server), then run EXPLAIN on it. If it shows type: ALL or a large rows number, add an index that matches the WHERE and ORDER BY columns. Select only the columns you need and page large results. On shared hosting you tune queries and indexes, not the server; my.cnf settings such as the buffer pool apply only on your own VPS. The old query cache no longer exists in MySQL 8.
1. Where the time actually goes
A query is slow when it examines many rows to return a few. A table with 500,000 orders and no index on customer_id means MySQL reads all 500,000 rows every time someone opens "My orders". The fix is almost never more hardware; it is letting MySQL jump straight to the rows it needs.
Other common causes:
- Sorting without an index (
ORDER BYon an unindexed column), which shows up asUsing filesort. SELECT *on wide tables, pulling text and blob columns nobody displays.- N+1 queries: one query for a list, then one more query per row, often hidden inside a plugin or ORM loop.
- Bloated tables, such as a WordPress
wp_optionstable full of autoloaded data, or years of revisions and expired transients. - Lock waits, where one long write blocks everyone reading the same rows.
2. Find the slow queries
| Where you run | How to find slow queries |
|---|---|
| WordPress, any hosting | The free Query Monitor plugin lists every query on a page, its time and the plugin that ran it |
| Your own PHP code, any hosting | Time each query in your code and log anything over, say, 0.5 seconds |
| Shared hosting, phpMyAdmin | Run the suspect query in the SQL tab; it reports the time taken. Run EXPLAIN on it there too |
| Your own VPS or server | Turn on the slow query log (section 6) and summarise it |
SHOW PROCESSLIST; (or SHOW FULL PROCESSLIST;) shows what is running right now. On shared hosting you see only your own database user's connections, which is enough to spot a query stuck for many seconds.
3. Read EXPLAIN
Put EXPLAIN in front of any SELECT to see how MySQL plans to run it:
EXPLAIN SELECT id, total, created_at
FROM orders
WHERE customer_id = 42
ORDER BY created_at DESC
LIMIT 20;The columns that matter:
| Column | Good sign | Bad sign |
|---|---|---|
| type | const, eq_ref, ref, range | ALL (full table scan), index on a big table |
| key | The index you expect | NULL: no index used |
| rows | Close to the number of rows returned | Thousands or millions for a small result |
| Extra | Using index, Using where | Using filesort, Using temporary |
On MySQL 8.0.18 and later you can run the query for real and see actual times per step (MariaDB uses ANALYZE SELECT ... for the same job):
EXPLAIN ANALYZE SELECT id, total FROM orders WHERE customer_id = 42;EXPLAIN ANALYZE executes the query, so don't use it on a slow UPDATE or DELETE on a live site.
4. Fix it with the right index
Check what already exists:
SHOW INDEX FROM orders;Then add an index that matches how the query filters and sorts. For the query above, one composite index covers both the WHERE and the ORDER BY:
ALTER TABLE orders ADD INDEX idx_customer_created (customer_id, created_at);Rules that hold in practice:
- Column order matters. Put the columns you compare with
=first, then the range or sort column. An index on(customer_id, created_at)helpsWHERE customer_id = ? ORDER BY created_at, but not a query that filters only oncreated_at. - One good composite index beats several single-column ones for multi-column filters.
- Prefix indexes for long text.
ADD INDEX idx_email (email(50))keeps the index small. Prefix indexes can't satisfy anORDER BYon that column. LIKE '%word%'can't use a normal index. For word search, use aFULLTEXTindex withMATCH ... AGAINST.- Don't index everything. Each index slows inserts and updates and uses disk. Remove indexes that
EXPLAINnever picks.
On a table with millions of rows, ALTER TABLE can run for minutes and slow the site while it works. Export the table in phpMyAdmin first and run it at a quiet hour.
5. Write lighter queries
- Select only the columns you use:
SELECT id, nameinstead ofSELECT *. - Page large results with
LIMIT. For deep pages, use keyset pagination (WHERE id < last_seen_id ORDER BY id DESC LIMIT 20) instead of a largeOFFSET, which still reads every skipped row. - Replace N+1 loops with one query using
JOINorWHERE id IN (...). - Don't wrap indexed columns in functions.
WHERE DATE(created_at) = '2026-09-01'can't use an index;WHERE created_at >= '2026-09-01' AND created_at < '2026-09-02'can. - Use prepared statements (PDO or mysqli). They protect against SQL injection, and the plan stays predictable.
- Clean up. On WordPress, remove old revisions, spam comments and expired transients, and check
wp_optionsfor large autoloaded rows. Take a backup first.
6. Server settings: only on your own VPS or server
Everything in this section applies to a MySQL or MariaDB server you manage, such as your own VPS. On shared hosting the database server is shared by many accounts, so you can't edit my.cnf, restart MySQL or read the server's slow query log. Tune your queries and indexes instead, and ask support if you think something is wrong on the server side.
Slow query log. Add under [mysqld] (/etc/my.cnf on AlmaLinux, /etc/mysql/mysql.conf.d/mysqld.cnf on Ubuntu with MySQL), make sure the log folder exists and is owned by the mysql user, and restart the service:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1Summarise it with mysqldumpslow -s t /var/log/mysql/slow.log | head -30, or pt-query-digest from Percona Toolkit.
Settings worth checking:
| Setting | What it does | Starting point |
|---|---|---|
| innodb_buffer_pool_size | Memory for cached data and indexes; the most important setting | About 50–70% of RAM on a server that runs only the database; less if the web server shares it |
| innodb_log_file_size / innodb_redo_log_capacity | Size of the redo log; larger smooths heavy writes | Leave the default unless writes are heavy (MySQL 8.0.30+ uses innodb_redo_log_capacity) |
| max_connections | Most connections at once | Size it to your app's real peak; each connection uses memory |
| tmp_table_size and max_heap_table_size | In-memory temporary tables | Set both to the same value, for example 64M |
Change one setting at a time and measure. Copying an "optimised my.cnf" from the internet, written for someone else's RAM and workload, often makes things worse.
MySQL 8.0 removed the query cache completely, and query_cache_* lines stop MySQL 8 from starting. MariaDB still has it, but disabled by default because it serialises busy servers. Cache in your application instead: page caching for websites, and Redis or Memcached for data on your own VPS.
7. Running this on Domain India
Shared hosting (cPanel, DirectAdmin, Webuzo). Use phpMyAdmin in your control panel to run EXPLAIN and add indexes. Port 3306 is closed to outside connections on our shared servers, so desktop tools such as MySQL Workbench or DBeaver need an SSH tunnel. Jailed SSH access is available on every shared hosting plan (cPanel, DirectAdmin, Webuzo). It is off by default; ask support to enable it for your account. Login is with an SSH key. See securing MySQL access with SSH tunnels. If slow queries are pushing your account into its limits, check your resource usage, and for database errors see database troubleshooting.
VPS. Our VPS is self-managed with full root access, so every setting in section 6 is yours to tune.
- 1 vCPU
- 2 GB DDR4 RAM
- 64 GB NVMe SSD Storage
- 2 TB Monthly Bandwidth
App Platform. Every plan includes a managed PostgreSQL database, if you are building a new app rather than tuning an existing MySQL one.
How do I find which MySQL query is slow?
On WordPress, install the free Query Monitor plugin, which lists each query, its time and the plugin that ran it. In your own code, time each query and log the slow ones. On your own VPS, turn on the slow query log and summarise it with mysqldumpslow or pt-query-digest.
What should I look for in EXPLAIN output?
Look at type, key, rows and Extra. type ALL with key NULL means a full table scan. A rows value far larger than the rows you get back, or Using filesort or Using temporary in Extra, means the query needs a better index or a rewrite.
Which columns should I index?
Index the columns used in WHERE, JOIN and ORDER BY for your slowest, most frequent queries. For queries filtering on several columns, one composite index with the equality columns first and the sort or range column last usually works best.
Can I enable the query cache to speed up MySQL?
No. MySQL 8.0 removed the query cache entirely, and on MariaDB it is disabled by default because it slows busy servers. Use page caching for websites and an application cache such as Redis on your own server.
Can I change my.cnf or enable the slow query log on shared hosting?
No. The database server on shared hosting is shared by many accounts, so server settings are managed for everyone. You can still fix slow queries with EXPLAIN, better indexes and lighter queries in phpMyAdmin. For full control of MySQL settings, use a VPS.
Will a bigger hosting plan fix slow queries?
Rarely. A query that scans a whole table stays slow on any plan. Fix the index or the query first; upgrade only if the account still runs out of resources after that.
Ready to speed up your database? Work through my website is slow, connect safely with how to connect to the MySQL database, or compare VPS plans if you need full control of MySQL.
Send us the slow page, the query if you have it and when it happens, and we will help you find where the time goes.
Open a support ticket