MySQL: MySQL Performance Optimization

Last updated: 2026-08-26

During its year-end sale, Alice’s e-commerce platform saw its database response time skyrocket from 50 ms to 5 seconds, with users complaining that pages wouldn’t load and order submissions timed out. The operations team conducted an emergency investigation and discovered that several unoptimized slow queries were bringing the entire system to a crawl. By enabling slow query logging to pinpoint the problematic SQL statements, analyzing the execution plans with EXPLAIN, adding missing indexes, rewriting inefficient queries, and tuning the InnoDB configuration parameters, they ultimately reduced the response time back to under 100 ms.

1. What You'll Learn



2. The End-to-End Performance Optimization Process

Performance optimization is not a one-time task, but rather a cyclical process of "discovery → analysis → optimization → validation."

100%
flowchart TD
    A[Identifying Slow Queries] --> B[EXPLAIN Analyze the Execution Plan]
    B --> C{Issue Type?}
    C -->|Missing Index| D[Index Optimization]
    C -->|Inefficient query syntax| E[Query Rewrite]
    C -->|Unreasonable configuration| F[Configuration Tuning]
    D --> G[Verifying Performance Improvements]
    E --> G
    F --> G
    G -->|Still does not meet the standards| A
    G -->|Meet the requirements| H[Deployment Monitoring]


3. Slow Query Log

The slow query log is the first line of defense for identifying performance issues; it automatically logs SQL statements whose execution time exceeds a certain threshold.

(1) Getting Started and Configuration

SQL
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

(2) Log Analysis Tools

mysqldumpslow is a slow-query log aggregation tool built into MySQL that sorts queries by execution time or frequency.

BASH
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log

(3) Performance Schema Alternatives

MySQL 5.6 and later versions support using Performance Schema to collect slow queries, which can be enabled dynamically without modifying the configuration file.

SQL
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES' WHERE NAME = 'events_statements_history_long';

▶ Example: Enabling the slow query log and identifying the top 5 slow queries

SQL
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 0.5;
SET GLOBAL min_examined_row_limit = 100;
SHOW VARIABLES LIKE 'slow_query_log_file';
▶ Try it Yourself
BASH
mysqldumpslow -s t -t 5 /var/lib/mysql/slow.log

Output:

TEXT 📖 Display only
---------+------------+------------+------------+---------------------+--------------
query_id | LEFT(query | query_text | exec_count | avg_timer_ms        | rows_examined
---------+------------+------------+------------+---------------------+--------------
1        | value_1    | value_1    | 10         | 2024-01-15 10:30:00 | value_1      
2        | value_2    | value_2    | 15         | 2024-01-15 10:30:00 | value_2      
3        | value_3    | value_3    | 20         | 2024-01-15 10:30:00 | value_3      
---------+------------+------------+------------+---------------------+--------------
3 rows in set

▶ Example: Quickly View Slow Queries Using the sys View

SQL
SELECT query_id, LEFT(query, 80) AS query_text,
       exec_count, avg_timer_ms, rows_examined
FROM sys.statements_with_runtimes_in_95th_percentile
ORDER BY avg_timer_ms DESC LIMIT 10;
▶ Try it Yourself

Output:

TEXT 📖 Display only
Output displayed


4. An In-Depth Analysis of the EXPLAIN Execution Plan

EXPLAIN is the most essential diagnostic tool for SQL tuning; it shows how MySQL executes queries.

(1) Overview of EXPLAIN Output Fields

Field Meaning Key Points
id Query Number Execution Order of Subqueries
select_type Query Type Avoid DERIVED, UNCACHEABLE
table Table Accessed Number of Related Tables
type Access Type From "system" to "ALL"; the further to the left, the better
possible_keys Possible indexes Index selection based on comparison with the key
key Index actually used NULL indicates no index was used
key_len Index Length Determines how many fields are used in a composite index
rows Estimated number of rows to scan The lower, the better
Extra Additional Information Using filesort/Using temporary files—needs optimization

(2) Detailed Explanation of the "type" Field

The type field is the most critical field in EXPLAIN; it directly reflects the query's efficiency.

Type Meaning Scan Method Performance Rating
system Only one row in the table Direct read ★★★★★
const Primary key/unique index equality queries Matches at most one row ★★★★★
eq_ref Primary key/unique index in a join Joins one row to each row ★★★★☆
ref Equality queries on non-unique indexes Matches multiple rows ★★★☆☆
range Index Range Scan BETWEEN/IN/>/< ★★★☆☆
index Full index scan Traverse the entire index tree ★★☆☆☆
ALL Full Table Scan Iterate Through the Entire Table ★☆☆☆☆

(3) Extra Field Key Values

▶ Example: Analyzing a Single-Table Query with EXPLAIN

SQL
EXPLAIN SELECT order_id, user_id, total_amount
FROM orders
WHERE user_id = 42 AND status = 'PAID';
▶ Try it Yourself
TEXT 📖 Display only
+----+-------------+--------+------+---------------+------+---------+------+------+-----------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | rows | Extra                     |
+----+-------------+--------+------+---------------+------+---------+------+------+-----------------------+
|  1 | SIMPLE      | orders | ref  | idx_user      | idx_user | 4    |  120 | Using where               |
+----+-------------+--------+------+---------------+------+---------+------+------+-----------------------+

▶ Example: Analyzing a Joined Query with EXPLAIN

SQL
EXPLAIN SELECT o.order_id, u.username
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.create_time > '2025-01-01';
▶ Try it Yourself

Output:

TEXT 📖 Display only
Output displayed


5. Index Optimization Strategies

Indexes are powerful tools for speeding up queries, but using the wrong index is more dangerous than having no index at all.

(1) Covering Index

A covering index is one in which all fields used in a query are included in the index, eliminating the need to access the table to read row data.

SQL
ALTER TABLE orders ADD INDEX idx_cover (user_id, status, total_amount);

A query for SELECT user_id, status, total_amount FROM orders WHERE user_id = 42 retrieves data entirely from the index.

(2) Composite Indexes and the Leftmost Prefix

Composite indexes follow the leftmost prefix principle: query conditions must begin matching from the leftmost column of the index.

SQL
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);
Query Conditions Can the Index Be Hit? Reason
WHERE user_id = 1 Matches leftmost column
WHERE user_id = 1 AND status = 'PAID' Matches first two columns
WHERE user_id = 1 AND create_time > '2025-01-01' ⚠️ Only hits user_id, skips status
WHERE status = 'PAID' Missing leftmost column user_id

(3) Quick Reference for Index Failure Scenarios

Failure Scenarios Incorrect Syntax Correct Syntax
Using Functions on Indexed Columns WHERE YEAR(create_time) = 2025 WHERE create_time >= '2025-01-01' AND create_time < '2026-01-01'
Implicit Type Conversion WHERE varchar_col = 123 WHERE varchar_col = '123'
Left Fuzzy Search WHERE name LIKE '%alice' WHERE name LIKE 'alice%'
OR joins non-indexed columns WHERE indexed_col = 1 OR unindexed = 2 Split into a UNION or add an index to the non-indexed column
Not Equal To WHERE status != 'PAID' WHERE status IN ('UNPAID', 'CANCELLED')
Index Columns Used in Calculations WHERE id + 1 = 100 WHERE id = 99

▶ Example: Validating the Leftmost Prefix of a Composite Index

SQL
ALTER TABLE products ADD INDEX idx_cat_brand_price (category_id, brand_id, price);

EXPLAIN SELECT * FROM products WHERE category_id = 5 AND brand_id = 10;
EXPLAIN SELECT * FROM products WHERE brand_id = 10;
▶ Try it Yourself

Output:

TEXT 📖 Display only
Output displayed

The second query has a type of ALL because it skips the leftmost column, category_id.

▶ Example: Using a covering index to eliminate table lookups

SQL
ALTER TABLE orders ADD INDEX idx_user_status_amount (user_id, status, total_amount);

EXPLAIN SELECT user_id, status, total_amount
FROM orders WHERE user_id = 42;
▶ Try it Yourself

Output:

TEXT 📖 Display only
Output displayed

If Using index appears in the Extra column, it means no table lookup is required.



6. Query Optimization Techniques

Even with an index, poorly written queries still cannot take advantage of it.

(1) Avoid using SELECT *

SELECT * reads all columns, increases I/O, and may cause the covering index to become ineffective.

SQL
SELECT id, username, email FROM users WHERE id = 100;

(2) Rewrite subqueries as JOINs

The related subquery is executed once for each row; rewriting it as a JOIN can significantly reduce the number of scans.

SQL
SELECT o.order_id, o.total_amount
FROM orders o
WHERE o.user_id IN (SELECT id FROM users WHERE vip_level >= 3);

Optimized to:

SQL
SELECT o.order_id, o.total_amount
FROM orders o
JOIN users u ON o.user_id = u.id AND u.vip_level >= 3;

(3) Deep Pagination Optimization

The traditional LIMIT offset, n performs extremely poorly when the offset is large.

SQL
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;

Optimization Plan—Cursor Pagination:

SQL
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;

(4) Quick Reference for Query Optimization Techniques

Optimization Tips Anti-Patterns Recommended Practices Applicable Scenarios
Avoid SELECT * SELECT * Query only the necessary columns All queries
Converting Subqueries to JOINs WHERE IN (SELECT ...) JOIN ... ON ... Joined Queries
Cursor Pagination LIMIT 100000, 10 WHERE id > last_id LIMIT 10 Deep Pagination
Bulk Insert Loop Through Individual Records INSERT INSERT INTO ... VALUES (...),(...),(...) Data Import
Avoid large transactions Long-held locks Break into smaller transactions High-concurrency writes


7. Table Structure Optimization

A well-designed table structure is the foundation of performance; rewriting SQL can only optimize the existing structure.

(1) Selecting a Field Type

Data Type Recommended Choice Reason
Primary Key BIGINT UNSIGNED Auto-incrementing integer; high insertion efficiency with B-trees
Status/Enum TINYINT 1-byte storage, used with a CHECK constraint
Amount DECIMAL(10,2) Calculate accurately to avoid floating-point errors
Short Text VARCHAR(N) Allocates space based on actual length
Long Text TEXT Stored separately to prevent overflow from affecting the main record
Time DATETIME / TIMESTAMP TIMESTAMP occupies 4 bytes but has a limited range
Boolean TINYINT(1) MySQL has no native BOOLEAN type

(2) Anti-paradigm Design

In scenarios with high query load, moderate redundancy can reduce the number of JOIN operations.

SQL
CREATE TABLE order_summary (
    order_id BIGINT PRIMARY KEY,
    user_id BIGINT,
    username VARCHAR(64),
    total_amount DECIMAL(10,2),
    INDEX idx_user (user_id)
);

Replicate the username column in the orders table to avoid having to JOIN the users table with every query.


8. Config Parameter Tuning

MySQL configuration parameters directly affect the behavior of storage engines and resource allocation.

Parameter Description Recommended Value Basis for Optimization
innodb_buffer_pool_size InnoDB buffer pool size 60%–80% of physical memory Caches data pages and index pages to reduce disk I/O
innodb_log_file_size Size of a single redo log file 256M–1G Too small causes frequent checkpoints
max_connections Maximum concurrent connections 200–500 Too high wastes memory; too low rejects connections
innodb_flush_method Flush Method O_DIRECT Bypasses OS caching to avoid double caching
sync_binlog Binlog sync frequency 1 (security) / 100 (performance) 1 means sync on every commit; this is the safest option
innodb_io_capacity InnoDB I/O Capacity SSD: 2000 / HDD: 200 Affects the speed of background dirty page flushing
query_cache_type Query Cache Toggle OFF Removed in MySQL 8.0; recommended to be disabled in 5.7

(2) Dynamically Adjusting Parameters

Some parameters can be modified online without restarting the instance.

SQL
SET GLOBAL innodb_buffer_pool_size = 8589934592;
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';


9. Performance Schema Introduction

Performance Schema is MySQL's built-in performance monitoring engine, which provides more granular diagnostic data than the slow query log.

(1) Enable the Performance Schema

SQL
SHOW VARIABLES LIKE 'performance_schema';
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME LIKE '%statement/%';

(2) Common Monitoring Views

sys view Purpose
sys.statements_with_runtimes_in_95th_percentile 95th percentile slow queries
sys.schema_index_statistics Index usage statistics
sys.memory_by_host_by_current_bytes Memory usage per connection
sys.io_by_thread_by_latency I/O latency distribution


10. Comprehensive Hands-On Exercise: The Complete Performance Diagnosis Process

Using Alice's e-commerce platform as an example, we'll demonstrate the complete process from identifying slow queries to verifying performance improvements.

SQL
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
BASH
mysqldumpslow -s t -t 5 /var/lib/mysql/slow.log

Output:

TEXT 📖 Display only
Query OK, 0 rows affected
TEXT 📖 Display only
Count: 328  Time=4.52s  Rows=1.0  Rows_examined=890000
SELECT * FROM orders WHERE YEAR(create_time)=2025 AND status='PAID';
SQL
EXPLAIN SELECT * FROM orders
WHERE YEAR(create_time) = 2025 AND status = 'PAID';
TEXT 📖 Display only
type: ALL | key: NULL | rows: 890000 | Extra: Using where

Reason for index failure: The YEAR() function was used on create_time. Solution:

SQL
ALTER TABLE orders ADD INDEX idx_status_time (status, create_time);

SELECT order_id, user_id, total_amount
FROM orders
WHERE create_time >= '2025-01-01' AND create_time < '2026-01-01'
  AND status = 'PAID';
SQL
EXPLAIN SELECT order_id, user_id, total_amount
FROM orders
WHERE create_time >= '2025-01-01' AND create_time < '2026-01-01'
  AND status = 'PAID';
TEXT 📖 Display only
type: range | key: idx_status_time | rows: 3200 | Extra: Using index condition

The number of rows scanned dropped from 890,000 to 3,200, and the query time decreased from 4.5 seconds to 0.08 seconds. Finally, adjust the buffer pool:

SQL
SET GLOBAL innodb_buffer_pool_size = 8589934592;

❓ FAQ

Q Does the slow query log affect production performance?
A The impact is minimal. When long_query_time is set to 1 second, only queries that time out are logged, and the overhead of writing to the log is negligible. If you are concerned about I/O pressure, you can output the log to a file instead of a table, or use Performance Schema as an alternative.
Q Is the rows field in EXPLAIN accurate?
A rows is an estimate based on statistics, not an exact value. For columns with a skewed data distribution, the estimate may be significantly off. You can use ANALYZE TABLE to update the statistics and improve accuracy.
Q Does having more indexes make queries faster?
A No. Indexes take up disk space, and every INSERT, UPDATE, or DELETE operation requires maintaining all indexes. It is recommended that a single table have no more than 5–6 indexes; prioritize using composite indexes to reduce the total number of indexes.
Q When should you consider sharding databases and tables?
A Consider it only when a single table contains more than 50 million rows and SQL and index optimization have reached their limits. Sharding databases and tables increases system complexity (cross-database JOINs, distributed transactions) and should not be the first choice.
Q What is the appropriate value for innodb_buffer_pool_size?
A For dedicated database servers, it is recommended to set this value to 60%–80% of physical memory. For shared servers, be sure to reserve sufficient memory for the OS and other processes. You can assess this by checking the innodb_buffer_pool_reads hit rate: a rate below 99% indicates that the buffer pool is too small.
Q Does the "Using filesort" message in a query always require optimization?
A Not necessarily. If the result set is small (a few dozen rows), the filesort is performed in memory, and the overhead is negligible. Optimization is only needed when the number of rows to be sorted is very large and causes temporary files to be written to disk; this can be mitigated by increasing the sort_buffer_size.

📖 Summary


📝 Exercises

  1. Basic Question (Difficulty: ⭐): Enable the slow query log, set the threshold to 0.5 seconds, and use mysqldumpslow to identify the top 5 slow queries with the highest number of executions.

  2. Advanced Exercise (Difficulty ⭐⭐): For a query with a type of ALL, use EXPLAIN to analyze it, then add appropriate indexes to verify that the type is upgraded to the ref or range level.

  3. Challenge Question (Difficulty: ⭐⭐⭐): Design an indexing scheme for an order table with 1 million rows, using a covering index to optimize the query SELECT user_id, status, total_amount FROM orders WHERE user_id = ? AND status = ?, and compare the differences in the rows and Extra fields of the EXPLAIN output before and after optimization.

  4. Practical Exercise (Difficulty: ⭐⭐⭐): Rewrite a slow SQL query containing a subquery using a JOIN, then optimize it with indexes to reduce the query time from seconds to less than 100 milliseconds. Document the entire optimization process.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏